From ae1a57278df1b9fedd83e648475040a7b3775a6d Mon Sep 17 00:00:00 2001 From: Eliav Maimon Date: Wed, 26 Aug 2026 14:43:46 +0300 Subject: [PATCH 01/22] fix: clear customValidationError on unmount --- .../components/layer-details/entity.dialog.tsx | 11 ++--------- .../raster/entity.raster.delete-dialog.tsx | 6 ------ .../layer-details/raster/entity.raster.dialog.tsx | 1 - .../raster/entity.raster.revert-dialog.tsx | 6 ------ .../polygon-parts-extent-query-vector-layer.tsx | 3 ++- src/discrete-layer/models/discreteLayersStore.ts | 7 +------ 6 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/discrete-layer/components/layer-details/entity.dialog.tsx b/src/discrete-layer/components/layer-details/entity.dialog.tsx index 92a38d4c9..990a48ad1 100644 --- a/src/discrete-layer/components/layer-details/entity.dialog.tsx +++ b/src/discrete-layer/components/layer-details/entity.dialog.tsx @@ -420,9 +420,8 @@ export const EntityDialog: React.FC = observer((props: Entity const closeDialog = useCallback(() => { onSetOpen(false); - store.discreteLayersStore.resetUpdateMode(); clearSyncWarnings(); - }, [onSetOpen, store.discreteLayersStore]); + }, [onSetOpen]); useEffect(() => { const hasAnyQuerySucceeded = checkHasQueriesSucceeded(); @@ -435,13 +434,7 @@ export const EntityDialog: React.FC = observer((props: Entity data: inputValues as ILayerImage, }); } - }, [ - mutationQuery.data, - mutationQuery.loading, - closeDialog, - store.discreteLayersStore, - inputValues, - ]); + }, [mutationQuery.data, mutationQuery.loading, closeDialog, inputValues]); return (
diff --git a/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx b/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx index 67f62d4f8..4830183b1 100644 --- a/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx +++ b/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx @@ -32,12 +32,6 @@ export const EntityDeleteRasterDialog: React.FC = observer( } }, [store.discreteLayersStore.customValidationError]); - useEffect(() => { - return () => { - store.discreteLayersStore.clearCustomValidationError(); - }; - }, []); - useEffect(() => { if (mutationQuery.data && !mutationQuery.error) { props.onSuccess?.(); diff --git a/src/discrete-layer/components/layer-details/raster/entity.raster.dialog.tsx b/src/discrete-layer/components/layer-details/raster/entity.raster.dialog.tsx index ee20de82f..2fb625d45 100644 --- a/src/discrete-layer/components/layer-details/raster/entity.raster.dialog.tsx +++ b/src/discrete-layer/components/layer-details/raster/entity.raster.dialog.tsx @@ -374,7 +374,6 @@ const EntityRasterDialogInner: React.FC = observer( if (job) { setJob?.(undefined); } - store.discreteLayersStore.resetUpdateMode(); clearSyncWarnings(); }, [onSetOpen, store.discreteLayersStore]); diff --git a/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx b/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx index ffe6be433..7b2a35e87 100644 --- a/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx +++ b/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx @@ -238,12 +238,6 @@ export const EntityRevertRasterDialog: React.FC = observer( } }, [store.discreteLayersStore.customValidationError]); - useEffect(() => { - return () => { - store.discreteLayersStore.clearCustomValidationError(); - }; - }, []); - useEffect(() => { if (mutationQuery.data && !mutationQuery.error) { props.onSuccess?.(); diff --git a/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx b/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx index 73af87640..181d049fa 100644 --- a/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx +++ b/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx @@ -85,8 +85,9 @@ export const PolygonPartsExtentQueryVectorLayer: React.FC< debounceCall.cancel(); mapOl.un('moveend', debounceCall); mapOl.un('postrender', handlePostRender); + store.discreteLayersStore.clearCustomValidationError(); } catch (e) { - console.log('OL "moveEnd" remove listener failed', e); + console.log('Failed to unmount PolygonPartsExtentQueryVectorLayer', e); } }; }, [queryExecutor]); diff --git a/src/discrete-layer/models/discreteLayersStore.ts b/src/discrete-layer/models/discreteLayersStore.ts index d6a6662d5..0e389f863 100644 --- a/src/discrete-layer/models/discreteLayersStore.ts +++ b/src/discrete-layer/models/discreteLayersStore.ts @@ -277,10 +277,6 @@ export const discreteLayersStore = ModelBase self.selectedLayerOperationMode = mode; } - function resetUpdateMode(): void { - self.customValidationError = undefined; - } - function selectLayerByID(layerID: string): void { const layer = self.layersImages?.find(layer => layer.id === layerID); self.selectedLayer = layer ? cloneDeep(layer) : undefined; @@ -467,7 +463,7 @@ export const discreteLayersStore = ModelBase self.mapViewerExtentPolygon = cloneDeep(feature); } - function setCustomValidationError(err: Record | undefined): void { + function setCustomValidationError(err: Record): void { self.customValidationError = cloneDeep(err); } @@ -693,7 +689,6 @@ export const discreteLayersStore = ModelBase resetAppState, resetTabView, getFieldConfig, - resetUpdateMode, setMapViewerExtentPolygon, setCustomValidationError, clearCustomValidationError, From 952a9feff290ca3096b9d484eb554f0b7e17ce13 Mon Sep 17 00:00:00 2001 From: Eliav Maimon Date: Wed, 2 Sep 2026 09:36:44 +0300 Subject: [PATCH 02/22] fix: refactor error handling --- .../error/graphql.error-presentor.tsx | 91 +++---- .../error/logic.error-presentor.tsx | 2 +- .../error/validations.error-presentor.tsx | 45 ---- src/common/components/grid/grid.tsx | 2 +- .../components/error/error-presentor.tsx | 76 ++++++ .../components/helpers/errorUtils.ts | 71 ++++- .../components/job-manager/jobs.dialog.tsx | 6 +- .../destructive-action-dialog.tsx | 245 +++++++++--------- .../layer-details/layer-datails-form.tsx | 37 +-- .../raster/entity.raster.delete-dialog.tsx | 23 +- .../raster/entity.raster.dialog.tsx | 2 - .../raster/entity.raster.revert-dialog.tsx | 51 ++-- .../raster/layer-details-form.raster.tsx | 40 ++- ...olygon-parts-extent-query-vector-layer.tsx | 7 +- .../raster/resolution-conflict.dialog.tsx | 22 +- .../raster/state-machine/helpers.ts | 2 +- .../components/map-container/bbox.dialog.tsx | 20 +- .../components/map-container/poi.dialog.tsx | 5 +- .../models/discreteLayersStore.ts | 18 +- 19 files changed, 406 insertions(+), 359 deletions(-) delete mode 100644 src/common/components/error/validations.error-presentor.tsx create mode 100644 src/discrete-layer/components/error/error-presentor.tsx diff --git a/src/common/components/error/graphql.error-presentor.tsx b/src/common/components/error/graphql.error-presentor.tsx index 3cc3e773d..966e91ce6 100644 --- a/src/common/components/error/graphql.error-presentor.tsx +++ b/src/common/components/error/graphql.error-presentor.tsx @@ -1,77 +1,44 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/naming-convention */ import React from 'react'; -import { FormattedMessage, useIntl } from 'react-intl'; -import { isEmpty } from 'lodash'; +import { useIntl } from 'react-intl'; import { IconButton } from '@map-colonies/react-core'; import { AutoDirectionBox } from '../auto-direction-box/auto-direction-box.component'; -import { - getErrorMessage, - IServerError, - SERVER_ERROR_RESPONSE_CODE, - USER_ERROR_RESPONSE_CODE, -} from './helpers'; +import { getGraphqlErrorItems } from '../../../discrete-layer/components/helpers/errorUtils'; import './error-presentor.css'; +const NONE = 0; + export interface IGpaphQLError { - error: any; + error: unknown; } export const GraphQLError: React.FC = ({ error }) => { const intl = useIntl(); + const errors = getGraphqlErrorItems([error].filter(Boolean), intl); + + if (errors.length === NONE) { + return null; + } return ( - <> - {!isEmpty(error?.response) && ( - - { - e.preventDefault(); - e.stopPropagation(); - }} - /> -
    - {error.response.errors?.map((error: IServerError, index: number) => { - return ( -
  • - ); - })} - {error.response.status >= USER_ERROR_RESPONSE_CODE && - error.response.status < SERVER_ERROR_RESPONSE_CODE && ( -
  • - -
  • - )} - {error.response.status >= SERVER_ERROR_RESPONSE_CODE && ( -
  • - -
  • - )} -
-
- )} - {isEmpty(error?.response) && !isEmpty(error?.message) && ( - - { - e.preventDefault(); - e.stopPropagation(); - }} - /> -
    -
  • -
-
- )} - + + { + e.preventDefault(); + e.stopPropagation(); + }} + /> +
    + {errors.map((err, index) => ( +
  • + ))} +
