Skip to content

Commit b0f6d29

Browse files
arsen-afaunovdatalens-weblate-robotdtikhonovaMatthew Casserly
authored
Enhance error handling for collection entity dialogs (#3026)
Co-authored-by: datalens-weblate-robot <[email protected]> Co-authored-by: Darya Tikhonova <[email protected]> Co-authored-by: Matthew Casserly <[email protected]>
1 parent c70ea5f commit b0f6d29

File tree

13 files changed

+454
-232
lines changed

13 files changed

+454
-232
lines changed

src/i18n-keysets/component.collections-structure/en.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"action_stay": "Remain",
1414
"button_choose-file": "Select a file",
1515
"button_deploy": "Deploy",
16+
"label_collection-already-exists-error": "A collection with that name already exists",
1617
"label_connection": "connection",
1718
"label_copy": "Copy to",
1819
"label_copy-denied-title": "Insufficient permissions to copy",
@@ -38,11 +39,14 @@
3839
"label_report": "report",
3940
"label_title": "Name",
4041
"label_wizard": "chart",
42+
"label_workbook-already-exists-error": "A workbook with that name already exists",
4143
"section_delete-collection": "Are you sure you want to delete the collection \"{{name}}\"?",
4244
"section_delete-workbook": "Are you sure you want to delete the workbook \"{{name}}\"?",
4345
"title_deploy": "Deploy to",
4446
"title_import-exit": "Do you want to close the import window?",
47+
"toast_collection-already-exists-error": "A collection with that name already exists",
4548
"toast_get-gallery-file-error": "Deployment error, please contact the support service",
4649
"toast_get-json-error": "An error occurred while trying to get a JSON from the file",
47-
"toast_outdated-workbook-info": "The workbook information is outdated. Please refresh the page."
50+
"toast_outdated-workbook-info": "The workbook information is outdated. Please refresh the page.",
51+
"toast_workbook-already-exists-error": "A workbook with that name already exists"
4852
}

src/i18n-keysets/component.collections-structure/ru.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"action_stay": "Остаться",
1414
"button_choose-file": "Выбрать файл",
1515
"button_deploy": "Развернуть",
16+
"label_collection-already-exists-error": "Коллекция с таким названием уже существует",
1617
"label_connection": "подключения",
1718
"label_copy": "Куда копировать",
1819
"label_copy-denied-title": "Недостаточно прав для копирования",
@@ -38,11 +39,14 @@
3839
"label_report": "отчёта",
3940
"label_title": "Название",
4041
"label_wizard": "чарта",
42+
"label_workbook-already-exists-error": "Воркбук с таким названием уже существует",
4143
"section_delete-collection": "Вы действительно хотите удалить коллекцию \"{{name}}\"?",
4244
"section_delete-workbook": "Вы действительно хотите удалить воркбук \"{{name}}\"?",
4345
"title_deploy": "Куда развернуть",
4446
"title_import-exit": "Закрыть окно импорта?",
47+
"toast_collection-already-exists-error": "Коллекция с таким названием уже существует",
4548
"toast_get-gallery-file-error": "Ошибка при развёртывании, обратитесь в поддержку",
4649
"toast_get-json-error": "Ошибка при попытке получить JSON из файла",
47-
"toast_outdated-workbook-info": "Информация о воркбуке устарела. Пожалуйста, обновите страницу."
50+
"toast_outdated-workbook-info": "Информация о воркбуке устарела. Пожалуйста, обновите страницу.",
51+
"toast_workbook-already-exists-error": "Воркбук с таким названием уже существует"
4852
}

src/shared/constants/error-codes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ export enum ErrorCode {
4545

4646
ChartEditorNotAvailable = 'ERR.CHARTS.CHART_EDITOR_NOT_AVAILABLE',
4747
InsufficientServicePlan = 'ERR.CHARTS.INSUFFICIENT_SERVICE_PLAN',
48+
WorkbookAlreadyExists = 'WORKBOOK_ALREADY_EXISTS',
49+
MetaManagerWorkbookAlreadyExists = 'META_MANAGER.WORKBOOK_ALREADY_EXISTS',
50+
CollectionAlreadyExists = 'COLLECTION_ALREADY_EXISTS',
4851
}
4952

