CB-3497 error message (#1699)

* CB-3497 fix: error message

* CB-3497 chore: update messages
This commit is contained in:
Alexey
2023-05-29 19:32:11 +03:00
committed by GitHub
parent 556f2ce9c3
commit d7e0c41a82
14 changed files with 143 additions and 149 deletions
@@ -93,6 +93,21 @@ const style = css`
}
error[|icon] {
padding: 0;
height: 100%;
width: 100%;
max-height: 24px;
max-width: 24px;
& error-icon {
display: flex;
align-items: center;
align-content: center;
& IconOrImage {
height: 100%;
width: 100%;
}
}
}
error-action-close {
cursor: pointer;
@@ -103,8 +118,6 @@ const style = css`
`;
interface Props {
name?: string;
message?: string;
exception?: Error;
icon?: boolean;
inline?: boolean;
@@ -114,21 +127,9 @@ interface Props {
onClose?: () => void;
}
export const ExceptionMessage = observer<Props>(function ExceptionMessage({
name,
message,
exception = null,
icon,
inline,
className,
styles,
onRetry,
onClose,
}) {
export const ExceptionMessage = observer<Props>(function ExceptionMessage({ exception = null, icon, inline, className, styles, onRetry, onClose }) {
const translate = useTranslate();
const error = useErrorDetails(exception);
name = name || error.name;
message = message || error.message;
if (error.refresh) {
const retry = onRetry;
@@ -141,22 +142,22 @@ export const ExceptionMessage = observer<Props>(function ExceptionMessage({
return styled(useStyles(style, styles))(
<error {...use({ inline, icon })} className={className}>
<error-icon title={message}>
<IconOrImage icon={inline ? '/icons/error_icon_sm.svg' : '/icons/error_icon.svg'} />
<error-icon title={error.message}>
<IconOrImage icon={inline || icon ? '/icons/error_icon_sm.svg' : '/icons/error_icon.svg'} />
</error-icon>
{!icon && (
<>
<error-data>
<error-name>
<span>{name}</span>
<span>{translate('core_blocks_exception_message_error_title')}</span>
</error-name>
<error-message>{message}</error-message>
<error-message>
{translate('core_blocks_exception_message_error_message')} {onRetry && translate('ui_please_retry')}
</error-message>
<error-actions>
{error.hasDetails && (
<Button type="button" mod={['outlined']} disabled={error.isOpen} onClick={error.open}>
{translate('ui_errors_details')}
</Button>
)}
<Button type="button" mod={['outlined']} disabled={error.isOpen} onClick={error.open}>
{translate('ui_errors_details')}
</Button>
{onRetry && (
<Button type="button" mod={['unelevated']} onClick={onRetry}>
{translate('ui_processing_retry')}
@@ -156,7 +156,12 @@ export const Loader = observer<Props>(function Loader({
}
if ('reload' in element) {
reload = element.reload;
const reloadLink = element.reload;
const reloadCopy = reload;
reload = () => {
reloadCopy?.();
reloadLink?.();
};
}
}
}
@@ -6,4 +6,6 @@ export default [
['core_blocks_properties_table_item_reset', 'Reset to default'],
['core_blocks_properties_table_item_remove', 'Remove property'],
['combobox_no_results_placeholder', 'No results'],
['core_blocks_exception_message_error_title', 'Unable to display content'],
['core_blocks_exception_message_error_message', 'Something went wrong.'],
];
@@ -11,7 +11,6 @@ import { Dependency, injectable } from '@cloudbeaver/core-di';
import { ExecutorInterrupter, IAsyncContextLoader, IExecutionContextProvider } from '@cloudbeaver/core-executor';
import {
INodeNavigationData,
NavigationType,
NavNodeInfoResource,
NavNodeManagerService,
NavTreeResource,
@@ -232,11 +231,7 @@ export class ConnectionNavNodeService extends Dependency {
this.navTreeResource.insertToNode(parentId, insertIndex, connection.nodePath);
}
private async navigateHandler({ type, nodeId }: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>): Promise<void> {
if (type !== NavigationType.open) {
return;
}
private async navigateHandler({ nodeId }: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>): Promise<void> {
let connection: Connection | undefined | null = await contexts.getContext(this.navigationNavNodeConnectionContext);
if (NodeManagerUtils.isDatabaseObject(nodeId) && connection) {
@@ -162,10 +162,22 @@ export class NotificationService {
const errorDetails = errorOf(exception, DetailsError);
if (!silent) {
const hasDetails = errorDetails?.hasDetails() ?? false;
if (hasDetails) {
if (!title) {
title = exception?.name;
}
if (!message) {
message = exception?.message;
}
}
this.logError({
title: title || exception?.name || 'Error',
message: message || exception?.message,
details: errorDetails?.hasDetails() ? exception : undefined,
title: title || 'ui_unexpected_error',
message: message || 'core_blocks_exception_message_error_message',
details: exception,
isSilent: silent,
});
}
@@ -23,9 +23,11 @@ export default [
['ui_minute_first_form', '{arg:interval} minute'],
['ui_minute_second_form', '{arg:interval} minutes'],
['ui_minute_third_form', '{arg:interval} minutes'],
['ui_please_retry', 'Please retry'],
['ui_custom', 'Custom'],
['ui_configure', 'Configure'],
['ui_apply', 'Apply'],
['ui_unexpected_error', 'Unexpected error'],
['ui_error', 'Error'],
['ui_error_message', 'Error:'],
['ui_error_close', 'Close'],
@@ -5,12 +5,13 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { makeObservable, observable } from 'mobx';
import { action } from 'mobx';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { Executor, IExecutionContextProvider, IExecutor, ISyncContextLoader } from '@cloudbeaver/core-executor';
import { resourceKeyList } from '@cloudbeaver/core-sdk';
import { Executor, IExecutionContextProvider, IExecutor, ISyncContextLoader, ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
import { resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
import { NavigationService } from '@cloudbeaver/core-ui';
import { MetadataMap } from '@cloudbeaver/core-utils';
import type { IDataContextProvider } from '@cloudbeaver/core-view';
import { ENodeFeature } from './ENodeFeature';
@@ -22,11 +23,6 @@ import { NavTreeResource } from './NavTreeResource';
import { NodeManagerUtils } from './NodeManagerUtils';
import { ProjectsNavNodeService } from './ProjectsNavNodeService';
export enum NavigationType {
open,
canOpen,
}
export interface NavNodeKey {
nodeId: string;
parentId?: string;
@@ -66,7 +62,6 @@ export interface INavNodeId {
}
export interface INodeNavigationContext {
type: NavigationType;
projectId: string | undefined;
nodeId: string;
parentId?: string;
@@ -81,7 +76,6 @@ export interface INodeNavigationContext {
}
export interface INodeNavigationData {
type: NavigationType;
projectId?: string;
nodeId: string;
parentId?: string;
@@ -106,10 +100,10 @@ export interface INavNodeCache {
@injectable()
export class NavNodeManagerService extends Bootstrap {
readonly syncNodeInfoCache: Map<string, INavNodeCache>;
readonly onCanOpen: ISyncExecutor<INodeNavigationData>;
readonly navigator: IExecutor<INodeNavigationData>;
readonly onMove: IExecutor<INodeMoveData>;
private readonly syncNodeInfoCache: MetadataMap<string, INavNodeCache>;
constructor(
readonly navTree: NavTreeResource,
@@ -118,22 +112,29 @@ export class NavNodeManagerService extends Bootstrap {
navigationService: NavigationService,
) {
super();
this.syncNodeInfoCache = new Map();
this.syncNodeInfoCache = new MetadataMap(() => ({ canOpen: false, canMove: false }));
this.onMove = new Executor<INodeMoveData>(null, (current, next) => current.type === next.type && current.targetNode === next.targetNode);
this.onCanOpen = new SyncExecutor<INodeNavigationData>({
nodeId: ROOT_NODE_PATH,
parentId: ROOT_NODE_PATH,
});
this.navigator = new Executor<INodeNavigationData>(
{
type: NavigationType.open,
nodeId: ROOT_NODE_PATH,
parentId: ROOT_NODE_PATH,
},
(active, current) => active.projectId === current.projectId && active.nodeId === current.nodeId && active.type === current.type,
(active, current) => active.projectId === current.projectId && active.nodeId === current.nodeId,
)
.before(navigationService.navigationTask, undefined, data => data.type === NavigationType.open)
.before(navigationService.navigationTask)
.addHandler(this.navigateHandler.bind(this));
makeObservable(this, {
syncNodeInfoCache: observable,
});
this.navNodeInfoResource.onItemDelete.addHandler(
action(key => {
ResourceKeyUtils.forEach(key, key => {
this.syncNodeInfoCache.delete(key);
});
}),
);
}
register(): void {}
@@ -141,7 +142,7 @@ export class NavNodeManagerService extends Bootstrap {
load(): void {}
getNavNodeCache(nodeId: string): INavNodeCache {
return this.syncNodeInfoCache.get(nodeId) || { canOpen: false, canMove: false };
return this.syncNodeInfoCache.get(nodeId);
}
async canMove(targetNode: NavNode, moveContexts: IDataContextProvider): Promise<boolean> {
@@ -153,25 +154,18 @@ export class NavNodeManagerService extends Bootstrap {
const move = contexts.getContext(navNodeMoveContext);
let cache = this.syncNodeInfoCache.get(targetNode.id);
if (!cache) {
cache = observable<INavNodeCache>({ canOpen: false, canMove: move.canMove });
this.syncNodeInfoCache.set(targetNode.id, cache);
}
const cache = this.syncNodeInfoCache.get(targetNode.id);
cache.canMove = move.canMove;
return move.canMove;
}
async canOpen(nodeId: string, parentId?: string, folderId?: string): Promise<boolean> {
canOpen(nodeId: string, parentId?: string, folderId?: string): boolean {
if (!this.navNodeInfoResource.has(nodeId)) {
return false;
}
const contexts = await this.navigator.execute({
type: NavigationType.canOpen,
const contexts = this.onCanOpen.execute({
nodeId,
parentId,
folderId,
@@ -179,13 +173,7 @@ export class NavNodeManagerService extends Bootstrap {
const data = contexts.getContext(this.navigationNavNodeContext);
let cache = this.syncNodeInfoCache.get(nodeId);
if (!cache) {
cache = observable<INavNodeCache>({ canOpen: data.canOpen, canMove: false });
this.syncNodeInfoCache.set(nodeId, cache);
}
const cache = this.syncNodeInfoCache.get(nodeId);
cache.canOpen = data.canOpen;
return data.canOpen;
@@ -193,7 +181,6 @@ export class NavNodeManagerService extends Bootstrap {
async navToNode(nodeId: string, parentId?: string, folderId?: string): Promise<void> {
await this.navigator.execute({
type: NavigationType.open,
nodeId,
parentId,
folderId,
@@ -385,7 +372,6 @@ export class NavNodeManagerService extends Bootstrap {
return canOpen;
},
type: data.type,
projectId,
nodeId,
parentId,
@@ -403,11 +389,7 @@ export class NavNodeManagerService extends Bootstrap {
data: INodeNavigationData,
contexts: IExecutionContextProvider<INodeNavigationData>,
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
): Promise<void> {
if (data.type !== NavigationType.open) {
return;
}
}
): Promise<void> {}
}
export function parseNodeParentId(nodeId: string): string {
@@ -12,6 +12,7 @@ import styled, { css } from 'reshadow';
import {
BASE_CONTAINERS_STYLES,
ErrorMessage,
ExceptionMessage,
IconOrImage,
Loader,
Placeholder,
@@ -122,7 +123,6 @@ export const ConnectionForm = observer<Props>(function ConnectionForm({ state, o
const style = [BASE_TAB_STYLES, tabsStyles, UNDERLINE_TAB_STYLES];
const styles = useStyles(style, BASE_CONTAINERS_STYLES, topBarStyles, formStyles);
const service = useService(ConnectionFormService);
const error = useErrorDetails(state.initError);
useExecutor({
executor: state.submittingTask,
@@ -143,8 +143,8 @@ export const ConnectionForm = observer<Props>(function ConnectionForm({ state, o
state.loadConnectionInfo();
}, [state]);
if (error.name) {
return styled(styles)(<ErrorMessage text={error.message || error.name} hasDetails={error.hasDetails} onShowDetails={error.open} />);
if (state.initError) {
return styled(styles)(<ExceptionMessage exception={state.initError} onRetry={() => state.loadConnectionInfo()} />);
}
if (!state.configured) {
@@ -181,8 +181,8 @@ export class ConnectionFormState implements IConnectionFormState {
async loadConnectionInfo(): Promise<DatabaseConnection | undefined> {
try {
await this.loadConnectionTask.execute(this);
this.initError = null;
await this.loadConnectionTask.execute(this);
return this.info;
} catch (exception: any) {
@@ -9,7 +9,7 @@ import { ConnectionInfoResource, ConnectionsManagerService, IConnectionExecutorD
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { ExecutorInterrupter, IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { INodeNavigationData, NavigationType, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { INodeNavigationData, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { resourceKeyList } from '@cloudbeaver/core-sdk';
import { ITab, NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
import { DBObjectPageService, IObjectViewerTabState, isObjectViewerTab, ObjectPage, ObjectViewerTabService } from '@cloudbeaver/plugin-object-viewer';
@@ -79,10 +79,6 @@ export class DataViewerTabService {
}
private async navigationHandler(data: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>) {
if (data.type !== NavigationType.open) {
return;
}
try {
const { nodeInfo, tabInfo, initTab, trySwitchPage } = await contexts.getContext(this.objectViewerTabService.objectViewerTabContext);
@@ -130,12 +130,7 @@ export class NavNodeContextMenuService extends Bootstrap {
}
if (action === ACTION_OPEN) {
// TODO: replace with loader, this call can lead to concurrent calls limit (Executor)
untracked(() => {
this.navNodeManagerService.canOpen(node.id, node.parentId); // trigger info
});
return this.navNodeManagerService.getNavNodeCache(node.id).canOpen;
return this.navNodeManagerService.canOpen(node.id, node.parentId);
}
return [ACTION_OPEN, ACTION_REFRESH].includes(action);
@@ -8,7 +8,7 @@
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { type INodeNavigationData, NavigationType, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { type INodeNavigationData, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { DBObjectPageService } from '../ObjectPage/DBObjectPageService';
import type { ObjectPage } from '../ObjectPage/ObjectPage';
@@ -39,10 +39,6 @@ export class ObjectPropertiesPageService {
}
private async navigationHandler(data: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>) {
if (data.type !== NavigationType.open) {
return;
}
if (!this.page) {
// TODO: it will be never true, because navHandler registers after page creation
return;
@@ -21,13 +21,7 @@ import {
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import type { IAsyncContextLoader, IExecutionContextProvider } from '@cloudbeaver/core-executor';
import {
type INodeNavigationData,
NavigationType,
NavNodeManagerService,
NodeManagerUtils,
objectNavNodeProvider,
} from '@cloudbeaver/core-navigation-tree';
import { type INodeNavigationData, NavNodeManagerService, NodeManagerUtils, objectNavNodeProvider } from '@cloudbeaver/core-navigation-tree';
import { projectProvider } from '@cloudbeaver/core-projects';
import { ResourceKey, resourceKeyList, ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
import { ITab, NavigationTabsService, TabHandler } from '@cloudbeaver/plugin-navigation-tabs';
@@ -77,6 +71,7 @@ export class ObjectViewerTabService {
}
registerTabHandler(): void {
this.navNodeManagerService.onCanOpen.addHandler(this.canOpenHandler.bind(this));
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
this.navNodeManagerService.navigator.addPostHandler(this.navigationPostHandler.bind(this));
this.connectionInfoResource.onConnectionClose.addHandler(this.closeConnectionInfoTabs.bind(this));
@@ -366,17 +361,17 @@ export class ObjectViewerTabService {
await this.dbObjectPageService.closePages(tab);
}
private canOpenHandler(data: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>) {
const nodeInfo = contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
if (NodeManagerUtils.isDatabaseObject(data.nodeId)) {
nodeInfo.markOpen();
}
}
private async navigationHandler(data: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>) {
try {
const { isSupported, nodeInfo, initTab } = await contexts.getContext(this.objectViewerTabContext);
if (isSupported) {
nodeInfo.markOpen();
}
if (data.type !== NavigationType.open) {
return;
}
const { nodeInfo, initTab } = await contexts.getContext(this.objectViewerTabContext);
const tab = initTab();
@@ -399,10 +394,6 @@ export class ObjectViewerTabService {
}
private async navigationPostHandler(data: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>) {
if (data.type !== NavigationType.open) {
return;
}
if (!contexts.hasContext(this.objectViewerTabContext)) {
return;
}
@@ -9,7 +9,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import type { IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { type INodeNavigationData, NavigationType, NavNodeInfoResource, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { type INodeNavigationData, NavNodeInfoResource, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { createResourceOfType, isResourceOfType, ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
import { NAV_NODE_TYPE_RM_RESOURCE, ResourceManagerResource, RESOURCES_NODE_PATH } from '@cloudbeaver/core-resource-manager';
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
@@ -59,6 +59,7 @@ export class PluginBootstrap extends Bootstrap {
}
register(): void | Promise<void> {
this.navNodeManagerService.onCanOpen.addHandler(this.canOpenHandler.bind(this));
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
this.actionService.addHandler({
@@ -193,46 +194,26 @@ export class PluginBootstrap extends Bootstrap {
load(): void | Promise<void> {}
private canOpenHandler(data: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>): void {
const nodeInfo = contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
if (this.canOpen(data, contexts)) {
nodeInfo.markOpen();
}
}
private async navigationHandler(data: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>) {
if (!this.resourceManagerService.enabled) {
if (!this.canOpen(data, contexts)) {
return;
}
try {
const nodeInfo = contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
if (!nodeInfo.projectId) {
return;
}
const node = this.navNodeInfoResource.get(data.nodeId);
const project = this.projectInfoResource.get(nodeInfo.projectId);
if (!project) {
return;
}
const resourceType = this.projectInfoResource.getResourceType(project, SCRIPTS_TYPE_ID);
if (!resourceType) {
return;
}
if (!node || node.nodeType !== NAV_NODE_TYPE_RM_RESOURCE || !isResourceOfType(resourceType, node.id)) {
return;
}
const resourceKey = getResourceKeyFromNodeId(node.id);
const resourceKey = getResourceKeyFromNodeId(data.nodeId);
if (!resourceKey) {
return;
}
nodeInfo.markOpen();
if (data.type !== NavigationType.open) {
return;
}
const resource = await this.resourceManagerResource.load(resourceKey);
const maxSize = this.sqlEditorSettingsService.settings.isValueDefault('maxFileSize')
@@ -272,4 +253,40 @@ export class PluginBootstrap extends Bootstrap {
this.notificationService.logException(exception as any, 'plugin_sql_editor_navigation_tab_resource_open_script_error');
}
}
private canOpen(data: INodeNavigationData, contexts: IExecutionContextProvider<INodeNavigationData>): boolean {
if (!this.resourceManagerService.enabled) {
return false;
}
const nodeInfo = contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
if (!nodeInfo.projectId) {
return false;
}
const node = this.navNodeInfoResource.get(data.nodeId);
const project = this.projectInfoResource.get(nodeInfo.projectId);
if (!project) {
return false;
}
const resourceType = this.projectInfoResource.getResourceType(project, SCRIPTS_TYPE_ID);
if (!resourceType) {
return false;
}
if (!node || node.nodeType !== NAV_NODE_TYPE_RM_RESOURCE || !isResourceOfType(resourceType, node.id)) {
return false;
}
const resourceKey = getResourceKeyFromNodeId(node.id);
if (!resourceKey) {
return false;
}
return true;
}
}