@retailcrm/datalens-ui 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/index.css +587 -587
  2. package/package.json +1 -1
  3. package/public/i18n/data.json +2 -1
  4. package/public/i18n/{en.144c7612.js → en.01e3d689.js} +2 -1
  5. package/public/i18n/manifest.json +2 -2
  6. package/public/i18n/{ru.b7e6f7b0.js → ru.a9fbfe3a.js} +2 -1
  7. package/ui/capabilities/capabilities.d.ts +1 -0
  8. package/ui/capabilities/capabilities.js +5 -0
  9. package/ui/components/ActionPanel/ActionPanel.js +2 -1
  10. package/ui/components/AsideHeaderAdapter/AsideHeaderAdapter.js +9 -1
  11. package/ui/components/DialogAddSharedEntryFromLink/DialogAddSharedEntryFromLink.js +4 -1
  12. package/ui/components/DialogSharedEntryPermissions/DialogSharedEntryPermissions.js +3 -8
  13. package/ui/components/EntryDialogues/DialogCreateDashboard/DialogCreateDashboard.js +4 -3
  14. package/ui/components/EntryDialogues/DialogCreateEditorChart/DialogCreateEditorChart.js +4 -3
  15. package/ui/components/EntryDialogues/DialogCreateQLChart/DialogCreateQLChart.js +4 -3
  16. package/ui/components/EntryDialogues/DialogCreateWizardChart/DialogCreateWizardChart.js +4 -3
  17. package/ui/components/EntryDialogues/DialogRenameEntry/DialogRenameEntry.js +4 -3
  18. package/ui/components/InaccessibleOnMobileInset/InaccessibleOnMobileInset.js +18 -4
  19. package/ui/components/Navigation/Base/configure.d.ts +0 -20
  20. package/ui/components/Navigation/Base/configure.js +23 -23
  21. package/ui/components/Navigation/Core/NavigationBreadcrumbs/BreadcrumbMenu.d.ts +1 -1
  22. package/ui/components/Navigation/Core/NavigationBreadcrumbs/BreadcrumbMenu.js +32 -2
  23. package/ui/units/connections/components/ConnectorForm/components/Datepicker/Datepicker.d.ts +1 -1
  24. package/ui/units/connections/components/ConnectorForm/components/FileInput/FileInput.d.ts +1 -1
  25. package/ui/units/connections/components/ConnectorForm/components/Input/Input.d.ts +1 -1
  26. package/ui/units/connections/components/ConnectorForm/components/Select/Select.d.ts +1 -1
  27. package/ui/units/dash/components/DashActionPanel/DashActionPanel.d.ts +1 -0
  28. package/ui/units/dash/components/DashActionPanel/DashActionPanel.js +35 -0
  29. package/ui/units/dash/components/DashActionPanel/ViewControls/ViewControls.d.ts +1 -0
  30. package/ui/units/dash/components/DashActionPanel/ViewControls/ViewControls.js +2 -1
  31. package/ui/units/dash/store/utils.js +2 -1
  32. package/ui/units/ql/containers/QL/QLActionPanel/QLActionPanel.js +15 -4
  33. package/ui/units/wizard/actions/widget.js +7 -1
  34. package/ui/units/wizard/containers/Wizard/Wizard.js +5 -3
  35. package/ui/units/workbooks/components/RenameEntryDialog/RenameEntryDialog.js +48 -12
  36. package/ui/units/workbooks/store/actions/index.d.ts +1 -1
  37. package/ui/units/workbooks/store/actions/index.js +1 -7
  38. package/ui/utils/errors/errorByCode.d.ts +1 -0
  39. package/ui/utils/errors/errorByCode.js +97 -0
@@ -13,6 +13,7 @@ const ViewControls = (props) => {
13
13
  canEdit,
14
14
  progress,
15
15
  isLoadingEditMode,
16
+ showEditButton = true,
16
17
  onEditClick,
17
18
  onAccessClick,
18
19
  entryDialoguesRef,
@@ -33,7 +34,7 @@ const ViewControls = (props) => {
33
34
  }
34
35
  ),
