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:
Ainur
2023-05-27 17:51:56 +03:00
committed by GitHub
co-authored by Aleksey Potsetsuev
parent c8c7307abf
commit 1985df12e2
20 changed files with 88 additions and 67 deletions
@@ -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;
}
@@ -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"));
}
@@ -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 {
+1
View File
@@ -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', '详情'],
];