+
); }; diff --git a/src/common/components/error/logic.error-presentor.tsx b/src/common/components/error/logic.error-presentor.tsx index 0f8b87453..2090426f7 100644 --- a/src/common/components/error/logic.error-presentor.tsx +++ b/src/common/components/error/logic.error-presentor.tsx @@ -36,7 +36,7 @@ export const LogicError: React.FC = ({ errors }) => { dir="auto" className={error.level} dangerouslySetInnerHTML={{ - __html: intl.formatMessage({ id: error.code }, { value: error.message }), + __html: intl.formatMessage({ id: error.code }, { value: error.errText }), }} /> ))} diff --git a/src/common/components/error/validations.error-presentor.tsx b/src/common/components/error/validations.error-presentor.tsx deleted file mode 100644 index 447bbf2e9..000000000 --- a/src/common/components/error/validations.error-presentor.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/naming-convention */ -import React from 'react'; -import { IconButton } from '@map-colonies/react-core'; -import { AutoDirectionBox } from '../auto-direction-box/auto-direction-box.component'; - -import './error-presentor.css'; - -const NONE = 0; - -interface IValidationsError { - errors: Record; -} - -export const ValidationsError: React.FC = ({ errors }) => { - return ( - <> - {Object.keys(errors).length > NONE && ( - - { - e.preventDefault(); - e.stopPropagation(); - }} - /> -
    - {Object.keys(errors).map((key: string) => { - return errors[key].map((errorMessage: string, index: number) => { - return ( -
  • - ); - }); - })} -
-
- )} - - ); -}; diff --git a/src/common/components/grid/grid.tsx b/src/common/components/grid/grid.tsx index 012a6ab96..930f54a9e 100644 --- a/src/common/components/grid/grid.tsx +++ b/src/common/components/grid/grid.tsx @@ -234,7 +234,7 @@ export const GridComponent: React.FC = (props) => { if (!row) { handleFocusError?.({ code: 'warning.row-not-found', - message: '', + errText: '', level: 'warning', }); setIsRowFound?.(false); diff --git a/src/discrete-layer/components/error/error-presentor.tsx b/src/discrete-layer/components/error/error-presentor.tsx new file mode 100644 index 000000000..aa0b505c3 --- /dev/null +++ b/src/discrete-layer/components/error/error-presentor.tsx @@ -0,0 +1,76 @@ +import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'; +import { observer } from 'mobx-react'; +import { useIntl } from 'react-intl'; +import { IconButton } from '@map-colonies/react-core'; +import { AutoDirectionBox } from '../../../common/components/auto-direction-box/auto-direction-box.component'; +import { useStore } from '../../models'; +import { ErrorType, IError, getGraphqlErrorItems } from '../helpers/errorUtils'; + +import '../../../common/components/error/error-presentor.css'; + +const NONE = 0; + +export interface IErrorPresentor { + errors?: ErrorType[]; + onErrorsChange?: (hasErrors: boolean) => void; +} + +export interface IErrorPresentorRef { + hasErrors: boolean; +} + +export const ErrorPresentor = observer( + forwardRef(({ errors, onErrorsChange }, ref) => { + const intl = useIntl(); + const store = useStore(); + const { serviceError, customValidationError } = store.discreteLayersStore; + + const allErrors: IError[] = useMemo(() => { + const combinedErrors = [ + ...getGraphqlErrorItems([...(errors ?? []), serviceError].filter(Boolean), intl), + ...(customValidationError ? [customValidationError] : []), + ]; + return combinedErrors; + }, [errors, serviceError, customValidationError, intl]); + + const hasErrors = allErrors.length > NONE; + + const iconButtonErrorLevel = useMemo(() => { + return allErrors.some((error) => error.level === 'error') ? 'error' : 'warning'; + }, [allErrors]); + + useImperativeHandle(ref, () => ({ hasErrors }), [hasErrors]); + + useEffect(() => { + onErrorsChange?.(hasErrors); + }, [hasErrors, onErrorsChange]); + + if (!hasErrors) { + return null; + } + + return ( + + { + e.preventDefault(); + e.stopPropagation(); + }} + /> +
    + {allErrors.map((error, index) => ( +
  • + ))} +
+
+ ); + }) +); + +ErrorPresentor.displayName = 'ErrorPresentor'; diff --git a/src/discrete-layer/components/helpers/errorUtils.ts b/src/discrete-layer/components/helpers/errorUtils.ts index b8d513540..410da6f11 100644 --- a/src/discrete-layer/components/helpers/errorUtils.ts +++ b/src/discrete-layer/components/helpers/errorUtils.ts @@ -1,7 +1,76 @@ +import { IntlShape } from 'react-intl'; +import { isEmpty } from 'lodash'; +import { + getErrorMessage, + IServerError, + SERVER_ERROR_RESPONSE_CODE, + USER_ERROR_RESPONSE_CODE, +} from '../../../common/components/error/helpers'; + +export type ErrorType = IError | IGraphqlError | undefined; + export type ErrorLevel = 'error' | 'warning'; export interface IError { code?: string; - message?: string; + errText?: string; level?: ErrorLevel; } + +export interface IGraphqlError { + response?: { + errors?: IServerError[]; + status?: number; + }; + message?: string; +} + +const isIError = (error: unknown): error is IError => { + const IERROR_MARKER_FIELD: keyof IError = 'errText'; + return typeof error === 'object' && error !== null && IERROR_MARKER_FIELD in error; +}; + +export const getGraphqlErrorItem = (error: unknown, intl: IntlShape): IError[] => { + const graphqlError = error as IGraphqlError; + + if (!isEmpty(graphqlError?.response)) { + const items: IError[] = (graphqlError.response?.errors ?? []).map((responseError) => ({ + title: getErrorMessage(responseError, intl), + level: 'error', + })); + + const status = graphqlError.response?.status; + if (status && status >= USER_ERROR_RESPONSE_CODE && status < SERVER_ERROR_RESPONSE_CODE) { + items.push({ + errText: intl.formatMessage({ id: `general.http-${status}.error` }), + level: 'error', + }); + } + if (status && status >= SERVER_ERROR_RESPONSE_CODE) { + items.push({ errText: intl.formatMessage({ id: 'general.server.error' }), level: 'error' }); + } + + return items; + } + + if (!isEmpty(graphqlError?.message)) { + return [ + { errText: getErrorMessage(graphqlError as unknown as IServerError, intl), level: 'error' }, + ]; + } + + return []; +}; + +export const getGraphqlErrorItems = (errors: unknown[], intl: IntlShape): IError[] => { + const graphQLErrors = errors.flatMap((error) => + isIError(error) ? [error] : getGraphqlErrorItem(error, intl) + ); + return graphQLErrors; +}; + +export const getErrorsItems = (errors: Record): IError[] => { + return Object.entries(errors).flatMap(([_, messages]) => + messages.map((message) => ({ errText: message, level: 'error' } as IError)) + ); +}; diff --git a/src/discrete-layer/components/job-manager/jobs.dialog.tsx b/src/discrete-layer/components/job-manager/jobs.dialog.tsx index 18a2df2e1..eca110e56 100644 --- a/src/discrete-layer/components/job-manager/jobs.dialog.tsx +++ b/src/discrete-layer/components/job-manager/jobs.dialog.tsx @@ -227,7 +227,7 @@ export const JobsDialog: React.FC = observer((props: JobsDialog const message = getResponseErrorMesssage(mutationQuery.error.response); newError = { code: 'error.server-error', - message: `${Number(status) > 0 ? status + ' ' : ''}${message}`, + errText: `${Number(status) > 0 ? status + ' ' : ''}${message}`, level: 'error', }; } @@ -239,7 +239,7 @@ export const JobsDialog: React.FC = observer((props: JobsDialog if (focusOnJob && focusError?.code) { newError = { code: focusError.code, - message: `${focusOnJob.resourceId} (${dateFormatter(focusOnJob.updated, true)})`, + errText: `${focusOnJob.resourceId} (${dateFormatter(focusOnJob.updated, true)})`, level: focusError.level, }; } @@ -379,7 +379,7 @@ export const JobsDialog: React.FC = observer((props: JobsDialog if (diff > CONFIG.JOB_MANAGER.MAX_DATE_RANGE_DAYS) { setDateRangeError({ code: 'warning.exceeded-date-range', - message: CONFIG.JOB_MANAGER.MAX_DATE_RANGE_DAYS, + errText: CONFIG.JOB_MANAGER.MAX_DATE_RANGE_DAYS, level: 'warning', }); } else { diff --git a/src/discrete-layer/components/layer-details/destructive-action-dialog.tsx b/src/discrete-layer/components/layer-details/destructive-action-dialog.tsx index 78e7ad53a..ccc2e202e 100644 --- a/src/discrete-layer/components/layer-details/destructive-action-dialog.tsx +++ b/src/discrete-layer/components/layer-details/destructive-action-dialog.tsx @@ -1,4 +1,5 @@ import React, { useMemo } from 'react'; +import { observer } from 'mobx-react'; import { FormattedMessage, useIntl } from 'react-intl'; import { Formik, FormikProps } from 'formik'; import { DialogContent } from '@material-ui/core'; @@ -7,8 +8,8 @@ import { Dialog } from '@map-colonies/react-core'; import { Box } from '@map-colonies/react-components'; import { Mode } from '../../../common/models/mode.enum'; import { getTextStyle } from '../../../common/helpers/style'; -import { GraphQLError } from '../../../common/components/error/graphql.error-presentor'; -import { ValidationsError } from '../../../common/components/error/validations.error-presentor'; +import { ErrorPresentor } from '../error/error-presentor'; +import { ErrorType, getErrorsItems } from '../helpers/errorUtils'; import { UserAction } from '../../models/userStore'; import { EntityDescriptorModelType, RecordType, useStore } from '../../models'; import { ILayerImage } from '../../models/layerImage'; @@ -24,8 +25,6 @@ import { LayerHeader } from './layer-header'; import './destructive-action-dialog.css'; -const NONE = 0; - export interface ActionDialogProps { layerRecord: ILayerImage; isOpen: boolean; @@ -43,9 +42,8 @@ interface DestructiveActionDialogProps { onClose: () => void; onSubmit: (approverName: string, approvalCode: string) => void; loading: boolean; - error: unknown; - polygonPartsError?: Record | null; map: JSX.Element | null; + error?: ErrorType[]; sidePanel?: JSX.Element | null; onFieldsValidate?: () => void; openRelatedJob?: { @@ -53,134 +51,133 @@ interface DestructiveActionDialogProps { }; } -export const DestructiveActionDialog: React.FC = ({ - elementId, - action, - isOpen, - layerRecord, - recordType, - disclaimerActionId, - onClose, - onSubmit, - loading, - error, - polygonPartsError = null, - map, - sidePanel = null, - onFieldsValidate, - openRelatedJob, -}) => { - const intl = useIntl(); - const store = useStore(); +export const DestructiveActionDialog: React.FC = observer( + ({ + elementId, + action, + isOpen, + layerRecord, + recordType, + disclaimerActionId, + onClose, + onSubmit, + loading, + error, + map, + sidePanel = null, + onFieldsValidate, + openRelatedJob, + }) => { + const intl = useIntl(); + const store = useStore(); - const fieldsSchema = useMemo(() => buildApprovalFieldsSchema(intl), [intl]); + const fieldsSchema = useMemo(() => buildApprovalFieldsSchema(intl), [intl]); - return ( - - - - - - + + - { - onFieldsValidate?.(); - }} - onSubmit={(values) => { - onSubmit(values.approverName, values.approvalCode); - }} - > - {(formikProps: FormikProps) => { - let fieldErrors: Record = {}; - Object.entries(formikProps.errors).forEach(([key, value]) => { - if (formikProps.getFieldMeta(key).touched) { - fieldErrors[key] = (Array.isArray(value) ? value : [value]) as string[]; - } - }); - const mergedErrors = { ...fieldErrors, ...(polygonPartsError ?? {}) }; + + + + { + onFieldsValidate?.(); + }} + onSubmit={(values) => { + onSubmit(values.approverName, values.approvalCode); + }} + > + {(formikProps: FormikProps) => { + const fieldErrors: Record = {}; + Object.entries(formikProps.errors).forEach(([key, value]) => { + if (formikProps.getFieldMeta(key).touched) { + fieldErrors[key] = (Array.isArray(value) ? value : [value]) as string[]; + } + }); - return ( -
- {sidePanel ? ( - - - {sidePanel} - + return ( + + {sidePanel ? ( + + + {sidePanel} + + + {map} - {map} - - ) : ( - <> - {map} - - - )} - - - - {Object.keys(mergedErrors).length > NONE && ( - - )} - - - {openRelatedJob && ( + ) : ( + <> + {map} + + + )} + + + + + + {openRelatedJob && ( + + )} + - )} - - + - - - ); - }} -
-
-
-
- ); -}; + + ); + }} + + + + + ); + } +); diff --git a/src/discrete-layer/components/layer-details/layer-datails-form.tsx b/src/discrete-layer/components/layer-details/layer-datails-form.tsx index 63c606011..4785146e9 100644 --- a/src/discrete-layer/components/layer-details/layer-datails-form.tsx +++ b/src/discrete-layer/components/layer-details/layer-datails-form.tsx @@ -9,8 +9,8 @@ import { get, isEmpty } from 'lodash'; import { Button } from '@map-colonies/react-core'; import { Box } from '@map-colonies/react-components'; import { Mode } from '../../../common/models/mode.enum'; -import { ValidationsError } from '../../../common/components/error/validations.error-presentor'; -import { GraphQLError } from '../../../common/components/error/graphql.error-presentor'; +import { ErrorPresentor } from '../error/error-presentor'; +import { getErrorsItems, IGraphqlError } from '../helpers/errorUtils'; import { MetadataFile } from '../../../common/components/file-picker'; import { Curtain } from '../../../common/components/curtain/curtain.component'; // import useSessionStoreWatcherForm from '../../../common/hooks/useSessionStoreWatcherForm'; @@ -52,7 +52,7 @@ interface LayerDetailsFormCustomProps { entityDescriptors: EntityDescriptorModelType[]; layerRecord: LayerMetadataMixedUnion; vestValidationResults: DraftResult; - mutationQueryError: unknown; + mutationQueryError: IGraphqlError | undefined; mutationQueryLoading: boolean; closeDialog: () => void; } @@ -101,7 +101,7 @@ const InnerForm = (props: LayerDetailsFormCustomProps & FormikProps) const status = props.status as StatusError | Record; const intl = useIntl(); - const [graphQLError, setGraphQLError] = useState(mutationQueryError); + const [graphQLError, setGraphQLError] = useState(mutationQueryError); const [isSelectedFiles, setIsSelectedFiles] = useState(false); const [firstPhaseErrors, setFirstPhaseErrors] = useState>({}); const [showCurtain, setShowCurtain] = useState(true); @@ -138,6 +138,19 @@ const InnerForm = (props: LayerDetailsFormCustomProps & FormikProps) }); }, [errors, getYupErrors, getStatusErrors]); + const formValidationErrorItems = useMemo(() => { + if (Object.keys(firstPhaseErrors).length > NONE && JSON.stringify(firstPhaseErrors) !== '{}') { + return getErrorsItems(firstPhaseErrors); + } + if ( + (Object.keys(errors).length === NONE || JSON.stringify(errors) === '{}') && + vestValidationResults.errorCount > NONE + ) { + return getErrorsItems(vestValidationResults.getErrors()); + } + return []; + }, [firstPhaseErrors, errors, vestValidationResults]); + const entityFormikHandlers: EntityFormikHandlers = useMemo( () => ({ handleChange: (e: React.ChangeEvent): void => { @@ -205,7 +218,7 @@ const InnerForm = (props: LayerDetailsFormCustomProps & FormikProps) ...ingestionFields, }); - setGraphQLError(metadata.error); + setGraphQLError(metadata.error as IGraphqlError); }; return ( @@ -240,19 +253,7 @@ const InnerForm = (props: LayerDetailsFormCustomProps & FormikProps) - {Object.keys(firstPhaseErrors).length > NONE && - JSON.stringify(firstPhaseErrors) !== '{}' && ( - - )} - {(Object.keys(errors).length === NONE || JSON.stringify(errors) === '{}') && - vestValidationResults.errorCount > NONE && ( - - )} - {graphQLError !== undefined && - graphQLError !== null && - graphQLError && - JSON.stringify(graphQLError) !== '{}' && - Object.keys(graphQLError).length > NONE && } + {mode !== Mode.VIEW && ( diff --git a/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx b/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx index 4830183b1..1fc61ae44 100644 --- a/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx +++ b/src/discrete-layer/components/layer-details/raster/entity.raster.delete-dialog.tsx @@ -6,6 +6,7 @@ import { ActionDialogProps, DestructiveActionDialog } from '../destructive-actio import './entity.raster.delete-dialog.css'; import { OlLayerMap } from './layer-map'; +import { IGraphqlError } from '../../helpers/errorUtils'; type DeleteRasterLayerResult = Awaited>; // see (MAPCO-11216) @@ -13,25 +14,12 @@ export const EntityDeleteRasterDialog: React.FC = observer( (props: ActionDialogProps) => { const store = useStore(); const mutationQuery = useQuery(); - - const [mutationError, setMutationError] = useState(null); - const [polygonPartsError, setPolygonPartsError] = useState | null>( - null - ); + const [mutationError, setMutationError] = useState(); const closeDialog = (): void => { props.onSetOpen(false); }; - useEffect(() => { - if (store.discreteLayersStore.customValidationError) { - setPolygonPartsError(store.discreteLayersStore.customValidationError); - setMutationError(null); - } else { - setPolygonPartsError(null); - } - }, [store.discreteLayersStore.customValidationError]); - useEffect(() => { if (mutationQuery.data && !mutationQuery.error) { props.onSuccess?.(); @@ -39,7 +27,6 @@ export const EntityDeleteRasterDialog: React.FC = observer( } if (mutationQuery.error) { setMutationError(mutationQuery.error); - setPolygonPartsError(null); } }, [mutationQuery.data, mutationQuery.error]); @@ -67,8 +54,7 @@ export const EntityDeleteRasterDialog: React.FC = observer( onClose={closeDialog} onSubmit={deleteLayer} loading={mutationQuery.loading} - error={mutationError} - polygonPartsError={polygonPartsError} + error={[mutationError]} map={ = observer( > } onFieldsValidate={() => { - setMutationError(null); - setPolygonPartsError(null); + setMutationError(undefined); }} /> ); diff --git a/src/discrete-layer/components/layer-details/raster/entity.raster.dialog.tsx b/src/discrete-layer/components/layer-details/raster/entity.raster.dialog.tsx index 2fb625d45..0eac581b5 100644 --- a/src/discrete-layer/components/layer-details/raster/entity.raster.dialog.tsx +++ b/src/discrete-layer/components/layer-details/raster/entity.raster.dialog.tsx @@ -421,8 +421,6 @@ const EntityRasterDialogInner: React.FC = observer( }} vestValidationResults={vestValidationResults} closeDialog={closeDialog} - customErrorReset={store.discreteLayersStore.clearCustomValidationError} - customError={store.discreteLayersStore.customValidationError} /> )} diff --git a/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx b/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx index 7b2a35e87..72c89511e 100644 --- a/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx +++ b/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { useIntl } from 'react-intl'; +import { IntlShape, useIntl } from 'react-intl'; import { observer } from 'mobx-react'; import { BBox, Feature, GeoJsonProperties, Geometry, Polygon } from 'geojson'; import { Style } from 'ol/style'; @@ -29,6 +29,7 @@ import { useStore, } from '../../../models'; import { ProductType } from '../../../models/ProductTypeEnum'; +import { getGraphqlErrorItem, IGraphqlError } from '../../helpers/errorUtils'; import useZoomLevelsTable from '../../export-layer/hooks/useZoomLevelsTable'; import { ActionDialogProps, DestructiveActionDialog } from '../destructive-action-dialog'; import { useRasterBackupData } from './use-raster-backup-data.hook'; @@ -58,16 +59,26 @@ const NO_VALUE = '–'; const JOB_KEYWORD_REGEX = /\bjob\b/i; const UUID_REGEX = /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/; -const extractJobIdFromError = (error: IServerError | undefined): string | undefined => { +const extractJobIdFromError = ( + error: IGraphqlError | undefined, + intl: IntlShape +): string | undefined => { if (!error) { return; } - const message = getErrorMessage(error); - const hasJobKeyword = JOB_KEYWORD_REGEX.test(message); - const uuidMatch = message.match(UUID_REGEX); - if (hasJobKeyword && uuidMatch) { - return uuidMatch[0]; + + const errors = getGraphqlErrorItem(error, intl); + const message = errors[0].errText; + + if (!message) { + return; + } + + if (!JOB_KEYWORD_REGEX.test(message)) { + return; } + + return message.match(UUID_REGEX)?.[0]; }; export const EntityRevertRasterDialog: React.FC = observer( @@ -79,11 +90,8 @@ export const EntityRevertRasterDialog: React.FC = observer( const currentLayer = props.layerRecord as LayerRasterRecordModelType; const mutationQuery = useQuery(); + const [mutationError, setMutationError] = useState(); - const [mutationError, setMutationError] = useState(null); - const [polygonPartsError, setPolygonPartsError] = useState | null>( - null - ); const [showChangedArea, setShowChangedArea] = useState(true); const [showBackup, setShowBackup] = useState(true); const [showExisting, setShowExisting] = useState(false); @@ -229,15 +237,6 @@ export const EntityRevertRasterDialog: React.FC = observer( [changedArea] ); - useEffect(() => { - if (store.discreteLayersStore.customValidationError) { - setPolygonPartsError(store.discreteLayersStore.customValidationError); - setMutationError(null); - } else { - setPolygonPartsError(null); - } - }, [store.discreteLayersStore.customValidationError]); - useEffect(() => { if (mutationQuery.data && !mutationQuery.error) { props.onSuccess?.(); @@ -245,7 +244,6 @@ export const EntityRevertRasterDialog: React.FC = observer( } if (mutationQuery.error) { setMutationError(mutationQuery.error); - setPolygonPartsError(null); } }, [mutationQuery.data, mutationQuery.error]); @@ -283,7 +281,7 @@ export const EntityRevertRasterDialog: React.FC = observer( }; const submitErrorJobId = useMemo( - () => extractJobIdFromError(mutationError as IServerError | undefined), + () => extractJobIdFromError(mutationError, intl), [mutationError] ); @@ -299,11 +297,12 @@ export const EntityRevertRasterDialog: React.FC = observer( onSubmit={revertLayer} loading={loading || mutationQuery.loading} openRelatedJob={submitErrorJobId ? { jobId: submitErrorJobId } : undefined} - error={metadataError || outerPerimeterError || mutationError} - polygonPartsError={polygonPartsError} + error={useMemo( + () => [metadataError, outerPerimeterError, mutationQuery.error], + [metadataError, outerPerimeterError, mutationQuery.error] + )} onFieldsValidate={(): void => { - setMutationError(null); - setPolygonPartsError(null); + setMutationError(undefined); }} map={ void; - customErrorReset: () => void; - customError?: Record | undefined; } export interface StatusError { @@ -112,8 +111,6 @@ export const InnerRasterForm = ( layerRecord, vestValidationResults, closeDialog, - customErrorReset, - customError, } = props; const status = props.status as StatusError | Record; @@ -154,9 +151,21 @@ export const InnerRasterForm = ( const getStatusErrors = useCallback((): StatusError | Record => { return { ...(get(status, 'errors') as Record), - ...customError, }; - }, [status, customError]); + }, [status]); + + const formValidationErrorItems = useMemo(() => { + if (Object.keys(firstPhaseErrors).length > NONE && JSON.stringify(firstPhaseErrors) !== '{}') { + return getErrorsItems(firstPhaseErrors); + } + if ( + (Object.keys(errors).length === NONE || JSON.stringify(errors) === '{}') && + vestValidationResults.errorCount > NONE + ) { + return getErrorsItems(vestValidationResults.getErrors()); + } + return []; + }, [firstPhaseErrors, errors, vestValidationResults]); const getYupErrors = useCallback((): Record => { const validationResults: Record = {}; @@ -214,7 +223,6 @@ export const InnerRasterForm = ( handleChange(e); }, handleBlur: (e: React.FocusEvent): void => { - customErrorReset(); handleBlur(e); setIngestionFieldsCurtain(true); }, @@ -343,19 +351,7 @@ export const InnerRasterForm = ( - {/* { - topLevelFieldsErrors && Object.keys(topLevelFieldsErrors).length > NONE && - JSON.stringify(topLevelFieldsErrors) !== '{}' && - - } */} - {Object.keys(firstPhaseErrors).length > NONE && - JSON.stringify(firstPhaseErrors) !== '{}' && ( - - )} - {(Object.keys(errors).length === NONE || JSON.stringify(errors) === '{}') && - vestValidationResults.errorCount > NONE && ( - - )} + {isGoToJobEnabled(state.context) && ( @@ -438,8 +434,6 @@ interface LayerDetailsFormProps { onSubmit: (values: Record) => void; vestValidationResults: DraftResult; closeDialog: () => void; - customErrorReset: () => void; - customError?: Record | undefined; } export default withFormik({ diff --git a/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx b/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx index 181d049fa..a2cef31e5 100644 --- a/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx +++ b/src/discrete-layer/components/layer-details/raster/polygon-parts-extent-query-vector-layer.tsx @@ -85,7 +85,7 @@ export const PolygonPartsExtentQueryVectorLayer: React.FC< debounceCall.cancel(); mapOl.un('moveend', debounceCall); mapOl.un('postrender', handlePostRender); - store.discreteLayersStore.clearCustomValidationError(); + store.discreteLayersStore.clearServiceError(); } catch (e) { console.log('Failed to unmount PolygonPartsExtentQueryVectorLayer', e); } @@ -172,12 +172,11 @@ export const PolygonPartsExtentQueryVectorLayer: React.FC< } if (activeRequestIdRef.current === requestId && hasSuccessfulQuery) { - store.discreteLayersStore.clearCustomValidationError(); + store.discreteLayersStore.clearServiceError(); } } catch (error) { if (activeRequestIdRef.current === requestId) { - const errorMessage = intl.formatMessage({ id: 'resolutionConflict.error.queryFailed' }); - store.discreteLayersStore.setCustomValidationError({ error: [errorMessage] }); + store.discreteLayersStore.setServiceError(error); } } finally { if (activeRequestIdRef.current === requestId) { diff --git a/src/discrete-layer/components/layer-details/raster/resolution-conflict.dialog.tsx b/src/discrete-layer/components/layer-details/raster/resolution-conflict.dialog.tsx index 4662ce81f..935326e7d 100644 --- a/src/discrete-layer/components/layer-details/raster/resolution-conflict.dialog.tsx +++ b/src/discrete-layer/components/layer-details/raster/resolution-conflict.dialog.tsx @@ -18,14 +18,15 @@ import { Typography, } from '@map-colonies/react-core'; import { AutoDirectionBox } from '../../../../common/components/auto-direction-box/auto-direction-box.component'; -import { ValidationsError } from '../../../../common/components/error/validations.error-presentor'; import { FlyTo } from '../../../../common/components/ol-map/fly-to'; +import { getErrorsItems } from '../../helpers/errorUtils'; import CONFIG from '../../../../common/config'; import { Domain } from '../../../../common/models/domain'; import { Mode } from '../../../../common/models/mode.enum'; import { isGeometryEmpty } from '../../../../common/utils/geo.tools'; import { EntityDescriptorModelType } from '../../../models'; import useZoomLevelsTable from '../../export-layer/hooks/useZoomLevelsTable'; +import { ErrorPresentor } from '../../error/error-presentor'; import { isEmptyLayerRecord } from '../utils'; import { LayerHeader } from '../layer-header'; import { FeatureType } from './feature-type.enum'; @@ -97,9 +98,6 @@ const ResolutionConflictDialogComponent: React.FC const [listFilterMode, setListFilterMode] = useState('all'); const [selectedItem, setSelectedItem] = useState(); const [polygonPartsErrors, setPolygonPartsErrors] = useState(); - const storePolygonPartsErrors = - state.context.store.discreteLayersStore.customValidationError?.error; - const displayedPolygonPartsErrors = polygonPartsErrors ?? storePolygonPartsErrors; const selectedLowResolutionFeatureId = getFeatureIdentifier(selectedItem); const entityDescriptors = state.context.store.discreteLayersStore ?.entityDescriptors as EntityDescriptorModelType[]; @@ -169,14 +167,6 @@ const ResolutionConflictDialogComponent: React.FC } }, [hasExceededFeatures]); - useEffect(() => { - if (storePolygonPartsErrors) { - setPolygonPartsErrors(storePolygonPartsErrors); - } else { - setPolygonPartsErrors(undefined); - } - }, [storePolygonPartsErrors]); - const selectedLowResolutionPosition = useMemo(() => { if (!selectedLowResolutionFeatureId) { return undefined; @@ -441,7 +431,7 @@ const ResolutionConflictDialogComponent: React.FC isLoadingLowResolutionParts || hasExceededFeatures || isEmptyLayerRecord(state.context.updatedLayer) || - !isEmpty(displayedPolygonPartsErrors); + !isEmpty(polygonPartsErrors); return ( @@ -668,9 +658,9 @@ const ResolutionConflictDialogComponent: React.FC )} - {displayedPolygonPartsErrors && ( - - )} + {!viewOnly && ( diff --git a/src/discrete-layer/components/layer-details/raster/state-machine/helpers.ts b/src/discrete-layer/components/layer-details/raster/state-machine/helpers.ts index 64b1e45e1..afb9043d4 100644 --- a/src/discrete-layer/components/layer-details/raster/state-machine/helpers.ts +++ b/src/discrete-layer/components/layer-details/raster/state-machine/helpers.ts @@ -133,7 +133,7 @@ export const buildError = ( source, level, code, - message, + errText: message, addPolicy, response, }; diff --git a/src/discrete-layer/components/map-container/bbox.dialog.tsx b/src/discrete-layer/components/map-container/bbox.dialog.tsx index 48a0814d4..99f8472ee 100644 --- a/src/discrete-layer/components/map-container/bbox.dialog.tsx +++ b/src/discrete-layer/components/map-container/bbox.dialog.tsx @@ -15,9 +15,10 @@ import { } from '@map-colonies/react-core'; import { BboxCorner, Box, DrawType, IDrawingEvent } from '@map-colonies/react-components'; import CONFIG from '../../../common/config'; -import { ValidationsError } from '../../../common/components/error/validations.error-presentor'; +import { ErrorPresentor } from '../error/error-presentor'; import { FieldLabelComponent } from '../../../common/components/form/field-label'; import { emphasizeByHTML } from '../../../common/helpers/formatters'; +import { getErrorsItems } from '../helpers/errorUtils'; import { BBoxCorner, Corner } from '../bbox/bbox-corner-indicator'; import './bbox.dialog.css'; @@ -256,14 +257,15 @@ export const BBoxDialog: React.FC = ({ - {!isEmpty(formik.errors) && ( - - )} - {isEmpty(formik.errors) && !isEmpty(formErrors) && ( - )} - /> - )} + ) + ) + )} + /> - {errorMessages.length > 0 && } + diff --git a/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.tsx b/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.tsx index e92615150..45fb99095 100644 --- a/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.tsx +++ b/src/discrete-layer/components/layer-details/raster/layer-details-form.raster.tsx @@ -1,6 +1,6 @@ // @ts-nocheck import React, { useMemo, useState, useEffect, useCallback } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import { withFormik, FormikProps, FormikErrors, Form, FormikHandlers, FormikBag } from 'formik'; import { Feature, GeoJsonProperties, Geometry } from 'geojson'; import { get } from 'lodash'; @@ -31,7 +31,6 @@ import { Curtain } from '../../../../common/components/curtain/curtain.component import { IngestionFields } from './ingestion-fields.raster'; import { JobInfo } from './job-info'; import { PPIngestionMap } from './pp-map'; -import { StateError } from './state-error'; import { formatErrors } from '../../helpers/errorUtils'; import { RasterWorkflowContext } from './state-machine/context'; import { @@ -257,6 +256,8 @@ export const InnerRasterForm = ( ] ); + const intl = useIntl(); + // const topLevelFieldsErrors = {} as Record; // firstPhaseErrors && Object.keys(firstPhaseErrors).forEach((err) => { // topLevelFieldsErrors[err] = firstPhaseErrors[err]; @@ -268,6 +269,11 @@ export const InnerRasterForm = ( } }, [dirty]); + const stateErrors = useMemo(() => { + const formatedErrors = formatErrors(state.context.errors, intl); + return formatedErrors; + }, [state.context.errors]); + return (
- - + {isGoToJobEnabled(state.context) && ( diff --git a/src/discrete-layer/components/layer-details/raster/state-error.tsx b/src/discrete-layer/components/layer-details/raster/state-error.tsx deleted file mode 100644 index 6a0749fe3..000000000 --- a/src/discrete-layer/components/layer-details/raster/state-error.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { GraphQLError } from '../../../../common/components/error/graphql.error-presentor'; -import { LogicError } from '../../../../common/components/error/logic.error-presentor'; -import { ErrorSource, IStateError } from './state-machine/types'; - -const FIRST = 0; -const LOGIC_ERROR: ErrorSource = 'logic'; -const API_ERROR: ErrorSource = 'api'; - -interface StateErrorProps { - errors: IStateError[]; -} - -export const StateError: React.FC = ({ errors }) => { - return ( - <> - - [LOGIC_ERROR, API_ERROR].includes(err.source))} - /> - - ); -}; From b114ad6bf5e4f3dc933a8bb4493ad6822224f95d Mon Sep 17 00:00:00 2001 From: Eliav Maimon Date: Sun, 6 Sep 2026 16:15:23 +0300 Subject: [PATCH 10/22] chore: remove comment --- .../raster/entity.raster.revert-dialog.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx b/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx index a3e7c3c64..ccb4dc785 100644 --- a/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx +++ b/src/discrete-layer/components/layer-details/raster/entity.raster.revert-dialog.tsx @@ -221,15 +221,7 @@ export const EntityRevertRasterDialog: React.FC = observer( const queryExecutorOverlapped = useMemo( () => buildQueryExecutor( - // changedArea.overlapped as Feature, - { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: [29.3495189, 20.8378315], - }, - } as Feature, + changedArea.overlapped as Feature, FeatureType.CHANGED_AREA_OVERLAPPED_PP ), [changedArea.overlapped] From ee0374db2ade480fd39bcf9cf42374deb33c5357 Mon Sep 17 00:00:00 2001 From: Eliav Maimon Date: Mon, 7 Sep 2026 10:31:25 +0300 Subject: [PATCH 11/22] fix: remove GraphQLError component --- .../error/graphql.error-presentor.tsx | 40 ------------------- .../components/dialogs/file-picker.dialog.tsx | 6 +-- .../components/helpers/errorUtils.ts | 15 +++++-- .../components/job-manager/jobs.dialog.tsx | 5 +-- .../3D/entity.3d.delete-dialog.tsx | 5 ++- .../layer-details/publish.dialog.tsx | 6 +-- .../raster/entity.raster.revert-dialog.tsx | 12 +++++- ...olygon-parts-extent-query-vector-layer.tsx | 5 ++- .../system-core-info.dialog.tsx | 15 ++++--- 9 files changed, 46 insertions(+), 63 deletions(-) delete mode 100644 src/common/components/error/graphql.error-presentor.tsx diff --git a/src/common/components/error/graphql.error-presentor.tsx b/src/common/components/error/graphql.error-presentor.tsx deleted file mode 100644 index 6742a2618..000000000 --- a/src/common/components/error/graphql.error-presentor.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import { useIntl } from 'react-intl'; -import { IconButton } from '@map-colonies/react-core'; -import { AutoDirectionBox } from '../auto-direction-box/auto-direction-box.component'; -import { formatErrors, IGraphqlError } from '../../../discrete-layer/components/helpers/errorUtils'; - -import './error-presentor.css'; - -const NONE = 0; - -export const GraphQLError: React.FC<{ error: IGraphqlError }> = ({ error }) => { - const intl = useIntl(); - const errors = formatErrors([error].filter(Boolean), intl); - - if (errors.length === NONE) { - return null; - } - - return ( - - { - e.preventDefault(); - e.stopPropagation(); - }} - /> -
    - {errors.map((err, index) => ( -
  • - ))} -
-
- ); -}; diff --git a/src/discrete-layer/components/dialogs/file-picker.dialog.tsx b/src/discrete-layer/components/dialogs/file-picker.dialog.tsx index 1d5b2575d..361f94fd9 100644 --- a/src/discrete-layer/components/dialogs/file-picker.dialog.tsx +++ b/src/discrete-layer/components/dialogs/file-picker.dialog.tsx @@ -6,7 +6,6 @@ import { observer } from 'mobx-react'; import { DialogContent } from '@material-ui/core'; import { Button, Dialog, DialogActions, DialogTitle, IconButton } from '@map-colonies/react-core'; import { Box, FileActionData, FileData, FilePickerActions } from '@map-colonies/react-components'; -import { GraphQLError } from '../../../common/components/error/graphql.error-presentor'; import { FilePickerComponent, FilePickerComponentHandle, @@ -22,7 +21,8 @@ import { useStore, } from '../../models'; import { isMultiSelection } from '../layer-details/utils'; -import { IGraphqlError } from '../helpers/errorUtils'; +import { formatError, IGraphqlError } from '../helpers/errorUtils'; +import { ErrorPresentor } from '../error/error-presentor'; import './file-picker.dialog.css'; @@ -212,7 +212,7 @@ export const FilePickerDialog: React.FC = observer( - +