CB-5342 fix: database data source tasks result (#2756)

* CB-5342 fix: database data source tasks result

* CB-5342 fix: database data source tasks tracking

* CB-5342 fix: add stop auto refresh button
This commit is contained in:
Alexey
2024-07-04 11:08:39 +03:00
committed by GitHub
parent 0d53211b88
commit 1be6ddaf1c
14 changed files with 96 additions and 51 deletions
@@ -31,7 +31,7 @@ export class DataViewerDataChangeConfirmationService {
const model = this.dataViewerTableService.get(modelId);
if (model && !model.onRequest.hasHandler(this.checkUnsavedData)) {
model.onRequest.addHandler(executorHandlerFilter(({ operation }) => operation !== DatabaseDataSourceOperation.Save, this.checkUnsavedData));
model.onRequest.addHandler(executorHandlerFilter(({ operation }) => operation === DatabaseDataSourceOperation.Request, this.checkUnsavedData));
}
}
@@ -111,14 +111,7 @@ export class DataViewerTabService {
const model = this.tableViewerStorageService.get(tab.handlerState.tableId || '');
if (model) {
let canClose = false;
try {
await model.source.runTask(async () => {
canClose = true;
});
} catch {}
return canClose;
return await model.source.canSafelyDispose();
}
return true;
@@ -125,7 +125,7 @@ export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabas
throw new Error('Failed to get value metadata information');
}
const fullText = await this.source.runTask(async () => {
const fullText = await this.source.runOperation(async () => {
try {
this.updateCache(element, { loading: true });
return await this.loadFileFullText(this.result, column.position, row);
@@ -134,6 +134,10 @@ export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabas
}
});
if (fullText === null) {
throw new Error('Failed to get value metadata information');
}
this.updateCache(element, { fullText });
return fullText;
@@ -167,7 +171,7 @@ export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabas
this.clearCache();
}
private async getFileDataUrl(element: IResultSetElementKey) {
private async getFileDataUrl(element: IResultSetElementKey): Promise<string> {
const column = this.data.getColumn(element.column);
const row = this.data.getRowValue(element.row);
@@ -175,7 +179,7 @@ export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabas
throw new Error('Failed to get value metadata information');
}
const url = await this.source.runTask(async () => {
const url = await this.source.runOperation(async () => {
try {
this.updateCache(element, { loading: true });
return await this.loadDataURL(this.result, column.position, row);
@@ -184,6 +188,10 @@ export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabas
}
});
if (url === null) {
throw new Error('Failed to get value metadata information');
}
return url;
}
@@ -65,7 +65,7 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
private get activeOperation(): Promise<void> | null {
return this.activeOperationStack[this.activeOperationStack.length - 1] ?? null;
}
private readonly activeOperationStack: Array<Promise<void>>;
private readonly activeOperationStack: Array<Promise<any>>;
constructor(serviceInjector: IServiceInjector) {
this.serviceInjector = serviceInjector;
@@ -286,12 +286,12 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
await this.lastAction();
}
async runTask(task: () => Promise<any>): Promise<any> {
async runOperation<T>(task: () => Promise<T>): Promise<T | null> {
return this.tryExecuteOperation(DatabaseDataSourceOperation.Task, task);
}
async requestData(mutation?: () => void): Promise<void> {
await this.tryExecuteOperation(DatabaseDataSourceOperation.Load, () => {
await this.tryExecuteOperation(DatabaseDataSourceOperation.Request, () => {
this.lastAction = this.requestData.bind(this);
mutation?.();
@@ -300,7 +300,7 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
}
async requestDataPortion(offset: number, count: number): Promise<void> {
await this.tryExecuteOperation(DatabaseDataSourceOperation.Load, () => {
await this.tryExecuteOperation<TResult[] | void>(DatabaseDataSourceOperation.Request, () => {
if (!this.isDataAvailable(offset, count)) {
this.lastAction = this.requestDataPortion.bind(this, offset, count);
@@ -312,7 +312,7 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
}
async refreshData(): Promise<void> {
await this.tryExecuteOperation(DatabaseDataSourceOperation.Refresh, () => {
await this.tryExecuteOperation(DatabaseDataSourceOperation.Request, () => {
this.lastAction = this.refreshData.bind(this);
if (this.prevOptions) {
@@ -333,6 +333,15 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
});
}
async canSafelyDispose(): Promise<boolean> {
try {
const result = await this.tryExecuteOperation(DatabaseDataSourceOperation.Request, async () => true);
return result || false;
} catch {
return false;
}
}
clearError(): this {
this.error = null;
return this;
@@ -358,7 +367,7 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
abstract loadTotalCount(resultIndex: number): Promise<ITask<number>>;
abstract cancelLoadTotalCount(): Promise<ITask<number> | null>;
private async requestDataAction(): Promise<TResult[] | null> {
private async requestDataAction(): Promise<TResult[]> {
this.prevOptions = toJS(this.options);
return this.request(this.results).then(data => {
this.outdated = false;
@@ -370,8 +379,8 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
});
}
private async tryExecuteOperation(type: DatabaseDataSourceOperation, operation: () => Promise<any>): Promise<void> {
if (this.activeOperation && type !== DatabaseDataSourceOperation.Task && type !== DatabaseDataSourceOperation.Save) {
private async tryExecuteOperation<T>(type: DatabaseDataSourceOperation, operation: () => Promise<T>): Promise<T | null> {
if (this.activeOperation && type !== DatabaseDataSourceOperation.Request) {
await this.activeOperation;
}
@@ -387,23 +396,25 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
}
}
private executeOperation(type: DatabaseDataSourceOperation, operation: () => Promise<void> | void): ITask<void> {
private executeOperation<T>(type: DatabaseDataSourceOperation, operation: () => Promise<T> | T): ITask<T | null> {
return new Task(async () => await this.onOperation.execute({ stage: 'request', operation: type })).run().then(contexts => {
// TODO: maybe it's better to throw an exception instead, so we will not have unexpected undefined results
if (ExecutorInterrupter.isInterrupted(contexts)) {
return;
return null;
}
return new Task(async () => await this.onOperation.execute({ stage: 'before', operation: type }))
.run()
.then(contexts => {
if (ExecutorInterrupter.isInterrupted(contexts)) {
return;
return null;
}
return operation();
})
.then(async () => {
.then(async result => {
await this.onOperation.execute({ stage: 'after', operation: type });
return result;
});
});
}
@@ -15,10 +15,12 @@ import type { IDatabaseDataActions } from './IDatabaseDataActions';
import type { IDatabaseDataResult } from './IDatabaseDataResult';
export enum DatabaseDataSourceOperation {
Load = 'load',
Save = 'save',
Refresh = 'refresh',
/** Abstract operation with data, should not lead to data lost */
Task = 'task',
/** Saving operation */
Save = 'save',
/** May lead to data lost */
Request = 'request',
}
export interface IDatabaseDataSourceOperationEvent {
stage: 'request' | 'before' | 'after';
@@ -102,15 +104,19 @@ export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResu
cancelLoadTotalCount: () => Promise<ITask<number> | null>;
retry: () => Promise<void>;
/** Allows to perform an asynchronous action on the data source, this action will wait previous action to finish and save or load requests.
* The data source will have a loading and disabled state while performing an action */
runTask: <T>(task: () => Promise<T>) => Promise<T>;
/**
* Perform operation with data source. This action should not lead to data lost. Can be cancelled when operation is Task.
* @param operation Task or Promise
* @returns
*/
runOperation: <T>(operation: () => Promise<T>) => Promise<T | null>;
requestDataPortion(offset: number, count: number): Promise<void>;
requestData: (mutation?: () => void) => Promise<void> | void;
refreshData: () => Promise<void> | void;
saveData: () => Promise<void> | void;
cancel: () => Promise<void> | void;
requestData: (mutation?: () => void) => Promise<void>;
refreshData: () => Promise<void>;
saveData: () => Promise<void>;
cancel: () => Promise<void>;
clearError: () => this;
resetData: () => this;
canSafelyDispose: () => Promise<boolean>;
dispose: (keepExecutionContext?: boolean) => Promise<void>;
}
@@ -7,4 +7,10 @@
*/
import { ACTION_REFRESH, createMenu } from '@cloudbeaver/core-view';
export const MENU_DATA_VIEWER_AUTO_REFRESH = createMenu('auto-refresh', 'Auto-Refresh', undefined, 'data_viewer_action_auto_refresh', ACTION_REFRESH);
export const MENU_DATA_VIEWER_AUTO_REFRESH = createMenu(
'auto-refresh',
'data_viewer_action_auto_refresh',
undefined,
'data_viewer_action_auto_refresh',
ACTION_REFRESH,
);
@@ -13,7 +13,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { LocalizationService } from '@cloudbeaver/core-localization';
import { declensionOfNumber } from '@cloudbeaver/core-utils';
import { ACTION_REFRESH, ActionService, MenuBaseItem, menuExtractItems, MenuService } from '@cloudbeaver/core-view';
import { ACTION_REFRESH, ActionService, MenuBaseItem, menuExtractItems, MenuSeparatorItem, MenuService } from '@cloudbeaver/core-view';
import { IDatabaseRefreshState } from '../../../../DatabaseDataModel/Actions/DatabaseRefreshAction';
import { DATA_CONTEXT_DV_DDM } from '../../../../DatabaseDataModel/DataContext/DATA_CONTEXT_DV_DDM';
@@ -72,12 +72,13 @@ export class TableRefreshActionBootstrap extends Bootstrap {
const state = getRefreshState(context);
items = [...items];
for (const interval of AUTO_REFRESH_INTERVALS) {
const label = this.getLabel(interval);
items.push(
new MenuBaseItem(
{
id: `auto-refresh-${interval}`,
label: this.getLabel(interval),
tooltip: 'data_viewer_action_auto_refresh',
label,
tooltip: label,
disabled: state?.interval === interval * 1000,
},
{
@@ -89,12 +90,14 @@ export class TableRefreshActionBootstrap extends Bootstrap {
);
}
items.push(new MenuSeparatorItem());
items.push(
new MenuBaseItem(
{
id: 'auto-refresh-custom',
label: 'ui_custom',
tooltip: 'data_viewer_action_auto_refresh',
tooltip: 'plugin_data_viewer_action_auto_refresh_custom',
},
{
onSelect: this.configureAutoRefresh.bind(this, context),
@@ -102,6 +105,22 @@ export class TableRefreshActionBootstrap extends Bootstrap {
),
);
items.push(
new MenuBaseItem(
{
id: 'auto-refresh-stop',
label: 'ui_processing_stop',
tooltip: 'data_viewer_action_auto_refresh_stop',
disabled: !state?.isAutoRefresh,
},
{
onSelect: () => {
state?.setInterval(0);
},
},
),
);
return items;
},
});
@@ -21,6 +21,7 @@ export default [
['data_viewer_action_refresh', 'Refresh'],
['data_viewer_action_auto_refresh', 'Auto-Refresh'],
['data_viewer_action_auto_refresh_stop', 'Stop auto-refresh'],
['plugin_data_viewer_action_auto_refresh_custom', 'Configure auto-refresh'],
['data_viewer_action_edit_delete', 'Delete selected'],
['data_viewer_action_edit_add', 'Add'],
['data_viewer_action_edit_add_copy', 'Duplicate'],
@@ -28,7 +29,7 @@ export default [
['data_viewer_result_edited_title', 'Save changes'],
['data_viewer_result_edited_message', 'Result set was edited. Do you want to save changes in database?'],
['data_viewer_data_save_error_title', 'Error occurred while saving changes'],
['data_viewer_auto_refresh_settings', 'Auto refresh Settings'],
['data_viewer_auto_refresh_settings', 'Auto-Refresh Settings'],
['data_viewer_auto_refresh_settings_stop_on_error', 'Stop on error'],
['data_viewer_presentation_value_no_active_elements', 'No selected table cells'],
['data_viewer_presentation_value_title', 'Value'],
@@ -21,6 +21,7 @@ export default [
['data_viewer_action_refresh', 'Actualiser'],
['data_viewer_action_auto_refresh', 'Actualisation automatique'],
['data_viewer_action_auto_refresh_stop', "Arrêter l'actualisation automatique"],
['plugin_data_viewer_action_auto_refresh_custom', 'Configure auto-refresh'],
['data_viewer_action_edit_delete', 'Supprimer la sélection'],
['data_viewer_action_edit_add', 'Ajouter'],
['data_viewer_action_edit_add_copy', 'Dupliquer'],
@@ -21,10 +21,11 @@ export default [
['data_viewer_action_refresh', 'Refresh'],
['data_viewer_action_auto_refresh', 'Auto-Refresh'],
['data_viewer_action_auto_refresh_stop', 'Stop auto-refresh'],
['plugin_data_viewer_action_auto_refresh_custom', 'Configure auto-refresh'],
['data_viewer_result_edited_title', 'Save changes'],
['data_viewer_result_edited_message', 'Result set was edited. Do you want to save changes in database?'],
['data_viewer_data_save_error_title', 'Error occurred while saving changes'],
['data_viewer_auto_refresh_settings', 'Auto refresh Settings'],
['data_viewer_auto_refresh_settings', 'Auto-Refresh Settings'],
['data_viewer_auto_refresh_settings_stop_on_error', 'Stop on error'],
['data_viewer_presentation_value_no_active_elements', 'No selected table cells'],
['data_viewer_presentation_value_title', 'Valore'],
@@ -21,6 +21,7 @@ export default [
['data_viewer_action_refresh', 'Обновить'],
['data_viewer_action_auto_refresh', 'Автообновление'],
['data_viewer_action_auto_refresh_stop', 'Остановить автообновление'],
['plugin_data_viewer_action_auto_refresh_custom', 'Настроить автообновление'],
['data_viewer_action_edit_delete', 'Удалить'],
['data_viewer_action_edit_add', 'Добавить'],
['data_viewer_action_edit_add_copy', 'Дублировать'],
@@ -21,6 +21,7 @@ export default [
['data_viewer_action_refresh', 'Refresh'],
['data_viewer_action_auto_refresh', 'Auto-Refresh'],
['data_viewer_action_auto_refresh_stop', 'Stop auto-refresh'],
['plugin_data_viewer_action_auto_refresh_custom', 'Configure auto-refresh'],
['data_viewer_action_edit_delete', '删除选中'],
['data_viewer_action_edit_add', '添加'],
['data_viewer_action_edit_add_copy', 'Duplicate'],
@@ -28,7 +29,7 @@ export default [
['data_viewer_result_edited_title', '保存更改'],
['data_viewer_result_edited_message', '结果集已编辑。是否将更改保存到数据库?'],
['data_viewer_data_save_error_title', 'Error occurred while saving changes'],
['data_viewer_auto_refresh_settings', 'Auto refresh Settings'],
['data_viewer_auto_refresh_settings', 'Auto-Refresh Settings'],
['data_viewer_auto_refresh_settings_stop_on_error', 'Stop on error'],
['data_viewer_presentation_value_no_active_elements', 'No selected table cells'],
['data_viewer_presentation_value_title', '值'],