5053
export const ErrorContentTypes = {

src/ui/components/CollectionsStructure/CollectionDialog/CollectionDialog.tsx

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,45 +10,54 @@ const i18n = I18n.keyset('component.collections-structure');
1010

1111
const b = block('dl-collection-dialog');
1212

13+
export type CollectionDialogValues = {
14+
title: string;
15+
description: string;
16+
};
17+
18+
type CollectionDialogErrors = Partial<Record<keyof CollectionDialogValues, string>>;
19+
1320
export type Props = {
21+
values: CollectionDialogValues;
22+
errors?: CollectionDialogErrors;
1423
title: string;
1524
textButtonApply: string;
1625
open: boolean;
1726
isLoading: boolean;
18-
titleValue?: string;
19-
descriptionValue?: string;
2027
titleAutoFocus?: boolean;
21-
onApply: (args: {title: string; description: string}) => Promise<unknown>;
28+
onChange: (values: CollectionDialogValues) => void;
29+
onApply: (values: CollectionDialogValues, onClose: () => void) => Promise<unknown>;
2230
onClose: () => void;
2331
};
2432

2533
export const CollectionDialog = React.memo<Props>(
2634
({
35+
values,
36+
errors,
2737
title,
2838
textButtonApply,
2939
open,
3040
isLoading,
31-
titleValue = '',
32-
descriptionValue = '',
3341
titleAutoFocus = false,
3442
onApply,
43+
onChange,
3544
onClose,
3645
}) => {
37-
const [innerTitleValue, setInnerTitleValue] = React.useState(titleValue);
38-
const [innerDescriptionValue, setInnerDescriptionValue] = React.useState(descriptionValue);
46+
const handleChange = React.useCallback(
47+
(params) => {
48+
const {target} = params;
3949

40-
React.useEffect(() => {
41-
if (open) {
42-
setInnerTitleValue(titleValue);
43-
setInnerDescriptionValue(descriptionValue);
44-
}
45-
}, [open, titleValue, descriptionValue]);
50+
onChange({
51+
...values,
52+
[target.name]: target.value,
53+
});
54+
},
55+
[onChange, values],
56+
);
4657

4758
const handleApply = React.useCallback(() => {
48-
onApply({title: innerTitleValue, description: innerDescriptionValue}).then(() => {
49-
onClose();
50-
});
51-
}, [innerTitleValue, innerDescriptionValue, onApply, onClose]);
59+
onApply(values, onClose);
60+
}, [onApply, values, onClose]);
5261

5362
return (
5463
<Dialog
@@ -63,16 +72,20 @@ export const CollectionDialog = React.memo<Props>(
6372
<div className={b('field')}>
6473
<div className={b('title')}>{i18n('label_title')}</div>
6574
<TextInput
66-
value={innerTitleValue}
67-
onUpdate={setInnerTitleValue}
75+
name="title"
76+
value={values.title}
77+
error={errors?.title}
78+
onChange={handleChange}
6879
autoFocus={titleAutoFocus}
6980
/>
7081
</div>
7182
<div className={b('field')}>
7283
<div className={b('title')}>{i18n('label_description')}</div>
7384
<TextArea
74-
value={innerDescriptionValue}
75-
onUpdate={setInnerDescriptionValue}
85+
name="description"
86+
value={values.description}
87+
error={errors?.description}
88+
onChange={handleChange}
7689
minRows={2}
7790
/>
7891
</div>
@@ -82,7 +95,7 @@ export const CollectionDialog = React.memo<Props>(
8295
onClickButtonApply={handleApply}
8396
textButtonApply={textButtonApply}
8497
propsButtonApply={{
85-
disabled: !innerTitleValue,
98+
disabled: !values.title,
8699
}}
87100
textButtonCancel={i18n('action_cancel')}
88101
loading={isLoading}

src/ui/components/CollectionsStructure/CreateCollectionDialog.tsx

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import React from 'react';
22

33
import {I18n} from 'i18n';
44
import {useDispatch, useSelector} from 'react-redux';
5+
import {ErrorCode} from 'shared/constants';
56
import type {CreateCollectionResponse} from 'shared/schema';
67
import type {AppDispatch} from 'store';
78

89
import DialogManager from '../../components/DialogManager/DialogManager';
910
import {createCollection} from '../../store/actions/collectionsStructure';
1011
import {selectCreateCollectionIsLoading} from '../../store/selectors/collectionsStructure';
1112

12-
import {CollectionDialog} from './CollectionDialog';
13+
import {CollectionDialog, type CollectionDialogValues} from './CollectionDialog';
14+
import {useCollectionEntityDialogState} from './hooks/useCollectionEntityDialogState';
1315

1416
const i18n = I18n.keyset('component.collections-structure');
1517

@@ -30,33 +32,61 @@ type Props = {
3032
export const CreateCollectionDialog: React.FC<Props> = (props) => {
3133
const dispatch: AppDispatch = useDispatch();
3234
const {open, onClose} = props;
35+
const {
36+
values: dialogValues,
37+
errors: dialogErrors,
38+
handleChange: handleDialogChange,
39+
handleError: handleDialogError,
40+
} = useCollectionEntityDialogState({
41+
title: '',
42+
description: '',
43+
});
3344

34-
const handleApply = async ({title, description}: {title: string; description: string}) => {
45+
const handleApply = async (
46+
{title, description}: CollectionDialogValues,
47+
dialogOnClose: () => void,
48+
) => {
3549
const {parentId, onApply} = props;
3650

37-
const result = await dispatch(
38-
createCollection({
39-
title,
40-
description,
41-
parentId,
42-
}),
43-
);
51+
try {
52+
const result = await dispatch(
53+
createCollection(
54+
{
55+
title,
56+
description,
57+
parentId,
58+
},
59+
true,
60+
),
61+
);
4462

45-
if (onApply) {
46-
onApply(result);
47-
}
63+
if (onApply) {
64+
onApply(result);
65+
}
66+
67+
dialogOnClose();
4868

49-
return result;
69+
return result;
70+
} catch (error) {
71+
if (error.code === ErrorCode.CollectionAlreadyExists) {
72+
handleDialogError({title: i18n('label_collection-already-exists-error')});
73+
}
74+
75+
return Promise.resolve();
76+
}
5077
};
5178

5279
const isLoading = useSelector(selectCreateCollectionIsLoading);
5380

5481
return (
5582
<CollectionDialog
83+
values={dialogValues}
84+
errors={dialogErrors}
5685
title={i18n('action_create-collection')}
5786
textButtonApply={i18n('action_create')}
5887
open={open}
5988
isLoading={isLoading}
89+
onChange={handleDialogChange}
6090
onApply={handleApply}
6191
onClose={onClose}
6292
titleAutoFocus

0 commit comments

Comments
 (0)