mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Fix/cb 3507/check use (#1692)
* CB-3507 fix: check use id in useResource for re-subscribe * CB-3507 use thread safe utc datetime in graphql dates * CB-3507 use thread safe utc datetime in graphql dates --------- Co-authored-by: Aleksey Potsetsuev <wrouds@gmail.com>
This commit is contained in:
co-authored by
Aleksey Potsetsuev
parent
c8c7307abf
commit
1985df12e2
@@ -198,7 +198,7 @@ public class WebConnectionInfo {
|
||||
@Property
|
||||
public String getConnectTime() {
|
||||
return dataSourceContainer.getConnectTime() == null ? null :
|
||||
CBModelConstants.ISO_DATE_FORMAT.format(dataSourceContainer.getConnectTime());
|
||||
CBModelConstants.ISO_DATE_FORMAT.format(dataSourceContainer.getConnectTime().toInstant());
|
||||
}
|
||||
|
||||
@Property
|
||||
|
||||
@@ -20,6 +20,9 @@ import io.cloudbeaver.utils.CBModelConstants;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
/**
|
||||
* Web server message
|
||||
@@ -35,7 +38,7 @@ public class WebServerMessage {
|
||||
}
|
||||
|
||||
private final MessageType type;
|
||||
private final long time;
|
||||
private final Instant time;
|
||||
private final String message;
|
||||
private final Throwable error;
|
||||
|
||||
@@ -50,7 +53,7 @@ public class WebServerMessage {
|
||||
|
||||
public WebServerMessage(MessageType type, String message, Throwable error) {
|
||||
this.type = type;
|
||||
this.time = System.currentTimeMillis();
|
||||
this.time = Instant.now();
|
||||
this.message = message;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
+3
-2
@@ -73,6 +73,7 @@ import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
@@ -147,12 +148,12 @@ public class WebSession extends BaseWebSession
|
||||
|
||||
@Property
|
||||
public String getCreateTime() {
|
||||
return CBModelConstants.ISO_DATE_FORMAT.format(createTime);
|
||||
return CBModelConstants.ISO_DATE_FORMAT.format(Instant.ofEpochMilli(createTime));
|
||||
}
|
||||
|
||||
@Property
|
||||
public synchronized String getLastAccessTime() {
|
||||
return CBModelConstants.ISO_DATE_FORMAT.format(lastAccessTime);
|
||||
return CBModelConstants.ISO_DATE_FORMAT.format(Instant.ofEpochMilli(lastAccessTime));
|
||||
}
|
||||
|
||||
public String getLastRemoteAddr() {
|
||||
|
||||
@@ -18,9 +18,10 @@ package io.cloudbeaver.utils;
|
||||
|
||||
import org.jkiss.dbeaver.model.DBConstants;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class CBModelConstants {
|
||||
public static final DateFormat ISO_DATE_FORMAT = new SimpleDateFormat(DBConstants.DEFAULT_ISO_TIMESTAMP_FORMAT);
|
||||
public static final DateTimeFormatter ISO_DATE_FORMAT = DateTimeFormatter.ofPattern(DBConstants.DEFAULT_ISO_TIMESTAMP_FORMAT)
|
||||
.withZone(ZoneId.of("UTC"));
|
||||
}
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ public class WebSQLUtils {
|
||||
return cellValue;
|
||||
}
|
||||
if (cellValue instanceof Date) {
|
||||
return CBModelConstants.ISO_DATE_FORMAT.format(cellValue);
|
||||
return CBModelConstants.ISO_DATE_FORMAT.format(((Date) cellValue).toInstant());
|
||||
} else if (cellValue instanceof Number) {
|
||||
if (cellValue instanceof Double) {
|
||||
return CommonUtils.niceFormatDouble((Double) cellValue);
|
||||
|
||||
@@ -11,9 +11,4 @@ export default [
|
||||
],
|
||||
['app_shared_sql_generators_panel_title', 'Generate SQL'],
|
||||
['app_shared_sql_generators_dialog_title', 'Generated SQL'],
|
||||
['app_log_view_clear_log', 'Clear logs'],
|
||||
['app_log_view_entry_type', 'Type'],
|
||||
['app_log_view_entry_timestamp', 'Timestamp'],
|
||||
['app_log_view_entry_message', 'Message'],
|
||||
['app_log_view_entry_details', 'Details'],
|
||||
];
|
||||
|
||||
@@ -9,9 +9,4 @@ export default [
|
||||
'app_shared_navigationTabsBar_placeholder',
|
||||
'Non ci sono oggetti da mostrare. Fai doppio click su un oggetto per aprirlo.',
|
||||
],
|
||||
['app_log_view_clear_log', 'Pulisci i log'],
|
||||
['app_log_view_entry_type', 'Digita'],
|
||||
['app_log_view_entry_timestamp', 'Timestamp'],
|
||||
['app_log_view_entry_message', 'Messaggio'],
|
||||
['app_log_view_entry_details', 'Dettagli'],
|
||||
];
|
||||
|
||||
@@ -11,9 +11,4 @@ export default [
|
||||
['app_shared_inlineEditor_dialog_cancel', 'Отменить'],
|
||||
['app_shared_sql_generators_panel_title', 'Сгенерировать SQL'],
|
||||
['app_shared_sql_generators_dialog_title', 'Сгенерированный SQL'],
|
||||
['app_log_view_clear_log', 'Очистить лог'],
|
||||
['app_log_view_entry_type', 'Тип'],
|
||||
['app_log_view_entry_timestamp', 'Время'],
|
||||
['app_log_view_entry_message', 'Сообщение'],
|
||||
['app_log_view_entry_details', 'Детали'],
|
||||
];
|
||||
|
||||
@@ -11,9 +11,4 @@ export default [
|
||||
],
|
||||
['app_shared_sql_generators_panel_title', '生成SQL'],
|
||||
['app_shared_sql_generators_dialog_title', '生成的SQL'],
|
||||
['app_log_view_clear_log', '清除日志'],
|
||||
['app_log_view_entry_type', '类型'],
|
||||
['app_log_view_entry_timestamp', '时间戳'],
|
||||
['app_log_view_entry_message', '消息'],
|
||||
['app_log_view_entry_details', '详情'],
|
||||
];
|
||||
|
||||
@@ -137,7 +137,7 @@ export function useResource<
|
||||
const errorContext = useContext(ErrorContext);
|
||||
let key: ResourceKey<TKeyArg> | null = keyObj as ResourceKey<TKeyArg>;
|
||||
let includes: TIncludes = [] as unknown as TIncludes;
|
||||
const [loadFunctionName] = useState(`${component.name}.useResource(${resource.getName()}).load`);
|
||||
const [loadFunctionName] = useState(`${component.name}.useResource(${resource.getName()}).load` as const);
|
||||
|
||||
if (isKeyWithIncludes<TKeyArg, TIncludes>(keyObj)) {
|
||||
key = keyObj.key;
|
||||
@@ -204,21 +204,21 @@ export function useResource<
|
||||
const refObj = useObservableRef(() => ({
|
||||
loadingPromise: null as (Promise<void> | null),
|
||||
exception: null as Error | null,
|
||||
useRef: [null, ''] as [TKeyArg | null, string],
|
||||
get resourceException() {
|
||||
useRef: [null, ''] as [ResourceKey<TKeyArg> | null, string],
|
||||
get resourceException(): Error | null {
|
||||
if (propertiesRef.key === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return propertiesRef.resource.getException(propertiesRef.key);
|
||||
},
|
||||
isResourceError() {
|
||||
isResourceError(): boolean {
|
||||
return isContainsException(this.resourceException);
|
||||
},
|
||||
use(key: TKeyArg | null) {
|
||||
use(key: ResourceKey<TKeyArg> | null): void {
|
||||
key = toJS(key);
|
||||
|
||||
if (this.useRef[0] !== null) {
|
||||
if (this.useRef[0] !== null && propertiesRef.resource.hasUseId(this.useRef[1])) {
|
||||
if (key !== null && propertiesRef.resource.isIntersect(key, this.useRef[0])) {
|
||||
return;
|
||||
}
|
||||
@@ -227,7 +227,7 @@ export function useResource<
|
||||
}
|
||||
this.useRef = [key, key === null ? '' : propertiesRef.resource.use(key)];
|
||||
},
|
||||
async [loadFunctionName](refresh?: boolean) {
|
||||
async [loadFunctionName](refresh?: boolean): Promise<void> {
|
||||
const { key, includes, resource } = propertiesRef;
|
||||
|
||||
if (refresh) {
|
||||
@@ -236,7 +236,7 @@ export function useResource<
|
||||
|
||||
await resource.load(key, includes as any);
|
||||
},
|
||||
async load(refresh?: boolean) {
|
||||
async load(refresh?: boolean): Promise<void> {
|
||||
if (this.loadingPromise) {
|
||||
return this.loadingPromise;
|
||||
}
|
||||
@@ -246,7 +246,7 @@ export function useResource<
|
||||
}
|
||||
|
||||
try {
|
||||
this.loadingPromise = this[loadFunctionName](refresh);
|
||||
this.loadingPromise = (this[loadFunctionName] as (refresh?: boolean) => Promise<void>)(refresh);
|
||||
await this.loadingPromise;
|
||||
this.exception = null;
|
||||
} catch (exception: any) {
|
||||
@@ -285,7 +285,7 @@ export function useResource<
|
||||
) & IResourcePrivateState
|
||||
>(() => ({
|
||||
preloaded,
|
||||
get canLoad() {
|
||||
get canLoad(): boolean {
|
||||
return (
|
||||
propertiesRef.key !== null
|
||||
&& this.preloaded
|
||||
@@ -399,24 +399,26 @@ export function useResource<
|
||||
return comparer.default(a, b);
|
||||
},
|
||||
}),
|
||||
tryGetData: computed<any>({
|
||||
equals: (a, b) => {
|
||||
if (Array.isArray(a) && Array.isArray(b)) {
|
||||
return isArraysEqual(a, b, undefined, true);
|
||||
}
|
||||
// TODO: in case when array is mutated, but not replaced, it will not be updated
|
||||
// tryGetData: computed<any>({
|
||||
// equals: (a, b) => {
|
||||
// console.log(a, b, isArraysEqual(a, b, undefined, true));
|
||||
// if (Array.isArray(a) && Array.isArray(b)) {
|
||||
// return isArraysEqual(a, b, undefined, true);
|
||||
// }
|
||||
|
||||
return comparer.default(a, b);
|
||||
},
|
||||
}),
|
||||
data: computed<any>({
|
||||
equals: (a, b) => {
|
||||
if (Array.isArray(a) && Array.isArray(b)) {
|
||||
return isArraysEqual(a, b, undefined, true);
|
||||
}
|
||||
// return comparer.default(a, b);
|
||||
// },
|
||||
// }),
|
||||
// data: computed<any>({
|
||||
// equals: (a, b) => {
|
||||
// if (Array.isArray(a) && Array.isArray(b)) {
|
||||
// return isArraysEqual(a, b, undefined, true);
|
||||
// }
|
||||
|
||||
return comparer.default(a, b);
|
||||
},
|
||||
}),
|
||||
// return comparer.default(a, b);
|
||||
// },
|
||||
// }),
|
||||
outdated: computed,
|
||||
loaded: computed,
|
||||
loading: computed,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { GraphQLService } from '../GraphQLService';
|
||||
import type { AsyncTaskInfo } from '../sdk';
|
||||
import { AsyncTask } from './AsyncTask';
|
||||
|
||||
const DELAY_BETWEEN_TRIES = 1000;
|
||||
const DELAY_BETWEEN_TRIES = 250;
|
||||
|
||||
@injectable()
|
||||
export class AsyncTaskInfoService {
|
||||
|
||||
@@ -29,6 +29,7 @@ export * from './isArraysEqual';
|
||||
export * from './isPrimitive';
|
||||
export * from './isPropertiesEqual';
|
||||
export * from './isSafari';
|
||||
export * from './isSameDay';
|
||||
export * from './isValuesEqual';
|
||||
export * from './md5';
|
||||
export * from './MetadataMap';
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export function isSameDay(dateA: Date, dateB: Date): boolean {
|
||||
return (
|
||||
dateA.getFullYear() === dateB.getFullYear()
|
||||
&& dateA.getMonth() === dateB.getMonth()
|
||||
&& dateA.getDate() === dateB.getDate()
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { BASE_TABLE_STYLES, IconOrImage, Link, TableColumnValue, TableItem, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { isSameDay } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { ILogEntry } from './ILogEntry';
|
||||
|
||||
@@ -68,6 +69,9 @@ export const LogEntry = observer<Props>(function LogEntry({
|
||||
const isError = !!item.stackTrace;
|
||||
const message = isError ? item.message || translate('ui_error') : item.message;
|
||||
let icon: string | null = null;
|
||||
const time = new Date(item.time);
|
||||
const fullTime = time.toLocaleString();
|
||||
const displayTime = isSameDay(time, new Date()) ? time.toLocaleTimeString() : fullTime;
|
||||
|
||||
switch (item.type) {
|
||||
case 'ERROR':
|
||||
@@ -83,7 +87,7 @@ export const LogEntry = observer<Props>(function LogEntry({
|
||||
<TableColumnValue title={item.type} centerContent flex {...use({ icon: true })}>
|
||||
<icon-box>{icon && <IconOrImage icon={icon} />}</icon-box>
|
||||
</TableColumnValue>
|
||||
<TableColumnValue title={item.time} ellipsis>{item.time}</TableColumnValue>
|
||||
<TableColumnValue title={fullTime} ellipsis>{displayTime}</TableColumnValue>
|
||||
<TableColumnValue>
|
||||
<message-cell>
|
||||
<message title={message}>
|
||||
|
||||
@@ -35,9 +35,6 @@ const styles = css`
|
||||
& message-title {
|
||||
flex: 1;
|
||||
}
|
||||
& Button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
[|buttons] {
|
||||
text-align: right;
|
||||
@@ -72,17 +69,14 @@ export const LogViewerTable = observer<Props>(function LogViewerTable({ items, s
|
||||
<Table {...use({ expanded: !!selectedItem })}>
|
||||
<TableHeader fixed>
|
||||
<TableColumnHeader min />
|
||||
<TableColumnHeader {...use({ timestamp: true })}>{translate('app_log_view_entry_timestamp')}</TableColumnHeader>
|
||||
<TableColumnHeader {...use({ timestamp: true })}>{translate('plugin_log_viewer_entry_timestamp')}</TableColumnHeader>
|
||||
<TableColumnHeader>
|
||||
<message-title-box>
|
||||
<message-title>{translate('app_log_view_entry_message')}</message-title>
|
||||
{/* <Button title={translate('app_log_view_clear_log')} onClick={onClear}>
|
||||
{translate('ui_clear')}
|
||||
</Button> */}
|
||||
<message-title>{translate('plugin_log_viewer_entry_message')}</message-title>
|
||||
<MenuBarSmallItem
|
||||
name='trash'
|
||||
viewBox='0 0 24 24'
|
||||
title={translate('app_log_view_clear_log')}
|
||||
title={translate('plugin_log_viewer_clear_log')}
|
||||
onClick={onClear}
|
||||
>
|
||||
{translate('ui_clear')}
|
||||
|
||||
@@ -45,7 +45,7 @@ export function useLogViewer() {
|
||||
return props.logViewerService.isActive;
|
||||
},
|
||||
get logItems() {
|
||||
return sessionLogsLoader.data;
|
||||
return sessionLogsLoader.tryGetData;
|
||||
},
|
||||
clearLog() {
|
||||
sessionLogsLoader.resource.clear();
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export default [
|
||||
['plugin_log_viewer_action_enable_label', 'Log Viewer'],
|
||||
['plugin_log_viewer_placeholder', 'Log viewer is not active'],
|
||||
['plugin_log_viewer_clear_log', 'Clear logs'],
|
||||
['plugin_log_viewer_entry_type', 'Type'],
|
||||
['plugin_log_viewer_entry_timestamp', 'Time'],
|
||||
['plugin_log_viewer_entry_message', 'Message'],
|
||||
['plugin_log_viewer_entry_details', 'Details'],
|
||||
];
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export default [
|
||||
['plugin_log_viewer_action_enable_label', 'Visualizzatore del Log'],
|
||||
['plugin_log_viewer_placeholder', 'Il visualizzatore del log non è attivo'],
|
||||
['plugin_log_viewer_clear_log', 'Pulisci i log'],
|
||||
['plugin_log_viewer_entry_type', 'Digita'],
|
||||
['plugin_log_viewer_entry_timestamp', 'Time'],
|
||||
['plugin_log_viewer_entry_message', 'Messaggio'],
|
||||
['plugin_log_viewer_entry_details', 'Dettagli'],
|
||||
];
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export default [
|
||||
['plugin_log_viewer_action_enable_label', 'Просмотр лога'],
|
||||
['plugin_log_viewer_placeholder', 'Просмотр логов неактивен'],
|
||||
['plugin_log_viewer_clear_log', 'Очистить лог'],
|
||||
['plugin_log_viewer_entry_type', 'Тип'],
|
||||
['plugin_log_viewer_entry_timestamp', 'Время'],
|
||||
['plugin_log_viewer_entry_message', 'Сообщение'],
|
||||
['plugin_log_viewer_entry_details', 'Детали'],
|
||||
];
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export default [
|
||||
['plugin_log_viewer_action_enable_label', '日志查看器'],
|
||||
['plugin_log_viewer_placeholder', '日志查看器未激活'],
|
||||
['plugin_log_viewer_clear_log', '清除日志'],
|
||||
['plugin_log_viewer_entry_type', '类型'],
|
||||
['plugin_log_viewer_entry_timestamp', '时间戳'],
|
||||
['plugin_log_viewer_entry_message', '消息'],
|
||||
['plugin_log_viewer_entry_details', '详情'],
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user