35
36
  /* @__PURE__ */ jsx(EntryDialogues, { ref: entryDialoguesRef }),
36
- canEdit && !DL.IS_MOBILE && /* @__PURE__ */ jsx(
37
+ showEditButton && canEdit && !DL.IS_MOBILE && /* @__PURE__ */ jsx(
37
38
  Button,
38
39
  {
39
40
  view: "normal",
@@ -8,6 +8,7 @@ import "../../../utils/index.js";
8
8
  import { Mode, DASHKIT_STATE_VERSION } from "../modules/constants.js";
9
9
  import { getLocation } from "../../../navigation/router.js";
10
10
  import { URL_QUERY, DL } from "../../../constants/common.js";
11
+ import { normalizeDestination } from "../../../../shared/modules/entry.js";
11
12
  import Utils from "../../../utils/utils.js";
12
13
  import { EntryScope } from "../../../../shared/types/common.js";
13
14
  const storeI18n = I18n.keyset("dash.store.view");
@@ -17,7 +18,7 @@ const getFakeDashEntry = (workbookId) => {
17
18
  const { counter, id: newTabId } = generateUniqId({ salt, counter: 0, ids: [] });
18
19
  const searchParams = getLocation().params();
19
20
  const searchCurrentPath = searchParams.get(URL_QUERY.CURRENT_PATH);
20
- const path = searchCurrentPath || DL.USER_FOLDER;
21
+ const path = normalizeDestination(searchCurrentPath || DL.USER_FOLDER);
21
22
  const initialKey = `${path}${dashCreateI18n("label_default-name")}`;
22
23
  const data = {
23
24
  tabs: [
@@ -11,10 +11,12 @@ import { isDraftVersion } from "../../../../../utils/revisions.js";
11
11
  import "../../../../../index.js";
12
12
  import ActionPanel from "../../../../../components/ActionPanel/ActionPanel.js";
13
13
  import { ChartSaveControls } from "../../../../../components/ActionPanel/components/ChartSaveControls/ChartSaveControl.js";
14
+ import "../../../../../navigation/index.js";
14
15
  import { registry } from "../../../../../registry/index.js";
15
16
  import { openDialogSaveDraftChartAsActualConfirm } from "../../../../../store/actions/dialog.js";
16
17
  import { resetEditHistoryUnit, addEditHistoryPoint } from "../../../../../store/actions/editHistory.js";
17
- import { reloadRevisionsOnSave } from "../../../../../store/actions/entryContent.js";
18
+ import { setIsRenameWithoutReload, reloadRevisionsOnSave } from "../../../../../store/actions/entryContent.js";
19
+ import { selectIsRenameWithoutReload } from "../../../../../store/selectors/entryContent.js";
18
20
  import DialogSettings from "../../../components/Dialogs/Settings/Settings.js";
19
21
  import { QL_EDIT_HISTORY_UNIT_ID } from "../../../constants/index.js";
20
22
  import { prepareChartDataBeforeSave } from "../../../modules/helpers.js";
@@ -25,6 +27,7 @@ import SvgMonitoring from "../../../../../assets/icons/monitoring.svg.js";
25
27
  /* empty css */
26
28
  import { getValid, getConnection, getExtraSettings, getPreviewData, getEntry } from "../../../store/selectors/ql.js";
27
29
  import { Feature } from "../../../../../../shared/types/feature.js";
30
+ import { useRouter } from "../../../../../navigation/router.js";
28
31
  import Utils from "../../../../../utils/utils.js";
29
32
  import { EntryDialogName } from "../../../../../components/EntryDialogues/EntryDialogues.js";
30
33
  import { EntryDialogResolveStatus } from "../../../../../components/EntryDialogues/constants.js";
@@ -42,6 +45,7 @@ const QLActionPanel = (props) => {
42
45
  const redirectUrl = useSelector(getRedirectUrl);
43
46
  const previewData = useSelector(getPreviewData);
44
47
  const entry = useSelector(getEntry);
48
+ const isRenameWithoutReload = useSelector(selectIsRenameWithoutReload);
45
49
  const canEdit = !(entry && entry.permissions && entry.permissions.edit === false);
46
50
  const isCurrentRevisionActual = entry?.revId && entry?.revId === entry?.publishedId;
47
51
  const isNewChart = typeof entry?.fake !== "undefined" && entry?.fake;
@@ -49,17 +53,18 @@ const QLActionPanel = (props) => {
49
53
  const { QlActionPanelExtension } = registry.ql.components.getAll();
50
54
  const enablePublish = entry && isEnabledFeature(Feature.EnablePublishEntry) && !entry.fake;
51
55
  const dispatch = useDispatch();
56
+ const router = useRouter();
52
57
  const [dialogNoRightsVisible, setDialogNoRightsVisible] = React__default.useState(false);
53
58
  const [dialogSettingsVisible, setDialogSettingsVisible] = React__default.useState(false);
54
59
  const qlState = useSelector((state) => state.ql);
55
60
  const wizardState = useSelector((state) => state.wizard);
56
61
  const handleBeforeunload = React__default.useCallback(
57
62
  (event) => {
58
- if (!entryNotChanged) {
63
+ if (!entryNotChanged && !isRenameWithoutReload) {
59
64
  event.returnValue = true;
60
65
  }
61
66
  },
62
- [entryNotChanged]
67
+ [entryNotChanged, isRenameWithoutReload]
63
68
  );
64
69
  React__default.useEffect(() => {
65
70
  window.removeEventListener("beforeunload", handleBeforeunload);
@@ -127,7 +132,12 @@ const QLActionPanel = (props) => {
127
132
  if (!result || !result.data || result.status === EntryDialogResolveStatus.Close) {
128
133
  return;
129
134
  }
130
- window.history.replaceState({}, document.title, `/ql/${result.data.entryId}`);
135
+ dispatch(setIsRenameWithoutReload(true));
136
+ try {
137
+ router.replace({ pathname: `/ql/${result.data.entryId}` });
138
+ } finally {
139
+ dispatch(setIsRenameWithoutReload(false));
140
+ }
131
141
  result.data.data = {
132
142
  shared: JSON.parse(result.data.data.shared)
133
143
  };
@@ -152,6 +162,7 @@ const QLActionPanel = (props) => {
152
162
  entryDialoguesRef,
153
163
  defaultChartName,
154
164
  qlState,
165
+ router,
155
166
  wizardState
156
167
  ]
157
168
  );
@@ -2,6 +2,7 @@ import { WIZARD_DATASET_ID_PARAMETER_KEY } from "../../../constants/misc.js";
2
2
  import { CHART_SETTINGS } from "../../../constants/visualizations/index.js";
3
3
  import "../../../navigation/index.js";
4
4
  import { saveWidget } from "../../../store/actions/chartWidget.js";
5
+ import { setIsRenameWithoutReload } from "../../../store/actions/entryContent.js";
5
6
  import { updateClientChartsConfig } from "./preview.js";
6
7
  import { mapClientConfigToChartsConfig } from "../utils/mappers/mapClientToChartsConfig.js";
7
8
  import { removeUrlParameters } from "../utils/wizard.js";
@@ -32,7 +33,12 @@ function receiveWidgetAndPrepareMetadata({
32
33
  pathname += entryId;
33
34
  }
34
35
  if (!location.pathname.includes(entryId)) {
35
- router.replace({ pathname });
36
+ dispatch(setIsRenameWithoutReload(true));
37
+ try {
38
+ router.replace({ pathname });
39
+ } finally {
40
+ dispatch(setIsRenameWithoutReload(false));
41
+ }
36
42
  }
37
43
  }
38
44
  if (error) {
@@ -23,6 +23,7 @@ import { registry } from "../../../../registry/index.js";
23
23
  import { openDialogSaveDraftChartAsActualConfirm } from "../../../../store/actions/dialog.js";
24
24
  import { addEditHistoryPoint, resetEditHistoryUnit, initEditHistoryUnit } from "../../../../store/actions/editHistory.js";
25
25
  import { reloadRevisionsOnSave, cleanRevisions, setRevisionsMode } from "../../../../store/actions/entryContent.js";
26
+ import { selectIsRenameWithoutReload } from "../../../../store/selectors/entryContent.js";
26
27
  import { RevisionsMode } from "../../../../store/typings/entryContent.js";
27
28
  import "../../../../utils/index.js";
28
29
  import { isDraft, isEditMode } from "../../../dash/store/selectors/dashTypedSelectors.js";
@@ -67,9 +68,9 @@ class Wizard extends React__default.Component {
67
68
  super(props);
68
69
  this.chartKitRef = React__default.createRef();
69
70
  this.unloadConfirmation = (event) => {
70
- const { previewHash, widgetHash, initialPreviewHash } = this.props;
71
+ const { previewHash, widgetHash, initialPreviewHash, isRenameWithoutReload } = this.props;
71
72
  const widgetChanged = previewHash !== widgetHash && previewHash !== initialPreviewHash;
72
- if (widgetChanged) {
73
+ if (widgetChanged && !isRenameWithoutReload) {
73
74
  const message = i18n("wizard", "toast_unsaved");
74
75
  (event || window.event).returnValue = message;
75
76
  return message;
@@ -478,7 +479,8 @@ const mapStateToProps = (state, ownProps) => {
478
479
  isParentDashWasChanged: isDraft(state) && isEditMode(state),
479
480
  initialPreviewHash: selectInitialPreviewHash(state),
480
481
  wizardState: state.wizard,
481
- description: selectPreviewDescription(state)
482
+ description: selectPreviewDescription(state),
483
+ isRenameWithoutReload: selectIsRenameWithoutReload(state)
482
484
  };
483
485
  };
484
486
  const mapDispatchToProps = (dispatch) => {
@@ -4,7 +4,9 @@ import { Dialog, TextInput } from "@gravity-ui/uikit";
4
4
  import block from "bem-cn-lite";
5
5
  import { I18n } from "../../../../../i18n/index.js";
6
6
  import { useDispatch, useSelector } from "react-redux";
7
+ import { getEntryNameInputError } from "../../../../utils/errors/errorByCode.js";
7
8
  import DialogManager from "../../../../components/DialogManager/DialogManager.js";
9
+ import { showToast } from "../../../../store/actions/toaster.js";
8
10
  import { renameEntry } from "../../store/actions/index.js";
9
11
  import { selectRenameEntryIsLoading } from "../../store/selectors/index.js";
10
12
  /* empty css */
@@ -15,21 +17,54 @@ const RenameEntryDialog = React__default.memo(({ open, data, onClose }) => {
15
17
  const dispatch = useDispatch();
16
18
  const isLoading = useSelector(selectRenameEntryIsLoading);
17
19
  const [newNameValue, setNewNameValue] = React__default.useState(data.name);
20
+ const [inputError, setInputError] = React__default.useState(false);
18
21
  const textInputControlRef = React__default.useRef(null);
19
- const handleApply = React__default.useCallback(() => {
20
- dispatch(
21
- renameEntry({
22
- entryId: data.entryId,
23
- name: newNameValue,
24
- updateInline: true
25
- })
26
- ).then(() => {
27
- onClose();
28
- });
22
+ React__default.useEffect(() => {
23
+ if (open) {
24
+ setNewNameValue(data.name);
25
+ setInputError(false);
26
+ }
27
+ }, [data.name, open]);
28
+ const handleApply = React__default.useCallback(async () => {
29
+ const name = newNameValue.trim();
30
+ try {
31
+ const renamedEntry = await dispatch(
32
+ renameEntry({
33
+ entryId: data.entryId,
34
+ name,
35
+ updateInline: true
36
+ })
37
+ );
38
+ if (renamedEntry) {
39
+ onClose();
40
+ }
41
+ } catch (error) {
42
+ const errorMessage = getEntryNameInputError(
43
+ error,
44
+ i18n("label_entry-name-already-exists")
45
+ );
46
+ if (errorMessage) {
47
+ setInputError(errorMessage);
48
+ return;
49
+ }
50
+ dispatch(
51
+ showToast({
52
+ title: error.message,
53
+ name: "RenameEntryDialogFailed",
54
+ error,
55
+ withReport: true
56
+ })
57
+ );
58
+ }
29
59
  }, [data.entryId, dispatch, newNameValue, onClose]);
60
+ const handleInputUpdate = React__default.useCallback((value) => {
61
+ setNewNameValue(value);
62
+ setInputError(false);
63
+ }, []);
30
64
  const propsButtonApply = React__default.useMemo(() => {
65
+ const normalizedValue = newNameValue.trim();
31
66
  return {
32
- disabled: !newNameValue || newNameValue === data.name
67
+ disabled: !normalizedValue || normalizedValue === data.name
33
68
  };
34
69
  }, [data.name, newNameValue]);
35
70
  return /* @__PURE__ */ jsxs(
@@ -49,7 +84,8 @@ const RenameEntryDialog = React__default.memo(({ open, data, onClose }) => {
49
84
  {
50
85
  value: newNameValue,
51
86
  controlRef: textInputControlRef,
52
- onUpdate: setNewNameValue
87
+ onUpdate: handleInputUpdate,
88
+ error: inputError
53
89
  }
54
90
  )
55
91
  ] }),
@@ -164,7 +164,7 @@ export declare const renameEntry: ({ entryId, name, updateInline, }: {
164
164
  entryId: string;
165
165
  name: string;
166
166
  updateInline: boolean;
167
- }) => (dispatch: WorkbooksDispatch) => CancellablePromise<RenameEntryResponse | null>;
167
+ }) => (dispatch: WorkbooksDispatch) => CancellablePromise<RenameEntryResponse>;
168
168
  type ChangeFavoriteEntryLoadingAction = {
169
169
  type: typeof CHANGE_FAVORITE_ENTRY_LOADING;
170
170
  };
@@ -319,18 +319,12 @@ const renameEntry = ({
319
319
  }).catch((error) => {
320
320
  if (!getSdk().sdk.isCancel(error)) {
321
321
  logger.logError("workbooks/renameEntry failed", error);
322
- dispatch(
323
- showToast({
324
- title: error.message,
325
- error
326
- })
327
- );
328
322
  }
329
323
  dispatch({
330
324
  type: RENAME_ENTRY_FAILED,
331
325
  error
332
326
  });
333
- return null;
327
+ throw error;
334
328
  });
335
329
  };
336
330
  };
@@ -14,4 +14,5 @@ interface EntryIsLockedError extends Error {
14
14
  export declare function isEntryIsLockedError(error: DataLensApiError): error is EntryIsLockedError;
15
15
  export declare function getLoginOrIdFromLockedError(error: EntryIsLockedError): string;
16
16
  export declare function isEntryAlreadyExists(error: DataLensApiError): boolean;
17
+ export declare function getEntryNameInputError(error: DataLensApiError, duplicateNameErrorText: string): string | null;
17
18
  export {};
@@ -1,3 +1,4 @@
1
+ import { I18n } from "../../../i18n/index.js";
1
2
  import "../../../shared/index.js";
2
3
  import { parseError } from "./parse.js";
3
4
  import { ErrorCode } from "../../../shared/constants/error-codes.js";
@@ -14,7 +15,103 @@ function isEntryAlreadyExists(error) {
14
15
  }
15
16
  return false;
16
17
  }
18
+ const NON_INPUT_ENTRY_ERROR_CODES = /* @__PURE__ */ new Set([
19
+ ErrorCode.EntryIsLocked,
20
+ ErrorCode.UsAccessDenied,
21
+ ErrorCode.EntryForbidden
22
+ ]);
23
+ const validationI18n = I18n.keyset("validation");
24
+ const GENERIC_VALIDATION_MESSAGES = /* @__PURE__ */ new Set(["validation error", "validation_error"]);
25
+ const VALIDATION_TEXT_KEYS = [
26
+ "validationErrors",
27
+ "validationError",
28
+ "errors",
29
+ "error",
30
+ "message",
31
+ "description",
32
+ "detail",
33
+ "reason"
34
+ ];
35
+ function normalizeErrorText(value) {
36
+ return typeof value === "string" && value.trim() ? value.trim() : null;
37
+ }
38
+ function normalizeEntryNameValidationText(value) {
39
+ const withoutFieldPrefix = value.replace(/^data\.name\s+/i, "").trim();
40
+ const isSlashValidation = /contain/i.test(withoutFieldPrefix) && /symbol/i.test(withoutFieldPrefix) && withoutFieldPrefix.includes("/");
41
+ if (isSlashValidation) {
42
+ return validationI18n("label_validation-slash_error");
43
+ }
44
+ const isSymbolsValidation = /should start and end with/i.test(withoutFieldPrefix) && /can contain only symbols/i.test(withoutFieldPrefix);
45
+ if (isSymbolsValidation) {
46
+ return validationI18n("label_validation-symbols_error");
47
+ }
48
+ return withoutFieldPrefix;
49
+ }
50
+ function isGenericValidationMessage(value) {
51
+ return GENERIC_VALIDATION_MESSAGES.has(value.toLowerCase());
52
+ }
53
+ function getValidationTextFromUnknown(value) {
54
+ const stringValue = normalizeErrorText(value);
55
+ if (stringValue) {
56
+ return isGenericValidationMessage(stringValue) ? null : normalizeEntryNameValidationText(stringValue);
57
+ }
58
+ if (Array.isArray(value)) {
59
+ const messages = value.map((item) => getValidationTextFromUnknown(item)).filter((item) => Boolean(item));
60
+ if (messages.length === 0) {
61
+ return null;
62
+ }
63
+ return Array.from(new Set(messages)).join("; ");
64
+ }
65
+ if (!value || typeof value !== "object") {
66
+ return null;
67
+ }
68
+ const record = value;
69
+ for (const key of VALIDATION_TEXT_KEYS) {
70
+ if (key in record) {
71
+ const text = getValidationTextFromUnknown(record[key]);
72
+ if (text) {
73
+ return text;
74
+ }
75
+ }
76
+ }
77
+ for (const nestedValue of Object.values(record)) {
78
+ const text = getValidationTextFromUnknown(nestedValue);
79
+ if (text) {
80
+ return text;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ function getDetailsValidationText(details) {
86
+ return getValidationTextFromUnknown(details);
87
+ }
88
+ function getEntryNameInputError(error, duplicateNameErrorText) {
89
+ if (isEntryAlreadyExists(error)) {
90
+ return duplicateNameErrorText;
91
+ }
92
+ const { code, message, status, details } = parseError(error);
93
+ const normalizedMessage = normalizeErrorText(message);
94
+ const detailsValidationText = getDetailsValidationText(details);
95
+ const messageForInput = detailsValidationText || (normalizedMessage && !isGenericValidationMessage(normalizedMessage) ? normalizeEntryNameValidationText(normalizedMessage) : null);
96
+ if (!messageForInput) {
97
+ return null;
98
+ }
99
+ if (code && NON_INPUT_ENTRY_ERROR_CODES.has(code)) {
100
+ return null;
101
+ }
102
+ if (!code) {
103
+ const isValidationStatus2 = status === 400 || status === 409 || status === 422;
104
+ return isValidationStatus2 ? messageForInput : null;
105
+ }
106
+ const isUsRelatedError = code.startsWith("ERR.US.") || code.startsWith("ERR.DS_API.US.");
107
+ if (isUsRelatedError) {
108
+ return messageForInput;
109
+ }
110
+ const isValidationStatus = status === 400 || status === 409 || status === 422;
111
+ return isValidationStatus ? messageForInput : null;
112
+ }
17
113
  export {
114
+ getEntryNameInputError,
18
115
  getLoginOrIdFromLockedError,
19
116
  isEntryAlreadyExists,
20
117
  isEntryIsLockedError