From defdbc8317e69b883d8be76c6a397fd72c4fd568 Mon Sep 17 00:00:00 2001 From: Andrey Kobelev Date: Wed, 24 Jun 2026 20:31:05 +0500 Subject: [PATCH] first --- .dockerignore | 32 + .editorconfig | 12 + .eslintignore | 5 + .eslintrc | 26 + .gitignore | 35 + .lintstagedrc.json | 14 + .npmrc | 4 + .nvmrc | 1 + .prettierignore | 6 + .prettierrc.js | 1 + .stylelintrc | 6 + CODE_OF_CONDUCT.md | 1 + CONTRIBUTING.md | 33 + Dockerfile | 133 + LICENSE | 202 + README.md | 80 + SECURITY.md | 21 + api/server/app-env.ts | 8 + api/server/callbacks.ts | 1 + api/server/charts-engine.ts | 9 + api/server/components.ts | 45 + api/server/configs.ts | 4 + api/server/constants.ts | 3 + api/server/controllers.ts | 3 + api/server/dev-env.ts | 1 + api/server/expresskit.ts | 1 + api/server/local-dev.ts | 1 + api/server/middlewares.ts | 10 + api/server/plugins.ts | 7 + api/server/registry.ts | 5 + api/server/types.ts | 16 + api/server/utils.ts | 14 + api/ui/components/Collapse.ts | 1 + api/ui/components/ViewError.ts | 1 + api/ui/constants/common.ts | 2 + api/ui/constants/yfm.ts | 1 + api/ui/hocs.ts | 2 + api/ui/hooks.ts | 1 + api/ui/libs/logger.ts | 1 + api/ui/libs/metrica.ts | 1 + api/ui/libs/oldSdk.ts | 2 + api/ui/libs/sdk.ts | 5 + api/ui/tsconfig.json | 12 + api/ui/typings.ts | 1 + api/ui/utils/absurd.ts | 1 + api/ui/utils/common.ts | 4 + app-builder.config.ts | 101 + deploy/nginx/entrypoint.sh | 30 + deploy/nginx/nginx.conf | 121 + deploy/supervisor/supervisord.conf | 28 + dev-nginx/nginx.example.conf | 37 + dev/env/opensource/development.env | 6 + .../robots/external/production/robots.txt | 3 + dist/public/favicon.ico | Bin 0 -> 1047 bytes dist/public/os-favicon.ico | Bin 0 -> 1681 bytes dist/public/robots.txt | 2 + jest.config.ts | 28 + jest/base-config.ts | 51 + jest/mappers/ignore-node-modules-mapper.ts | 29 + jest/mappers/moduleNameMappers.ts | 13 + jest/mocks/globals.mock.js | 71 + jest/mocks/index.ts | 3 + jest/mocks/style.mock.js | 1 + jest/setup-files/index.ts | 3 + .../testing-library.setup-after.ts | 2 + jest/setup-files/testing-library.setup.ts | 4 + jest/transformers/image-transformer.js | 11 + jest/transformers/index.ts | 7 + package.json | 295 + pnpm-lock.yaml | 24773 ++++++++++++++++ scripts/ci/lint-typecheck/features.sh | 4 + .../lint-typecheck/helpers/check-features.ts | 40 + scripts/ci/statoscope-comment.jora | 37 + scripts/e2e/us-dump.sh | 213 + scripts/maintenance/utils/http-requests.js | 129 + scripts/maintenance/utils/logger.js | 40 + scripts/maintenance/utils/uitls.js | 7 + scripts/setup-dev-env.js | 31 + secrets/.keep | 0 src/@types/chartkit-dl.d.ts | 9 + src/@types/express.d.ts | 19 + src/@types/global.d.ts | 47 + src/@types/highcharts-preparers.d.ts | 33 + src/@types/json-fn.d.ts | 9 + src/@types/nodekit.d.ts | 155 + src/@types/svg.d.ts | 4 + src/@types/window.d.ts | 20 + src/i18n-keysets/.arcignore | 1 + src/i18n-keysets/.gitignore | 1 + .../auth.dialog-change-password/context.json | 1 + .../auth.dialog-change-password/en.json | 14 + .../auth.dialog-change-password/keyset.json | 60 + .../auth.dialog-change-password/ru.json | 14 + .../auth.dialog-change-user-role/context.json | 1 + .../auth.dialog-change-user-role/en.json | 10 + .../auth.dialog-change-user-role/keyset.json | 44 + .../auth.dialog-change-user-role/ru.json | 10 + .../auth.dialog-delete-user/context.json | 1 + .../auth.dialog-delete-user/en.json | 8 + .../auth.dialog-delete-user/keyset.json | 36 + .../auth.dialog-delete-user/ru.json | 8 + .../auth.dialog-edit-profile/context.json | 1 + .../auth.dialog-edit-profile/en.json | 6 + .../auth.dialog-edit-profile/keyset.json | 28 + .../auth.dialog-edit-profile/ru.json | 6 + .../auth.form-controls/context.json | 1 + src/i18n-keysets/auth.form-controls/en.json | 10 + .../auth.form-controls/keyset.json | 44 + src/i18n-keysets/auth.form-controls/ru.json | 10 + src/i18n-keysets/auth.sign-in/context.json | 1 + src/i18n-keysets/auth.sign-in/en.json | 9 + src/i18n-keysets/auth.sign-in/keyset.json | 40 + src/i18n-keysets/auth.sign-in/ru.json | 9 + src/i18n-keysets/auth.sign-up/context.json | 1 + src/i18n-keysets/auth.sign-up/en.json | 5 + src/i18n-keysets/auth.sign-up/keyset.json | 24 + src/i18n-keysets/auth.sign-up/ru.json | 5 + .../auth.user-form-validation/context.json | 1 + .../auth.user-form-validation/en.json | 13 + .../auth.user-form-validation/keyset.json | 56 + .../auth.user-form-validation/ru.json | 13 + .../auth.user-profile.view/context.json | 1 + .../auth.user-profile.view/en.json | 16 + .../auth.user-profile.view/keyset.json | 68 + .../auth.user-profile.view/ru.json | 16 + .../chartkit.button-spin-input/context.json | 1 + .../chartkit.button-spin-input/en.json | 3 + .../chartkit.button-spin-input/keyset.json | 16 + .../chartkit.button-spin-input/ru.json | 3 + .../chartkit.control.items/context.json | 1 + .../chartkit.control.items/en.json | 6 + .../chartkit.control.items/keyset.json | 28 + .../chartkit.control.items/ru.json | 6 + .../chartkit.control/context.json | 1 + src/i18n-keysets/chartkit.control/en.json | 3 + src/i18n-keysets/chartkit.control/keyset.json | 16 + src/i18n-keysets/chartkit.control/ru.json | 3 + .../chartkit.custom-error/context.json | 1 + .../chartkit.custom-error/en.json | 14 + .../chartkit.custom-error/keyset.json | 60 + .../chartkit.custom-error/ru.json | 14 + .../chartkit.data-provider/context.json | 1 + .../chartkit.data-provider/en.json | 22 + .../chartkit.data-provider/keyset.json | 92 + .../chartkit.data-provider/ru.json | 22 + .../chartkit.error-details/context.json | 1 + .../chartkit.error-details/en.json | 5 + .../chartkit.error-details/keyset.json | 24 + .../chartkit.error-details/ru.json | 5 + .../chartkit.insights/context.json | 1 + src/i18n-keysets/chartkit.insights/en.json | 3 + .../chartkit.insights/keyset.json | 16 + src/i18n-keysets/chartkit.insights/ru.json | 3 + .../context.json | 1 + .../chartkit.menu.download-dialog/en.json | 15 + .../chartkit.menu.download-dialog/keyset.json | 64 + .../chartkit.menu.download-dialog/ru.json | 15 + .../chartkit.menu.export/context.json | 1 + src/i18n-keysets/chartkit.menu.export/en.json | 21 + .../chartkit.menu.export/keyset.json | 88 + src/i18n-keysets/chartkit.menu.export/ru.json | 21 + .../chartkit.menu.inspector/context.json | 1 + .../chartkit.menu.inspector/en.json | 24 + .../chartkit.menu.inspector/keyset.json | 100 + .../chartkit.menu.inspector/ru.json | 24 + src/i18n-keysets/chartkit.menu/context.json | 1 + src/i18n-keysets/chartkit.menu/en.json | 33 + src/i18n-keysets/chartkit.menu/keyset.json | 136 + src/i18n-keysets/chartkit.menu/ru.json | 33 + src/i18n-keysets/chartkit/context.json | 1 + src/i18n-keysets/chartkit/en.json | 6 + src/i18n-keysets/chartkit/keyset.json | 28 + src/i18n-keysets/chartkit/ru.json | 6 + src/i18n-keysets/collections/context.json | 1 + src/i18n-keysets/collections/en.json | 30 + src/i18n-keysets/collections/keyset.json | 124 + src/i18n-keysets/collections/ru.json | 30 + .../common.components/context.json | 1 + src/i18n-keysets/common.components/en.json | 3 + .../common.components/keyset.json | 16 + src/i18n-keysets/common.components/ru.json | 3 + .../common.data-types/context.json | 1 + src/i18n-keysets/common.data-types/en.json | 14 + .../common.data-types/keyset.json | 60 + src/i18n-keysets/common.data-types/ru.json | 14 + src/i18n-keysets/common.errors/context.json | 1 + src/i18n-keysets/common.errors/en.json | 7 + src/i18n-keysets/common.errors/keyset.json | 32 + src/i18n-keysets/common.errors/ru.json | 7 + .../common.read-only/context.json | 1 + src/i18n-keysets/common.read-only/en.json | 3 + src/i18n-keysets/common.read-only/keyset.json | 16 + src/i18n-keysets/common.read-only/ru.json | 3 + .../component.action-panel.view/context.json | 1 + .../component.action-panel.view/en.json | 9 + .../component.action-panel.view/keyset.json | 40 + .../component.action-panel.view/ru.json | 9 + .../context.json | 1 + .../en.json | 17 + .../keyset.json | 72 + .../ru.json | 17 + .../component.aside-header.view/context.json | 1 + .../component.aside-header.view/en.json | 15 + .../component.aside-header.view/keyset.json | 64 + .../component.aside-header.view/ru.json | 15 + .../context.json | 1 + .../component.chart-modeling-settings/en.json | 20 + .../keyset.json | 84 + .../component.chart-modeling-settings/ru.json | 20 + .../context.json | 1 + .../component.chart-save-controls/en.json | 8 + .../component.chart-save-controls/keyset.json | 36 + .../component.chart-save-controls/ru.json | 8 + .../context.json | 1 + .../component.chartkit-alerts.view/en.json | 68 + .../keyset.json | 276 + .../component.chartkit-alerts.view/ru.json | 68 + .../context.json | 1 + .../component.chartkit-error.codes/en.json | 34 + .../keyset.json | 140 + .../component.chartkit-error.codes/ru.json | 34 + .../context.json | 1 + .../component.chartkit-error.view/en.json | 4 + .../component.chartkit-error.view/keyset.json | 20 + .../component.chartkit-error.view/ru.json | 4 + .../context.json | 1 + .../component.collection-breadcrumbs/en.json | 3 + .../keyset.json | 16 + .../component.collection-breadcrumbs/ru.json | 3 + .../component.collection-filters/context.json | 1 + .../component.collection-filters/en.json | 13 + .../component.collection-filters/keyset.json | 56 + .../component.collection-filters/ru.json | 13 + .../context.json | 1 + .../component.collections-structure/en.json | 54 + .../keyset.json | 220 + .../component.collections-structure/ru.json | 54 + .../context.json | 1 + .../component.color-palette-editor/en.json | 29 + .../keyset.json | 120 + .../component.color-palette-editor/ru.json | 29 + .../context.json | 1 + .../en.json | 24 + .../keyset.json | 72 + .../ru.json | 24 + .../component.dashkit.view/context.json | 1 + .../component.dashkit.view/en.json | 9 + .../component.dashkit.view/keyset.json | 40 + .../component.dashkit.view/ru.json | 9 + .../context.json | 1 + .../en.json | 12 + .../keyset.json | 52 + .../ru.json | 12 + .../context.json | 1 + .../component.dialog-aliases.view/en.json | 14 + .../component.dialog-aliases.view/keyset.json | 60 + .../component.dialog-aliases.view/ru.json | 14 + .../context.json | 1 + .../en.json | 5 + .../keyset.json | 24 + .../ru.json | 5 + .../context.json | 1 + .../component.dialog-copy-entry.view/en.json | 15 + .../keyset.json | 64 + .../component.dialog-copy-entry.view/ru.json | 15 + .../context.json | 1 + .../en.json | 5 + .../keyset.json | 24 + .../ru.json | 5 + .../context.json | 1 + .../en.json | 9 + .../keyset.json | 40 + .../ru.json | 9 + .../context.json | 1 + .../en.json | 9 + .../keyset.json | 40 + .../ru.json | 9 + .../context.json | 1 + .../en.json | 9 + .../keyset.json | 40 + .../ru.json | 9 + .../context.json | 1 + .../en.json | 3 + .../keyset.json | 16 + .../ru.json | 3 + .../component.dialog-dash-meta/context.json | 1 + .../component.dialog-dash-meta/en.json | 8 + .../component.dialog-dash-meta/keyset.json | 36 + .../component.dialog-dash-meta/ru.json | 8 + .../context.json | 1 + .../en.json | 7 + .../keyset.json | 32 + .../ru.json | 7 + .../context.json | 1 + .../en.json | 9 + .../keyset.json | 40 + .../ru.json | 9 + .../context.json | 1 + .../en.json | 6 + .../keyset.json | 28 + .../ru.json | 6 + .../context.json | 1 + .../en.json | 8 + .../keyset.json | 36 + .../ru.json | 8 + .../context.json | 1 + .../component.dialog-move-entry.view/en.json | 7 + .../keyset.json | 32 + .../component.dialog-move-entry.view/ru.json | 7 + .../component.dialog-parameter/context.json | 1 + .../component.dialog-parameter/en.json | 26 + .../component.dialog-parameter/keyset.json | 108 + .../component.dialog-parameter/ru.json | 26 + .../context.json | 1 + .../en.json | 15 + .../keyset.json | 64 + .../ru.json | 15 + .../context.json | 1 + .../component.dialog-relations.view/en.json | 56 + .../keyset.json | 228 + .../component.dialog-relations.view/ru.json | 56 + .../context.json | 1 + .../en.json | 8 + .../keyset.json | 36 + .../ru.json | 8 + .../context.json | 1 + .../en.json | 11 + .../keyset.json | 48 + .../ru.json | 11 + .../context.json | 1 + .../component.dialog-revisions.view/en.json | 22 + .../keyset.json | 92 + .../component.dialog-revisions.view/ru.json | 22 + .../context.json | 1 + .../en.json | 10 + .../keyset.json | 44 + .../ru.json | 10 + .../context.json | 1 + .../component.dialog-save-widget.view/en.json | 9 + .../keyset.json | 40 + .../component.dialog-save-widget.view/ru.json | 9 + .../context.json | 1 + .../en.json | 4 + .../keyset.json | 20 + .../ru.json | 4 + .../context.json | 1 + .../en.json | 6 + .../keyset.json | 28 + .../ru.json | 6 + .../context.json | 1 + .../en.json | 28 + .../keyset.json | 116 + .../ru.json | 28 + .../context.json | 1 + .../en.json | 13 + .../keyset.json | 56 + .../ru.json | 13 + .../context.json | 1 + .../en.json | 19 + .../keyset.json | 80 + .../ru.json | 19 + .../context.json | 1 + .../en.json | 21 + .../keyset.json | 88 + .../ru.json | 21 + .../context.json | 1 + .../en.json | 45 + .../keyset.json | 184 + .../ru.json | 45 + .../context.json | 1 + .../en.json | 3 + .../keyset.json | 16 + .../ru.json | 3 + .../context.json | 1 + .../component.dl-dialog-confirm.view/en.json | 7 + .../keyset.json | 32 + .../component.dl-dialog-confirm.view/ru.json | 7 + .../context.json | 1 + .../component.dl-dialog-filter.view/en.json | 16 + .../keyset.json | 68 + .../component.dl-dialog-filter.view/ru.json | 16 + .../context.json | 1 + .../en.json | 6 + .../keyset.json | 28 + .../ru.json | 6 + .../context.json | 1 + .../component.dl-dialog-warning.view/en.json | 3 + .../keyset.json | 16 + .../component.dl-dialog-warning.view/ru.json | 3 + .../context.json | 1 + .../component.dl-field-editor.view/en.json | 52 + .../keyset.json | 212 + .../component.dl-field-editor.view/ru.json | 52 + .../context.json | 1 + .../component.editor-console.view/en.json | 4 + .../component.editor-console.view/keyset.json | 20 + .../component.editor-console.view/ru.json | 4 + .../component.editor-controls/context.json | 1 + .../component.editor-controls/en.json | 5 + .../component.editor-controls/keyset.json | 24 + .../component.editor-controls/ru.json | 5 + .../component.editor-search.view/context.json | 1 + .../component.editor-search.view/en.json | 5 + .../component.editor-search.view/keyset.json | 24 + .../component.editor-search.view/ru.json | 5 + .../component.entity-row.view/context.json | 1 + .../component.entity-row.view/en.json | 5 + .../component.entity-row.view/keyset.json | 24 + .../component.entity-row.view/ru.json | 5 + .../context.json | 1 + .../component.entry-context-menu.view/en.json | 20 + .../keyset.json | 84 + .../component.entry-context-menu.view/ru.json | 20 + .../component.entry-label.view/context.json | 1 + .../component.entry-label.view/en.json | 6 + .../component.entry-label.view/keyset.json | 28 + .../component.entry-label.view/ru.json | 6 + .../component.entry-row.view/context.json | 1 + .../component.entry-row.view/en.json | 3 + .../component.entry-row.view/keyset.json | 16 + .../component.entry-row.view/ru.json | 3 + .../component.entry-suggest.view/context.json | 1 + .../component.entry-suggest.view/en.json | 12 + .../component.entry-suggest.view/keyset.json | 52 + .../component.entry-suggest.view/ru.json | 12 + .../component.error-content.view/context.json | 1 + .../component.error-content.view/en.json | 7 + .../component.error-content.view/keyset.json | 32 + .../component.error-content.view/ru.json | 7 + .../component.error-dialog.view/context.json | 1 + .../component.error-dialog.view/en.json | 18 + .../component.error-dialog.view/keyset.json | 76 + .../component.error-dialog.view/ru.json | 18 + .../context.json | 1 + .../component.expandable-panel.view/en.json | 4 + .../keyset.json | 20 + .../component.expandable-panel.view/ru.json | 4 + .../component.field-editor.view/context.json | 1 + .../component.field-editor.view/en.json | 51 + .../component.field-editor.view/keyset.json | 208 + .../component.field-editor.view/ru.json | 51 + .../context.json | 1 + .../component.formatted-value.view/en.json | 7 + .../keyset.json | 32 + .../component.formatted-value.view/ru.json | 7 + .../component.iam-access-dialog/context.json | 1 + .../component.iam-access-dialog/en.json | 32 + .../component.iam-access-dialog/keyset.json | 132 + .../component.iam-access-dialog/ru.json | 32 + .../context.json | 1 + .../en.json | 5 + .../keyset.json | 24 + .../ru.json | 5 + .../context.json | 1 + .../component.list-with-menu.view/en.json | 5 + .../component.list-with-menu.view/keyset.json | 24 + .../component.list-with-menu.view/ru.json | 5 + .../context.json | 1 + .../en.json | 27 + .../keyset.json | 84 + .../ru.json | 27 + .../component.monaco.view/context.json | 1 + .../component.monaco.view/en.json | 3 + .../component.monaco.view/keyset.json | 16 + .../component.monaco.view/ru.json | 3 + .../component.navigation-prompt/context.json | 1 + .../component.navigation-prompt/en.json | 3 + .../component.navigation-prompt/keyset.json | 16 + .../component.navigation-prompt/ru.json | 3 + .../component.navigation.view/context.json | 1 + .../component.navigation.view/en.json | 82 + .../component.navigation.view/keyset.json | 332 + .../component.navigation.view/ru.json | 82 + .../context.json | 1 + .../component.need-reset-dialog.view/en.json | 5 + .../keyset.json | 24 + .../component.need-reset-dialog.view/ru.json | 5 + .../context.json | 1 + .../en.json | 18 + .../keyset.json | 76 + .../ru.json | 18 + .../component.operations/context.json | 1 + src/i18n-keysets/component.operations/en.json | 27 + .../component.operations/keyset.json | 112 + src/i18n-keysets/component.operations/ru.json | 27 + .../component.path-select.view/context.json | 1 + .../component.path-select.view/en.json | 3 + .../component.path-select.view/keyset.json | 16 + .../component.path-select.view/ru.json | 3 + .../context.json | 1 + .../component.permission-select.view/en.json | 7 + .../keyset.json | 32 + .../component.permission-select.view/ru.json | 7 + .../context.json | 1 + .../en.json | 104 + .../keyset.json | 168 + .../ru.json | 104 + .../context.json | 1 + .../component.revisions-panel.view/en.json | 30 + .../keyset.json | 124 + .../component.revisions-panel.view/ru.json | 30 + .../context.json | 1 + .../en.json | 19 + .../keyset.json | 80 + .../ru.json | 19 + .../component.toaster.view/context.json | 1 + .../component.toaster.view/en.json | 3 + .../component.toaster.view/keyset.json | 16 + .../component.toaster.view/ru.json | 3 + .../component.user-menu.view/context.json | 1 + .../component.user-menu.view/en.json | 3 + .../component.user-menu.view/keyset.json | 16 + .../component.user-menu.view/ru.json | 3 + .../component.users-suggest.view/context.json | 1 + .../component.users-suggest.view/en.json | 7 + .../component.users-suggest.view/keyset.json | 32 + .../component.users-suggest.view/ru.json | 7 + .../component.view-error.view/context.json | 1 + .../component.view-error.view/en.json | 6 + .../component.view-error.view/keyset.json | 28 + .../component.view-error.view/ru.json | 6 + .../context.json | 1 + .../en.json | 22 + .../keyset.json | 92 + .../ru.json | 22 + .../context.json | 1 + .../en.json | 23 + .../keyset.json | 96 + .../ru.json | 23 + .../context.json | 1 + .../en.json | 9 + .../keyset.json | 40 + .../ru.json | 9 + .../context.json | 1 + .../en.json | 8 + .../keyset.json | 36 + .../ru.json | 8 + .../context.json | 1 + .../component.wysiwyg-editor.view/en.json | 3 + .../component.wysiwyg-editor.view/keyset.json | 16 + .../component.wysiwyg-editor.view/ru.json | 3 + .../components.common.Datepicker/context.json | 1 + .../components.common.Datepicker/en.json | 54 + .../components.common.Datepicker/keyset.json | 220 + .../components.common.Datepicker/ru.json | 54 + .../context.json | 1 + .../en.json | 11 + .../keyset.json | 48 + .../ru.json | 11 + .../components.common.YCSelect/context.json | 1 + .../components.common.YCSelect/en.json | 18 + .../components.common.YCSelect/keyset.json | 76 + .../components.common.YCSelect/ru.json | 18 + .../context.json | 1 + .../connections.connectors-list.view/en.json | 10 + .../keyset.json | 44 + .../connections.connectors-list.view/ru.json | 10 + .../connections.file.view/context.json | 1 + .../connections.file.view/en.json | 32 + .../connections.file.view/keyset.json | 132 + .../connections.file.view/ru.json | 32 + .../connections.form/context.json | 1 + src/i18n-keysets/connections.form/en.json | 183 + src/i18n-keysets/connections.form/keyset.json | 736 + src/i18n-keysets/connections.form/ru.json | 183 + .../connections.gsheet.view/context.json | 1 + .../connections.gsheet.view/en.json | 42 + .../connections.gsheet.view/keyset.json | 172 + .../connections.gsheet.view/ru.json | 42 + .../connections.yadocs.view/context.json | 1 + .../connections.yadocs.view/en.json | 29 + .../connections.yadocs.view/keyset.json | 120 + .../connections.yadocs.view/ru.json | 29 + .../dash.action-panel.view/context.json | 1 + .../dash.action-panel.view/en.json | 42 + .../dash.action-panel.view/keyset.json | 172 + .../dash.action-panel.view/ru.json | 42 + .../dash.chartkit-menu.view/context.json | 1 + .../dash.chartkit-menu.view/en.json | 4 + .../dash.chartkit-menu.view/keyset.json | 20 + .../dash.chartkit-menu.view/ru.json | 4 + .../dash.control-dialog.edit/context.json | 1 + .../dash.control-dialog.edit/en.json | 106 + .../dash.control-dialog.edit/keyset.json | 428 + .../dash.control-dialog.edit/ru.json | 106 + .../dash.dashkit-control.error/context.json | 1 + .../dash.dashkit-control.error/en.json | 7 + .../dash.dashkit-control.error/keyset.json | 32 + .../dash.dashkit-control.error/ru.json | 7 + .../context.json | 1 + .../dash.dashkit-plugin-common.view/en.json | 7 + .../keyset.json | 32 + .../dash.dashkit-plugin-common.view/ru.json | 7 + .../context.json | 1 + .../dash.dashkit-plugin-control.view/en.json | 9 + .../keyset.json | 40 + .../dash.dashkit-plugin-control.view/ru.json | 9 + .../dash.dialogs-common.edit/context.json | 1 + .../dash.dialogs-common.edit/en.json | 4 + .../dash.dialogs-common.edit/keyset.json | 20 + .../dash.dialogs-common.edit/ru.json | 4 + .../dash.edit-query-dialog/context.json | 1 + .../dash.edit-query-dialog/en.json | 9 + .../dash.edit-query-dialog/keyset.json | 40 + .../dash.edit-query-dialog/ru.json | 9 + .../dash.empty-state.view/context.json | 1 + .../dash.empty-state.view/en.json | 11 + .../dash.empty-state.view/keyset.json | 48 + .../dash.empty-state.view/ru.json | 11 + src/i18n-keysets/dash.error.view/context.json | 1 + src/i18n-keysets/dash.error.view/en.json | 5 + src/i18n-keysets/dash.error.view/keyset.json | 24 + src/i18n-keysets/dash.error.view/ru.json | 5 + .../context.json | 1 + .../en.json | 17 + .../keyset.json | 72 + .../ru.json | 17 + .../context.json | 1 + .../dash.group-controls-dialog.edit/en.json | 29 + .../keyset.json | 120 + .../dash.group-controls-dialog.edit/ru.json | 29 + .../dash.image-dialog.edit/context.json | 1 + .../dash.image-dialog.edit/en.json | 11 + .../dash.image-dialog.edit/keyset.json | 48 + .../dash.image-dialog.edit/ru.json | 11 + src/i18n-keysets/dash.main.view/context.json | 1 + src/i18n-keysets/dash.main.view/en.json | 36 + src/i18n-keysets/dash.main.view/keyset.json | 148 + src/i18n-keysets/dash.main.view/ru.json | 36 + .../dash.navigation-input.edit/context.json | 1 + .../dash.navigation-input.edit/en.json | 14 + .../dash.navigation-input.edit/keyset.json | 60 + .../dash.navigation-input.edit/ru.json | 14 + .../dash.palette-background/context.json | 1 + .../dash.palette-background/en.json | 10 + .../dash.palette-background/keyset.json | 44 + .../dash.palette-background/ru.json | 10 + .../context.json | 1 + .../dash.params-button-dialog.view/en.json | 4 + .../keyset.json | 20 + .../dash.params-button-dialog.view/ru.json | 4 + .../dash.params-setting.view/context.json | 1 + .../dash.params-setting.view/en.json | 6 + .../dash.params-setting.view/keyset.json | 28 + .../dash.params-setting.view/ru.json | 6 + .../context.json | 1 + .../dash.popup-widgets-order.view/en.json | 13 + .../dash.popup-widgets-order.view/keyset.json | 56 + .../dash.popup-widgets-order.view/ru.json | 13 + .../dash.settings-dialog.edit/context.json | 1 + .../dash.settings-dialog.edit/en.json | 43 + .../dash.settings-dialog.edit/keyset.json | 176 + .../dash.settings-dialog.edit/ru.json | 43 + src/i18n-keysets/dash.store.view/context.json | 1 + src/i18n-keysets/dash.store.view/en.json | 12 + src/i18n-keysets/dash.store.view/keyset.json | 52 + src/i18n-keysets/dash.store.view/ru.json | 12 + .../dash.table-of-content.view/context.json | 1 + .../dash.table-of-content.view/en.json | 4 + .../dash.table-of-content.view/keyset.json | 20 + .../dash.table-of-content.view/ru.json | 4 + .../dash.tabs-dialog.edit/context.json | 1 + .../dash.tabs-dialog.edit/en.json | 13 + .../dash.tabs-dialog.edit/keyset.json | 56 + .../dash.tabs-dialog.edit/ru.json | 13 + .../dash.text-dialog.edit/context.json | 1 + .../dash.text-dialog.edit/en.json | 10 + .../dash.text-dialog.edit/keyset.json | 44 + .../dash.text-dialog.edit/ru.json | 10 + .../dash.title-dialog.edit/context.json | 1 + .../dash.title-dialog.edit/en.json | 12 + .../dash.title-dialog.edit/keyset.json | 52 + .../dash.title-dialog.edit/ru.json | 12 + .../dash.widget-dialog.edit/context.json | 1 + .../dash.widget-dialog.edit/en.json | 37 + .../dash.widget-dialog.edit/keyset.json | 152 + .../dash.widget-dialog.edit/ru.json | 37 + .../datalens.landing.error/context.json | 1 + .../datalens.landing.error/en.json | 19 + .../datalens.landing.error/keyset.json | 80 + .../datalens.landing.error/ru.json | 19 + .../context.json | 1 + .../dataset.dataset-creation.create/en.json | 36 + .../keyset.json | 148 + .../dataset.dataset-creation.create/ru.json | 36 + .../context.json | 1 + .../dataset.dataset-editor.modify/en.json | 111 + .../dataset.dataset-editor.modify/keyset.json | 448 + .../dataset.dataset-editor.modify/ru.json | 111 + .../context.json | 1 + .../en.json | 16 + .../keyset.json | 40 + .../ru.json | 16 + .../dataset.filters-tab.modify/context.json | 1 + .../dataset.filters-tab.modify/en.json | 10 + .../dataset.filters-tab.modify/keyset.json | 44 + .../dataset.filters-tab.modify/ru.json | 10 + .../dataset.notifications.view/context.json | 1 + .../dataset.notifications.view/en.json | 16 + .../dataset.notifications.view/keyset.json | 68 + .../dataset.notifications.view/ru.json | 16 + .../context.json | 1 + .../dataset.parameters-tab.modify/en.json | 14 + .../dataset.parameters-tab.modify/keyset.json | 60 + .../dataset.parameters-tab.modify/ru.json | 14 + .../dataset.rls.modify/context.json | 1 + src/i18n-keysets/dataset.rls.modify/en.json | 6 + .../dataset.rls.modify/keyset.json | 28 + src/i18n-keysets/dataset.rls.modify/ru.json | 6 + .../dataset.sources-tab.modify/context.json | 1 + .../dataset.sources-tab.modify/en.json | 64 + .../dataset.sources-tab.modify/keyset.json | 260 + .../dataset.sources-tab.modify/ru.json | 64 + .../dataset.validation/context.json | 1 + src/i18n-keysets/dataset.validation/en.json | 5 + .../dataset.validation/keyset.json | 24 + src/i18n-keysets/dataset.validation/ru.json | 5 + .../dl-dialog-settings/context.json | 1 + src/i18n-keysets/dl-dialog-settings/en.json | 8 + .../dl-dialog-settings/keyset.json | 36 + src/i18n-keysets/dl-dialog-settings/ru.json | 8 + .../editor.button-save.view/context.json | 1 + .../editor.button-save.view/en.json | 9 + .../editor.button-save.view/keyset.json | 40 + .../editor.button-save.view/ru.json | 9 + .../editor.common.view/context.json | 1 + src/i18n-keysets/editor.common.view/en.json | 9 + .../editor.common.view/keyset.json | 40 + src/i18n-keysets/editor.common.view/ru.json | 9 + .../context.json | 1 + .../en.json | 4 + .../keyset.json | 20 + .../ru.json | 4 + .../context.json | 1 + .../editor.dialog-no-edit-rights.view/en.json | 6 + .../keyset.json | 28 + .../editor.dialog-no-edit-rights.view/ru.json | 6 + .../editor.docs.view/context.json | 1 + src/i18n-keysets/editor.docs.view/en.json | 7 + src/i18n-keysets/editor.docs.view/keyset.json | 32 + src/i18n-keysets/editor.docs.view/ru.json | 7 + .../context.json | 1 + .../editor.editor-page-error.view/en.json | 5 + .../editor.editor-page-error.view/keyset.json | 24 + .../editor.editor-page-error.view/ru.json | 5 + .../editor.fetch.view/context.json | 1 + src/i18n-keysets/editor.fetch.view/en.json | 4 + .../editor.fetch.view/keyset.json | 20 + src/i18n-keysets/editor.fetch.view/ru.json | 4 + .../context.json | 1 + .../editor.grid-scheme-select.view/en.json | 10 + .../keyset.json | 16 + .../editor.grid-scheme-select.view/ru.json | 10 + .../context.json | 1 + .../en.json | 4 + .../keyset.json | 20 + .../ru.json | 4 + .../main.service-settings.view/context.json | 1 + .../main.service-settings.view/en.json | 4 + .../main.service-settings.view/keyset.json | 20 + .../main.service-settings.view/ru.json | 4 + .../new-workbooks.empty-table/context.json | 1 + .../new-workbooks.empty-table/en.json | 12 + .../new-workbooks.empty-table/keyset.json | 52 + .../new-workbooks.empty-table/ru.json | 12 + .../new-workbooks.table-filters/context.json | 1 + .../new-workbooks.table-filters/en.json | 12 + .../new-workbooks.table-filters/keyset.json | 52 + .../new-workbooks.table-filters/ru.json | 12 + src/i18n-keysets/new-workbooks/context.json | 1 + src/i18n-keysets/new-workbooks/en.json | 53 + src/i18n-keysets/new-workbooks/keyset.json | 216 + src/i18n-keysets/new-workbooks/ru.json | 53 + src/i18n-keysets/preview/context.json | 1 + src/i18n-keysets/preview/en.json | 3 + src/i18n-keysets/preview/keyset.json | 16 + src/i18n-keysets/preview/ru.json | 3 + .../public.dash.error/context.json | 1 + src/i18n-keysets/public.dash.error/en.json | 3 + .../public.dash.error/keyset.json | 16 + src/i18n-keysets/public.dash.error/ru.json | 3 + .../public.dash.footer/context.json | 1 + src/i18n-keysets/public.dash.footer/en.json | 3 + .../public.dash.footer/keyset.json | 16 + src/i18n-keysets/public.dash.footer/ru.json | 3 + .../revisions.dialog-confirm/context.json | 1 + .../revisions.dialog-confirm/en.json | 7 + .../revisions.dialog-confirm/keyset.json | 32 + .../revisions.dialog-confirm/ru.json | 7 + .../context.json | 1 + .../service-settings.create-user.view/en.json | 6 + .../keyset.json | 28 + .../service-settings.create-user.view/ru.json | 6 + .../service-settings.main.view/context.json | 1 + .../service-settings.main.view/en.json | 7 + .../service-settings.main.view/keyset.json | 32 + .../service-settings.main.view/ru.json | 7 + .../context.json | 1 + .../service-settings.users-list.view/en.json | 17 + .../keyset.json | 72 + .../service-settings.users-list.view/ru.json | 17 + src/i18n-keysets/shared-entry/context.json | 1 + src/i18n-keysets/shared-entry/en.json | 11 + src/i18n-keysets/shared-entry/keyset.json | 48 + src/i18n-keysets/shared-entry/ru.json | 11 + src/i18n-keysets/sql/context.json | 1 + src/i18n-keysets/sql/en.json | 58 + src/i18n-keysets/sql/keyset.json | 236 + src/i18n-keysets/sql/ru.json | 58 + src/i18n-keysets/validation/context.json | 1 + src/i18n-keysets/validation/en.json | 4 + src/i18n-keysets/validation/keyset.json | 20 + src/i18n-keysets/validation/ru.json | 4 + .../wizard.hierarchy-editor/context.json | 1 + .../wizard.hierarchy-editor/en.json | 10 + .../wizard.hierarchy-editor/keyset.json | 44 + .../wizard.hierarchy-editor/ru.json | 10 + src/i18n-keysets/wizard.palette/context.json | 1 + src/i18n-keysets/wizard.palette/en.json | 10 + src/i18n-keysets/wizard.palette/keyset.json | 44 + src/i18n-keysets/wizard.palette/ru.json | 10 + src/i18n-keysets/wizard.prepares/context.json | 1 + src/i18n-keysets/wizard.prepares/en.json | 11 + src/i18n-keysets/wizard.prepares/keyset.json | 48 + src/i18n-keysets/wizard.prepares/ru.json | 11 + src/i18n-keysets/wizard/context.json | 1 + src/i18n-keysets/wizard/en.json | 402 + src/i18n-keysets/wizard/keyset.json | 1612 + src/i18n-keysets/wizard/ru.json | 402 + src/i18n/constants.ts | 4 + src/i18n/index.ts | 22 + src/i18n/prepare-keysets/constants.ts | 2 + src/i18n/prepare-keysets/index.ts | 22 + src/i18n/prepare-keysets/prepare-keysets.ts | 48 + src/i18n/prepare-keysets/utils.ts | 143 + src/i18n/read-keysets.ts | 26 + src/i18n/tsconfig.json | 10 + src/i18n/types.ts | 13 + src/i18n/utils.ts | 39 + src/server/.eslintrc | 3 + src/server/app-env.ts | 15 + src/server/callbacks/index.ts | 17 + src/server/callbacks/page-error.ts | 28 + src/server/components/api-docs/constants.ts | 1 + src/server/components/api-docs/index.ts | 1 + src/server/components/api-docs/types.ts | 14 + .../app-layout/app-layout-settings.ts | 65 + .../app-layout/plugins/layout/index.ts | 104 + .../components/auth/callbacks/auth-layout.ts | 23 + src/server/components/auth/callbacks/index.ts | 15 + .../auth/middlewares/auth/api-auth.ts | 50 + .../auth/middlewares/auth/constants.ts | 3 + .../components/auth/middlewares/auth/index.ts | 12 + .../auth/middlewares/auth/ui-auth.ts | 166 + src/server/components/auth/routes.ts | 20 + src/server/components/auth/types/token.ts | 12 + src/server/components/auth/types/user.ts | 15 + src/server/components/axios.ts | 31 + src/server/components/cache-client.ts | 123 + ...hart-editor-developer-mode-access-check.ts | 4 + .../__tests__/chart-validator.test.ts | 9 + .../__tests__/mocks/chart-validator.ts | 442 + .../components/__tests__/source-alias.test.ts | 115 + .../components/__tests__/utils.test.ts | 242 + .../components/chart-generator.ts | 337 + .../components/chart-validator.ts | 116 + .../charts-engine/components/error-handler.ts | 49 + .../charts-engine/components/markdown.ts | 16 + .../components/processor/chart-api-context.ts | 168 + .../components/processor/comments-fetcher.ts | 283 + .../components/processor/console.ts | 87 + .../components/processor/control-builder.ts | 151 + .../components/processor/data-fetcher.ts | 1248 + .../components/processor/hooks.ts | 107 + .../components/processor/index.ts | 1098 + .../components/processor/paramsUtils.ts | 87 + .../components/processor/source-alias.ts | 52 + .../components/processor/sources.ts | 213 + .../processor/stack-trace-prepaper.ts | 26 + .../components/processor/types.ts | 198 + .../components/processor/utils.ts | 65 + .../processor/worker-chart-builder.ts | 232 + .../charts-engine/components/request/axios.ts | 559 + .../charts-engine/components/request/index.ts | 29 + .../charts-engine/components/storage/base.ts | 257 + .../charts-engine/components/storage/index.ts | 32 + .../charts-engine/components/storage/types.ts | 93 + .../storage/united-storage/index.ts | 3 + .../storage/united-storage/provider.ts | 811 + .../charts-engine/components/utils.ts | 182 + .../components/wizard-worker/index.ts | 183 + .../components/wizard-worker/types.ts | 90 + .../components/wizard-worker/utils.ts | 15 + .../charts-engine/constants/index.ts | 76 + .../charts-engine/controllers/charts.ts | 331 + .../charts-engine/controllers/config.ts | 15 + .../charts-engine/controllers/csvConverter.ts | 144 + .../controllers/embedded-entry.ts | 116 + .../charts-engine/controllers/embeds.ts | 510 + .../charts-engine/controllers/export.ts | 83 + .../charts-engine/controllers/markdown.ts | 46 + .../charts-engine/controllers/run.ts | 193 + .../charts-engine/controllers/utils.ts | 91 + src/server/components/charts-engine/index.ts | 155 + .../charts-engine/runners/common.ts | 375 + .../charts-engine/runners/constants.ts | 4 + .../charts-engine/runners/control.ts | 80 + .../components/charts-engine/runners/index.ts | 76 + .../components/charts-engine/runners/utils.ts | 42 + .../charts-engine/runners/wizard.ts | 44 + .../charts-engine/runners/worker.ts | 129 + src/server/components/charts-engine/types.ts | 246 + .../features-list/AsideHeaderEnabled.ts | 10 + .../features-list/AuthUpdateWithTimeout.ts | 10 + .../ChartEditorDeveloperModeCheck.ts | 10 + .../features/features-list/ChartModeling.ts | 10 + .../features/features-list/ChartkitAlerts.ts | 10 + .../features-list/CollectionsAccessEnabled.ts | 10 + .../features-list/CollectionsE2ETestsMode.ts | 10 + .../features-list/CollectionsEnabled.ts | 10 + .../features/features-list/Comments.ts | 10 + .../features-list/ConnectionBasedControl.ts | 10 + .../features-list/CopyEntriesToWorkbook.ts | 10 + .../DashBoardSupportDescription.ts | 10 + .../DashBoardWidgetParamsStrictValidation.ts | 10 + .../DashServerMigrationEnable.ts | 10 + .../DashServerValidationEnabled.ts | 10 + .../features/features-list/DatasetsRLS.ts | 10 + .../features-list/DisableFnAndHtml.ts | 10 + .../features-list/EnableAutocreateDataset.ts | 10 + .../features-list/EnableChartEditorDocs.ts | 10 + .../EnableCommonChartDashSettings.ts | 10 + .../features-list/EnableCustomDashMargins.ts | 10 + .../features-list/EnableCustomMonitoring.ts | 10 + .../features-list/EnableDashAutoFocus.ts | 10 + .../features-list/EnableDashChartStat.ts | 10 + .../EnableDashColorPickersByTheme.ts | 10 + .../EnableDatasetEarlyInvalidationCache.ts | 10 + .../EnableEmbedsInDialogShare.ts | 10 + .../features-list/EnableEntryMenuItemShare.ts | 10 + .../features-list/EnableExportSettings.ts | 10 + .../features-list/EnableExportWorkbookFile.ts | 10 + .../features/features-list/EnableFooter.ts | 10 + .../features-list/EnableMobileFixedHeader.ts | 10 + .../features-list/EnableMobileHeader.ts | 10 + .../features-list/EnableNewDashSettings.ts | 10 + .../features-list/EnableNewServiceSettings.ts | 10 + .../features-list/EnablePublishEntry.ts | 10 + .../features/features-list/EnableRLSV2.ts | 10 + .../features-list/EnableSaveAsEditorScript.ts | 10 + .../features-list/EnableServerlessEditor.ts | 10 + .../features-list/EnableSharedEntries.ts | 10 + .../EnableUpdatingDsSettingsByAction.ts | 10 + .../features-list/EntryMenuItemCopy.ts | 10 + .../features-list/EntryMenuItemMove.ts | 10 + .../features-list/ExternalSelectors.ts | 10 + .../features-list/FetchDocumentation.ts | 10 + .../features-list/FieldEditorDocSection.ts | 10 + .../features/features-list/FunnelChart.ts | 10 + .../GravityChartsForBarYAndScatter.ts | 10 + .../GravityChartsForLineAreaAndBarX.ts | 10 + .../GravityChartsForPieAndTreemap.ts | 10 + .../features/features-list/HideMultitenant.ts | 10 + .../features/features-list/HolidaysOnChart.ts | 10 + .../MassRemoveCollectionsWorkbooks.ts | 10 + .../features-list/MenuItemsFlatView.ts | 10 + .../features/features-list/MultiDatasets.ts | 10 + .../MultipleColorsInVisualization.ts | 10 + .../features-list/NewTableWidgetForCE.ts | 10 + .../features/features-list/NoJsonFn.ts | 10 + .../features/features-list/QLMonitoring.ts | 10 + .../features/features-list/QLPrometheus.ts | 10 + .../QlAutoExecuteMonitoringChart.ts | 10 + .../features/features-list/ReadOnlyMode.ts | 10 + .../features-list/RevisionsListNoLimit.ts | 10 + .../features-list/ShouldCheckEditorAccess.ts | 10 + .../ShowChartsEngineDebugInfo.ts | 10 + .../features-list/ShowCreateEntryWithMenu.ts | 10 + .../features-list/ShowInspectorDetails.ts | 10 + .../features-list/UsDynamicMasterToken.ts | 10 + .../UsDynamicMasterTokenInProxy.ts | 10 + .../features-list/UseChartsEngineLogin.ts | 10 + .../UseChartsEngineResponseConfig.ts | 10 + .../features-list/UseComponentHeader.ts | 10 + .../features-list/UseConfigurableChartkit.ts | 10 + .../features-list/UsePublicDistincts.ts | 10 + .../features/features-list/UseSourceAlias.ts | 10 + .../features/features-list/UseYqlFolderKey.ts | 10 + .../features-list/XlsxChartExportEnabled.ts | 10 + .../features-list/XlsxFilesEnabled.ts | 10 + .../features/features-list/index.ts | 21 + src/server/components/features/index.ts | 40 + src/server/components/features/utils.ts | 26 + .../auth-args-middleware.ts | 64 + .../gateway-auth-helpers/bi-auth-helpers.ts | 46 + .../components/gateway-auth-helpers/index.ts | 3 + .../gateway-auth-helpers/us-auth-helpers.ts | 53 + src/server/components/index.ts | 7 + src/server/components/layout/index.ts | 2 + .../components/layout/landing-layout.ts | 21 + .../layout/opensource-layout-config.ts | 119 + src/server/components/layout/utils.ts | 57 + .../components/metrika-data-formatter.js | 345 + .../components/public-api/config/index.ts | 1 + src/server/components/public-api/config/v1.ts | 437 + .../public-api/constants/action-names.ts | 4 + .../components/public-api/constants/common.ts | 29 + .../components/public-api/constants/index.ts | 2 + src/server/components/public-api/index.ts | 10 + src/server/components/public-api/types.ts | 57 + .../components/public-api/utils/index.ts | 5 + .../utils/init-public-api-swagger.ts | 111 + .../utils/parse-public-api-version-header.ts | 25 + .../pick-additional-public-api-headers.ts | 3 + .../utils/prepare-public-api-base-config.ts | 33 + .../utils/register-action-to-open-api.ts | 77 + .../components/resolve-entry-by-link.ts | 96 + src/server/components/sdk/dash.ts | 493 + src/server/components/sdk/index.ts | 3 + src/server/components/sdk/us.ts | 239 + .../components/workbook-transfer/charts.ts | 321 + .../create-transfer-notifications.ts | 26 + .../components/workbook-transfer/dash.ts | 122 + src/server/configs/common.ts | 52 + src/server/configs/opensource/common.ts | 236 + src/server/configs/opensource/development.ts | 5 + src/server/configs/secrets.js | 5 + .../shared/control-dash-chart-template.ts | 14 + .../configs/shared/datalens-chart-template.ts | 80 + .../configs/shared/ql-chart-template.ts | 106 + src/server/configs/sources-secrets.json | 1 + src/server/constants/axios.ts | 3 + src/server/constants/index.ts | 88 + src/server/constants/public-api.ts | 1 + src/server/controllers/api.ts | 31 + src/server/controllers/dl-main.ts | 10 + src/server/controllers/index.ts | 13 + src/server/controllers/navigate.ts | 85 + src/server/controllers/navigation.ts | 63 + src/server/controllers/ping.ts | 5 + .../controllers/public-api/constants.ts | 11 + src/server/controllers/public-api/index.ts | 157 + src/server/controllers/public-api/utils.ts | 122 + src/server/controllers/stats.js | 20 + .../utils/handle-entry-redirect.ts | 21 + src/server/controllers/workbook-transfer.ts | 349 + src/server/controllers/xlsx-converter.ts | 134 + src/server/expresskit.ts | 26 + src/server/index.ts | 49 + src/server/lib/validation/index.ts | 27 + src/server/lib/validation/types.ts | 15 + src/server/local-dev.ts | 50 + src/server/middlewares/app-layout.ts | 20 + .../middlewares/before-auth-defaults.ts | 18 + src/server/middlewares/connector-icons.ts | 50 + src/server/middlewares/ctx.ts | 22 + src/server/middlewares/index.ts | 19 + src/server/middlewares/patch-logger.ts | 21 + src/server/middlewares/scr-requests.ts | 13 + .../server-feature-with-bounded-context.ts | 16 + src/server/middlewares/tenant-settings.ts | 44 + src/server/middlewares/x-dl-context.ts | 71 + src/server/modes/charts/init-charts-engine.ts | 109 + .../plugins/constants/middleware-urls.ts | 4 + .../modes/charts/plugins/constants/misc.ts | 14 + .../charts/plugins/control/constants/misc.ts | 1 + .../charts/plugins/control/controls/index.ts | 5 + .../charts/plugins/control/helpers/misc.ts | 31 + .../modes/charts/plugins/control/index.ts | 20 + .../dataset/process-dataset-content.ts | 61 + .../js/helpers/dataset/process-fields.ts | 52 + .../control/js/helpers/process-content.ts | 42 + .../process-typed-query-content.ts | 11 + .../process-typed-query-parameters.ts | 17 + .../modes/charts/plugins/control/js/index.ts | 122 + .../modes/charts/plugins/control/js/types.ts | 11 + .../modes/charts/plugins/control/types.ts | 61 + .../charts/plugins/control/url/constants.ts | 8 + .../url/distincts/build-distincts-body.ts | 205 + .../plugins/control/url/distincts/index.ts | 21 + .../plugins/control/url/fields/index.ts | 12 + .../modes/charts/plugins/control/url/index.ts | 59 + .../plugins/control/url/typed-query/index.ts | 31 + .../modes/charts/plugins/control/url/types.ts | 34 + src/server/modes/charts/plugins/dash-api.ts | 210 + .../charts/plugins/data-api-json-schema.ts | 639 + .../plugins/datalens/chart-editor-module.ts | 136 + .../plugins/datalens/config/constants.ts | 5 + .../charts/plugins/datalens/config/index.ts | 230 + .../charts/plugins/datalens/config/types.ts | 87 + .../gravity-charts/utils/dataLabels.ts | 18 + .../datalens/gravity-charts/utils/format.ts | 28 + .../datalens/gravity-charts/utils/index.ts | 231 + .../gravity-charts/utils/range-slider.ts | 59 + .../datalens/gravity-charts/utils/tooltip.ts | 28 + .../charts/plugins/datalens/highcharts.ts | 422 + .../plugins/datalens/js/constants/errors.ts | 6 + .../mergeResultForCombinedCharts.test.ts | 66 + .../mocks/totals/getMergedTotals.mock.ts | 64 + .../__tests__/totals/getMergedTotals.test.ts | 54 + .../datalens/js/helpers/colors/index.ts | 54 + .../js/helpers/errors/oversize-error/index.ts | 49 + .../js/helpers/errors/oversize-error/types.ts | 16 + .../js/helpers/errors/oversize-error/utils.ts | 59 + .../datalens/js/helpers/layer-chart/index.ts | 144 + .../datalens/js/helpers/layer-chart/types.ts | 8 + .../js/helpers/misc/prepare-single-result.ts | 352 + .../js/helpers/notifications/index.ts | 59 + .../datalens/js/helpers/totals/index.ts | 29 + .../datalens/js/helpers/totals/types.ts | 11 + .../plugins/datalens/js/js-v1.5-private.ts | 507 + .../modes/charts/plugins/datalens/js/js.ts | 1073 + .../preparers/__tests__/common.mock.ts | 41 + .../plugins/datalens/preparers/area/index.ts | 360 + .../plugins/datalens/preparers/area/types.ts | 14 + .../__tests__/table-head-generator.test.ts | 186 + .../backend-pivot-table/constants/misc.ts | 1 + .../helpers/__tests__/color.test.ts | 216 + .../helpers/__tests__/mocks/color.mock.ts | 174 + .../helpers/backgroundColor.ts | 386 + .../backend-pivot-table/helpers/color.ts | 133 + .../backend-pivot-table/helpers/misc.ts | 211 + .../backend-pivot-table/helpers/sort.ts | 69 + .../backend-pivot-table/helpers/totals.ts | 108 + .../backend-pivot-table/helpers/types.ts | 6 + .../preparers/backend-pivot-table/index.ts | 256 + .../table-head-generator.ts | 597 + .../table-rows-generator.ts | 364 + .../preparers/backend-pivot-table/types.ts | 157 + .../bar-x/__tests__/mocks/prepareBarX.mock.ts | 101 + .../mocks/prepareHighchartsBarX.mock.ts | 768 + .../bar-x/__tests__/prepareBarX.test.ts | 99 + .../bar-x/__tests__/prepareHighchartsBarX.ts | 41 + .../preparers/bar-x/gravity-charts.ts | 446 + .../datalens/preparers/bar-x/highcharts.ts | 245 + .../plugins/datalens/preparers/bar-x/index.ts | 2 + .../datalens/preparers/bar-x/prepare-bar-x.ts | 586 + .../plugins/datalens/preparers/bar-x/types.ts | 16 + .../preparers/bar-y/__tests__/bar-y.test.ts | 124 + .../preparers/bar-y/gravity-charts.ts | 281 + .../datalens/preparers/bar-y/highcharts.ts | 160 + .../plugins/datalens/preparers/bar-y/index.ts | 2 + .../preparers/bar-y/prepare-bar-y-data.ts | 543 + .../plugins/datalens/preparers/constants.ts | 11 + .../flat-table/__tests__/flat-table.test.ts | 87 + .../__tests__/mocks/flat-table.mock.ts | 249 + ...nd-colors-map-by-continuous-column.test.ts | 93 + .../get-flat-table-background-styles.test.ts | 34 + .../__tests__/footer/getFooter.test.ts | 148 + .../helpers/__tests__/mocks/getFooter.mock.ts | 78 + ...kground-colors-map-by-continuous-column.ts | 86 + .../get-flat-table-background-styles.ts | 141 + .../helpers/background-settings/index.ts | 2 + .../helpers/background-settings/types.ts | 43 + .../flat-table/helpers/footer/index.ts | 137 + .../flat-table/helpers/footer/types.ts | 32 + .../preparers/flat-table/helpers/misc.ts | 43 + .../datalens/preparers/flat-table/index.ts | 364 + .../datalens/preparers/funnel/index.ts | 213 + .../preparers/geopoint-with-cluster.ts | 4 + .../__tests__/mocks/geopoints.mock.ts | 74 + .../__tests__/prepareGeopoint.test.ts | 157 + .../datalens/preparers/geopoint/constants.ts | 3 + .../datalens/preparers/geopoint/index.ts | 514 + .../datalens/preparers/geopoint/types.ts | 44 + .../plugins/datalens/preparers/geopolygon.ts | 505 + .../plugins/datalens/preparers/heatmap.ts | 214 + .../preparers/helpers/action-params.ts | 32 + .../helpers/axis/addAxisFormatter.ts | 37 + .../helpers/axis/addAxisFormatting.ts | 17 + .../preparers/helpers/axis/data-labels.ts | 1 + .../helpers/axis/get-axis-formatting.ts | 49 + .../preparers/helpers/axis/getAxisType.ts | 30 + .../datalens/preparers/helpers/axis/index.ts | 34 + .../helpers/axis/isAxisLabelDateFormat.ts | 16 + .../helpers/backgroundSettings/misc.ts | 13 + .../preparers/helpers/barsSettings/index.ts | 225 + .../preparers/helpers/barsSettings/types.ts | 6 + .../datalens/preparers/helpers/colors.ts | 60 + .../preparers/helpers/columnSettings.ts | 16 + .../datalens/preparers/helpers/common.ts | 21 + .../preparers/helpers/get-formatted-value.ts | 26 + .../datalens/preparers/helpers/highcharts.ts | 47 + .../datalens/preparers/helpers/layers.ts | 44 + .../datalens/preparers/helpers/legend.ts | 65 + .../datalens/preparers/helpers/segments.ts | 56 + .../preparers/helpers/visualizations.ts | 8 + .../line-time/__tests__/line-time.test.ts | 14 + .../__tests__/mocks/line-time.mock.ts | 438 + .../datalens/preparers/line-time/index.ts | 280 + .../preparers/line/__tests__/line.test.ts | 38 + .../line/__tests__/mocks/bar.mock.ts | 531 + .../line/__tests__/mocks/line.mock.ts | 464 + .../datalens/preparers/line/gravity-charts.ts | 415 + .../__tests__/getSegmentsYAxis.test.ts | 27 + .../__tests__/getSortedLineKeys.test.ts | 61 + .../axis/getLayerPlaceholderWithItems.ts | 34 + .../preparers/line/helpers/axis/types.ts | 3 + .../color-helpers/colorizeByGradient.ts | 27 + .../line/helpers/color-helpers/types.ts | 7 + .../line/helpers/getSortedCategories.ts | 181 + .../line/helpers/getSortedLineKeys.ts | 98 + .../preparers/line/helpers/getXAxisValue.ts | 57 + .../datalens/preparers/line/helpers/index.ts | 11 + .../line-mappers/__tests__/helpers.test.ts | 45 + .../line-mappers/getColoredLineLegendTitle.ts | 55 + .../line/helpers/line-mappers/helpers.ts | 23 + .../mapDataToDimensionColoredLines.ts | 209 + .../helpers/line-mappers/mapDataToLines.ts | 98 + .../mapDataToMeasureColoredLines.ts | 103 + .../line/helpers/line-mappers/prepareLines.ts | 374 + .../line/helpers/line-mappers/types.ts | 164 + .../line/helpers/segments/getSegmentName.ts | 6 + .../segments/getSegmentsIndexInOrder.ts | 17 + .../line/helpers/segments/getSegmentsList.ts | 45 + .../line/helpers/segments/getSegmentsMap.ts | 44 + .../line/helpers/segments/getSegmentsYAxis.ts | 93 + .../preparers/line/helpers/segments/types.ts | 27 + .../datalens/preparers/line/helpers/types.ts | 87 + .../datalens/preparers/line/helpers/utils.ts | 91 + .../datalens/preparers/line/highcharts.ts | 257 + .../plugins/datalens/preparers/line/index.ts | 2 + .../preparers/line/prepare-line-data.ts | 637 + .../plugins/datalens/preparers/line/types.ts | 60 + .../preparers/metric/__tests__/metric.test.ts | 14 + .../metric/__tests__/mocks/metric.mock.ts | 93 + .../datalens/preparers/metric/index.ts | 59 + .../datalens/preparers/metric/utils.ts | 21 + .../preparers/metric/variants/basic.ts | 89 + .../preparers/metric/variants/markup.ts | 97 + .../__tests__/mocks/old-pivot-table.mock.ts | 6972 +++++ .../__tests__/old-pivot-table.test.ts | 262 + .../old-pivot-table/old-pivot-table.ts | 1154 + .../pie/__tests__/highcharts.test.ts | 16 + .../preparers/pie/__tests__/mocks/pie.mock.ts | 326 + .../preparers/pie/__tests__/pie.test.ts | 95 + .../datalens/preparers/pie/gravity-charts.ts | 146 + .../datalens/preparers/pie/highcharts.ts | 71 + .../plugins/datalens/preparers/pie/index.ts | 2 + .../preparers/pie/prepare-pie-data.ts | 353 + .../plugins/datalens/preparers/pie/utils.ts | 51 + .../plugins/datalens/preparers/polyline.ts | 375 + .../scatter/__tests__/mocks/scatter.mock.ts | 295 + .../scatter/__tests__/scatter.test.ts | 158 + .../preparers/scatter/gravity-charts.ts | 258 + .../preparers/scatter/helpers/shape.ts | 79 + .../preparers/scatter/helpers/tooltip.ts | 28 + .../datalens/preparers/scatter/highcharts.ts | 174 + .../datalens/preparers/scatter/index.ts | 2 + .../preparers/scatter/prepare-scatter.ts | 511 + .../datalens/preparers/scatter/types.ts | 19 + .../preparers/treemap/gravity-charts.ts | 291 + .../datalens/preparers/treemap/highcharts.ts | 326 + .../datalens/preparers/treemap/index.ts | 2 + .../plugins/datalens/preparers/types.ts | 101 + .../plugins/datalens/preparers/utils.ts | 43 + .../charts/plugins/datalens/private-module.ts | 13 + .../modes/charts/plugins/datalens/types.ts | 113 + .../buildTotalRequestFields.test.ts | 106 + .../mocks/buildTotalRequests.mock.ts | 84 + .../url/build-request-body/constants.ts | 1 + .../url/build-request-body/default-request.ts | 273 + .../datalens/url/build-request-body/index.ts | 768 + .../__tests__/getAnnotations.test.ts | 110 + .../__tests__/getRegularFields.test.ts | 159 + .../__tests__/getTotalsForPivot.test.ts | 58 + .../__tests__/mocks/common.mock.ts | 21 + .../__tests__/mocks/getAnnotations.mock.ts | 33 + .../__tests__/mocks/getRegularFields.mock.ts | 14 + .../__tests__/mocks/getTotalsForPivot.mock.ts | 8 + .../pivot-request/helpers/index.ts | 300 + .../pivot-request/helpers/totals.ts | 17 + .../pivot-request/helpers/types.ts | 57 + .../build-request-body/pivot-request/index.ts | 175 + .../plugins/datalens/url/build-sources.ts | 161 + .../url/helpers/__tests__/filters.test.ts | 112 + .../url/helpers/__tests__/parameters.test.ts | 28 + .../plugins/datalens/url/helpers/fields.ts | 75 + .../plugins/datalens/url/helpers/filters.ts | 27 + .../plugins/datalens/url/helpers/index.ts | 3 + .../plugins/datalens/url/helpers/misc.ts | 45 + .../datalens/url/helpers/parameters.ts | 98 + .../datalens/url/helpers/table-settings.ts | 158 + .../charts/plugins/datalens/url/types.ts | 51 + .../utils/__tests__/color-helpers.test.ts | 59 + .../utils/__tests__/geo-helpers.test.ts | 55 + .../__tests__/get-gradient-stops.test.ts | 307 + .../utils/__tests__/misc-helpers.test.ts | 176 + .../plugins/datalens/utils/axis-helpers.ts | 147 + .../plugins/datalens/utils/color-helpers.ts | 568 + .../plugins/datalens/utils/config-helpers.ts | 51 + .../plugins/datalens/utils/constants.ts | 61 + .../plugins/datalens/utils/export-helpers.ts | 65 + .../plugins/datalens/utils/geo-helpers.ts | 264 + .../datalens/utils/get-gradient-stops.ts | 48 + .../datalens/utils/hierarchy-helpers.ts | 159 + .../plugins/datalens/utils/markup-helpers.ts | 34 + .../plugins/datalens/utils/misc-helpers.ts | 485 + .../datalens/utils/pivot-table/totals.ts | 33 + .../plugins/datalens/utils/shape-helpers.ts | 69 + src/server/modes/charts/plugins/dataset/v2.ts | 281 + .../charts/plugins/helpers/color-palettes.ts | 163 + .../modes/charts/plugins/helpers/misc.ts | 55 + .../modes/charts/plugins/logins-blacklist.ts | 27 + src/server/modes/charts/plugins/ql/index.ts | 28 + .../plugins/ql/module/chart-editor-module.ts | 58 + .../ql/module/config/build-chart-config.ts | 85 + .../charts/plugins/ql/module/config/index.ts | 13 + .../charts/plugins/ql/module/highcharts.ts | 110 + .../plugins/ql/module/js/build-graph.ts | 738 + .../library-config/build-library-config.ts | 61 + .../plugins/ql/module/library-config/index.ts | 11 + .../plugins/ql/module/private-module.ts | 14 + .../plugins/ql/module/url/build-sources.ts | 234 + .../charts/plugins/ql/module/url/index.ts | 20 + .../charts/plugins/ql/module/url/types.ts | 10 + .../charts/plugins/ql/preparers/line-time.ts | 436 + .../modes/charts/plugins/ql/preparers/line.ts | 391 + .../charts/plugins/ql/preparers/metric.ts | 116 + .../modes/charts/plugins/ql/preparers/pie.ts | 153 + .../plugins/ql/preparers/preview-table.ts | 110 + .../charts/plugins/ql/preparers/table.ts | 165 + .../charts/plugins/ql/ql-chart-runner.ts | 51 + src/server/modes/charts/plugins/ql/types.ts | 5 + .../ql/utils/__tests__/misc-helpers.test.ts | 158 + .../plugins/ql/utils/autofill-helpers.ts | 166 + .../modes/charts/plugins/ql/utils/colors.ts | 619 + .../charts/plugins/ql/utils/connection.ts | 40 + .../charts/plugins/ql/utils/constants.ts | 22 + .../plugins/ql/utils/identify-params.ts | 40 + .../plugins/ql/utils/migrate-helpers.ts | 295 + .../charts/plugins/ql/utils/misc-helpers.ts | 757 + .../charts/plugins/ql/utils/value-helpers.ts | 53 + .../plugins/ql/utils/visualization-utils.ts | 224 + src/server/modes/charts/plugins/ql/worker.ts | 202 + src/server/modes/charts/plugins/ql/yagr.ts | 184 + .../plugins/request-with-dataset/index.ts | 52 + .../middlewareAdapters/charts-with-dataset.ts | 79 + .../controls-with-dataset.ts | 59 + .../request-with-dataset/request-dataset.ts | 189 + .../charts/plugins/runner-key-adapter.ts | 71 + src/server/modes/charts/plugins/types.ts | 83 + src/server/modes/charts/telemetry.ts | 124 + src/server/modes/opensource/app.ts | 150 + .../modes/opensource/middlewares/index.ts | 1 + .../middlewares/subrequest-headers.ts | 56 + src/server/modes/opensource/routes.ts | 260 + src/server/nodekit.ts | 36 + src/server/registry/index.ts | 144 + .../units/common/auth-functions-map.ts | 26 + .../units/common/functions-map/index.ts | 30 + .../on-embeds-controller-before-response.ts | 12 + .../on-embeds-controller-start.ts | 5 + src/server/registry/units/common/index.ts | 13 + src/server/registry/units/common/register.ts | 38 + .../registry/utils/register-app-plugins.ts | 5 + src/server/tsconfig.json | 9 + src/server/types/app-layout.ts | 18 + src/server/types/connections.ts | 3 + src/server/types/controllers.ts | 24 + src/server/types/gateway.ts | 10 + src/server/types/xlsxConverter.ts | 17 + src/server/utils/axios.ts | 21 + src/server/utils/env-utils.ts | 1 + src/server/utils/gateway.ts | 80 + src/server/utils/index.ts | 176 + src/server/utils/language.ts | 76 + src/server/utils/redis.ts | 89 + src/server/utils/routes.ts | 53 + src/server/utils/schema-validate.ts | 9 + src/shared/__tests__/.eslintrc | 5 + src/shared/__tests__/types.test.ts | 13 + src/shared/__tests__/utils.test.ts | 43 + .../components/auth/constants/cookie.ts | 1 + src/shared/components/auth/constants/role.ts | 7 + src/shared/components/auth/constants/token.ts | 1 + src/shared/components/auth/constants/url.ts | 1 + src/shared/components/auth/types/layout.ts | 7 + src/shared/components/auth/utils.ts | 16 + .../components/public-api/utils/index.ts | 11 + src/shared/constants/api-v2.ts | 4 + src/shared/constants/chart-modeling.ts | 16 + src/shared/constants/chartkit-handlers.ts | 14 + src/shared/constants/charts.ts | 7 + src/shared/constants/collections.ts | 5 + .../constants/colors/common/classic-20.ts | 29 + .../constants/colors/common/datalens.ts | 29 + .../constants/colors/common/default-20.ts | 29 + .../constants/colors/common/emerald-20.ts | 29 + .../constants/colors/common/golden-20.ts | 29 + .../constants/colors/common/neutral-20.ts | 29 + .../constants/colors/common/oceanic-20.ts | 29 + .../colors/common/traffic-light-9.ts | 18 + src/shared/constants/colors/index.ts | 95 + .../constants/colors/tableau/blue-red-12.ts | 20 + .../constants/colors/tableau/blue-red-6.ts | 7 + .../colors/tableau/color-blind-10.ts | 18 + src/shared/constants/colors/tableau/gray-5.ts | 7 + .../colors/tableau/green-orange-12.ts | 20 + .../colors/tableau/green-orange-6.ts | 7 + .../colors/tableau/purpure-gray-12.ts | 20 + .../colors/tableau/purpure-gray-6.ts | 7 + .../colors/tableau/tableau-10-light.ts | 18 + .../colors/tableau/tableau-10-medium.ts | 18 + .../constants/colors/tableau/tableau-10.ts | 18 + .../constants/colors/tableau/tableau-20.ts | 28 + .../constants/colors/tableau/traffic-light.ts | 17 + src/shared/constants/colors/types.ts | 53 + src/shared/constants/common.ts | 108 + src/shared/constants/connections.ts | 110 + src/shared/constants/cookie.ts | 2 + src/shared/constants/dash.ts | 59 + src/shared/constants/datasets.ts | 4 + .../datepicker/relative-datepicker.ts | 11 + src/shared/constants/entry.ts | 88 + src/shared/constants/error-codes.ts | 64 + src/shared/constants/error/index.ts | 16 + src/shared/constants/exports.ts | 18 + src/shared/constants/gradients/default.ts | 8 + src/shared/constants/gradients/index.ts | 111 + .../gradients/three-point-gradients.ts | 36 + .../gradients/three-point/blue-gray-red.ts | 4 + .../gradients/three-point/blue-yellow-red.ts | 4 + .../three-point/orange-blue-green.ts | 4 + .../gradients/three-point/orange-gray-blue.ts | 4 + .../three-point/orange-violet-blue.ts | 4 + .../gradients/three-point/pink-gray-green.ts | 4 + .../gradients/three-point/red-orange-green.ts | 4 + .../gradients/three-point/traffic-light.ts | 4 + .../three-point/violet-blue-green.ts | 4 + .../three-point/yellow-green-blue.ts | 4 + .../gradients/two-point-gradients.ts | 45 + .../constants/gradients/two-point/blue.ts | 4 + .../constants/gradients/two-point/cyan.ts | 4 + .../constants/gradients/two-point/golden.ts | 4 + .../constants/gradients/two-point/gray.ts | 4 + .../gradients/two-point/green-blue.ts | 4 + .../constants/gradients/two-point/green.ts | 4 + .../constants/gradients/two-point/oceanic.ts | 4 + .../gradients/two-point/orange-yellow.ts | 4 + .../constants/gradients/two-point/red-blue.ts | 4 + .../constants/gradients/two-point/red.ts | 4 + .../gradients/two-point/violet-orange.ts | 4 + .../constants/gradients/two-point/violet.ts | 4 + .../constants/gradients/two-point/yellow.ts | 4 + src/shared/constants/header.ts | 54 + src/shared/constants/iam/index.ts | 5 + src/shared/constants/index.ts | 35 + src/shared/constants/markdown.ts | 1 + src/shared/constants/markup.ts | 1 + src/shared/constants/meta-manager/index.ts | 11 + src/shared/constants/navigation.ts | 21 + src/shared/constants/old-schema.ts | 5 + src/shared/constants/operations.ts | 9 + src/shared/constants/placeholder.ts | 28 + src/shared/constants/print.ts | 2 + src/shared/constants/qa/action-panel.ts | 16 + src/shared/constants/qa/chart.ts | 62 + src/shared/constants/qa/collections.ts | 29 + src/shared/constants/qa/components.ts | 234 + src/shared/constants/qa/connections.ts | 42 + src/shared/constants/qa/control.ts | 87 + src/shared/constants/qa/dash.ts | 111 + src/shared/constants/qa/datasets.ts | 152 + src/shared/constants/qa/dl-navigation.ts | 9 + src/shared/constants/qa/edit-history.ts | 4 + src/shared/constants/qa/field-editor.ts | 3 + src/shared/constants/qa/index.ts | 18 + src/shared/constants/qa/ql.ts | 34 + src/shared/constants/qa/settings.ts | 12 + src/shared/constants/qa/shared-entries.ts | 22 + src/shared/constants/qa/sign-in.ts | 5 + src/shared/constants/qa/uikit.ts | 7 + src/shared/constants/qa/visualization.ts | 18 + src/shared/constants/qa/wizard.ts | 211 + src/shared/constants/qa/workbooks.ts | 21 + src/shared/constants/ql.ts | 29 + src/shared/constants/shapes/index.ts | 2 + src/shared/constants/shapes/lines/index.ts | 53 + src/shared/constants/shapes/points/index.ts | 9 + src/shared/constants/themes.ts | 32 + src/shared/constants/timeout.ts | 11 + src/shared/constants/ui-sandbox.ts | 1 + src/shared/constants/url.ts | 7 + src/shared/constants/user.ts | 4 + src/shared/constants/visualization.ts | 62 + src/shared/constants/widgets.ts | 111 + src/shared/constants/wizard.ts | 86 + src/shared/constants/workbook-transfer.ts | 20 + src/shared/constants/workbooks.ts | 10 + src/shared/constants/yfm.ts | 25 + src/shared/endpoints/constants/index.ts | 7 + src/shared/endpoints/constants/opensource.ts | 58 + src/shared/endpoints/index.ts | 12 + src/shared/endpoints/schema/index.ts | 32 + src/shared/endpoints/schema/opensource.ts | 34 + src/shared/endpoints/sources/index.ts | 10 + src/shared/endpoints/sources/opensource.ts | 21 + src/shared/endpoints/sources/types.ts | 28 + src/shared/endpoints/types.ts | 18 + src/shared/endpoints/utils.ts | 10 + src/shared/index.ts | 7 + .../modules/__tests__/charts-shared.test.ts | 289 + src/shared/modules/__tests__/helpers.test.ts | 71 + src/shared/modules/bi-api/index.ts | 1 + src/shared/modules/bi-api/mappers/index.ts | 1 + src/shared/modules/bi-api/mappers/v2.ts | 156 + src/shared/modules/charts-shared.ts | 350 + src/shared/modules/colors/common-helpers.ts | 45 + .../modules/colors/distincts-helpers.ts | 14 + .../mapQlConfigToLatestVersion.test.ts | 25 + .../modules/config/ql/__tests__/v1.test.ts | 63 + .../modules/config/ql/__tests__/v2.test.ts | 66 + src/shared/modules/config/ql/index.ts | 1 + .../config/ql/mapQlConfigToLatestVersion.ts | 52 + .../config/ql/v1/mapUndefinedConfigToV1.ts | 41 + .../modules/config/ql/v2/mapV1ConfigToV2.ts | 26 + .../modules/config/ql/v3/mapV2ConfigToV3.ts | 14 + .../modules/config/ql/v4/mapV3ConfigToV4.ts | 58 + .../modules/config/ql/v5/mapV4ConfigToV5.ts | 67 + .../modules/config/ql/v6/mapV5ConfigToV6.ts | 31 + .../modules/config/ql/v7/mapV6ConfigToV7.ts | 15 + src/shared/modules/config/utils.ts | 32 + .../mapChartsConfigToLatestVersion.test.ts | 13 + .../modules/config/wizard/__test__/v4.test.ts | 65 + .../modules/config/wizard/__test__/v8.test.ts | 41 + src/shared/modules/config/wizard/index.ts | 1 + .../wizard/mapChartsConfigToLatestVersion.ts | 92 + .../modules/config/wizard/mocks/v4.mock.ts | 63 + .../config/wizard/v1/mapV1ConfigToV2.ts | 47 + .../config/wizard/v10/mapV10ConfigToV11.ts | 26 + .../config/wizard/v11/mapV11ConfigToV12.ts | 24 + .../config/wizard/v12/mapV12ConfigToV13.ts | 74 + .../config/wizard/v13/mapV13ConfigToV14.ts | 31 + .../config/wizard/v14/mapV14ConfigToV15.ts | 19 + .../config/wizard/v2/mapV2ConfigToV3.ts | 44 + .../config/wizard/v3/mapV3ConfigToV4.ts | 126 + .../config/wizard/v4/mapV4ConfigToV5.ts | 122 + .../config/wizard/v5/mapV5ConfigToV6.ts | 20 + .../config/wizard/v6/mapV6ConfigToV7.ts | 156 + .../config/wizard/v7/mapV7ConfigToV8.ts | 74 + .../config/wizard/v8/mapV8ConfigToV9.ts | 20 + .../config/wizard/v9/mapV9ConfigToV10.ts | 45 + .../modules/control/typed-query-helpers.ts | 12 + src/shared/modules/dash-scheme-converter.ts | 409 + src/shared/modules/entry.ts | 23 + src/shared/modules/fields.ts | 56 + .../modules/format-units/formatUnit.test.ts | 31 + src/shared/modules/format-units/formatUnit.ts | 181 + src/shared/modules/format-units/i18n/en.json | 17 + src/shared/modules/format-units/i18n/i18n.ts | 19 + src/shared/modules/format-units/i18n/ru.json | 17 + src/shared/modules/format-units/index.ts | 1 + src/shared/modules/format-units/types.ts | 17 + src/shared/modules/helpers.ts | 271 + src/shared/modules/index.ts | 20 + src/shared/modules/language.ts | 7 + src/shared/modules/markdown/emoji-defs.ts | 266 + .../markdown/markdown-plugins/unify-terms.ts | 81 + src/shared/modules/markdown/markdown.ts | 144 + src/shared/modules/markup.ts | 17 + src/shared/modules/qa-helpers.ts | 5 + src/shared/modules/ql-helpers.ts | 232 + src/shared/modules/sleep.ts | 1 + src/shared/modules/slugify.ts | 28 + src/shared/modules/tenant.ts | 13 + src/shared/modules/transliterate.ts | 45 + .../typed-query-api/helpers/parameters.ts | 10 + src/shared/modules/typed-query-api/index.ts | 1 + .../typed-query-api/mappers/parameters.ts | 25 + src/shared/modules/url.ts | 5 + src/shared/modules/user.ts | 12 + .../modules/visualization/placeholder.ts | 19 + src/shared/modules/wizard-helpers.ts | 263 + src/shared/modules/wizard/axis-mode.ts | 114 + src/shared/modules/wizard/axis-settings.ts | 16 + src/shared/modules/wizard/config.ts | 12 + src/shared/modules/wizard/index.ts | 4 + src/shared/modules/wizard/tooltip.ts | 15 + src/shared/old-schema/charts.ts | 178 + src/shared/old-schema/index.js | 9 + src/shared/schema/auth-schema/auth/actions.ts | 37 + src/shared/schema/auth-schema/auth/index.ts | 9 + src/shared/schema/auth-schema/auth/types.ts | 12 + src/shared/schema/auth-schema/index.ts | 5 + src/shared/schema/auth/actions.ts | 112 + src/shared/schema/auth/index.ts | 9 + src/shared/schema/auth/types/common.ts | 3 + src/shared/schema/auth/types/users.ts | 102 + src/shared/schema/bi/actions/connections.ts | 303 + src/shared/schema/bi/actions/datasets.ts | 374 + src/shared/schema/bi/actions/index.ts | 9 + src/shared/schema/bi/actions/oauth.ts | 25 + src/shared/schema/bi/helpers/datasets.ts | 68 + src/shared/schema/bi/helpers/index.ts | 1 + src/shared/schema/bi/index.ts | 9 + src/shared/schema/bi/schemas/connections.ts | 45 + src/shared/schema/bi/schemas/datasets.ts | 57 + src/shared/schema/bi/schemas/index.ts | 1 + src/shared/schema/bi/types/common.ts | 6 + src/shared/schema/bi/types/connections.ts | 408 + src/shared/schema/bi/types/datasets.ts | 330 + src/shared/schema/bi/types/forms.ts | 247 + src/shared/schema/bi/types/index.ts | 4 + src/shared/schema/bi/types/oauth.ts | 21 + .../extensions/actions/iam-access-dialog.ts | 56 + src/shared/schema/extensions/actions/index.ts | 5 + src/shared/schema/extensions/index.ts | 6 + .../extensions/types/iam-access-dialog.ts | 144 + src/shared/schema/extensions/types/index.ts | 1 + src/shared/schema/gateway-utils.ts | 122 + src/shared/schema/googleapis/actions.ts | 2 + src/shared/schema/index.ts | 31 + .../actions/export/cancel-workbook-export.ts | 19 + .../export/get-workbook-export-result.ts | 19 + .../export/get-workbook-export-status.ts | 19 + .../meta-manager/actions/export/index.ts | 11 + .../actions/export/start-workbook-export.ts | 19 + .../import/get-workbook-import-status.ts | 19 + .../meta-manager/actions/import/index.ts | 7 + .../actions/import/start-workbook-import.ts | 19 + .../schema/meta-manager/actions/index.ts | 7 + src/shared/schema/meta-manager/index.ts | 9 + .../meta-manager/schemas/common/index.ts | 17 + .../schemas/export/cancel-workbook-export.ts | 9 + .../export/get-workbook-export-result.ts | 16 + .../export/get-workbook-export-status.ts | 14 + .../meta-manager/schemas/export/index.ts | 4 + .../schemas/export/start-workbook-export.ts | 9 + .../import/get-workbook-import-status.ts | 15 + .../meta-manager/schemas/import/index.ts | 2 + .../schemas/import/start-workbook-import.ts | 13 + .../schema/meta-manager/schemas/index.ts | 2 + .../schema/meta-manager/types/common.ts | 5 + .../schema/meta-manager/types/export.ts | 13 + .../schema/meta-manager/types/import.ts | 10 + src/shared/schema/meta-manager/types/index.ts | 3 + .../mix/actions/dash/create-dashboard-v1.ts | 69 + .../mix/actions/dash/delete-dashboard.ts | 23 + .../mix/actions/dash/get-dashboard-v1.ts | 51 + src/shared/schema/mix/actions/dash/index.ts | 208 + .../mix/actions/dash/update-dashboard-v1.ts | 74 + src/shared/schema/mix/actions/editor.ts | 48 + src/shared/schema/mix/actions/entries.ts | 280 + src/shared/schema/mix/actions/index.ts | 17 + src/shared/schema/mix/actions/markdown.ts | 29 + src/shared/schema/mix/actions/navigation.ts | 45 + .../schema/mix/actions/ql/create-ql-chart.ts | 39 + .../schema/mix/actions/ql/delete-ql-chart.ts | 23 + .../schema/mix/actions/ql/get-ql-chart.ts | 64 + src/shared/schema/mix/actions/ql/index.ts | 17 + .../schema/mix/actions/ql/update-ql-chart.ts | 54 + .../mix/actions/wizard/create-wizard-chart.ts | 39 + .../mix/actions/wizard/delete-wizard-chart.ts | 23 + .../mix/actions/wizard/get-wizard-chart.ts | 59 + src/shared/schema/mix/actions/wizard/index.ts | 17 + .../mix/actions/wizard/update-wizard-chart.ts | 57 + .../mix/helpers/__tests__/editor.test.ts | 103 + src/shared/schema/mix/helpers/dash/index.ts | 154 + .../mix/helpers/dash/migrate-dash-to-v1.ts | 85 + .../mix/helpers/dash/normalize-dash-data.ts | 85 + src/shared/schema/mix/helpers/editor/index.ts | 50 + .../schema/mix/helpers/editor/validation.ts | 66 + src/shared/schema/mix/helpers/entries.ts | 272 + src/shared/schema/mix/helpers/index.ts | 2 + src/shared/schema/mix/helpers/validation.ts | 9 + src/shared/schema/mix/index.ts | 6 + .../mix/schemas/dash/create-dashboard-v1.ts | 26 + src/shared/schema/mix/schemas/dash/dash-v1.ts | 34 + .../mix/schemas/dash/delete-dashboard.ts | 8 + .../mix/schemas/dash/get-dashboard-v1.ts | 30 + .../mix/schemas/dash/update-dashboard-v1.ts | 25 + src/shared/schema/mix/schemas/ql.ts | 19 + src/shared/schema/mix/schemas/wizard.ts | 19 + src/shared/schema/mix/types/dash/index.ts | 70 + src/shared/schema/mix/types/entries.ts | 127 + src/shared/schema/mix/types/index.ts | 6 + src/shared/schema/mix/types/markdown.ts | 17 + src/shared/schema/mix/types/navigation.ts | 24 + src/shared/schema/mix/types/ql.ts | 7 + src/shared/schema/mix/types/wizard.ts | 7 + src/shared/schema/stat-api/types/index.ts | 14 + src/shared/schema/types.ts | 4 + .../schema/us-private/actions/entries.ts | 74 + src/shared/schema/us-private/actions/index.ts | 7 + .../schema/us-private/actions/tenant.ts | 14 + src/shared/schema/us-private/index.ts | 20 + src/shared/schema/us-private/types/entries.ts | 20 + src/shared/schema/us-private/types/index.ts | 1 + .../actions/collections/create-collection.ts | 30 + .../actions/collections/delete-collection.ts | 24 + .../actions/collections/delete-collections.ts | 27 + .../collections/get-collection-breadcrumbs.ts | 27 + .../us/actions/collections/get-collection.ts | 25 + .../collections/get-collections-by-ids.ts | 20 + .../get-root-collection-permissions.ts | 22 + .../collections/get-structure-items.ts | 85 + .../schema/us/actions/collections/index.ts | 25 + .../us/actions/collections/move-collection.ts | 30 + .../actions/collections/move-collections.ts | 28 + .../actions/collections/update-collection.ts | 30 + .../schema/us/actions/color-palettes.ts | 44 + src/shared/schema/us/actions/editor.ts | 77 + .../schema/us/actions/embeds/create-embed.ts | 17 + .../schema/us/actions/embeds/delete-embed.ts | 16 + src/shared/schema/us/actions/embeds/index.ts | 9 + .../schema/us/actions/embeds/list-embeds.ts | 17 + .../schema/us/actions/entries/create-entry.ts | 39 + .../actions/entries/get-entries-relations.ts | 52 + .../schema/us/actions/entries/get-entries.ts | 29 + .../schema/us/actions/entries/get-entry.ts | 36 + src/shared/schema/us/actions/entries/index.ts | 360 + .../us/actions/entries/list-directory.ts | 30 + .../schema/us/actions/entries/update-entry.ts | 25 + src/shared/schema/us/actions/favorites.ts | 49 + src/shared/schema/us/actions/index.ts | 33 + src/shared/schema/us/actions/locks.ts | 36 + src/shared/schema/us/actions/operations.ts | 27 + src/shared/schema/us/actions/presets.ts | 15 + src/shared/schema/us/actions/private.ts | 12 + src/shared/schema/us/actions/state.ts | 24 + src/shared/schema/us/actions/template.ts | 14 + src/shared/schema/us/actions/tenant.ts | 18 + src/shared/schema/us/actions/user.ts | 20 + .../us/actions/workbooks/copy-workbook.ts | 10 + .../us/actions/workbooks/create-workbook.ts | 30 + .../us/actions/workbooks/delete-workbook.ts | 22 + .../us/actions/workbooks/delete-workbooks.ts | 27 + .../actions/workbooks/get-workbook-entries.ts | 33 + .../workbooks/get-workbook-shared-entries.ts | 29 + .../us/actions/workbooks/get-workbook.ts | 25 + .../actions/workbooks/get-workbooks-by-ids.ts | 20 + .../actions/workbooks/get-workbooks-list.ts | 52 + .../schema/us/actions/workbooks/index.ts | 31 + .../migrate-entries-to-workbook-by-copy.ts | 11 + ...migrate-entries-to-workbook-by-transfer.ts | 11 + .../us/actions/workbooks/move-workbook.ts | 24 + .../us/actions/workbooks/move-workbooks.ts | 28 + .../us/actions/workbooks/update-workbook.ts | 24 + src/shared/schema/us/index.ts | 9 + .../collections/get-collections-by-ids.ts | 9 + .../schema/us/schemas/collections/index.ts | 28 + .../schema/us/schemas/embeds/create-embed.ts | 28 + .../schema/us/schemas/embeds/delete-embed.ts | 19 + src/shared/schema/us/schemas/embeds/embed.ts | 40 + src/shared/schema/us/schemas/embeds/index.ts | 4 + .../schema/us/schemas/embeds/list-embeds.ts | 15 + .../schema/us/schemas/entries/get-entries.ts | 86 + .../us/schemas/entries/list-directory.ts | 69 + .../schema/us/schemas/operation/index.ts | 16 + .../schemas/workbooks/get-workbook-entries.ts | 53 + .../schemas/workbooks/get-workbooks-by-ids.ts | 9 + .../schema/us/schemas/workbooks/index.ts | 34 + src/shared/schema/us/types/access-bindings.ts | 0 src/shared/schema/us/types/collections.ts | 131 + src/shared/schema/us/types/color-palettes.ts | 30 + src/shared/schema/us/types/common.ts | 18 + src/shared/schema/us/types/editor.ts | 37 + src/shared/schema/us/types/embeds.ts | 25 + src/shared/schema/us/types/entries.ts | 405 + src/shared/schema/us/types/favorites.ts | 60 + src/shared/schema/us/types/fields.ts | 155 + src/shared/schema/us/types/index.ts | 17 + src/shared/schema/us/types/locks.ts | 35 + src/shared/schema/us/types/operations.ts | 56 + src/shared/schema/us/types/presets.ts | 6 + src/shared/schema/us/types/private.ts | 3 + src/shared/schema/us/types/sort.ts | 5 + src/shared/schema/us/types/state.ts | 22 + src/shared/schema/us/types/template.ts | 15 + src/shared/schema/us/types/tenant.ts | 20 + src/shared/schema/us/types/workbooks.ts | 147 + src/shared/schema/utils.ts | 45 + src/shared/server/axios.ts | 15 + src/shared/types/bi-api/index.ts | 1 + src/shared/types/bi-api/v2.ts | 187 + src/shared/types/chartkit/common.ts | 38 + src/shared/types/chartkit/dl-chartkit.ts | 41 + .../types/chartkit/gravity-charts/index.ts | 11 + .../types/chartkit/gravity-charts/scatter.ts | 26 + src/shared/types/chartkit/index.ts | 4 + src/shared/types/chartkit/table.ts | 155 + src/shared/types/charts.ts | 355 + src/shared/types/color-palettes.ts | 7 + src/shared/types/common-update.ts | 21 + src/shared/types/common.ts | 360 + src/shared/types/config/ql/index.ts | 56 + src/shared/types/config/ql/v1.ts | 100 + src/shared/types/config/ql/v2.ts | 101 + src/shared/types/config/ql/v3.ts | 99 + src/shared/types/config/ql/v4.ts | 106 + src/shared/types/config/ql/v5.ts | 106 + src/shared/types/config/ql/v6.ts | 104 + src/shared/types/config/ql/v7.ts | 104 + src/shared/types/config/wizard/index.ts | 116 + src/shared/types/config/wizard/v10.ts | 299 + src/shared/types/config/wizard/v11.ts | 318 + src/shared/types/config/wizard/v12.ts | 331 + src/shared/types/config/wizard/v13.ts | 331 + src/shared/types/config/wizard/v14.ts | 332 + src/shared/types/config/wizard/v15.ts | 342 + src/shared/types/config/wizard/v2.ts | 262 + src/shared/types/config/wizard/v3.ts | 271 + src/shared/types/config/wizard/v4.ts | 286 + src/shared/types/config/wizard/v5.ts | 287 + src/shared/types/config/wizard/v6.ts | 286 + src/shared/types/config/wizard/v7.ts | 288 + src/shared/types/config/wizard/v8.ts | 293 + src/shared/types/config/wizard/v9.ts | 296 + src/shared/types/configs/index.ts | 22 + src/shared/types/connections.ts | 29 + src/shared/types/dash.ts | 457 + src/shared/types/dataset.ts | 396 + src/shared/types/entry.ts | 23 + src/shared/types/feature.ts | 112 + src/shared/types/formatting.ts | 27 + src/shared/types/index.ts | 30 + src/shared/types/language.ts | 5 + src/shared/types/menu.ts | 33 + src/shared/types/meta-manager/index.ts | 6 + src/shared/types/metrica.ts | 6 + src/shared/types/notification.ts | 5 + src/shared/types/permissions.ts | 5 + src/shared/types/ql/common.ts | 182 + src/shared/types/ql/versions.ts | 15 + src/shared/types/typed-query-api/index.ts | 1 + src/shared/types/typed-query-api/v1.ts | 34 + src/shared/types/ui-sandbox.ts | 5 + src/shared/types/utility-types.ts | 44 + src/shared/types/widget.ts | 51 + .../types/wizard/background-settings.ts | 22 + src/shared/types/wizard/bars.ts | 95 + src/shared/types/wizard/column.ts | 17 + src/shared/types/wizard/export.ts | 14 + src/shared/types/wizard/field.ts | 166 + src/shared/types/wizard/index.ts | 539 + src/shared/types/wizard/misc.ts | 74 + src/shared/types/wizard/placeholder.ts | 55 + src/shared/types/wizard/sub-totals.ts | 3 + src/shared/types/wizard/versions.ts | 36 + src/shared/utils/__tests__/date-time.test.ts | 10 + .../utils/array/assert-non-empty-array.ts | 5 + src/shared/utils/array/index.ts | 1 + src/shared/utils/dash.ts | 60 + src/shared/utils/dataset.ts | 31 + src/shared/utils/date-time.ts | 6 + src/shared/utils/entry.ts | 11 + src/shared/utils/feature.ts | 19 + src/shared/utils/functions-registry.ts | 36 + src/shared/utils/index.ts | 8 + src/shared/utils/makeFunctionTemplate.ts | 3 + src/shared/utils/manualError.ts | 39 + src/shared/utils/markdown.ts | 7 + src/shared/utils/markup.ts | 7 + src/shared/utils/openapi/index.ts | 1 + src/shared/utils/palettes.ts | 21 + src/shared/utils/ui-sandbox.ts | 16 + src/shared/utils/visualization-check.ts | 74 + src/shared/utils/wizard.ts | 33 + src/shared/zod-schemas/dash.ts | 305 + src/shared/zod-schemas/entry.ts | 31 + src/shared/zod-schemas/permissions.ts | 8 + .../zod-schemas/shared-entry-permissions.ts | 14 + src/ui/.ci-eslintrc | 17 + src/ui/.eslintrc | 17 + src/ui/assets/icons/alert.svg | 3 + src/ui/assets/icons/arr-reverse.svg | 1 + src/ui/assets/icons/arr-right.svg | 1 + src/ui/assets/icons/arrow-left.svg | 3 + src/ui/assets/icons/arrow-right.svg | 3 + src/ui/assets/icons/avatar-middle.png | Bin 0 -> 8353 bytes src/ui/assets/icons/branded-question.svg | 4 + src/ui/assets/icons/broken.svg | 1 + src/ui/assets/icons/browse.svg | 3 + src/ui/assets/icons/calendar-2.svg | 1 + src/ui/assets/icons/chart-monitoringql.svg | 6 + src/ui/assets/icons/chart-promql.svg | 6 + .../charts-visualisations/vis-area-100p.svg | 1 + .../icons/charts-visualisations/vis-area.svg | 1 + .../charts-visualisations/vis-bar-100p.svg | 1 + .../icons/charts-visualisations/vis-bar.svg | 1 + .../charts-visualisations/vis-column-100p.svg | 1 + .../charts-visualisations/vis-column.svg | 1 + .../icons/charts-visualisations/vis-donut.svg | 1 + .../charts-visualisations/vis-flat-table.svg | 1 + .../charts-visualisations/vis-geolayers.svg | 1 + .../charts-visualisations/vis-geopoint.svg | 1 + .../charts-visualisations/vis-geopolygon.svg | 1 + .../charts-visualisations/vis-heatmap.svg | 1 + .../icons/charts-visualisations/vis-lines.svg | 1 + .../charts-visualisations/vis-metric.svg | 1 + .../icons/charts-visualisations/vis-pie.svg | 1 + .../icons/charts-visualisations/vis-pivot.svg | 1 + .../charts-visualisations/vis-scatter.svg | 1 + .../charts-visualisations/vis-treemap.svg | 1 + src/ui/assets/icons/check-folder.svg | 4 + src/ui/assets/icons/choveryt.svg | 23 + src/ui/assets/icons/clickhouse.svg | 18 + .../collection-colored-big-dark.svg | 7 + .../collections/collection-colored-big.svg | 7 + .../collections/collection-colored-dark.svg | 11 + .../icons/collections/collection-colored.svg | 11 + .../assets/icons/collections/collection.svg | 6 + src/ui/assets/icons/collections/grid.svg | 3 + .../icons/collections/workbook-colored.svg | 4 + src/ui/assets/icons/collections/workbook.svg | 4 + src/ui/assets/icons/connected.svg | 16 + .../assets/icons/connections/appmetrica.svg | 1 + src/ui/assets/icons/connections/choveryt.svg | 1 + src/ui/assets/icons/connections/chydb.svg | 1 + .../assets/icons/connections/clickhouse.svg | 18 + src/ui/assets/icons/connections/csv.svg | 1 + src/ui/assets/icons/connections/greenplum.svg | 1 + src/ui/assets/icons/connections/metrica.svg | 1 + .../assets/icons/connections/monitoring.svg | 4 + src/ui/assets/icons/connections/mssql.svg | 1 + src/ui/assets/icons/connections/mysql.svg | 1 + src/ui/assets/icons/connections/oracle.svg | 1 + src/ui/assets/icons/connections/postgres.svg | 33 + src/ui/assets/icons/connections/undefined.svg | 16 + src/ui/assets/icons/connections/ydb.svg | 1 + src/ui/assets/icons/connector.svg | 3 + src/ui/assets/icons/copy-xmark.svg | 4 + src/ui/assets/icons/cross.svg | 6 + src/ui/assets/icons/csv.svg | 15 + src/ui/assets/icons/dimensions.svg | 10 + src/ui/assets/icons/dl-auth-logo-dark.svg | 1 + src/ui/assets/icons/dl-auth-logo-light.svg | 1 + src/ui/assets/icons/error-close.svg | 3 + src/ui/assets/icons/error.svg | 3 + src/ui/assets/icons/file-diff.svg | 7 + src/ui/assets/icons/filter-tooltip.svg | 1 + src/ui/assets/icons/folder-selection.svg | 14 + src/ui/assets/icons/folder.svg | 1 + src/ui/assets/icons/funnel-clear.svg | 1 + .../assets/icons/gradients/blue-gray-red.svg | 1 + .../icons/gradients/blue-yellow-red.svg | 1 + src/ui/assets/icons/gradients/blue.svg | 1 + src/ui/assets/icons/gradients/cyan.svg | 1 + src/ui/assets/icons/gradients/golden.svg | 1 + src/ui/assets/icons/gradients/gray.svg | 1 + src/ui/assets/icons/gradients/green-blue.svg | 1 + src/ui/assets/icons/gradients/green.svg | 1 + src/ui/assets/icons/gradients/oceanic.svg | 1 + .../icons/gradients/orange-blue-green.svg | 1 + .../icons/gradients/orange-gray-blue.svg | 1 + .../icons/gradients/orange-violet-blue.svg | 1 + .../assets/icons/gradients/orange-yellow.svg | 1 + .../icons/gradients/pink-gray-green.svg | 1 + src/ui/assets/icons/gradients/red-blue.svg | 1 + .../icons/gradients/red-orange-green.svg | 1 + src/ui/assets/icons/gradients/red.svg | 1 + .../assets/icons/gradients/traffic-light.svg | 1 + .../icons/gradients/violet-blue-green.svg | 1 + .../assets/icons/gradients/violet-orange.svg | 1 + src/ui/assets/icons/gradients/violet.svg | 1 + .../icons/gradients/yellow-green-blue.svg | 1 + src/ui/assets/icons/gradients/yellow.svg | 1 + src/ui/assets/icons/greenplum.svg | 33 + src/ui/assets/icons/header-nav.svg | 3 + src/ui/assets/icons/header-star-active.svg | 3 + src/ui/assets/icons/header-star-inactive.svg | 3 + src/ui/assets/icons/id-square.svg | 1 + src/ui/assets/icons/info.svg | 1 + src/ui/assets/icons/legacy-wizard.svg | 8 + src/ui/assets/icons/line-cap-butt.svg | 4 + src/ui/assets/icons/line-cap-round.svg | 4 + src/ui/assets/icons/line-join-miter.svg | 3 + src/ui/assets/icons/line-join-round.svg | 3 + src/ui/assets/icons/lock.svg | 3 + src/ui/assets/icons/logo-ya-dl.svg | 5 + src/ui/assets/icons/menu.svg | 3 + src/ui/assets/icons/monitoring.svg | 3 + src/ui/assets/icons/mono-collection.svg | 3 + src/ui/assets/icons/mono-service-settings.svg | 1 + src/ui/assets/icons/mssql.svg | 15 + src/ui/assets/icons/mysql.svg | 15 + src/ui/assets/icons/okay.svg | 12 + src/ui/assets/icons/os-logo.svg | 1 + .../icons/palettes/datalens-classic20.svg | 1 + .../icons/palettes/datalens-default20.svg | 27 + .../assets/icons/palettes/datalens-neo20.svg | 29 + src/ui/assets/icons/palettes/emerald-20.svg | 1 + src/ui/assets/icons/palettes/golden-20.svg | 1 + src/ui/assets/icons/palettes/neutral-20.svg | 1 + src/ui/assets/icons/palettes/oceanic-20.svg | 1 + .../assets/icons/palettes/traffic-light-9.svg | 1 + src/ui/assets/icons/parameter-section.svg | 5 + src/ui/assets/icons/placeholder-tooltip.svg | 1 + src/ui/assets/icons/plus.svg | 3 + src/ui/assets/icons/postgres.svg | 33 + src/ui/assets/icons/preview-bottom.svg | 35 + src/ui/assets/icons/preview-side.svg | 35 + src/ui/assets/icons/product.svg | 1 + src/ui/assets/icons/public-filled.svg | 1 + src/ui/assets/icons/ql-chart.svg | 1 + src/ui/assets/icons/relations-alias.svg | 1 + src/ui/assets/icons/relations-any.svg | 1 + src/ui/assets/icons/relations-controls.svg | 1 + src/ui/assets/icons/relations-no-data.svg | 1 + src/ui/assets/icons/relations-no-link.svg | 1 + src/ui/assets/icons/relations.svg | 1 + src/ui/assets/icons/relative-mode.svg | 1 + src/ui/assets/icons/search-clear.svg | 4 + src/ui/assets/icons/shapes.svg | 1 + src/ui/assets/icons/sort-asc.svg | 13 + src/ui/assets/icons/sort-desc.svg | 13 + src/ui/assets/icons/source-table.svg | 7 + src/ui/assets/icons/sync.svg | 3 + src/ui/assets/icons/tick.svg | 3 + src/ui/assets/icons/toast-attention.svg | 1 + src/ui/assets/icons/toggle-preview.svg | 3 + src/ui/assets/icons/undefined.svg | 16 + src/ui/assets/icons/vis-area-100p.svg | 6 + src/ui/assets/icons/vis-area.svg | 14 + src/ui/assets/icons/vis-bar-100p.svg | 11 + src/ui/assets/icons/vis-bar.svg | 13 + src/ui/assets/icons/vis-column-100p.svg | 9 + src/ui/assets/icons/vis-column.svg | 15 + src/ui/assets/icons/vis-combined.svg | 5 + src/ui/assets/icons/vis-donut.svg | 5 + src/ui/assets/icons/vis-flat-table.svg | 15 + src/ui/assets/icons/vis-funnel.svg | 10 + src/ui/assets/icons/vis-geolayers.svg | 1 + src/ui/assets/icons/vis-geopoint.svg | 5 + src/ui/assets/icons/vis-geopolygon.svg | 5 + src/ui/assets/icons/vis-heatmap.svg | 6 + src/ui/assets/icons/vis-lines.svg | 14 + src/ui/assets/icons/vis-metric.svg | 4 + src/ui/assets/icons/vis-pie.svg | 16 + src/ui/assets/icons/vis-pivot.svg | 14 + src/ui/assets/icons/vis-scatter.svg | 6 + src/ui/assets/icons/vis-treemap.svg | 7 + src/ui/assets/icons/visualization.svg | 10 + src/ui/assets/icons/warning-colored.svg | 3 + src/ui/assets/icons/yt.svg | 16 + .../assets/images/dl-auth-background-dark.jpg | Bin 0 -> 172463 bytes .../images/dl-auth-background-light.jpg | Bin 0 -> 228700 bytes .../assets/images/illustration/dark/403.svg | 1 + .../assets/images/illustration/dark/404.svg | 1 + .../assets/images/illustration/dark/500.svg | 1 + .../images/illustration/dark/bad_request.svg | 1 + .../illustration/dark/empty_directory.svg | 1 + .../images/illustration/dark/empty_state.svg | 1 + .../images/illustration/dark/no_accounts.svg | 1 + .../illustration/dark/no_permission.svg | 1 + .../illustration/dark/nothing_found.svg | 1 + .../images/illustration/dark/project.svg | 1 + .../illustration/dark/success_operation.svg | 1 + .../assets/images/illustration/light/403.svg | 1 + .../assets/images/illustration/light/404.svg | 1 + .../assets/images/illustration/light/500.svg | 1 + .../images/illustration/light/bad_request.svg | 1 + .../illustration/light/empty_directory.svg | 1 + .../images/illustration/light/empty_state.svg | 1 + .../images/illustration/light/no_accounts.svg | 1 + .../illustration/light/no_permission.svg | 1 + .../illustration/light/nothing_found.svg | 1 + .../images/illustration/light/project.svg | 1 + .../illustration/light/success_operation.svg | 1 + .../AccessRights/AccessRightsUrlOpen.tsx | 10 + .../components/ActionPanel/ActionPanel.scss | 54 + src/ui/components/ActionPanel/ActionPanel.tsx | 302 + .../ActionPanel/ActionPanelHelpers.ts | 7 + .../ChartSaveControls/ChartSaveControl.scss | 35 + .../ChartSaveControls/ChartSaveControl.tsx | 146 + .../components/SaveButton/SaveButton.scss | 10 + .../components/SaveButton/SaveButton.tsx | 53 + .../components/SaveDropdown/SaveDropdown.scss | 5 + .../components/SaveDropdown/SaveDropdown.tsx | 43 + .../components/ChartSaveControls/types.ts | 21 + .../ChartSaveControls/useAdditionalItems.tsx | 57 + .../components/EntryPanel/EntryPanel.scss | 66 + .../components/EntryPanel/EntryPanel.tsx | 365 + src/ui/components/ActionPanel/index.ts | 2 + src/ui/components/ActionPanel/utils.ts | 7 + .../AdaptiveDialog/AdaptiveDialog.tsx | 69 + .../components/AnimateBlock/AnimateBlock.scss | 13 + .../components/AnimateBlock/AnimateBlock.tsx | 50 + src/ui/components/AnimateBlock/index.ts | 1 + .../AsideHeaderAdapter.scss | 53 + .../AsideHeaderAdapter/AsideHeaderAdapter.tsx | 358 + .../AsideHeaderAdapter/LogoText/LogoText.scss | 12 + .../AsideHeaderAdapter/LogoText/LogoText.tsx | 34 + .../Appearance/HighcontrastSettings.tsx | 26 + .../Settings/Appearance/ThemeSettings.tsx | 23 + .../Settings/ItemField/ItemField.tsx | 33 + .../Settings/LanguageSettings.tsx | 46 + .../AsideHeaderAdapter/Settings/Settings.tsx | 99 + .../AsideHeaderAdapter/Settings/types.ts | 11 + .../AsideHeaderAdapter/Settings/utils.ts | 56 + .../VersionDialog/VersionDialog.tsx | 35 + .../AsideHeaderAdapter/constants.ts | 1 + .../components/AsideHeaderAdapter/helpers.ts | 10 + src/ui/components/AsideHeaderAdapter/index.ts | 2 + src/ui/components/AsyncImage/AsyncImage.tsx | 102 + .../AutogeneratedPaletteIcon.scss | 4 + .../AutogeneratedPaletteIcon.tsx | 90 + src/ui/components/BetaMark/BetaMark.scss | 4 + src/ui/components/BetaMark/BetaMark.tsx | 15 + .../CollectionsStructureBreadcrumbs.scss | 5 + .../CollectionsStructureBreadcrumbs.tsx | 59 + .../BreadcrumbsItemLink.tsx | 22 + .../components/ButtonAttach/ButtonAttach.scss | 16 + .../components/ButtonAttach/ButtonAttach.tsx | 44 + .../ChartModelingSettings.scss | 77 + .../ChartModelingSettings.tsx | 141 + .../components/ShapeSelect/ShapeSelect.tsx | 55 + .../components/SmothSettings.tsx | 149 + .../components/TrendSettings.tsx | 133 + .../ChartModelingSettings/constants.ts | 11 + .../components/ChartModelingSettings/i18n.ts | 3 + .../components/ChartModelingSettings/utils.ts | 15 + src/ui/components/Collapse/Collapse.scss | 75 + src/ui/components/Collapse/Collapse.tsx | 183 + src/ui/components/Collapse/types.ts | 22 + .../CollectionFilters/CollectionFilters.scss | 53 + .../CollectionFilters/CollectionFilters.tsx | 280 + src/ui/components/CollectionFilters/index.ts | 1 + .../CollectionIcon/CollectionIcon.tsx | 26 + .../CollectionDialog/CollectionDialog.scss | 13 + .../CollectionDialog/CollectionDialog.tsx | 108 + .../CollectionDialog/index.ts | 1 + .../CollectionNoCreatePermissionDialog.tsx | 51 + .../index.ts | 1 + .../CollectionStructureDialog.scss | 10 + .../CollectionStructureDialog.tsx | 388 + .../CreateEntityDialog.scss | 13 + .../CreateEntityDialog/CreateEntityDialog.tsx | 68 + .../NewTitleDialog/NewTitleDialog.tsx | 55 + .../StructureItemSelect/Item/Item.scss | 43 + .../StructureItemSelect/Item/Item.tsx | 74 + .../StructureItemSelect/Item/index.ts | 1 + .../StructureItemSelect.scss | 57 + .../StructureItemSelect.tsx | 215 + .../StructureItemSelect/index.ts | 1 + .../CollectionStructureDialog/index.ts | 1 + .../CopyEntriesDialog.tsx | 60 + .../CopyWorkbookDialog.tsx | 82 + .../CreateCollectionDialog.tsx | 97 + .../CreateEntryInWorkbookDialog.tsx | 86 + .../CreateWorkbookDialog.scss | 5 + .../CreateWorkbookDialog.tsx | 452 + .../ImportFileField/ImportFileField.scss | 5 + .../ImportFileField/ImportFileField.tsx | 98 + .../ImportWorkbookView.scss | 9 + .../ImportWorkbookView/ImportWorkbookView.tsx | 100 + .../CreateWorkbookDialog/utils.ts | 38 + .../DeleteCollectionDialog.tsx | 69 + .../DeleteCollectionsWorkbooksContent.scss | 31 + .../DeleteCollectionsWorkbooksContent.tsx | 129 + .../index.ts | 1 + .../DeleteCollectionsWorkbooksDialog.tsx | 115 + .../DeleteDialog/DeleteDialog.tsx | 46 + .../DeleteDialog/index.ts | 1 + .../DeleteWorkbookDialog.tsx | 69 + .../EditCollectionDialog.tsx | 103 + .../EditSharedEntryDialog.tsx | 72 + .../EditWorkbookDialog.tsx | 100 + .../ExportInfo/ExportInfo.tsx | 46 + .../ExportWorkbookDialog.scss | 15 + .../ExportWorkbookDialog.tsx | 344 + .../ExportWorkbookDialog/utils.ts | 24 + .../MigrateEntryToWorkbookDialog.tsx | 62 + .../MoveCollectionDialog.tsx | 83 + .../MoveCollectionsWorkbooksDialog.tsx | 111 + .../MoveSharedEntryDialog.tsx | 83 + .../MoveWorkbookDialog.tsx | 83 + .../WorkbookDialog/WorkbookDialog.scss | 15 + .../WorkbookDialog/WorkbookDialog.tsx | 152 + .../WorkbookDialog/index.ts | 2 + .../WorkbookDialog/types.ts | 8 + .../EntriesNotificationCut.scss | 29 + .../EntriesNotificationCut.tsx | 135 + .../EntriesNotificationCut/helpers.ts | 84 + .../EntriesNotificationCut/types.ts | 13 + .../hooks/useCollectionEntityDialogState.ts | 38 + .../useCollectionStructureDialogState.ts | 173 + .../hooks/useNotificationsAndDetails.ts | 59 + .../components/CollectionsStructure/index.ts | 35 + .../components/CollectionsStructure/types.ts | 20 + .../ColorPaletteChartkitPreview.tsx | 25 + .../ColorPaletteCharkitPreview/types.ts | 4 + .../ColorPaletteCharkitPreview/utils.ts | 75 + .../ColorPaletteEditorContainer.scss | 14 + .../ColorPaletteEditorContainer.tsx | 159 + .../ColorPalettesCard/ColorPalettesCard.scss | 80 + .../ColorPalettesCard/ColorPalettesCard.tsx | 137 + .../DefaultPaletteSelect.scss | 7 + .../DefaultPaletteSelect.tsx | 109 + .../ColorTextInput/ColorTextInput.scss | 34 + .../ColorTextInput/ColorTextInput.tsx | 68 + .../GradientPaletteEditor.scss | 44 + .../GradientPaletteEditor.tsx | 300 + .../PaletteEditor/PaletteEditor.scss | 72 + .../PaletteEditor/PaletteEditor.tsx | 306 + .../ColorPaletteSelect.scss | 9 + .../ColorPaletteSelect/ColorPaletteSelect.tsx | 146 + .../ColorPickerInput/ColorPickerInput.scss | 54 + .../ColorPickerInput/ColorPickerInput.tsx | 262 + src/ui/components/ColorPickerInput/utils.ts | 58 + .../ColorValueItem/ColorValueItem.scss | 22 + .../ColorValueItem/ColorValueItem.tsx | 31 + .../ConnectorIcon/ConnectorIcon.scss | 5 + .../ConnectorIcon/ConnectorIcon.tsx | 90 + .../BackButton/BackButton.scss | 3 + .../BackButton/BackButton.tsx | 19 + .../ControlComponents/Dialog/Dialog.scss | 11 + .../ControlComponents/Dialog/Dialog.tsx | 59 + .../AppearanceSection/AppearanceSection.scss | 12 + .../AppearanceSection/AppearanceSection.tsx | 25 + .../Rows/ColorAccentRow/ColorAccentRow.tsx | 65 + .../Rows/HintRow/HintRow.tsx | 60 + .../Rows/InnerTitleRow/InnerTitleRow.tsx | 89 + .../TitlePlacementRow/TitlePlacementRow.tsx | 60 + .../Rows/TitleRow/TitleRow.tsx | 50 + .../CommonGroupSettingsSection.tsx | 68 + .../ConnectionSettings/ConnectionSettings.tsx | 54 + .../ConnectionSelector/ConnectionSelector.tsx | 103 + .../components/ConnectionSelector/helpers.ts | 24 + .../QueryTypeControl/QueryTypeControl.tsx | 98 + .../EditLabelControl/EditLabelControl.tsx | 37 + .../EditQueryControl/EditQueryControl.tsx | 36 + .../components/QueryTypeControl/helpers.ts | 12 + .../helpers/get-distincts-by-typed-query.ts | 47 + .../DatasetSelectorSettings.tsx | 199 + .../EntrySelector/EntrySelector.scss | 11 + .../EntrySelector/EntrySelector.tsx | 73 + .../ExternalSelectorSettings.scss | 19 + .../ExternalSelectorSettings.tsx | 145 + .../CurrentTabOption/CurrentTabOption.tsx | 46 + .../ImpactTypeSelect/ImpactTypeSelect.scss | 6 + .../ImpactTypeSelect/ImpactTypeSelect.tsx | 392 + .../SelectedTabsOption/SelectedTabsOption.tsx | 23 + .../ImpactTypeSelect/constants.ts | 18 + .../ImpactTypeSelect/helpers.tsx | 73 + .../useTabVisibilityValidation.ts | 89 + .../InputTypeSelector/InputTypeSelector.tsx | 97 + .../ParameterNameInput/ParameterNameInput.tsx | 61 + .../helpers/input-type-select.tsx | 45 + .../Sections/Date/Acceptable/Acceptable.scss | 9 + .../Sections/Date/Acceptable/Acceptable.tsx | 149 + .../Sections/Date/Default/Default.scss | 31 + .../Sections/Date/Default/Default.tsx | 248 + .../OperationSelector/OperationSelector.tsx | 89 + .../ParametersSection/ParametersSection.scss | 13 + .../ParametersSection/ParametersSection.tsx | 108 + .../Sections/Switchers/Button/Button.scss | 8 + .../Sections/Switchers/Button/Button.tsx | 60 + .../Switchers/DatasetField/DatasetField.scss | 17 + .../Switchers/DatasetField/DatasetField.tsx | 195 + .../Acceptable/Acceptable.tsx | 124 + .../Acceptable/SelectorValuesDialogBody.scss | 46 + .../Acceptable/SelectorValuesDialogBody.tsx | 174 + .../ListValueControl/ListValueControl.tsx | 61 + .../MultiselectableCheckbox.tsx | 47 + .../ValueSelector/DynamicValueSelect.tsx | 60 + .../ValueSelector/StaticValueSelect.tsx | 59 + .../ListValueControl/ValueSelector/hooks.ts | 18 + .../ListValueControl/ValueSelector/types.ts | 22 + .../ListValueControl/ValueSelector/utils.ts | 6 + .../RequiredValueCheckbox.tsx | 53 + .../Sections/ValueSelector/ValueSelector.scss | 9 + .../Sections/ValueSelector/ValueSelector.tsx | 293 + .../helpers/get-distincts-by-dataset-field.ts | 79 + .../Sections/Wrapper/Wrapper.scss | 17 + .../Sections/Wrapper/Wrapper.tsx | 27 + .../SelectorPreview/SelectorPreview.tsx | 5 + .../SelectorTypeSelect/SelectorTypeSelect.tsx | 106 + .../CopyEntriesToWorkbookDialog.scss | 28 + .../CopyEntriesToWorkbookDialog.tsx | 179 + .../components/Body/Body.scss | 33 + .../components/Body/Body.tsx | 100 + .../components/Body/index.ts | 1 + .../components/EntriesGroup/EntriesGroup.scss | 49 + .../components/EntriesGroup/EntriesGroup.tsx | 82 + .../components/EntriesGroup/index.ts | 1 + .../components/EntryRow/EntryRow.scss | 36 + .../components/EntryRow/EntryRow.tsx | 63 + .../components/EntryRow/index.ts | 1 + .../CopyEntriesToWorkbookDialog/index.ts | 4 + .../CopyEntriesToWorkbookDialog/utils.ts | 8 + src/ui/components/DNDPane/DNDItem/DNDItem.tsx | 52 + src/ui/components/DNDPane/DNDPane.scss | 43 + src/ui/components/DNDPane/DNDPane.tsx | 57 + src/ui/components/DashKit/DashKit.scss | 95 + src/ui/components/DashKit/DashKit.ts | 127 + src/ui/components/DashKit/constants.ts | 26 + .../DashKit/context/WidgetContext.tsx | 58 + src/ui/components/DashKit/helpers.tsx | 170 + .../DashKit/plugins/Control/Control.scss | 55 + .../DashKit/plugins/Control/Control.tsx | 885 + .../ControlItems/ControlItemSelect.scss | 65 + .../ControlItems/ControlItemSelect.tsx | 320 + .../DashKit/plugins/Control/Error/Error.scss | 28 + .../DashKit/plugins/Control/Error/Error.tsx | 70 + .../DashKit/plugins/Control/constants.ts | 25 + .../plugins/Control/prerenderMiddleware.js | 77 + .../DashKit/plugins/Control/types.ts | 156 + .../DashKit/plugins/Control/utils.ts | 309 + .../plugins/DebugInfoTool/DebugInfoTool.scss | 47 + .../plugins/DebugInfoTool/DebugInfoTool.tsx | 52 + .../plugins/GroupControl/Control/Control.tsx | 665 + .../GroupControl/Control/store/reducer.ts | 103 + .../GroupControl/Control/store/types.ts | 112 + .../plugins/GroupControl/GroupControl.scss | 134 + .../plugins/GroupControl/GroupControl.tsx | 994 + .../DashKit/plugins/GroupControl/types.ts | 36 + .../DashKit/plugins/GroupControl/utils.ts | 91 + .../DashKit/plugins/Image/Image.scss | 8 + .../DashKit/plugins/Image/Image.tsx | 84 + .../RendererWrapper/RendererWrapper.scss | 34 + .../RendererWrapper/RendererWrapper.tsx | 45 + .../components/DashKit/plugins/Text/Text.scss | 30 + .../components/DashKit/plugins/Text/Text.tsx | 261 + .../plugins/Title/AnchorLink/AnchorLink.tsx | 32 + .../DashKit/plugins/Title/Title.scss | 117 + .../DashKit/plugins/Title/Title.tsx | 275 + .../DashKit/plugins/Title/constants.ts | 2 + .../components/DashKit/plugins/Title/utils.ts | 77 + .../DashKit/plugins/Widget/WidgetPlugin.tsx | 100 + .../plugins/Widget/components/Footer.tsx | 35 + .../plugins/Widget/components/Header.tsx | 98 + .../plugins/Widget/components/helpers.ts | 23 + .../DashKit/plugins/Widget/store/actions.ts | 21 + .../DashKit/plugins/Widget/store/reducer.ts | 26 + .../DashKit/plugins/Widget/types.ts | 48 + src/ui/components/DashKit/plugins/mixins.scss | 9 + src/ui/components/DashKit/plugins/types.ts | 48 + src/ui/components/DashKit/utils.ts | 428 + .../components/DataTypeIcon/DataTypeIcon.scss | 15 + .../components/DataTypeIcon/DataTypeIcon.tsx | 102 + .../DatasetFieldList/DatasetFieldList.scss | 43 + .../DatasetFieldList/DatasetFieldList.tsx | 75 + .../DebouncedInput/DebouncedInput.tsx | 64 + src/ui/components/DebouncedInput/index.ts | 1 + .../DialogAddSharedEntryFromLink.tsx | 177 + .../DialogChartWidget/DialogChartWidget.scss | 194 + .../DialogChartWidget/DialogChartWidget.tsx | 1074 + .../components/DialogCommon/DialogCommon.scss | 90 + .../components/DialogCommon/DialogCommon.tsx | 136 + .../DialogConfirm/DialogConfirm.tsx | 95 + .../DialogDefault/DialogDefault.tsx | 106 + .../DialogEditItem/DialogEditItem.tsx | 276 + .../DialogEntryDescription.scss | 49 + .../DialogEntryDescription.tsx | 191 + .../DialogEntryDescriptionWrapper.tsx | 20 + .../EntryDescriptionButton.tsx | 55 + .../DialogEntryDescription/index.ts | 7 + .../DebugInfo/DebugInfo.scss | 44 + .../DebugInfo/DebugInfo.tsx | 89 + .../DialogErrorWithTabs.scss | 64 + .../DialogErrorWithTabs.tsx | 360 + .../ErrorText/ErrorText.scss | 44 + .../ErrorText/ErrorText.tsx | 30 + .../DatabaseRequestTab/DatabaseRequestTab.tsx | 17 + .../DatabaseResponseTab.tsx | 17 + .../Tabs/DebugTab/DebugTab.tsx | 52 + .../Tabs/DebugTab/utils.test.ts | 21 + .../Tabs/DebugTab/utils.ts | 7 + .../DocumentationTab/DocumentationTab.scss | 6 + .../DocumentationTab/DocumentationTab.tsx | 33 + .../DialogExternalControl.scss | 26 + .../DialogExternalControl.tsx | 162 + .../DialogFieldEditor/DialogFieldEditor.scss | 0 .../DialogFieldEditor/DialogFieldEditor.tsx | 84 + .../BooleanFilter/BooleanFilter.scss | 11 + .../BooleanFilter/BooleanFilter.tsx | 46 + .../DialogFilter/DateFilter/DateFilter.tsx | 43 + .../components/DialogFilter/DialogFilter.scss | 88 + .../components/DialogFilter/DialogFilter.tsx | 721 + .../DialogFilter/InputFilter/InputFilter.scss | 9 + .../DialogFilter/InputFilter/InputFilter.tsx | 99 + .../SelectFilter/SelectFilter.scss | 88 + .../SelectFilter/SelectFilter.tsx | 215 + src/ui/components/DialogFilter/constants.ts | 62 + src/ui/components/DialogFilter/typings.ts | 6 + src/ui/components/DialogFilter/utils.ts | 91 + .../DialogGroupControl.scss | 75 + .../DialogGroupControl/DialogGroupControl.tsx | 151 + .../GroupControlBody/GroupControlBody.tsx | 81 + .../SimilarSelectorsBlock.tsx | 108 + .../GroupControlFooter/GroupControlFooter.tsx | 31 + .../GroupControlSidebar.tsx | 187 + .../TabItemWrapper/TabItemWrapper.scss | 12 + .../TabItemWrapper/TabItemWrapper.tsx | 32 + .../ControlPlacementRow.tsx | 87 + .../GroupExtendedSettings.scss | 65 + .../GroupExtendedSettings.tsx | 367 + .../hooks/useSimilarSelectorsActions.ts | 303 + .../DialogImageWidget/DialogImageWidget.scss | 12 + .../DialogImageWidget/DialogImageWidget.tsx | 248 + src/ui/components/DialogImageWidget/index.ts | 1 + .../components/DialogManager/DialogManager.ts | 16 + .../DialogManager/DialogManagerContainer.tsx | 41 + .../DialogNoRights/DialogNoRights.scss | 7 + .../DialogNoRights/DialogNoRights.tsx | 38 + .../DialogParameter/DefaultValueSection.tsx | 109 + .../DialogParameter/DialogParameter.scss | 79 + .../DialogParameter/DialogParameter.tsx | 277 + .../DialogParameter/TemplateSection.tsx | 154 + src/ui/components/DialogParameter/helpers.tsx | 90 + .../DialogParameter/useParameterForm.ts | 64 + .../useParameterTooltipMarkdown.tsx | 29 + .../DialogParameter/useValidationErrors.ts | 51 + .../DialogQLParameter/DialogQLParameter.scss | 50 + .../DialogQLParameter/DialogQLParameter.tsx | 107 + .../DialogRelatedEntities.scss | 89 + .../DialogRelatedEntities.tsx | 271 + .../DialogRelatedEntities/constants.ts | 6 + .../DialogRelations/DialogRelations.scss | 119 + .../DialogRelations/DialogRelations.tsx | 678 + .../components/Content/Content.scss | 195 + .../components/Content/Content.tsx | 100 + .../components/Content/Row.tsx | 395 + .../components/Content/helpers.tsx | 264 + .../components/Content/types.ts | 11 + .../DialogAliases/DialogAliases.scss | 51 + .../DialogAliases/DialogAliases.tsx | 402 + .../components/AddAliases/AddAliases.scss | 53 + .../components/AddAliases/AddAliases.tsx | 299 + .../components/AddAliases/helpers.ts | 94 + .../AliasesDetail/AliasesDetail.scss | 30 + .../AliasesDetail/AliasesDetail.tsx | 64 + .../AliasesList/AliasesInvalidList.tsx | 114 + .../components/AliasesList/AliasesList.scss | 103 + .../components/AliasesList/AliasesList.tsx | 237 + .../components/Filters/Filters.scss | 14 + .../components/Filters/Filters.tsx | 103 + .../components/DialogRelations/constants.ts | 45 + src/ui/components/DialogRelations/helpers.tsx | 496 + .../DialogRelations/hooks/helpers.ts | 626 + .../DialogRelations/hooks/helpersChart.ts | 313 + .../DialogRelations/hooks/helpersControls.ts | 249 + .../DialogRelations/hooks/helpersDatasets.ts | 103 + .../hooks/useFilteredRelations.tsx | 62 + .../DialogRelations/hooks/useRelations.tsx | 441 + .../DialogRelations/test/helpers.test.ts | 270 + src/ui/components/DialogRelations/types.ts | 117 + .../DialogSelectSharedEntry.scss | 19 + .../DialogSelectSharedEntry.tsx | 173 + .../DialogSharedEntryBindings.scss | 55 + .../DialogSharedEntryBindings.tsx | 130 + .../components/DeleteAlert.tsx | 64 + .../components/DirectionControl.tsx | 34 + .../components/Relations.tsx | 226 + .../components/SharedBindingsFooter.tsx | 52 + .../components/SharedBindingsHeader.tsx | 23 + .../DialogSharedEntryBindings/constants.ts | 15 + .../hooks/useSharedEntryBindings.ts | 143 + .../DialogSharedEntryBindings/types.ts | 10 + .../DialogSharedEntryBindings/utils.ts | 19 + .../DialogSharedEntryPermissions.scss | 19 + .../DialogSharedEntryPermissions.tsx | 131 + .../PermissionButton/PermissionButton.scss | 37 + .../PermissionButton/PermissionButton.tsx | 63 + .../DialogSharedEntryUnbind.scss | 13 + .../DialogSharedEntryUnbind.tsx | 159 + .../DialogSharedRelatedEntities.scss | 46 + .../DialogSharedRelatedEntities.tsx | 241 + .../DialogSuccessWithAction.scss | 11 + .../DialogSuccessWithAction.tsx | 40 + .../DialogTextWidget/DialogTextWidget.scss | 22 + .../DialogTextWidget/DialogTextWidget.tsx | 274 + src/ui/components/DialogTextWidget/index.tsx | 11 + .../DialogTitleWidget/DialogTitleWidget.scss | 38 + .../DialogTitleWidget/DialogTitleWidget.tsx | 568 + .../DialogTitleWidget/useColorSettings.ts | 164 + .../DialogWarning/DialogWarning.tsx | 81 + src/ui/components/DragAndDrop/DragAndDrop.tsx | 28 + .../DropdownAction/DropdownAction.scss | 8 + .../DropdownAction/DropdownAction.tsx | 26 + .../DropdownNavigation/DropdownNavigation.js | 207 + .../DropdownNavigation.scss | 16 + .../components/EditableText/EditableText.tsx | 76 + .../components/EntitiesList/EntitiesList.scss | 17 + .../components/EntitiesList/EntitiesList.tsx | 68 + src/ui/components/EntitiesList/types.ts | 30 + src/ui/components/EntityIcon/EntityIcon.scss | 95 + src/ui/components/EntityIcon/EntityIcon.tsx | 82 + src/ui/components/EntityLink/EntityLink.scss | 18 + src/ui/components/EntityLink/EntityLink.tsx | 24 + src/ui/components/EntityRow/EntityRow.scss | 66 + src/ui/components/EntityRow/EntityRow.tsx | 120 + .../components/EntriesList/EntriesList.scss | 35 + src/ui/components/EntriesList/EntriesList.tsx | 43 + src/ui/components/EntriesList/Item/Item.tsx | 56 + src/ui/components/EntriesList/types.ts | 6 + .../EntryBreadcrumbs/EntryBreadcrumbs.scss | 10 + .../EntryBreadcrumbs/EntryBreadcrumbs.tsx | 65 + src/ui/components/EntryBreadcrumbs/helpers.ts | 93 + .../EntryContextMenu/EntryContextMenu.tsx | 203 + .../EntryContextMenuBase.scss | 9 + .../EntryContextMenuBase.tsx | 139 + .../components/EntryContextMenu/constants.ts | 268 + src/ui/components/EntryContextMenu/helpers.ts | 366 + src/ui/components/EntryContextMenu/index.ts | 3 + .../withConfiguredEntryContextMenu.test.ts | 502 + src/ui/components/EntryContextMenu/types.ts | 38 + src/ui/components/EntryContextMenu/utils.ts | 5 + .../withConfiguredEntryContextMenu/helpers.ts | 66 + .../withConfiguredEntryContextMenu.tsx | 99 + .../DialogAccess/DialogAccess.tsx | 17 + .../DialogAccessDescription.scss | 9 + .../DialogAccessDescription.tsx | 52 + .../DialogCopyEntry/DialogCopyEntry.tsx | 213 + .../EntryDialogues/DialogCopyEntry/index.ts | 2 + .../DialogCreateDashboard.tsx | 120 + .../DialogCreateDashboard/index.ts | 2 + .../DialogCreateEditorChart.tsx | 122 + .../DialogCreateEditorChart/index.ts | 2 + .../DialogCreateFolder/DialogCreateFolder.tsx | 83 + .../DialogCreateFolder/index.ts | 2 + .../DialogCreateQLChart.tsx | 127 + .../DialogCreateQLChart/index.ts | 2 + .../DialogCreateWizardChart.tsx | 129 + .../DialogCreateWizardChart/index.ts | 2 + .../DialogCreateWorkbookEntry.scss | 18 + .../DialogCreateWorkbookEntry.tsx | 195 + .../DialogDeleteEntry/DialogDeleteEntry.scss | 11 + .../DialogDeleteEntry/DialogDeleteEntry.tsx | 110 + .../EntryDialogues/DialogDeleteEntry/index.ts | 2 + .../DialogEditFavoritesAlias.scss | 5 + .../DialogEditFavoritesAlias.tsx | 118 + .../DialogEditFavoritesAlias/index.ts | 1 + .../DialogEditWarning/DialogEditWarning.scss | 13 + .../DialogEditWarning/DialogEditWarning.tsx | 94 + .../DialogEntrySaveAsNew.tsx | 116 + .../DialogEntrySaveAsNew/index.ts | 2 + .../DialogMakeActualConfirm.scss | 33 + .../DialogMakeActualConfirm.tsx | 82 + .../DialogMigrateToWorkbook.tsx | 28 + .../DialogMigrateToWorkbook/index.ts | 1 + .../DialogMoveEntry/DialogMoveEntry.tsx | 89 + .../EntryDialogues/DialogMoveEntry/index.ts | 2 + .../DialogRenameEntry/DialogRenameEntry.tsx | 118 + .../EntryDialogues/DialogRenameEntry/index.ts | 2 + .../DialogShareEntry/DialogShareEntry.tsx | 21 + .../EntryDialogues/DialogShareEntry/index.ts | 1 + .../AlertTooltip/AlertTooltip.scss | 12 + .../AlertTooltip/AlertTooltip.tsx | 31 + .../AuthorSection/AuthorSection.scss | 30 + .../AuthorSection/AuthorSection.tsx | 57 + .../ContentError/ContentError.scss | 18 + .../ContentError/ContentError.tsx | 30 + .../ContentLoader/ContentLoader.scss | 11 + .../ContentLoader/ContentLoader.tsx | 18 + .../CurrentEntrySection.scss | 54 + .../CurrentEntrySection.tsx | 85 + .../DialogPublic/DialogAlert/DialogAlert.scss | 7 + .../DialogPublic/DialogAlert/DialogAlert.tsx | 64 + .../DialogPublic/DialogPublic.scss | 32 + .../DialogPublic/DialogPublic.tsx | 209 + .../RelationsList/RelationsList.scss | 38 + .../RelationsList/RelationsList.tsx | 117 + .../DialogPublic/SharedLink/SharedLink.scss | 23 + .../DialogPublic/SharedLink/SharedLink.tsx | 55 + .../DialogPublic/constants.ts | 10 + .../DialogPublic/helpers.ts | 7 + .../DialogSwitchPublic/DialogPublic/types.ts | 148 + .../DialogPublic/useDialogPublicState.ts | 549 + .../DialogSwitchPublic/DialogSwitchPublic.tsx | 25 + .../DialogSwitchPublic/index.ts | 2 + .../DialogUnlock/DialogUnlock.tsx | 32 + .../EntryDialogues/DialogUnlock/index.ts | 2 + .../EntryDialogBase/EntryDialogBase.scss | 5 + .../EntryDialogBase/EntryDialogBase.tsx | 247 + .../EntryDialogues/EntryDialogues.tsx | 201 + src/ui/components/EntryDialogues/constants.ts | 4 + src/ui/components/EntryDialogues/index.ts | 8 + src/ui/components/EntryDialogues/types.ts | 16 + src/ui/components/EntryDialogues/utils.ts | 3 + src/ui/components/EntryIcon/EntryIcon.tsx | 138 + src/ui/components/EntryRow/EntryRow.scss | 55 + src/ui/components/EntryRow/EntryRow.tsx | 135 + .../components/EntrySuggest/EntrySuggest.scss | 23 + .../components/EntrySuggest/EntrySuggest.tsx | 261 + src/ui/components/EntrySuggest/types.ts | 18 + src/ui/components/EntryTitle/EntryTitle.scss | 32 + src/ui/components/EntryTitle/EntryTitle.tsx | 77 + src/ui/components/EntryTitle/index.ts | 2 + src/ui/components/EntryTitle/types.ts | 6 + src/ui/components/EntryTitle/utils.ts | 7 + .../ErrorBoundary/ErrorBoundary.tsx | 32 + .../ErrorContent/DebugInfo/DebugInfo.scss | 30 + .../ErrorContent/DebugInfo/DebugInfo.tsx | 53 + .../components/ErrorContent/ErrorContent.scss | 153 + .../components/ErrorContent/ErrorContent.tsx | 309 + src/ui/components/ErrorPage/ErrorPage.tsx | 26 + src/ui/components/ErrorPage/withErrorPage.tsx | 40 + .../ExpandablePanel/ExpandablePanel.scss | 128 + .../ExpandablePanel/ExpandablePanel.tsx | 96 + .../components/FieldEditor/FieldEditor.scss | 274 + src/ui/components/FieldEditor/FieldEditor.tsx | 482 + .../FieldEditor/components/DocSection.tsx | 411 + .../FieldEditor/components/FormulaSection.tsx | 304 + .../FieldEditor/components/NameHeader.tsx | 31 + .../FieldEditor/components/Settings.tsx | 208 + .../FieldEditor/components/SourceSection.tsx | 224 + .../FieldEditor/components/helpers.ts | 14 + src/ui/components/FieldEditor/constants.ts | 21 + src/ui/components/FieldEditor/index.tsx | 59 + src/ui/components/FieldEditor/typings.ts | 19 + src/ui/components/FieldEditor/utils.ts | 133 + .../components/FieldWrapper/FieldWrapper.scss | 34 + .../components/FieldWrapper/FieldWrapper.tsx | 30 + src/ui/components/FloatMenu/FloatMenu.scss | 34 + src/ui/components/FloatMenu/FloatMenu.tsx | 61 + .../components/FormSection/FormSection.scss | 13 + src/ui/components/FormSection/FormSection.tsx | 29 + .../FormattedValue/FormattedValue.tsx | 109 + .../AccessList/AccessList.scss | 18 + .../IamAccessDialog/AccessList/AccessList.tsx | 252 + .../AccessList/ActionPanel/ActionPanel.scss | 14 + .../AccessList/ActionPanel/ActionPanel.tsx | 48 + .../AccessList/Block/Block.scss | 30 + .../AccessList/Block/Block.tsx | 39 + .../Controls/Controls.scss | 28 + .../DirectAccessesTable/Controls/Controls.tsx | 128 + .../DirectAccessesTable.scss | 15 + .../DirectAccessesTable.tsx | 239 + .../AccessList/DirectAccessesTable/utils.ts | 19 + .../AccessList/Filters/Filters.scss | 12 + .../AccessList/Filters/Filters.tsx | 59 + .../InheritedAccessesTable.scss | 31 + .../InheritedAccessesTable.tsx | 176 + .../AddSubjects/ActionPanel/ActionPanel.scss | 15 + .../AddSubjects/ActionPanel/ActionPanel.tsx | 54 + .../AddSubjects/AddSubjects.scss | 14 + .../AddSubjects/AddSubjects.tsx | 126 + .../SubjectsList/SubjectsList.scss | 28 + .../AddSubjects/SubjectsList/SubjectsList.tsx | 192 + .../IamAccessDialog/IamAccessDialog.tsx | 23 + .../IamAccessDialogComponent.tsx | 326 + .../ResourceInfo/ResourceInfo.scss | 16 + .../ResourceInfo/ResourceInfo.tsx | 61 + .../components/IamAccessDialog/constants.ts | 1 + src/ui/components/IamAccessDialog/index.ts | 1 + src/ui/components/IamAccessDialog/utils.ts | 115 + src/ui/components/IconById/IconById.tsx | 22 + .../components/Illustration/Illustration.scss | 22 + .../components/Illustration/Illustration.tsx | 25 + src/ui/components/Illustration/context.ts | 40 + .../Illustration/getIllustrationStore.ts | 28 + src/ui/components/Illustration/types.ts | 28 + src/ui/components/Illustration/utils.tsx | 30 + .../InaccessibleOnMobileInset.js | 48 + .../InaccessibleOnMobileInset.scss | 14 + src/ui/components/Interpolate/Interpolate.tsx | 53 + src/ui/components/Interpolate/README.md | 40 + src/ui/components/Interpolate/index.ts | 2 + src/ui/components/Interpolate/utils.tsx | 13 + .../InterpolatedText/InterpolatedText.tsx | 72 + .../components/LandingPage/LandingPage.scss | 5 + src/ui/components/LandingPage/LandingPage.tsx | 46 + .../components/ListWithMenu/ListWithMenu.scss | 56 + .../components/ListWithMenu/ListWithMenu.tsx | 237 + .../ListWithRemove/ListWithRemove.scss | 33 + .../ListWithRemove/ListWithRemove.tsx | 66 + .../LocationChange/LocationChange.tsx | 35 + .../MarkdownControl/MarkdownControl.scss | 14 + .../MarkdownControl/MarkdownControl.tsx | 34 + .../MarkdownHelpPopover/Content.tsx | 21 + .../MarkdownHelpPopover.scss | 23 + .../MarkdownHelpPopover.tsx | 46 + src/ui/components/Markup/Markup.tsx | 199 + .../MarkupTooltip/MarkupTooltip.tsx | 31 + .../Markup/components/UserInfo/UserInfo.tsx | 21 + src/ui/components/Markup/components/index.ts | 2 + src/ui/components/Markup/index.ts | 1 + src/ui/components/Markup/types.ts | 14 + src/ui/components/Markup/utils.test.ts | 18 + src/ui/components/Markup/utils.ts | 40 + .../MigrateToWorkbookDialog.scss | 28 + .../MigrateToWorkbookDialog.tsx | 215 + .../components/Body/Body.scss | 38 + .../components/Body/Body.tsx | 139 + .../components/Body/index.ts | 1 + .../components/EntriesGroup/EntriesGroup.scss | 49 + .../components/EntriesGroup/EntriesGroup.tsx | 82 + .../components/EntriesGroup/index.ts | 1 + .../components/EntryRow/EntryRow.scss | 32 + .../components/EntryRow/EntryRow.tsx | 56 + .../components/EntryRow/index.ts | 1 + .../MigrateToWorkbookDialog/index.ts | 1 + .../components/MobileHeader/MobileHeader.scss | 43 + .../components/MobileHeader/MobileHeader.tsx | 9 + .../BurgerMenuFooter/BurgerMenuFooter.tsx | 47 + .../MobileHeaderComponent.tsx | 105 + .../UserPanel/UserPanel.scss | 41 + .../UserPanel/UserPanel.tsx | 41 + src/ui/components/MobileHeader/constants.ts | 1 + .../MobileTocToggle/MobileTocToggle.scss | 17 + .../MobileTocToggle/MobileTocToggle.tsx | 22 + src/ui/components/Monaco/LazyMonaco.scss | 12 + src/ui/components/Monaco/LazyMonaco.tsx | 78 + src/ui/components/Monaco/Monaco.scss | 25 + src/ui/components/Monaco/Monaco.tsx | 114 + .../Navigation/Base/NavigationBase.js | 514 + .../Navigation/Base/NavigationBase.scss | 23 + .../components/Navigation/Base/configure.ts | 193 + src/ui/components/Navigation/Base/types.ts | 9 + src/ui/components/Navigation/Base/utils.ts | 9 + .../Core/CreateEntry/CreateEntry.scss | 28 + .../Core/CreateEntry/CreateEntry.tsx | 108 + .../Core/CreateEntry/getCreateEntryItems.tsx | 78 + .../EntryContextButton/EntryContextButton.tsx | 36 + .../Navigation/Core/ErrorView/ErrorView.scss | 12 + .../Navigation/Core/ErrorView/ErrorView.tsx | 57 + .../FavoritesNameWithAliasItem.scss | 31 + .../FavoritesNameWithAliasItem.tsx | 82 + .../NavigationBreadcrumbs/BreadcrumbMenu.scss | 8 + .../NavigationBreadcrumbs/BreadcrumbMenu.tsx | 214 + .../NavigationBreadcrumbs.scss | 44 + .../NavigationBreadcrumbs.tsx | 162 + .../Navigation/Core/NavigationEntries.js | 655 + .../Navigation/Core/NavigationEntries.scss | 91 + .../Navigation/Core/NavigationInline.js | 122 + .../Navigation/Core/NavigationInline.scss | 37 + .../Navigation/Core/NavigationMinimal.js | 196 + .../Navigation/Core/NavigationMinimal.scss | 60 + .../Navigation/Core/NavigationModal.js | 200 + .../Navigation/Core/NavigationModal.scss | 47 + .../Core/OrderSelect/OrderSelect.tsx | 49 + .../Core/SearchInput/SearchInput.tsx | 38 + .../Navigation/Core/Sidebar/Sidebar.js | 112 + .../Navigation/Core/Sidebar/Sidebar.scss | 47 + .../TableView/BatchDialog/BatchDialog.tsx | 39 + .../Navigation/Core/TableView/List/List.tsx | 204 + .../Navigation/Core/TableView/Row/Row.tsx | 370 + .../Core/TableView/Row/RowHeader.tsx | 75 + .../Navigation/Core/TableView/TableView.scss | 394 + .../Navigation/Core/TableView/TableView.tsx | 71 + .../Navigation/Core/TableView/types.ts | 61 + .../Core/TableView/useBatchSelect.ts | 128 + .../Navigation/NavigationMinimal.d.ts | 36 + .../Navigation/NavigationMinimal.js | 218 + .../Navigation/NavigationModal.d.ts | 16 + .../components/Navigation/NavigationModal.js | 79 + .../Navigation/PlaceSelect/PlaceSelect.js | 107 + .../Navigation/PlaceSelect/PlaceSelect.scss | 18 + .../components/BatchPanel/BatchPanel.tsx | 52 + .../components/EmptyState/EmptyState.scss | 5 + .../components/EmptyState/EmptyState.tsx | 61 + .../Navigation/components/EmptyState/utils.ts | 42 + src/ui/components/Navigation/constants.ts | 36 + .../BatchMove/AccessError/AccessError.scss | 16 + .../BatchMove/AccessError/AccessError.tsx | 85 + .../dialogs/BatchMove/BatchMove.tsx | 171 + .../BatchMove/MoveError/MoveError.scss | 16 + .../dialogs/BatchMove/MoveError/MoveError.tsx | 79 + .../SelectDestination/SelectDestination.scss | 5 + .../SelectDestination/SelectDestination.tsx | 64 + .../Navigation/dialogs/BatchMove/types.ts | 6 + .../Navigation/hoc/resolveNavigationPath.js | 76 + src/ui/components/Navigation/index.ts | 4 + src/ui/components/Navigation/settings.ts | 36 + src/ui/components/Navigation/types.ts | 59 + src/ui/components/Navigation/util.ts | 50 + .../NavigationPrompt/NavigationPrompt.tsx | 28 + .../NavigationTabs/NavigationTabs.tsx | 111 + .../NumberFormatSettings.tsx | 192 + .../NumberInput/NumberInput.scss | 18 + .../NumberInput/NumberInput.tsx | 67 + .../NumberInputWithSlider.scss | 18 + .../NumberInputWithSlider.tsx | 62 + .../OpenDialogNeedReset.tsx | 55 + .../OrderBySelect/OrderBySelect.tsx | 60 + src/ui/components/OrderBySelect/constants.ts | 6 + src/ui/components/OrderBySelect/index.tsx | 3 + src/ui/components/OrderBySelect/types.ts | 15 + src/ui/components/PageTitle/PageTitle.tsx | 16 + src/ui/components/PageTitle/index.ts | 2 + src/ui/components/PageTitle/usePageTitle.ts | 63 + src/ui/components/PaletteIcon/PaletteIcon.tsx | 37 + .../components/PaletteItem/PaletteItem.scss | 45 + src/ui/components/PaletteItem/PaletteItem.tsx | 59 + src/ui/components/PathSelect/PathSelect.scss | 28 + src/ui/components/PathSelect/PathSelect.tsx | 140 + src/ui/components/Picture/Picture.tsx | 19 + .../PlaceholderIllustration.tsx | 42 + .../PlaceholderIllustration/types.ts | 13 + .../Progress/DialogCounterProgress.scss | 10 + .../Progress/DialogCounterProgress.tsx | 61 + .../components/ProgressBar/ProgressBar.scss | 5 + src/ui/components/ProgressBar/ProgressBar.tsx | 32 + .../RelativeDatesPicker.scss | 127 + .../RelativeDatesPicker.tsx | 689 + .../RelativeDatesPicker/constants.ts | 42 + .../components/RelativeDatesPicker/types.ts | 44 + .../components/RelativeDatesPicker/utils.ts | 170 + .../RevisionStatusPoint.scss | 78 + .../RevisionStatusPoint.tsx | 32 + src/ui/components/Revisions/Revisions.scss | 133 + src/ui/components/Revisions/Revisions.tsx | 235 + .../RevisionsList/RevisionsList.scss | 127 + .../Revisions/RevisionsList/RevisionsList.tsx | 218 + src/ui/components/Revisions/helpers.ts | 59 + src/ui/components/Revisions/types.ts | 26 + .../RevisionsPanel/RevisionsPanel.scss | 58 + .../RevisionsPanel/RevisionsPanel.tsx | 280 + .../components/RevisionsControls.tsx | 110 + src/ui/components/RevisionsPanel/utils.ts | 16 + .../ScrollableWithShadow.scss | 78 + .../ScrollableWithShadow.tsx | 89 + .../SectionWrapper/SectionWrapper.scss | 30 + .../SectionWrapper/SectionWrapper.tsx | 81 + .../Select/constants/SelectStyles.ts | 3 + .../useSelectAsyncFetch/CustomLoader.scss | 12 + .../hooks/useSelectAsyncFetch/README.md | 37 + .../useSelectAsyncFetch/useIntersection.ts | 26 + .../useSelectAsyncFetch.tsx | 112 + .../hooks/useSelectInfinityFetch/types.ts | 8 + .../useSelectInfinityFetch.ts | 89 + .../Select/hooks/useSelectOptionsEnhancer.tsx | 11 + .../useSelectRenderEmptyOptions.tsx | 32 + .../Select/hooks/useSelectRenderError.tsx | 44 + .../useSelectRenderFilter/ListControls.scss | 15 + .../useSelectRenderFilter.tsx | 174 + .../components/Select/styles/SelectUtil.scss | 13 + .../Select/wrappers/SelectAsync.tsx | 20 + .../Select/wrappers/SelectFeatured.tsx | 59 + .../Select/wrappers/SelectFeaturedAsync.tsx | 64 + .../SelectOptionWithIcon.scss | 24 + .../SelectOptionWithIcon.tsx | 27 + src/ui/components/SelectComponents/index.ts | 1 + .../Body/Body.scss | 60 + .../Body/Body.tsx | 70 + .../SelectMigrationToWorkbookDialog.scss | 21 + .../SelectMigrationToWorkbookDialog.tsx | 108 + .../constants.tsx | 67 + .../SelectMigrationToWorkbookDialog/index.ts | 4 + .../SharedBindingsList/ListSearch.tsx | 45 + .../SharedBindingsList.scss | 36 + .../SharedBindingsList/SharedBindingsList.tsx | 89 + .../SharedEntryIcon/SharedEntryIcon.scss | 10 + .../SharedEntryIcon/SharedEntryIcon.tsx | 47 + src/ui/components/SlugifyUrl/SlugifyUrl.tsx | 36 + src/ui/components/SlugifyUrl/index.ts | 1 + .../components/SmartLoader/SmartLoader.scss | 17 + src/ui/components/SmartLoader/SmartLoader.tsx | 45 + src/ui/components/TabMenu/TabMenu.scss | 85 + src/ui/components/TabMenu/TabMenu.tsx | 406 + src/ui/components/TabMenu/types.ts | 52 + src/ui/components/Tabs/Tabs.scss | 72 + src/ui/components/Tabs/Tabs.tsx | 5 + .../components/Tabs/withDatalensSpecific.tsx | 92 + src/ui/components/TagInput/TagInput.scss | 15 + src/ui/components/TagInput/TagInput.tsx | 180 + .../TemplateTextPaper/TemplateTextPaper.scss | 25 + .../TemplateTextPaper/TemplateTextPaper.tsx | 49 + .../components/UserAvatar/UserAvatarById.tsx | 28 + src/ui/components/UserMenu/UserAvatar.tsx | 10 + src/ui/components/UserMenu/UserMenu.scss | 66 + src/ui/components/UserMenu/UserMenu.tsx | 75 + .../components/UsersSuggest/UsersSuggest.scss | 29 + .../components/UsersSuggest/UsersSuggest.tsx | 166 + src/ui/components/UsersSuggest/types.ts | 15 + src/ui/components/ValuesList/ValuesList.scss | 43 + src/ui/components/ValuesList/ValuesList.tsx | 88 + src/ui/components/Veil/Veil.scss | 16 + src/ui/components/Veil/Veil.tsx | 45 + src/ui/components/ViewAsync/ViewAsync.scss | 7 + src/ui/components/ViewAsync/ViewAsync.tsx | 67 + src/ui/components/ViewError/ViewError.scss | 24 + src/ui/components/ViewError/ViewError.tsx | 160 + src/ui/components/ViewLoader/ViewLoader.scss | 17 + src/ui/components/ViewLoader/ViewLoader.tsx | 29 + .../WidgetRoundingsInput.scss | 3 + .../WidgetRoundingsInput.tsx | 51 + src/ui/components/Widgets/Chart/Chart.scss | 263 + src/ui/components/Widgets/Chart/Chart.tsx | 328 + .../components/Widgets/Chart/ChartAlert.tsx | 204 + .../Widgets/Chart/ChartSelector.tsx | 375 + .../components/Widgets/Chart/ChartWidget.scss | 161 + .../components/Widgets/Chart/ChartWidget.tsx | 734 + .../Widgets/Chart/ChartWidgetWithProvider.tsx | 83 + .../Chart/components/ChartInfoIcon.scss | 14 + .../Chart/components/ChartInfoIcon.tsx | 52 + .../Widgets/Chart/components/Content.tsx | 241 + .../Widgets/Chart/components/Header.tsx | 284 + .../Widgets/Chart/components/WidgetFooter.tsx | 56 + .../Chart/components/WidgetHeader.scss | 157 + .../Widgets/Chart/components/WidgetHeader.tsx | 264 + .../Widgets/Chart/helpers/helpers.ts | 530 + .../Widgets/Chart/helpers/yandex-map.ts | 102 + .../hooks/useAutoHeightResizeObserver.ts | 78 + .../Chart/hooks/useChartActivities.tsx | 190 + .../Chart/hooks/useIntersectionObserver.ts | 131 + .../Widgets/Chart/hooks/useLoadingChart.ts | 1152 + .../Chart/hooks/useLoadingChartSelector.tsx | 419 + .../Chart/hooks/useLoadingChartWidget.ts | 721 + .../Widgets/Chart/hooks/useMemoCallback.ts | 17 + .../components/Widgets/Chart/store/reducer.ts | 81 + .../components/Widgets/Chart/store/types.ts | 88 + src/ui/components/Widgets/Chart/types.ts | 395 + .../Widgets/Chart/utils-chart-alert.ts | 163 + src/ui/components/Widgets/Chart/utils.ts | 21 + .../components/WorkbookIcon/WorkbookIcon.scss | 12 + .../components/WorkbookIcon/WorkbookIcon.tsx | 80 + .../WorkbookNavigationMinimal.scss | 119 + .../WorkbookNavigationMinimal.tsx | 385 + .../components/ListWithSharedEntries.tsx | 71 + .../WorkbookNavigationMinimal/constants.ts | 1 + .../WorkbookNavigationMinimal/types.ts | 11 + .../WysiwygEditor/MarkdownEditorDefault.tsx | 228 + .../WysiwygEditor/WysiwygEditor.scss | 22 + .../WysiwygEditor/WysiwygEditor.tsx | 54 + src/ui/components/WysiwygEditor/mixins.scss | 33 + src/ui/components/WysiwygEditor/preset.ts | 207 + .../components/WysiwygEditor/variables.scss | 5 + src/ui/components/YfmWrapper/YfmWrapper.tsx | 147 + .../YfmWrapper/YfmWrapperContent.scss | 235 + .../YfmWrapper/YfmWrapperContent.tsx | 43 + .../common/Datepicker/Calendar/Calendar.js | 685 + .../common/Datepicker/Calendar/Calendar.scss | 4 + .../common/Datepicker/Datepicker.d.ts | 38 + .../common/Datepicker/Datepicker.js | 629 + .../common/Datepicker/Datepicker.scss | 38 + .../common/Datepicker/Month/Month.js | 214 + .../common/Datepicker/Month/Month.scss | 112 + .../common/Datepicker/Months/Months.js | 162 + .../common/Datepicker/Months/Months.scss | 75 + .../Datepicker/PopupContent/PopupContent.js | 101 + .../Datepicker/PopupContent/PopupContent.scss | 36 + .../common/Datepicker/Presets/Presets.js | 52 + .../common/Datepicker/Presets/Presets.scss | 24 + .../common/Datepicker/Quarters/Quarters.js | 142 + .../common/Datepicker/Quarters/Quarters.scss | 74 + .../Datepicker/YearSwitcher/YearSwitcher.js | 63 + .../Datepicker/YearSwitcher/YearSwitcher.scss | 45 + .../common/Datepicker/Years/Years.js | 104 + .../common/Datepicker/Years/Years.scss | 61 + .../components/common/Datepicker/constants.js | 57 + src/ui/components/common/Datepicker/index.tsx | 10 + src/ui/components/common/Datepicker/utils.js | 260 + .../DatepickerControl/DatepickerControl.tsx | 9 + .../RangeInputPicker/RangeInputPicker.scss | 192 + .../RangeInputPicker/RangeInputPicker.tsx | 470 + .../common/RangeInputPicker/constants.ts | 16 + .../common/RangeInputPicker/index.ts | 1 + .../common/RangeInputPicker/types.ts | 35 + .../common/RangeInputPicker/utils.ts | 100 + .../components/common/YCSelect/Item/Item.js | 440 + .../components/common/YCSelect/Item/Item.scss | 182 + .../components/common/YCSelect/Items/Items.js | 491 + .../common/YCSelect/Items/Items.scss | 108 + .../common/YCSelect/Search/Search.js | 93 + .../common/YCSelect/Search/Search.scss | 13 + .../components/common/YCSelect/YCSelect.d.ts | 98 + src/ui/components/common/YCSelect/YCSelect.js | 1708 ++ .../components/common/YCSelect/YCSelect.scss | 240 + .../components/common/YCSelect/constants.js | 3 + src/ui/components/common/YCSelect/index.tsx | 10 + .../components/common/YCSelect/isBothEmpty.ts | 14 + src/ui/components/common/YCSelect/utils.js | 67 + src/ui/components/common/styles/mixins.scss | 24 + src/ui/configs/common/icons.ts | 65 + src/ui/configs/index.ts | 5 + src/ui/constants/common.ts | 428 + src/ui/constants/connections.ts | 13 + src/ui/constants/dialogs.ts | 32 + src/ui/constants/embedded.ts | 4 + src/ui/constants/index.ts | 6 + src/ui/constants/misc.ts | 163 + src/ui/constants/mobile.ts | 8 + src/ui/constants/navigation.ts | 1 + src/ui/constants/operations.ts | 197 + .../visualizations/combined-chart.ts | 13 + src/ui/constants/visualizations/donut.ts | 13 + src/ui/constants/visualizations/flatTable.ts | 66 + src/ui/constants/visualizations/funnel.ts | 32 + src/ui/constants/visualizations/geolayer.ts | 16 + src/ui/constants/visualizations/geopoint.ts | 57 + src/ui/constants/visualizations/geopolygon.ts | 31 + src/ui/constants/visualizations/heatmap.ts | 30 + src/ui/constants/visualizations/index.ts | 218 + src/ui/constants/visualizations/line.ts | 432 + src/ui/constants/visualizations/metric.ts | 38 + src/ui/constants/visualizations/pie.ts | 71 + src/ui/constants/visualizations/pivotTable.ts | 117 + src/ui/constants/visualizations/polyline.ts | 63 + src/ui/constants/visualizations/scatter.ts | 124 + src/ui/constants/visualizations/treemap.ts | 117 + src/ui/constants/visualizations/utils.ts | 16 + src/ui/constants/workbooks.ts | 2 + src/ui/constants/yfm.ts | 4 + src/ui/datalens/helpers.ts | 12 + src/ui/datalens/hooks.ts | 15 + src/ui/datalens/index.tsx | 150 + src/ui/datalens/pages/AuthPage/AuthPage.tsx | 7 + .../CollectionsNavigationPage.tsx | 12 + .../pages/ConnectionsPage/ConnectionsPage.tsx | 10 + .../DashAndWizardQLPages.tsx | 96 + src/ui/datalens/pages/DashPage/DashPage.tsx | 30 + .../pages/DatasetPage/DatasetPage.tsx | 15 + .../pages/FallbackPage/FallbackPage.scss | 7 + .../pages/FallbackPage/FallbackPage.tsx | 17 + .../pages/LandingPage/LandingPage.tsx | 9 + src/ui/datalens/pages/MainPage/MainPage.tsx | 8 + .../OwnUserProfilePage.scss | 9 + .../OwnUserProfilePage/OwnUserProfilePage.tsx | 38 + .../pages/PreviewPage/PreviewPage.tsx | 6 + src/ui/datalens/pages/QLPage/QLPage.tsx | 27 + .../ServiceSettingsPage.tsx | 6 + .../datalens/pages/WizardPage/WizardPage.tsx | 13 + src/ui/datalens/render.ts | 20 + src/ui/entries/dl-main.tsx | 82 + src/ui/fonts/inter-cyrillic-ext.woff2 | Bin 0 -> 25960 bytes src/ui/fonts/inter-cyrillic.woff2 | Bin 0 -> 18748 bytes src/ui/fonts/inter-greek.woff2 | Bin 0 -> 18996 bytes src/ui/fonts/inter-latin-ext.woff2 | Bin 0 -> 85068 bytes src/ui/fonts/inter-latin.woff2 | Bin 0 -> 48256 bytes src/ui/fonts/inter-vietnamese.woff2 | Bin 0 -> 10252 bytes src/ui/fonts/inter.css | 70 + src/ui/hoc/index.ts | 1 + src/ui/hoc/withEnabledFeature.tsx | 21 + src/ui/hoc/withHiddenUnmount.tsx | 20 + src/ui/hoc/withHotkeysContext.tsx | 20 + src/ui/hoc/withInaccessibleOnMobile.tsx | 25 + src/ui/hoc/withPromiseOpen.js | 38 + src/ui/hooks/index.ts | 7 + src/ui/hooks/useBeforeLoad.ts | 14 + src/ui/hooks/useBindHotkey.ts | 36 + src/ui/hooks/useChangedProp.ts | 15 + src/ui/hooks/useEditHistoryActions.ts | 108 + src/ui/hooks/useEffectOnce.ts | 5 + src/ui/hooks/useEnterClick.ts | 27 + src/ui/hooks/useHover.ts | 19 + src/ui/hooks/useLocalStorageFallback.ts | 79 + src/ui/hooks/useMarkdown.tsx | 51 + src/ui/hooks/useMermaidTheme.ts | 5 + src/ui/hooks/useMountedState.ts | 16 + src/ui/hooks/useOutsideClick.ts | 36 + src/ui/hooks/usePrevious.ts | 11 + src/ui/hooks/useRefMounted.js | 16 + src/ui/hooks/useSetState.js | 15 + src/ui/hooks/useShallowEqualSelector.ts | 8 + src/ui/hooks/useSyncedState.ts | 18 + src/ui/hooks/useTitle.ts | 14 + src/ui/index.ts | 82 + .../DatalensChartkit/ChartKit/ChartKit.js | 119 + .../DatalensChartkit/ChartKit/ChartKit.scss | 101 + .../ChartKit/ChartKitAdapter.tsx | 202 + .../ChartKitTooltip/ChartKitTooltip.scss | 8 + .../ChartKitTooltip/ChartKitTooltip.tsx | 188 + .../ChartKit/components/Error/Error.scss | 22 + .../ChartKit/components/Error/Error.tsx | 23 + .../components/IconRenderer/IconRenderer.tsx | 68 + .../IconRenderer/Icons/CirclePointIcon.tsx | 15 + .../IconRenderer/Icons/DashDotLineIcon.tsx | 22 + .../IconRenderer/Icons/DashLineIcon.tsx | 24 + .../IconRenderer/Icons/DiamondPointIcon.tsx | 18 + .../IconRenderer/Icons/DotLineIcon.tsx | 19 + .../Icons/LongDashDotDotLineIcon.tsx | 22 + .../Icons/LongDashDotLineIcon.tsx | 22 + .../IconRenderer/Icons/LongDashLineIcon.tsx | 19 + .../Icons/ShortDashDotDotLineIcon.tsx | 22 + .../Icons/ShortDashDotLineIcon.tsx | 22 + .../IconRenderer/Icons/ShortDashLineIcon.tsx | 22 + .../IconRenderer/Icons/ShortDotLineIcon.tsx | 22 + .../IconRenderer/Icons/SolidLineIcon.tsx | 15 + .../IconRenderer/Icons/SquarePointIcon.tsx | 15 + .../Icons/TriangleDownPointIcon.tsx | 15 + .../IconRenderer/Icons/TrianglePointIcon.tsx | 15 + .../components/IconRenderer/constants.ts | 2 + .../IconRenderer/renderTooltipIcon.ts | 41 + .../template-icons/DashDotLineIcon.ts | 23 + .../template-icons/DashLineIcon.ts | 28 + .../template-icons/DotLineIcon.ts | 23 + .../template-icons/LongDashDotDotLineIcon.ts | 21 + .../template-icons/LongDashDotLineIcon.ts | 21 + .../template-icons/LongDashLineIcon.ts | 18 + .../template-icons/ShortDashDotDotLineIcon.ts | 21 + .../template-icons/ShortDashDotLineIcon.ts | 21 + .../template-icons/ShortDashLineIcon.ts | 21 + .../template-icons/ShortDotLineIcon.ts | 23 + .../template-icons/SolidLineIcon.ts | 16 + .../IconRenderer/template-icons/index.ts | 11 + .../ChartKit/components/IconRenderer/types.ts | 4 + .../ChartKit/components/IconRenderer/utils.ts | 6 + .../ChartKit/components/Loader/Loader.scss | 26 + .../ChartKit/components/Loader/Loader.tsx | 23 + .../TooltipWithComments.scss | 45 + .../TooltipWithComments.tsx | 77 + .../components/TooltipWithComments/index.ts | 7 + .../ChartKit/components/Widget/Widget.js | 71 + .../Widget/components/AlertWidget/Alert.scss | 95 + .../components/AlertWidget/AlertWidget.tsx | 146 + .../Widget/components/AlertWidget/utils.ts | 415 + .../Widget/components/Markdown/Markdown.scss | 10 + .../Widget/components/Markdown/Markdown.tsx | 57 + .../Widget/components/Table/Bar/Bar.scss | 37 + .../Widget/components/Table/Bar/Bar.tsx | 65 + .../Widget/components/Table/Bar/types.ts | 62 + .../Widget/components/Table/Bar/utils.ts | 277 + .../components/Table/Paginator/Paginator.scss | 47 + .../components/Table/Paginator/Paginator.tsx | 122 + .../Widget/components/Table/Table.scss | 226 + .../Widget/components/Table/Table.tsx | 460 + .../components/Table/WrappedHTMLNode.tsx | 23 + .../Table/__tests__/Paginator.test.tsx | 51 + .../Table/__tests__/action-params.test.ts | 104 + .../Table/__tests__/bar-utils.test.ts | 130 + .../components/Table/__tests__/misc.test.ts | 148 + .../Table/__tests__/valueFormatter.test.tsx | 124 + .../Widget/components/Table/types.ts | 3 + .../components/Table/utils/action-params.ts | 393 + .../components/Table/utils/event-handlers.ts | 94 + .../Widget/components/Table/utils/index.ts | 4 + .../Widget/components/Table/utils/misc.ts | 303 + .../Widget/components/Table/utils/render.tsx | 573 + .../Widget/components/Table/utils/types.ts | 6 + .../components/Table/utils/validation.ts | 104 + .../components/Widget/components/constants.js | 6 + .../components/Widget/components/index.js | 3 + .../ChartKit/components/index.ts | 6 + .../helpers/action-params-handlers.ts | 303 + .../ChartKit/helpers/apply-hc-handlers.ts | 227 + .../ChartKit/helpers/chartkit-adapter.ts | 284 + .../ChartKit/helpers/chartkitAdapter.ts | 1 + .../ChartKit/helpers/constants.ts | 8 + .../ChartKit/helpers/getRandomCKId.ts | 9 + .../helpers/gravity-charts/chart-to-table.ts | 142 + .../gravity-charts/chartkit-adapter.ts | 206 + .../helpers/gravity-charts/comments.ts | 67 + .../helpers/gravity-charts/event-handlers.ts | 195 + .../helpers/gravity-charts/holidays.ts | 53 + .../ChartKit/helpers/gravity-charts/utils.ts | 84 + .../ChartKit/helpers/helpers.test.ts | 25 + .../ChartKit/helpers/tooltip.tsx | 354 + .../ChartKit/helpers/types.ts | 6 + .../ChartKit/helpers/utils.test.ts | 37 + .../ChartKit/helpers/utils.ts | 103 + .../ChartKit/helpers/wait-for-content.ts | 33 + src/ui/libs/DatalensChartkit/ChartKit/init.ts | 94 + .../chartkit-custom-error.ts | 70 + .../ChartKit/modules/comments/drawing.js | 737 + .../ChartKit/modules/graph/config/config.js | 2045 ++ .../graph/config/handleLegendItemClick.js | 89 + .../ChartKit/modules/graph/config/options.js | 483 + .../utils/addShowInNavigatorToSeries.test.ts | 123 + .../utils/addShowInNavigatorToSeries.ts | 62 + .../utils/buildNavigatorFallback.test.ts | 36 + .../config/utils/buildNavigatorFallback.ts | 16 + .../config/utils/calculatePrecision.test.ts | 63 + .../graph/config/utils/calculatePrecision.ts | 31 + .../graph/config/utils/concatStrings.ts | 1 + .../config/utils/getChartKitFormattedValue.ts | 33 + .../utils/getFormatOptionsFromLine.test.ts | 16 + .../config/utils/getFormatOptionsFromLine.ts | 23 + .../utils/getXAxisThresholdValue.test.ts | 26 + .../config/utils/getXAxisThresholdValue.ts | 17 + .../modules/graph/config/utils/index.ts | 11 + .../modules/graph/config/utils/isSafari.ts | 13 + .../config/utils/isTooltipShared.test.ts | 34 + .../graph/config/utils/isTooltipShared.ts | 12 + .../graph/config/utils/localStorage.ts | 23 + .../config/utils/mergeArrayWithObject.ts | 15 + .../graph/config/utils/numberFormat.test.ts | 24 + .../graph/config/utils/numberFormat.ts | 31 + .../utils/setNavigatorDefaultPeriod.test.ts | 59 + .../config/utils/setNavigatorDefaultPeriod.ts | 54 + .../ChartKit/modules/graph/graph.js | 119 + .../ChartKit/modules/graph/graph.scss | 94 + .../ChartKit/modules/graph/prepare-data.js | 192 + .../ChartKit/modules/graph/tooltip/helpers.ts | 6 + .../modules/graph/tooltip/tooltip.scss | 418 + .../ChartKit/modules/graph/tooltip/tooltip.ts | 391 + .../ChartKit/modules/graph/tooltip/types.ts | 106 + .../ChartKit/modules/graph/types/index.ts | 37 + .../ChartKit/modules/highcharts/colors.js | 19 + .../calculateClosestPointManually.test.ts | 52 + .../helpers/calcucalteClosestPointManually.ts | 31 + .../modules/highcharts/helpers/index.ts | 1 + .../ChartKit/modules/highcharts/highcharts.js | 450 + .../ChartKit/modules/i18n/i18n.js | 18 + .../ChartKit/modules/i18n/keysets/en.js | 63 + .../ChartKit/modules/i18n/keysets/ru.js | 65 + .../ChartKit/modules/map/map.js | 278 + .../ChartKit/modules/map/options.js | 30 + .../ChartKit/modules/map/tooltip/tooltip.js | 36 + .../ChartKit/modules/map/tooltip/tooltip.scss | 74 + .../ChartKit/modules/perfomance.ts | 28 + .../yandex-map/helpers/fetch-script.ts | 13 + .../modules/yandex-map/helpers/index.ts | 2 + .../yandex-map/helpers/number-format.ts | 9 + .../yandex-map/modules/components/Canvas.js | 291 + .../modules/components/DataConverter.js | 185 + .../yandex-map/modules/components/Heatmap.js | 224 + .../modules/components/TileUrlsGenerator.js | 199 + .../modules/components/gridmap/Gridmap.js | 193 + .../modules/components/gridmap/index.js | 0 .../components/gridmap/utils/hexagonGrid.js | 71 + .../components/gridmap/utils/squareGrid.js | 53 + .../components/polygonmap/Polygonmap.js | 466 + .../polygonmap/utils/colorLegend.css | 27 + .../polygonmap/utils/colorLegend.js | 100 + .../components/polygonmap/utils/colorize.js | 99 + .../polygonmap/utils/colorize.test.js | 95 + .../polygonmap/utils/defaultBalloonContent.js | 13 + .../polygonmap/utils/defaultFilter.js | 12 + .../polygonmap/utils/defaultFilter.test.js | 27 + .../polygonmap/utils/defaultMapper.js | 35 + .../polygonmap/utils/defaultMapper.test.js | 58 + .../polygonmap/utils/defaultOnClick.js | 79 + .../polygonmap/utils/defaultOnMouseEnter.js | 52 + .../polygonmap/utils/defaultOnMouseLeave.js | 21 + .../components/polygonmap/utils/inside.js | 50 + .../polygonmap/utils/inside.test.js | 63 + .../polygonmap/utils/normalizeFeature.js | 30 + .../polygonmap/utils/normalizeFeature.test.js | 70 + .../modules/yandex-map/modules/modules.js | 13 + .../ChartKit/modules/yandex-map/yandex-map.js | 724 + .../modules/yandex-map/yandex-map.scss | 7 + .../ChartKit/plugins/AdvancedChart/index.ts | 8 + .../renderer/AdvancedChartWidget.scss | 13 + .../renderer/AdvancedChartWidget.tsx | 263 + .../renderer/components/Tooltip/Tooltip.scss | 12 + .../renderer/components/Tooltip/Tooltip.tsx | 49 + .../plugins/AdvancedChart/renderer/types.ts | 5 + .../AdvancedChart/renderer/utils/common.ts | 9 + .../AdvancedChart/renderer/utils/index.ts | 2 + .../renderer/utils/validation.ts | 14 + .../ChartKit/plugins/AdvancedChart/types.ts | 39 + .../ChartKit/plugins/HighchartsMap/index.ts | 8 + .../renderer/HighchartsMapComponent.tsx | 136 + .../renderer/HighchartsMapWidget.tsx | 20 + .../ChartKit/plugins/HighchartsMap/types.ts | 27 + .../ChartKit/plugins/Markup/index.ts | 8 + .../plugins/Markup/renderer/MarkupWidget.scss | 55 + .../plugins/Markup/renderer/MarkupWidget.tsx | 52 + .../ChartKit/plugins/Markup/types.ts | 11 + .../ChartKit/plugins/Metric/index.ts | 8 + .../plugins/Metric/renderer/MetricTile.scss | 147 + .../plugins/Metric/renderer/MetricTile.tsx | 207 + .../plugins/Metric/renderer/MetricWidget.scss | 9 + .../plugins/Metric/renderer/MetricWidget.tsx | 58 + .../plugins/Metric/renderer/metricHelpers.ts | 214 + .../ChartKit/plugins/Metric/types.ts | 60 + .../ChartKit/plugins/Table/index.ts | 8 + .../plugins/Table/renderer/TableWidget.scss | 15 + .../plugins/Table/renderer/TableWidget.tsx | 117 + .../renderer/components/BarCell/BarCell.tsx | 49 + .../renderer/components/DiffCell/DiffCell.tsx | 45 + .../renderer/components/HtmlCell/HtmlCell.tsx | 65 + .../components/MarkupCell/MarkupCell.tsx | 30 + .../renderer/components/SortIcon/SortIcon.tsx | 30 + .../components/Table/BackgroundTable.tsx | 101 + .../renderer/components/Table/Table.scss | 357 + .../Table/renderer/components/Table/Table.tsx | 364 + .../renderer/components/Table/TableBody.tsx | 93 + .../renderer/components/Table/TableFooter.tsx | 53 + .../renderer/components/Table/TableHead.tsx | 72 + .../renderer/components/Table/cell-width.ts | 26 + .../Table/renderer/components/Table/types.ts | 156 + .../components/Table/usePreparedTableData.ts | 555 + .../Table/renderer/components/Table/utils.ts | 358 + .../TableTitleView/TableTitleView.tsx | 24 + .../components/TreeCell/TreeCell.scss | 11 + .../renderer/components/TreeCell/TreeCell.tsx | 43 + .../ChartKit/plugins/Table/renderer/types.ts | 4 + .../Table/renderer/utils/action-params.ts | 92 + .../Table/renderer/utils/drill-down.ts | 21 + .../plugins/Table/renderer/utils/index.ts | 5 + .../plugins/Table/renderer/utils/math.ts | 33 + .../renderer/utils/migrate-to-old-format.ts | 84 + .../plugins/Table/renderer/utils/renderer.tsx | 197 + .../plugins/Table/renderer/utils/tree.ts | 136 + .../ChartKit/plugins/Table/types.ts | 21 + .../ChartKit/plugins/YandexMap/index.ts | 8 + .../renderer/Legend/Icons/EyeSlashIcon.tsx | 12 + .../renderer/Legend/Icons/SizeIcon.tsx | 15 + .../renderer/Legend/Layer/Layer.scss | 126 + .../YandexMap/renderer/Legend/Layer/Layer.tsx | 258 + .../YandexMap/renderer/Legend/Legend.scss | 16 + .../YandexMap/renderer/Legend/Legend.tsx | 52 + .../YandexMap/renderer/YandexMapComponent.js | 606 + .../renderer/YandexMapComponent.scss | 74 + .../YandexMap/renderer/YandexMapWidget.tsx | 17 + .../plugins/YandexMap/renderer/events.ts | 236 + .../plugins/YandexMap/renderer/types.ts | 57 + .../plugins/YandexMap/renderer/utils.ts | 32 + .../ChartKit/plugins/YandexMap/types.ts | 31 + .../ChartKit/plugins/chart-storage.ts | 1 + .../StyledSplitPane/StyledSplitPane.scss | 52 + .../StyledSplitPane/StyledSplitPane.tsx | 56 + .../ChartKit/plugins/components/index.ts | 3 + .../withSplitPane/WithSplitPane.scss | 4 + .../withSplitPane/withSplitPane.tsx | 335 + .../ChartKit/plugins/index.ts | 32 + .../libs/DatalensChartkit/ChartKit/theme.scss | 82 + .../libs/DatalensChartkit/ChartKit/types.ts | 74 + .../DatalensChartkit/DatalensChartKitOld.js | 87 + src/ui/libs/DatalensChartkit/Error/Error.scss | 78 + src/ui/libs/DatalensChartkit/Error/Error.tsx | 332 + .../getAdditionalChartkitErrorContent.ts | 5 + .../components/ChartKitBase/ChartKitBase.scss | 38 + .../components/ChartKitBase/ChartKitBase.tsx | 818 + .../ChartKitBase/components/Chart/Chart.tsx | 82 + .../ChartkitError/ChartkitError.tsx | 42 + .../components/Content/store/reducer.ts | 58 + .../components/Content/store/types.ts | 61 + .../components/Control/Control.tsx | 46 + .../components/Header/Header.scss | 11 + .../ChartKitBase/components/Header/Header.tsx | 78 + .../ChartsInsights/ChartsInsights.tsx | 119 + .../ChartsInsightsIcon.scss | 15 + .../ChartsInsightsIcon/ChartsInsightsIcon.tsx | 34 + .../ChartsInsightsIcon/getIconData.ts | 12 + .../TooltipContent/TooltipContent.scss | 26 + .../TooltipContent/TooltipContent.tsx | 52 + .../helpers/getIconLevel.test.ts | 160 + .../ChartsInsights/helpers/getIconLevel.ts | 34 + .../Menu/Items/ButtonSpin/ButtonSpin.js | 56 + .../Menu/Items/ButtonSpin/ButtonSpin.scss | 17 + .../Items/ButtonSpinInput/ButtonSpinInput.js | 96 + .../ButtonSpinInput/ButtonSpinInput.scss | 13 + .../Menu/Items/ChartsModal/ChartsModal.js | 108 + .../Menu/Items/ChartsModal/ChartsModal.scss | 33 + .../Items/ChartsModalMenu/ChartsModalMenu.js | 52 + .../ChartsModalMenu/ChartsModalMenu.scss | 46 + .../Menu/Items/DownloadCsv/DownloadCsv.scss | 31 + .../Menu/Items/DownloadCsv/DownloadCsv.tsx | 157 + .../Menu/Items/Export/CsvExport/CsvExport.tsx | 34 + .../components/Menu/Items/Export/Export.tsx | 211 + .../Export/ToastContent/ToastContent.scss | 49 + .../Export/ToastContent/ToastContent.tsx | 78 + .../components/Menu/Items/Export/types.ts | 44 + .../components/Menu/Items/Export/utils.tsx | 194 + .../Menu/Items/Inspector/Inspector.scss | 26 + .../Menu/Items/Inspector/Inspector.tsx | 157 + .../Items/Inspector/SourceMeta/Datalens.tsx | 53 + .../Items/Inspector/Sources/Source/Source.tsx | 133 + .../Sources/SourceError/SourceError.tsx | 82 + .../Sources/SourceSuccess/SourceSuccess.tsx | 126 + .../Menu/Items/Inspector/Sources/Sources.scss | 65 + .../Menu/Items/Inspector/Sources/Sources.tsx | 82 + .../Menu/Items/Inspector/Timings/Timings.scss | 29 + .../Menu/Items/Inspector/Timings/Timings.tsx | 305 + .../components/Menu/Items/Inspector/types.tsx | 6 + .../components/Menu/Items/Modal/Body/Body.js | 19 + .../Menu/Items/Modal/Body/Body.scss | 3 + .../Menu/Items/Modal/Context/Context.js | 15 + .../Menu/Items/Modal/Footer/Footer.js | 51 + .../Menu/Items/Modal/Footer/Footer.scss | 7 + .../Menu/Items/Modal/Header/Header.js | 33 + .../Menu/Items/Modal/Header/Header.scss | 7 + .../components/Menu/Items/Modal/Modal.js | 38 + .../components/Menu/Items/Modal/Modal.scss | 3 + .../Menu/Items/PopupMessage/PopupMessage.js | 68 + .../Menu/Items/PopupMessage/PopupMessage.scss | 73 + .../Header/components/Menu/Items/constants.ts | 8 + .../Header/components/Menu/Items/helpers.ts | 54 + .../components/Header/components/Menu/Menu.js | 332 + .../Header/components/Menu/Menu.scss | 45 + .../MenuWithErrorBoundary.ts | 4 + .../components/Header/components/index.ts | 2 + .../components/Loader/Loader.scss | 26 + .../ChartKitBase/components/Loader/Loader.tsx | 49 + .../components/ChartKitBase/helpers.ts | 56 + .../components/ChartKitBase/types.ts | 142 + .../components/ChartKitIcon/ChartKitIcon.js | 67 + .../components/Control/Control.scss | 101 + .../components/Control/Control.tsx | 426 + .../components/Control/Items/Items.js | 581 + .../components/Control/Items/Items.scss | 103 + .../Control/Items/withWrapForControls.js | 87 + .../components/Control/types.ts | 47 + .../DeferredInitializer.scss | 4 + .../DeferredInitializer.tsx | 145 + .../components/Drill/Drill.scss | 46 + .../components/Drill/Drill.tsx | 158 + .../components/Error/Error.scss | 44 + .../components/Error/Error.tsx | 70 + .../components/Error/More/More.js | 102 + .../components/Error/More/More.scss | 87 + .../components/ErrorBoundary/ErrorBoundary.js | 32 + .../ErrorBoundary/withErrorBoundary.js | 17 + .../components/Palette/Attach/Attach.tsx | 35 + .../components/Palette/ColorItem/ColorItem.js | 50 + .../Palette/ColorItem/ColorItem.scss | 22 + .../DraggableModalHOC/DraggableModalHOC.js | 54 + .../DraggableModalHOC/DraggableModalHOC.scss | 29 + .../Palette/PaletteEditor/PaletteEditor.js | 125 + .../PaletteSettings/PaletteSettings.js | 64 + .../PaletteSettings/PaletteSettings.scss | 20 + .../components/SideMarkdown/SideMarkdown.scss | 24 + .../components/SideMarkdown/SideMarkdown.tsx | 50 + .../libs/DatalensChartkit/extensions/alert.js | 7 + .../DatalensChartkit/extensions/control.js | 6 + .../DatalensChartkit/extensions/markdown.js | 7 + .../libs/DatalensChartkit/extensions/table.js | 7 + .../helpers/convert-data-to-table.ts | 96 + .../libs/DatalensChartkit/helpers/helpers.ts | 74 + src/ui/libs/DatalensChartkit/index.ts | 17 + src/ui/libs/DatalensChartkit/menu/Menu.tsx | 91 + .../libs/DatalensChartkit/menu/MenuItems.tsx | 307 + .../DatalensChartkit/menu/chart-modeling.tsx | 116 + .../libs/DatalensChartkit/menu/constants.ts | 9 + src/ui/libs/DatalensChartkit/menu/helpers.ts | 132 + .../DatalensChartkit/modules/axios/axios.ts | 73 + .../modules/axios/axiosConcurrency.ts | 98 + .../modules/constants/constants.ts | 37 + .../charts/__tests__/ui-sandbox.test.ts | 14 + .../charts/__tests__/utils.test.ts | 67 + .../data-provider/charts/chartkit-handlers.ts | 181 + .../charts/get-graph/get-graph.js | 12 + .../charts/get-graph/prepare-config.js | 237 + .../charts/get-graph/prepare-data.js | 194 + .../helpers/getChartsInsightsData.test.ts | 281 + .../charts/helpers/getChartsInsightsData.ts | 101 + .../data-provider/charts/helpers/index.ts | 1 + .../modules/data-provider/charts/index.ts | 1081 + .../modules/data-provider/charts/node.ts | 492 + .../modules/data-provider/charts/types.ts | 593 + .../charts/ui-sandbox-runtime.ts | 261 + .../data-provider/charts/ui-sandbox.ts | 544 + .../modules/data-provider/charts/utils.ts | 167 + .../modules/data-provider/charts/wizard.js | 428 + .../modules/data-provider/example.js | 39 + .../modules/data-provider/helpers.ts | 15 + .../data-provider/withDataProvider.tsx | 57 + .../datalens-chartkit-custom-error.ts | 233 + .../DatalensChartkit/modules/export/export.js | 505 + .../extensions-manager/extensions-manager.jsx | 50 + .../html-generator/__tests__/index.test.ts | 112 + .../modules/html-generator/constants.ts | 196 + .../modules/html-generator/index.ts | 134 + .../modules/html-generator/utils.ts | 175 + .../DatalensChartkit/modules/localStorage.ts | 23 + .../DatalensChartkit/modules/menu/menu.ts | 81 + .../DatalensChartkit/modules/navigation.ts | 3 + .../DatalensChartkit/modules/perfomance.ts | 28 + .../modules/settings/settings.js | 103 + src/ui/libs/DatalensChartkit/modules/table.ts | 19 + .../libs/DatalensChartkit/modules/uri/uri.js | 128 + .../libs/DatalensChartkit/styles/theme.scss | 7 + src/ui/libs/DatalensChartkit/types/common.ts | 7 + src/ui/libs/DatalensChartkit/types/control.ts | 92 + .../DatalensChartkit/types/dataProvider.ts | 72 + src/ui/libs/DatalensChartkit/types/index.ts | 4 + src/ui/libs/DatalensChartkit/types/menu.ts | 51 + src/ui/libs/DatalensChartkit/types/widget.ts | 414 + src/ui/libs/auth/refreshToken.ts | 33 + src/ui/libs/axios/axios.ts | 55 + src/ui/libs/axios/interceptors.ts | 16 + src/ui/libs/datasetHelper/index.ts | 53 + src/ui/libs/datasetSdk/index.js | 152 + src/ui/libs/google-analytics/index.ts | 9 + src/ui/libs/index.ts | 9 + src/ui/libs/logger/index.ts | 20 + src/ui/libs/metrica/index.ts | 47 + src/ui/libs/monaco/constants.ts | 2 + src/ui/libs/monaco/dl-lang/constants.ts | 50 + src/ui/libs/monaco/dl-lang/index.ts | 234 + src/ui/libs/monaco/index.ts | 6 + src/ui/libs/monaco/load-monaco-core.ts | 1 + src/ui/libs/monaco/ql-lang/constants.ts | 484 + src/ui/libs/monaco/ql-lang/index.ts | 188 + src/ui/libs/monaco/theme-mappers.ts | 79 + src/ui/libs/monaco/types.ts | 16 + src/ui/libs/monaco/utils.ts | 69 + src/ui/libs/navigateHelper/index.js | 76 + src/ui/libs/schematic-sdk/decorator.ts | 72 + src/ui/libs/schematic-sdk/index.ts | 112 + src/ui/libs/schematic-sdk/parse-error.ts | 116 + src/ui/libs/sdk/index.ts | 204 + src/ui/libs/sdk/types.ts | 27 + src/ui/libs/userSettings/index.ts | 27 + src/ui/modules/index.ts | 1 + src/ui/modules/markdownProvider.ts | 76 + src/ui/registry/components/DefaultEmpty.tsx | 7 + src/ui/registry/functions/example-function.ts | 3 + src/ui/registry/index.ts | 38 + src/ui/registry/libs/index.ts | 5 + src/ui/registry/libs/registryLibsPlugins.ts | 6 + src/ui/registry/libs/schematic-sdk/index.ts | 20 + src/ui/registry/units/auth/components-map.ts | 11 + src/ui/registry/units/auth/functions-map.ts | 13 + src/ui/registry/units/auth/index.ts | 16 + src/ui/registry/units/auth/register.tsx | 14 + .../components/AdditionalProfileSections.ts | 5 + .../components/AdditionalUsersFilters.ts | 4 + .../units/auth/types/components/Signin.ts | 1 + .../getAdditionalAddProfileFields.ts | 15 + .../functions/getAdditionalProfileFields.ts | 7 + .../types/functions/getUsersListColumns.ts | 4 + .../auth/types/functions/getUsersRoles.ts | 3 + .../registry/units/chart/components-map.tsx | 9 + src/ui/registry/units/chart/functions-map.ts | 46 + src/ui/registry/units/chart/index.ts | 14 + src/ui/registry/units/chart/register.tsx | 24 + .../chart/types/components/AlertDialog.ts | 8 + .../components/ChartActionPanelButton.ts | 13 + .../types/functions/get-chartkit-holidays.ts | 6 + .../types/functions/getChartkitMenuByType.ts | 24 + .../types/functions/getDefaultChartMenu.ts | 11 + .../functions/getPanePreviewChartMenu.ts | 5 + .../types/functions/getWizardChartMenu.ts | 10 + .../units/collections/components-map.tsx | 5 + .../units/collections/functionts-map.ts | 29 + src/ui/registry/units/collections/index.ts | 13 + src/ui/registry/units/collections/register.ts | 19 + .../types/customizeEmptyPlaceholder.ts | 19 + .../customizeNoCreatePermissionDialog.ts | 5 + .../registry/units/collections/types/index.ts | 3 + .../types/useCreateWorkbookDialogHandlers.ts | 21 + .../registry/units/common/components-map.tsx | 71 + .../common/components/Example/Example.tsx | 19 + .../units/common/constants/components.ts | 1 + .../units/common/constants/functions.ts | 1 + src/ui/registry/units/common/functions-map.ts | 195 + src/ui/registry/units/common/index.tsx | 14 + src/ui/registry/units/common/register.tsx | 117 + .../types/components/AIChatSupportForm.ts | 9 + .../common/types/components/AccessRights.ts | 10 + .../types/components/AccessRightsUrlOpen.ts | 5 + .../common/types/components/AclSubject.ts | 5 + .../types/components/AclSubjectSuggest.ts | 24 + .../components/ActionPanelEntrySelect.ts | 3 + .../types/components/AsideHeaderAdapter.ts | 5 + .../common/types/components/ButtonFavorite.ts | 5 + .../types/components/DialogAddParticipants.ts | 12 + .../components/DialogEntryDescription.ts | 13 + .../components/DialogImageWidgetLinkHint.ts | 5 + .../DialogRelatedEntitiesRadioHint.ts | 6 + .../common/types/components/DialogShare.ts | 21 + .../types/components/DownloadScreenshot.tsx | 6 + .../types/components/EntryBreadcrumbs.ts | 20 + .../types/components/IamAccessDialog.ts | 18 + .../common/types/components/LoginById.ts | 5 + .../types/components/MarkdownControl.ts | 7 + .../types/components/MarkupShareLink.ts | 5 + .../common/types/components/MarkupUserInfo.ts | 7 + .../types/components/MobileHeaderComponent.ts | 8 + .../types/components/OAuthTokenButton.ts | 7 + .../PlaceholderIllustrationImage.ts | 12 + .../types/components/RangeInputPicker.ts | 1 + .../common/types/components/ReportButton.ts | 12 + .../common/types/components/UserAvatarById.ts | 5 + .../common/types/components/YfmWrapper.ts | 11 + .../common/types/functions/GetUsersById.ts | 6 + .../types/functions/fetchDocumentation.ts | 11 + .../functions/fetchFunctionsDocumentation.ts | 5 + .../getAdditionalChartkitErrorContent.ts | 8 + .../functions/getBasicActionPanelItems.ts | 7 + .../functions/getFunctionsDocumentation.ts | 38 + .../functions/getGloballyEntrySettings.ts | 4 + ...eaderWithoutHelpCenterErrorContentTypes.ts | 1 + ...eaderWithoutNavigationErrorContentTypes.ts | 1 + .../types/functions/getIllustrationStore.ts | 3 + .../getImageNameFromErrorContentType.ts | 5 + .../common/types/functions/getLoginById.ts | 3 + .../common/types/functions/getLogoIcon.ts | 5 + .../getNotAuthenticatedErrorContentTypes.ts | 1 + .../functions/getShouldShowAIAssistant.ts | 5 + .../common/types/functions/getUIEntryRoute.ts | 9 + .../common/types/functions/isValidLogoUrl.ts | 6 + .../functions/openDialogOrganizationInvite.ts | 3 + .../openDialogOrganizationInviteUsers.ts | 3 + .../renderDialogRelatedEntitiesAlertHint.ts | 11 + .../types/functions/resolveUsersByIds.ts | 4 + .../common/types/functions/setEntryKey.ts | 13 + .../types/functions/useSubjectsListId.ts | 1 + .../units/connections/components-map.tsx | 9 + .../units/connections/functions-map.ts | 20 + src/ui/registry/units/connections/index.ts | 14 + .../registry/units/connections/register.tsx | 18 + .../types/CreateEditorChartButtonProps.ts | 6 + .../connections/types/PreparedRowItem.ts | 3 + .../types/beforeConnectorFormUnmount.ts | 3 + .../units/connections/types/getFakeEntry.ts | 4 + .../units/connections/types/getMockedForm.ts | 4 + .../types/getNewConnectionDestination.ts | 8 + .../types/getRenderConnectionSettingsPopup.ts | 5 + src/ui/registry/units/dash/components-map.tsx | 16 + src/ui/registry/units/dash/functions-map.ts | 34 + src/ui/registry/units/dash/index.ts | 15 + src/ui/registry/units/dash/register.tsx | 28 + .../units/dash/types/BeforeCloseDialogItem.ts | 8 + .../units/dash/types/BeforeOpenDialogItem.ts | 8 + .../types/DashActionPanelAdditionalButtons.ts | 6 + .../types/DialogDashOtherSettingsPrepend.ts | 8 + .../units/dash/types/GetExtendedItemData.ts | 8 + .../units/datasets/components-map.tsx | 7 + .../registry/units/datasets/functions-map.ts | 9 + src/ui/registry/units/datasets/index.ts | 15 + src/ui/registry/units/datasets/register.tsx | 9 + .../datasets/types/CreateDashboardButton.ts | 3 + .../getRenderDatasetSettingsPopup.ts | 5 + .../types/functions/renderRLSDialog.ts | 7 + src/ui/registry/units/docs/functions-map.ts | 5 + src/ui/registry/units/docs/index.ts | 13 + src/ui/registry/units/docs/register.tsx | 6 + .../registry/units/editor/components-map.tsx | 8 + .../units/editor/constants/components.ts | 1 + .../units/editor/constants/functions.ts | 1 + src/ui/registry/units/editor/functions-map.ts | 14 + src/ui/registry/units/editor/index.ts | 15 + .../types/components/ActionPanelButton.ts | 9 + .../functions/fetchEditorDocumentation.ts | 5 + .../types/functions/getEditorTemplates.ts | 11 + .../units/fieldEditor/components-map.tsx | 21 + src/ui/registry/units/fieldEditor/index.ts | 9 + src/ui/registry/units/main/components-map.tsx | 5 + src/ui/registry/units/main/index.ts | 13 + src/ui/registry/units/main/register.tsx | 5 + .../registry/units/preview/components-map.tsx | 5 + src/ui/registry/units/preview/index.ts | 14 + src/ui/registry/units/preview/register.tsx | 1 + .../registry/units/public/components-map.tsx | 7 + src/ui/registry/units/public/index.ts | 14 + src/ui/registry/units/public/register.tsx | 1 + .../units/public/types/components/Header.ts | 4 + src/ui/registry/units/ql/components-map.tsx | 6 + src/ui/registry/units/ql/functions-map.ts | 8 + src/ui/registry/units/ql/index.ts | 14 + src/ui/registry/units/ql/register.tsx | 12 + .../registry/units/wizard/components-map.tsx | 7 + src/ui/registry/units/wizard/index.ts | 13 + src/ui/registry/units/wizard/register.tsx | 1 + .../components/WizardActionPanelExtension.ts | 3 + .../units/workbooks/components-map.tsx | 8 + .../registry/units/workbooks/functions-map.ts | 50 + src/ui/registry/units/workbooks/index.ts | 15 + src/ui/registry/units/workbooks/register.tsx | 26 + .../workbookTableRowExtendedContent.ts | 8 + .../checkWbCreateEntryButtonVisibility.ts | 7 + .../types/functions/getWorkbookEntryUrl.ts | 8 + src/ui/registry/utils/components-registry.tsx | 69 + .../registry/utils/register-app-plugins.tsx | 32 + src/ui/store/actions/asideHeader/index.ts | 126 + .../store/actions/asideHeader/navigation.ts | 32 + src/ui/store/actions/chartWidget.ts | 151 + .../actions/collectionsStructure/common.ts | 1412 + .../actions/collectionsStructure/export.ts | 469 + .../actions/collectionsStructure/index.ts | 77 + .../showCollectionEntityErrorToast.ts | 31 + .../actions/collectionsStructure/templates.ts | 81 + src/ui/store/actions/colorPaletteEditor.ts | 159 + .../actions/controlDialog/controlDialog.ts | 656 + src/ui/store/actions/controlDialog/helpers.ts | 44 + src/ui/store/actions/copyEntriesToWorkbook.ts | 204 + src/ui/store/actions/dialog.ts | 321 + src/ui/store/actions/editHistory.ts | 265 + src/ui/store/actions/entryContent.ts | 316 + src/ui/store/actions/iamAccessDialog.ts | 481 + src/ui/store/actions/landing.ts | 15 + src/ui/store/actions/localStorageFallback.ts | 18 + src/ui/store/actions/migrationToWorkbook.ts | 299 + src/ui/store/actions/openDialogTypes.ts | 136 + src/ui/store/actions/toaster.ts | 62 + src/ui/store/actions/user.ts | 53 + src/ui/store/actions/usersByIds.ts | 6 + src/ui/store/configure.ts | 46 + .../store/constants/collectionsStructure.ts | 167 + src/ui/store/constants/controlDialog.ts | 71 + .../store/constants/copyEntriesToWorkbook.ts | 19 + src/ui/store/constants/editHistory.ts | 6 + src/ui/store/constants/iamAccessDialog.ts | 43 + src/ui/store/constants/migrationToWorkbook.ts | 27 + src/ui/store/index.ts | 19 + src/ui/store/reducer-registry.ts | 70 + src/ui/store/reducers/asideHeader.ts | 130 + src/ui/store/reducers/collectionsStructure.ts | 1317 + src/ui/store/reducers/colorPaletteEditor.ts | 36 + .../reducers/controlDialog/controlDialog.ts | 694 + .../store/reducers/controlDialog/helpers.ts | 68 + .../store/reducers/copyEntriesToWorkbook.ts | 165 + src/ui/store/reducers/dialog.ts | 84 + src/ui/store/reducers/editHistory.ts | 179 + src/ui/store/reducers/entryContent.ts | 123 + src/ui/store/reducers/iamAccessDialog.ts | 327 + src/ui/store/reducers/index.tsx | 31 + src/ui/store/reducers/landing.ts | 26 + src/ui/store/reducers/localStorageFallback.ts | 22 + src/ui/store/reducers/migrationToWorkbook.ts | 208 + src/ui/store/reducers/user.ts | 42 + src/ui/store/selectors/asideHeader.ts | 22 + .../store/selectors/collectionsStructure.ts | 278 + src/ui/store/selectors/colorPaletteEditor.ts | 16 + src/ui/store/selectors/controlDialog.ts | 239 + .../store/selectors/copyEntriesToWorkbook.ts | 42 + src/ui/store/selectors/editHistory.ts | 25 + src/ui/store/selectors/entryContent.ts | 50 + src/ui/store/selectors/iamAccessDialog.ts | 66 + src/ui/store/selectors/landing.ts | 3 + .../store/selectors/localStorageFallback.ts | 6 + src/ui/store/selectors/migrationToWorkbook.ts | 60 + src/ui/store/selectors/user.ts | 50 + .../store/toolkit/chart-modeling/actions.ts | 3 + .../store/toolkit/chart-modeling/reducer.ts | 58 + .../store/toolkit/chart-modeling/selectors.ts | 12 + src/ui/store/toolkit/chart-modeling/types.ts | 12 + src/ui/store/toolkit/chartkit/api.ts | 22 + src/ui/store/toolkit/chartkit/types.ts | 4 + src/ui/store/toolkit/index.ts | 1 + src/ui/store/typings/asideHeader.ts | 27 + src/ui/store/typings/controlDialog.ts | 150 + src/ui/store/typings/entryContent.ts | 22 + src/ui/store/typings/iamAccessDialog.ts | 7 + src/ui/store/typings/user.ts | 13 + src/ui/store/utils/asideHeader.ts | 17 + src/ui/store/utils/collectionStructure.ts | 56 + src/ui/store/utils/controlDialog.ts | 404 + src/ui/store/utils/jdp.ts | 49 + src/ui/store/utils/user.ts | 24 + src/ui/styles/base.scss | 61 + src/ui/styles/dash.scss | 46 + src/ui/styles/dl-monaco.scss | 22 + src/ui/styles/min-width.scss | 9 + src/ui/styles/mixins.scss | 191 + src/ui/styles/overrides.scss | 3 + src/ui/styles/preview.scss | 5 + src/ui/styles/split-pane-resizer.scss | 41 + src/ui/styles/theme.scss | 104 + src/ui/styles/variables.scss | 131 + src/ui/tsconfig.json | 27 + src/ui/typings/common.ts | 42 + src/ui/typings/components.ts | 8 + src/ui/typings/error.ts | 35 + src/ui/typings/index.ts | 3 + .../auth/components/AuthPage/AuthPage.scss | 30 + .../auth/components/AuthPage/AuthPage.tsx | 78 + .../components/AuthPage/useAuthPageInit.ts | 50 + .../ChangePasswordDialog.scss | 6 + .../ChangePasswordDialog.tsx | 234 + .../ChangeUserRoleDialog.tsx | 148 + .../DeleteUserDialog/DeleteUserDialog.tsx | 54 + .../EditUserProfileDialog.tsx | 159 + .../auth/components/LoginById/LoginById.scss | 23 + .../auth/components/LoginById/LoginById.tsx | 29 + .../units/auth/components/LoginById/utils.ts | 3 + .../units/auth/components/Logout/Logout.tsx | 59 + .../units/auth/components/Reload/Reload.tsx | 63 + .../units/auth/components/Signin/Signin.scss | 28 + .../units/auth/components/Signin/Signin.tsx | 98 + .../components/Signin/components/Login.tsx | 32 + .../components/Signin/components/Password.tsx | 32 + .../units/auth/components/Signup/Signup.scss | 13 + .../units/auth/components/Signup/Signup.tsx | 88 + .../components/Signup/components/Back.tsx | 31 + .../components/UserProfile/UserProfile.tsx | 186 + .../UserRoleLabel/UserRoleLabel.tsx | 28 + .../components/formControls/CustomRow.tsx | 21 + .../auth/components/formControls/Email.tsx | 39 + .../components/formControls/FirstName.tsx | 39 + .../formControls/GeneratePasswordButton.tsx | 27 + .../auth/components/formControls/LastName.tsx | 39 + .../auth/components/formControls/Login.tsx | 39 + .../auth/components/formControls/Password.tsx | 50 + .../formControls/RepeatPassword.tsx | 43 + .../auth/components/formControls/Roles.tsx | 50 + .../components/formControls/RowButton.tsx | 17 + .../components/formControls/formControls.scss | 6 + .../test/generatePassword.test.ts | 9 + .../auth/components/formControls/types.ts | 14 + .../auth/components/formControls/utils.ts | 44 + src/ui/units/auth/constants/errors.ts | 3 + src/ui/units/auth/constants/routes.ts | 8 + .../containers/UserProfile/UserProfile.tsx | 64 + .../units/auth/hooks/useClearReloadedQuery.ts | 28 + src/ui/units/auth/hooks/useUserById.ts | 73 + src/ui/units/auth/store/actions/common.ts | 21 + src/ui/units/auth/store/actions/index.ts | 9 + src/ui/units/auth/store/actions/logout.ts | 24 + src/ui/units/auth/store/actions/signin.ts | 63 + .../units/auth/store/actions/userInfoForm.ts | 156 + .../units/auth/store/actions/userProfile.ts | 366 + src/ui/units/auth/store/actions/usersByIds.ts | 130 + src/ui/units/auth/store/constants/common.ts | 3 + src/ui/units/auth/store/constants/signin.ts | 1 + .../auth/store/constants/userInfoForm.ts | 6 + .../units/auth/store/constants/userProfile.ts | 24 + .../units/auth/store/constants/usersByIds.ts | 2 + src/ui/units/auth/store/reducers/common.ts | 26 + src/ui/units/auth/store/reducers/index.ts | 27 + src/ui/units/auth/store/reducers/signin.ts | 26 + .../units/auth/store/reducers/userInfoForm.ts | 80 + .../units/auth/store/reducers/userProfile.ts | 252 + .../units/auth/store/reducers/usersByIds.ts | 42 + src/ui/units/auth/store/selectors/common.ts | 6 + src/ui/units/auth/store/selectors/signin.ts | 6 + .../auth/store/selectors/userInfoForm.ts | 29 + .../units/auth/store/selectors/userProfile.ts | 22 + .../units/auth/store/selectors/usersByIds.ts | 41 + .../units/auth/store/typings/userInfoForm.ts | 16 + src/ui/units/auth/store/typings/usersByIds.ts | 48 + src/ui/units/auth/utils/fields.ts | 10 + src/ui/units/auth/utils/getUsersRoles.ts | 5 + src/ui/units/auth/utils/userProfile.ts | 12 + src/ui/units/auth/utils/validation.ts | 49 + .../CollectionBreadcrumbs.scss | 17 + .../CollectionBreadcrumbs.tsx | 127 + .../components/CollectionBreadcrumbs/index.ts | 2 + .../components/CollectionBreadcrumbs/utils.ts | 17 + .../CollectionsNavigationApp.tsx | 97 + .../CollectionsNavigationApp/index.ts | 1 + .../CollectionsNavigationLayout.scss | 118 + .../CollectionsNavigationLayout.tsx | 190 + .../CollectionsNavigationLayout/index.ts | 1 + .../collections-navigation/constants/index.ts | 2 + .../contexts/LayoutContext.ts | 40 + .../store/actions/index.tsx | 103 + .../store/constants/index.ts | 13 + .../store/reducers/index.ts | 84 + .../store/selectors/index.tsx | 22 + .../collections-navigation/utils/index.ts | 11 + .../CollectionActions/CollectionActions.scss | 40 + .../CollectionActions/CollectionActions.tsx | 188 + .../SharedEntryNotice/SharedEntryNotice.scss | 16 + .../SharedEntryNotice/SharedEntryNotice.tsx | 19 + .../components/CollectionActions/index.ts | 1 + .../components/CollectionActions/utils.tsx | 53 + .../CollectionBatchPanel.tsx | 80 + .../CollectionContent/CollectionContent.scss | 33 + .../CollectionContent/CollectionContent.tsx | 291 + .../CollectionContent/hooks/index.ts | 1 + .../CollectionContent/hooks/useActions.tsx | 500 + .../components/CollectionContent/index.ts | 1 + .../components/CollectionContent/utils.ts | 32 + .../CollectionContentGrid.scss | 84 + .../CollectionContentGrid.tsx | 155 + .../CollectionItemIcon.tsx | 39 + .../components/CollectionContentGrid/index.ts | 1 + .../CollectionContentTable.scss | 152 + .../CollectionContentTable.tsx | 215 + .../CollectionCheckboxCell.tsx | 69 + .../TableComponents/CollectionLinkRow.tsx | 122 + .../TableComponents/CollectionTitleCell.tsx | 96 + .../CollectionContentTable/helpers.ts | 15 + .../CollectionContentTable/index.ts | 1 + .../CollectionPage/CollectionPage.scss | 48 + .../CollectionPage/CollectionPage.tsx | 335 + .../components/CollectionPage/hooks/index.ts | 5 + .../hooks/useCreateWorkbookDialogHandlers.ts | 81 + .../CollectionPage/hooks/useData.ts | 148 + .../CollectionPage/hooks/useFilters.ts | 39 + .../CollectionPage/hooks/useLayout.tsx | 493 + .../hooks/useOpenCreateWorkbookDialog.ts | 61 + .../CollectionPage/hooks/useSelection.ts | 190 + .../CollectionPage/hooks/useViewMode.ts | 49 + .../components/CollectionPage/index.ts | 1 + .../components/CollectionPage/utils.ts | 18 + .../units/collections/components/constants.ts | 27 + .../units/collections/components/helpers.ts | 51 + src/ui/units/collections/components/types.ts | 8 + .../hooks/useRefreshPageAfterImport.ts | 37 + .../units/collections/store/actions/index.tsx | 328 + .../collections/store/constants/index.ts | 28 + .../units/collections/store/reducers/index.ts | 246 + .../collections/store/selectors/index.tsx | 53 + .../connections/assets/icons/file-item.svg | 1 + .../assets/icons/gauth-btn-dark-en.svg | 1 + .../assets/icons/gauth-btn-dark-ru.svg | 1 + .../assets/icons/gauth-btn-light-en.svg | 1 + .../assets/icons/gauth-btn-light-ru.svg | 1 + .../ConnectorForm/ConnectorForm.scss | 18 + .../ConnectorForm/ConnectorForm.tsx | 73 + .../ErrorActions/ErrorActions.scss | 5 + .../ErrorActions/ErrorActions.tsx | 39 + .../CheckParamsButton/CheckParamsButton.scss | 24 + .../CheckParamsButton/CheckParamsButton.tsx | 61 + .../ConnectorForm/FormActions/FormActions.tsx | 218 + .../FormActions/SubmitButton/SubmitButton.tsx | 24 + .../ConnectorForm/FormActions/selectors.ts | 50 + .../ConnectorForm/FormActions/utils.ts | 7 + .../ConnectorForm/FormRow/FormItem.tsx | 95 + .../ConnectorForm/FormRow/FormRow.scss | 8 + .../ConnectorForm/FormRow/FormRow.tsx | 88 + .../ConnectorForm/FormRow/PreparedRowItem.tsx | 37 + .../components/Checkbox/Checkbox.tsx | 63 + .../components/Datepicker/Datepicker.scss | 3 + .../components/Datepicker/Datepicker.tsx | 80 + .../components/Description/Description.scss | 6 + .../components/Description/Description.tsx | 21 + .../components/FileInput/FileInput.tsx | 119 + .../components/FileInput/utils.test.ts | 12 + .../components/FileInput/utils.ts | 24 + .../ConnectorForm/components/Input/Input.tsx | 106 + .../components/KeyValue/KeyValue.scss | 35 + .../components/KeyValue/KeyValue.tsx | 117 + .../components/KeyValue/hooks.ts | 138 + .../components/KeyValue/types.ts | 10 + .../ConnectorForm/components/Label/Label.scss | 17 + .../ConnectorForm/components/Label/Label.tsx | 71 + .../components/MarkdownItem/MarkdownItem.tsx | 70 + .../components/PlainText/PlainText.scss | 10 + .../components/PlainText/PlainText.tsx | 25 + .../components/RadioButton/RadioButton.scss | 17 + .../components/RadioButton/RadioButton.tsx | 83 + .../components/RadioGroup/RadioGroup.scss | 32 + .../components/RadioGroup/RadioGroup.tsx | 118 + .../components/Select/Select.scss | 14 + .../components/Select/Select.tsx | 141 + .../ConnectorForm/components/index.ts | 13 + .../withControlWrap/withControlWrap.scss | 30 + .../withControlWrap/withControlWrap.tsx | 59 + .../preparedRows/CacheTtlRow/CacheTtlRow.tsx | 109 + .../preparedRows/CollapseRow/CollapseRow.scss | 7 + .../preparedRows/CollapseRow/CollapseRow.tsx | 72 + .../EarlyInvalidationCacheRow.scss | 14 + .../EarlyInvalidationCacheRow.tsx | 133 + .../RawSQLLevelRow/RawSQLLevelRow.scss | 8 + .../RawSQLLevelRow/RawSQLLevelRow.tsx | 49 + .../ConnectorForm/preparedRows/index.ts | 4 + .../__tests__/getCollapsesToExpand.test.ts | 107 + .../components/ConnectorForm/utils/index.ts | 92 + .../ConnectorsList/ConnectorsList.scss | 63 + .../ConnectorsList/ConnectorsList.tsx | 162 + .../components/ConnectorsList/ListItem.tsx | 39 + .../components/ConnectorsList/utils/index.ts | 46 + .../components/ErrorView/ErrorView.scss | 12 + .../components/ErrorView/ErrorView.tsx | 130 + .../components/FormTitle/FormTitle.scss | 18 + .../components/FormTitle/FormTitle.tsx | 60 + .../components/Page/ConnPanelActions.scss | 9 + .../components/Page/ConnPanelActions.tsx | 54 + .../connections/components/Page/Page.scss | 15 + .../connections/components/Page/Page.tsx | 389 + .../Page/components/ConnSettings.tsx | 81 + .../Page/components/CreateDatasetButton.tsx | 89 + .../Page/components/CreateQlChartButton.tsx | 64 + .../Page/components/DescriptionButton.tsx | 87 + .../Page/components/S3BasedConnButton.tsx | 138 + .../Page/components/UnloadConfirmation.tsx | 37 + .../components/Page/components/index.ts | 5 + .../connections/components/Page/constants.ts | 14 + .../components/Page/filterContextMenuItems.ts | 31 + .../Page/useAdditionalContextMenuItems.tsx | 207 + .../components/Page/useApiErrors.ts | 40 + .../connections/components/Page/utils.test.ts | 24 + .../connections/components/Page/utils.ts | 26 + .../connections/components/Router/Router.tsx | 197 + .../WrappedLoader/WrappedLoader.scss | 22 + .../WrappedLoader/WrappedLoader.tsx | 27 + .../custom-forms/ChOverYT/ChOverYT.scss | 64 + .../custom-forms/ChOverYT/ChOverYT.tsx | 44 + .../custom-forms/ChOverYT/ChoicePage.tsx | 58 + .../custom-forms/ChOverYT/ConnectorCard.tsx | 49 + .../custom-forms/ChOverYT/Notification.tsx | 20 + .../custom-forms/ChOverYT/constants.ts | 7 + .../components/custom-forms/File/File.scss | 210 + .../components/custom-forms/File/File.tsx | 79 + .../File/components/AddFileButton.tsx | 34 + .../File/components/FilesList.tsx | 112 + .../File/components/ListItem/FileItem.tsx | 90 + .../File/components/ListItem/ListItem.tsx | 16 + .../File/components/ListItem/SourceItem.tsx | 124 + .../File/components/ReplaceSourceButton.tsx | 35 + .../components/Workspace/ColumnFilter.tsx | 26 + .../components/Workspace/FileSettings.tsx | 166 + .../File/components/Workspace/TypeSelect.tsx | 93 + .../File/components/Workspace/Workspace.tsx | 185 + .../custom-forms/File/components/index.ts | 3 + .../components/custom-forms/File/context.ts | 58 + .../components/custom-forms/File/types.ts | 33 + .../custom-forms/File/useHandlers.ts | 232 + .../components/custom-forms/File/utils.tsx | 263 + .../custom-forms/GSheetsV2/GSheetsV2.scss | 167 + .../custom-forms/GSheetsV2/GSheetsV2.tsx | 30 + .../GSheetsV2/components/AddSection.tsx | 101 + .../AdditionalTitleContent/GAuthButton.tsx | 56 + .../AdditionalTitleContent/index.tsx | 89 + .../GSheetSourceInfoView.tsx | 51 + .../GSheetListItemView/GSheetSourceView.tsx | 62 + .../GSheetListItemView/UploadedGSheetView.tsx | 66 + .../components/GSheetListItemView/index.tsx | 55 + .../GSheetsV2/components/SheetsList.tsx | 82 + .../components/Workspace/EmptyWorkspace.tsx | 15 + .../Workspace/GSheetSourceInfoWorkspace.tsx | 21 + .../Workspace/GSheetSourceWorkspace.tsx | 62 + .../components/Workspace/GSheetWorkspace.tsx | 21 + .../components/Workspace/Workspace.tsx | 117 + .../GSheetsV2/components/Workspace/utils.ts | 31 + .../GSheetsV2/components/index.ts | 3 + .../containers/ActionBarContainer.tsx | 92 + .../containers/SheetsListContainer.tsx | 263 + .../containers/WorkspaceContainer.tsx | 58 + .../GSheetsV2/containers/index.ts | 3 + .../GSheetsV2/containers/useGSheetDialogs.ts | 212 + .../GSheetsV2/containers/useGoogleAuth.ts | 40 + .../GSheetsV2/containers/utils.ts | 60 + .../custom-forms/GSheetsV2/types.ts | 9 + .../custom-forms/Yadocs/Yadocs.scss | 138 + .../components/custom-forms/Yadocs/Yadocs.tsx | 30 + .../components/AdditionalTitleContent.tsx | 70 + .../DialogAddDocument/DialogAddDocument.scss | 26 + .../DialogAddDocument/DialogAddDocument.tsx | 235 + .../Yadocs/components/DocsList.tsx | 82 + .../Yadocs/components/Workspace.tsx | 186 + .../YadocListItemView/UploadedYadocView.tsx | 67 + .../YadocListItemView/YadocSourceInfoView.tsx | 49 + .../YadocListItemView/YadocSourceView.tsx | 64 + .../components/YadocListItemView/index.tsx | 53 + .../custom-forms/Yadocs/components/index.ts | 4 + .../Yadocs/containers/ActionBarContainer.tsx | 113 + .../Yadocs/containers/DocsListContainer.tsx | 275 + .../Yadocs/containers/WorkspaceContainer.tsx | 46 + .../custom-forms/Yadocs/containers/index.ts | 3 + .../Yadocs/containers/useYadocsDialogs.ts | 268 + .../components/custom-forms/Yadocs/types.ts | 6 + .../components/ButtonLogout/ButtonLogout.scss | 27 + .../components/ButtonLogout/ButtonLogout.tsx | 30 + .../components/ColumnFilter/ColumnFilter.scss | 16 + .../components/ColumnFilter/ColumnFilter.tsx | 28 + .../ColumnsHeaderSwitcher.scss | 10 + .../ColumnsHeaderSwitcher.tsx | 56 + .../DialogWithInput/DialogWithInput.tsx | 88 + .../components/DialogWithInput/index.ts | 13 + .../components/DialogWithInput/types.ts | 23 + .../components/ListItem/ActionDeleteView.tsx | 29 + .../components/ListItem/ActionErrorView.tsx | 57 + .../components/ListItem/ActionMoreView.tsx | 74 + .../components/ListItem/ListItem.scss | 55 + .../components/ListItem/ListItem.tsx | 33 + .../components/ListItem/ListItemActions.tsx | 59 + .../custom-forms/components/ListItem/types.ts | 29 + .../custom-forms/components/index.ts | 6 + .../hooks/useFileSourceTableWidgetData.ts | 75 + .../components/custom-forms/index.ts | 4 + .../components/custom-forms/utils/render.scss | 4 + .../components/custom-forms/utils/render.tsx | 88 + .../components/dialogs/Confirm/Confirm.tsx | 21 + .../components/dialogs/Confirm/index.ts | 13 + .../components/dialogs/Confirm/types.ts | 16 + .../CreateConnection/CreateConnection.tsx | 52 + .../CreateConnectionInWbOrCollection.tsx | 55 + .../dialogs/S3Source/DialogS3Source.scss | 44 + .../dialogs/S3Source/DialogS3Source.tsx | 109 + .../dialogs/S3Source/ListHeader.tsx | 41 + .../components/dialogs/S3Source/ListItem.tsx | 31 + .../components/dialogs/S3Source/index.ts | 13 + .../components/dialogs/S3Source/types.ts | 17 + .../components/dialogs/S3Source/utils.ts | 67 + .../connections/components/dialogs/index.ts | 4 + .../connections/components/hooks/index.ts | 2 + .../hooks/useCreationHandler/index.ts | 90 + .../components/hooks/useValidateForm.ts | 46 + src/ui/units/connections/components/index.ts | 9 + .../units/connections/components/mixins.scss | 5 + src/ui/units/connections/constants/form.ts | 48 + src/ui/units/connections/constants/index.ts | 12 + .../units/connections/constants/validation.ts | 6 + src/ui/units/connections/index.tsx | 50 + src/ui/units/connections/store/actions/api.ts | 495 + .../units/connections/store/actions/base.ts | 407 + .../units/connections/store/actions/file.ts | 585 + .../connections/store/actions/gsheet/index.ts | 320 + .../store/actions/gsheet/misc-actions.ts | 235 + .../connections/store/actions/gsheet/utils.ts | 228 + .../units/connections/store/actions/index.ts | 583 + .../connections/store/actions/s3-based.ts | 125 + .../connections/store/actions/technotes.md | 3 + .../connections/store/actions/yadoc/index.ts | 268 + .../store/actions/yadoc/misc-actions.ts | 241 + .../connections/store/actions/yadoc/utils.ts | 220 + src/ui/units/connections/store/constants.ts | 64 + src/ui/units/connections/store/index.ts | 4 + .../units/connections/store/reducers/index.ts | 419 + .../store/selectors/connection-data.ts | 24 + .../units/connections/store/selectors/file.ts | 5 + .../units/connections/store/selectors/form.ts | 32 + .../connections/store/selectors/gsheet.ts | 26 + .../connections/store/selectors/index.ts | 70 + .../store/selectors/initial-form.ts | 5 + .../connections/store/selectors/inner-form.ts | 12 + .../connections/store/selectors/yadocs.ts | 22 + .../units/connections/store/typings/file.ts | 63 + .../units/connections/store/typings/gsheet.ts | 97 + .../units/connections/store/typings/index.ts | 422 + .../connections/store/typings/s3-based.ts | 18 + .../units/connections/store/typings/yadocs.ts | 94 + .../store/utils/__tests__/connectors.test.ts | 23 + .../store/utils/__tests__/forms.test.ts | 108 + .../connections/store/utils/connectors.ts | 49 + src/ui/units/connections/store/utils/entry.ts | 14 + src/ui/units/connections/store/utils/file.ts | 104 + src/ui/units/connections/store/utils/forms.ts | 150 + src/ui/units/connections/store/utils/index.ts | 5 + .../connections/store/utils/validation.ts | 139 + src/ui/units/connections/typings/common.ts | 1 + src/ui/units/connections/typings/form.ts | 10 + src/ui/units/connections/typings/index.ts | 2 + .../utils/__tests__/common.test.ts | 19 + .../utils/__tests__/connectors.test.ts | 33 + .../connections/utils/__tests__/entry.test.ts | 16 + src/ui/units/connections/utils/common.ts | 57 + src/ui/units/connections/utils/connectors.ts | 41 + src/ui/units/connections/utils/entry.ts | 19 + src/ui/units/connections/utils/gsheet.ts | 70 + src/ui/units/connections/utils/i18n.ts | 15 + src/ui/units/connections/utils/index.ts | 7 + src/ui/units/connections/utils/validation.ts | 28 + src/ui/units/connections/utils/yadocs.ts | 71 + .../DashActionPanel/DashActionPanel.scss | 52 + .../DashActionPanel/DashActionPanel.tsx | 421 + .../DashActionPanelMobile.scss | 27 + .../DashActionPanel/DashActionPanelMobile.tsx | 54 + .../Description/Description.tsx | 117 + .../EditControls/EditControls.tsx | 170 + .../SaveDropDown/SaveDropDown.tsx | 63 + .../ViewControls/ViewControls.tsx | 70 + .../components/DashActionPanel/helpers.ts | 42 + .../components/EmptyState/EmptyState.scss | 11 + .../dash/components/EmptyState/EmptyState.tsx | 81 + .../GlobalSelectorIcon.scss | 4 + .../GlobalSelectorIcon/GlobalSelectorIcon.tsx | 44 + .../units/dash/components/Loader/Loader.scss | 16 + .../units/dash/components/Loader/Loader.tsx | 25 + .../NavigationInput/InputLink/InputLink.js | 183 + .../NavigationInput/InputLink/InputLink.scss | 25 + .../NavigationInput/NavigationInput.js | 136 + .../NavigationInput/NavigationInput.scss | 12 + .../components/ParamsSettings/ParamsRow.tsx | 219 + .../ParamsSettings/ParamsSettings.scss | 74 + .../ParamsSettings/ParamsSettings.tsx | 154 + .../dash/components/ParamsSettings/helpers.ts | 100 + .../test/validateParamTitle.test.ts | 32 + .../dash/components/ParamsSettings/types.ts | 16 + .../components/ShareButton/ShareButton.scss | 5 + .../components/ShareButton/ShareButton.tsx | 121 + src/ui/units/dash/containers/App/App.scss | 23 + src/ui/units/dash/containers/App/App.tsx | 122 + src/ui/units/dash/containers/Body/Body.scss | 201 + src/ui/units/dash/containers/Body/Body.tsx | 883 + .../units/dash/containers/Body/LazyBody.tsx | 3 + .../Body/components/Content/Content.tsx | 275 + .../components/Content/hooks/useCopiedData.ts | 34 + .../DashkitWrapper/DashkitWrapper.tsx | 371 + .../DashkitWrapper/hooks/useConfig.ts | 44 + .../DashkitWrapper/hooks/useDashKitContext.ts | 44 + .../DashkitWrapper/hooks/useDashkitRef.ts | 18 + .../hooks/useDataProviderContext.ts | 26 + .../DashkitWrapper/hooks/useHashStates.ts | 129 + .../hooks/useOverlay/OverlayControls.scss | 17 + .../hooks/useOverlay/getGearGlobalIcon.tsx | 54 + .../hooks/useOverlay/helpers.ts | 41 + .../hooks/useOverlay/useOverlay.tsx | 290 + .../hooks/usePreparedCopyItemOptions.ts | 45 + src/ui/units/dash/containers/Body/context.tsx | 61 + src/ui/units/dash/containers/Dash/Dash.tsx | 495 + .../dash/containers/DashError/DashError.tsx | 54 + .../containers/DashHotkes/DashHotkeys.tsx | 71 + .../DialogEditQuery/DialogEditQuery.scss | 10 + .../DialogEditQuery/DialogEditQuery.tsx | 127 + .../QueryEditor/QueryEditor.scss | 19 + .../QueryEditor/QueryEditor.tsx | 35 + .../DialogEditQuery/QueryError/QueryError.tsx | 26 + .../DialogEditQuery/helpers/validate-query.ts | 9 + .../Dialogs/DialogParams/DialogParams.scss | 12 + .../Dialogs/DialogParams/DialogParams.tsx | 72 + .../units/dash/containers/Dialogs/Dialogs.tsx | 143 + .../containers/Dialogs/Settings/Settings.scss | 62 + .../containers/Dialogs/Settings/Settings.tsx | 351 + .../Dialogs/Settings/SettingsDrawer.scss | 136 + .../Dialogs/Settings/SettingsDrawer.tsx | 548 + .../Settings/components/AutoRefresh.tsx | 75 + .../Dialogs/Settings/components/Display.tsx | 172 + .../Settings/components/LoadPriority.tsx | 51 + .../Settings/components/MaxConnection.tsx | 65 + .../Settings/components/OtherSettings.tsx | 154 + .../Dialogs/Settings/components/Params.scss | 5 + .../Dialogs/Settings/components/Params.tsx | 61 + .../Dialogs/Settings/components/Row.tsx | 16 + .../Dialogs/Settings/components/Title.tsx | 20 + .../Dialogs/Settings/useDashSettings.ts | 444 + .../Dialogs/Settings/useGlobalParams.ts | 82 + .../dash/containers/Dialogs/Settings/utils.ts | 3 + .../containers/Dialogs/Tabs/EditedTabItem.tsx | 39 + .../PopupWidgetsOrder/PopupWidgetsOrder.scss | 48 + .../PopupWidgetsOrder/PopupWidgetsOrder.tsx | 199 + .../Dialogs/Tabs/PopupWidgetsOrder/helpers.ts | 157 + .../PopupWidgetsOrder/test/helpers.test.ts | 162 + .../dash/containers/Dialogs/Tabs/TabItem.scss | 57 + .../dash/containers/Dialogs/Tabs/TabItem.tsx | 214 + .../dash/containers/Dialogs/Tabs/Tabs.scss | 37 + .../dash/containers/Dialogs/Tabs/Tabs.tsx | 306 + .../Dialogs/__tests__/utils.test.ts | 21 + .../ColorInputsGroup/ColorInputsGroup.scss | 22 + .../ColorInputsGroup/ColorInputsGroup.tsx | 94 + .../ColorPalette/ColorItem/ColorItem.scss | 33 + .../ColorPalette/ColorItem/ColorItem.tsx | 57 + .../components/ColorPalette/ColorPalette.scss | 106 + .../components/ColorPalette/ColorPalette.tsx | 215 + .../components/ColorPicker/ColorPicker.scss | 26 + .../components/ColorPicker/ColorPicker.tsx | 94 + .../ColorPickerInputWithPreset.scss | 122 + .../ColorPickerInputWithPreset.tsx | 98 + .../InternalMarginsToggler.tsx | 60 + .../PaletteBackground/PaletteBackground.tsx | 107 + .../components/PaletteText/PaletteText.tsx | 63 + .../dash/containers/Dialogs/constants.ts | 34 + src/ui/units/dash/containers/Dialogs/utils.ts | 34 + .../containers/FixedHeader/FixedHeader.scss | 210 + .../containers/FixedHeader/FixedHeader.tsx | 283 + .../FixedHeader/FixedHeaderMobile.scss | 29 + .../FixedHeader/FixedHeaderMobile.tsx | 77 + .../units/dash/containers/Header/Header.tsx | 104 + .../MobileFloatMenu/MobileFloatMenu.tsx | 83 + .../TableOfContent/TableOfContent.scss | 97 + .../TableOfContent/TableOfContent.tsx | 279 + .../dash/containers/TableOfContent/helpers.ts | 37 + src/ui/units/dash/containers/Tabs/Tabs.scss | 5 + src/ui/units/dash/containers/Tabs/Tabs.tsx | 88 + .../units/dash/contexts/WidgetMetaContext.tsx | 118 + src/ui/units/dash/hooks/useIframeFeatures.tsx | 76 + src/ui/units/dash/modules/constants.ts | 95 + src/ui/units/dash/modules/helpers.ts | 546 + src/ui/units/dash/modules/markdownProvider.ts | 76 + src/ui/units/dash/modules/postMessage.ts | 35 + src/ui/units/dash/modules/pushStats.ts | 30 + .../store/actions/__tests__/helpers.test.ts | 295 + .../actions/base/__tests__/helpers.test.ts | 316 + .../units/dash/store/actions/base/actions.ts | 504 + .../units/dash/store/actions/base/helpers.ts | 147 + .../store/actions/copy-and-paste/helpers.ts | 293 + src/ui/units/dash/store/actions/dash.ts | 2 + src/ui/units/dash/store/actions/dashTyped.ts | 1338 + .../dash/store/actions/dialogs/actions.ts | 80 + .../actions/dialogs/dialog-edit-query.ts | 9 + src/ui/units/dash/store/actions/helpers.ts | 396 + src/ui/units/dash/store/actions/index.ts | 93 + .../dash/store/actions/relations/actions.ts | 116 + .../dash/store/constants/dashActionTypes.ts | 7 + src/ui/units/dash/store/constants/index.ts | 3 + src/ui/units/dash/store/reducers/dash.js | 507 + .../units/dash/store/reducers/dashHelpers.ts | 519 + .../dash/store/reducers/dashTypedReducer.ts | 475 + src/ui/units/dash/store/selectors/dash.js | 0 .../store/selectors/dashTypedSelectors.ts | 235 + src/ui/units/dash/store/typings/dash.ts | 52 + src/ui/units/dash/store/utils.ts | 92 + src/ui/units/dash/styles/dash.scss | 16 + src/ui/units/dash/typings/context.ts | 15 + src/ui/units/dash/typings/dash.ts | 32 + src/ui/units/dash/typings/entry.ts | 4 + .../dash/utils/__tests__/selectors.test.ts | 115 + src/ui/units/dash/utils/colors.ts | 6 + src/ui/units/dash/utils/context.ts | 5 + src/ui/units/dash/utils/customEvents.ts | 8 + src/ui/units/dash/utils/dashkitProps.ts | 26 + src/ui/units/dash/utils/getTabId.ts | 5 + src/ui/units/dash/utils/scrollUtils.ts | 53 + src/ui/units/dash/utils/selectors.ts | 161 + src/ui/units/dash/utils/url.ts | 27 + .../datasets/components/Avatar/Avatar.scss | 112 + .../datasets/components/Avatar/Avatar.tsx | 177 + .../components/ColorsDialog/ColorsDialog.scss | 72 + .../components/ColorsDialog/ColorsDialog.tsx | 241 + .../ContainerLoader/ContainerLoader.scss | 28 + .../ContainerLoader/ContainerLoader.tsx | 75 + .../DatasetCacheGrid/DatasetCacheGrid.scss | 8 + .../DatasetCacheGrid/DatasetCacheGrid.tsx | 42 + .../components/CacheModeSelector.tsx | 53 + .../components/CacheParameterRow.tsx | 50 + .../components/FormulaContent.tsx | 117 + .../components/LastResultRow.tsx | 55 + .../DatasetCacheGrid/components/Row.tsx | 17 + .../components/SqlContent.tsx | 23 + .../components/DatasetPanel/DatasetPanel.scss | 66 + .../components/DatasetPanel/DatasetPanel.tsx | 180 + .../components/DatasetPanel/helpers.ts | 48 + .../DatasetRouter/DatasetRouter.scss | 11 + .../DatasetRouter/DatasetRouter.tsx | 172 + .../DatasetRouter/UnloadConfirmation.tsx | 62 + .../DatasetTabFieldList.scss | 21 + .../DatasetTabFieldList.tsx | 79 + .../components/FieldRow/FieldRow.scss | 45 + .../components/FieldRow/FieldRow.tsx | 124 + .../components/TitleColumn/TitleColumn.scss | 7 + .../components/TitleColumn/TitleColumn.tsx | 25 + .../components/TypeColumn/TypeColumn.scss | 11 + .../components/TypeColumn/TypeColumn.tsx | 30 + .../ValidationColumn/ValidationColumn.scss | 10 + .../ValidationColumn/ValidationColumn.tsx | 27 + .../components/ValueColumn/ValueColumn.scss | 6 + .../components/ValueColumn/ValueColumn.tsx | 17 + .../DatasetTabFieldList/constants.ts | 8 + .../DatasetTabFieldList/helpers.tsx | 67 + .../components/DatasetTabFieldList/types.ts | 84 + .../DatasetTabSection/DatasetTabSection.scss | 24 + .../DatasetTabSection/DatasetTabSection.tsx | 81 + .../components/DatasetTable/DatasetTable.scss | 277 + .../components/DatasetTable/DatasetTable.tsx | 597 + .../DatasetTable/columns/Aggregation.tsx | 45 + .../components/DatasetTable/columns/Cast.tsx | 45 + .../DatasetTable/columns/Description.tsx | 52 + .../DatasetTable/columns/FieldSettings.tsx | 66 + .../DatasetTable/columns/Hidden.tsx | 54 + .../components/DatasetTable/columns/Id.tsx | 55 + .../DatasetTable/columns/IndexColumn.tsx | 77 + .../components/DatasetTable/columns/More.tsx | 41 + .../components/DatasetTable/columns/Rls.tsx | 49 + .../DatasetTable/columns/Source.tsx | 62 + .../components/DatasetTable/columns/Title.tsx | 55 + .../components/DatasetTable/columns/index.ts | 10 + .../AggregationSelect/AggregationSelect.scss | 41 + .../AggregationSelect/AggregationSelect.tsx | 164 + .../BatchActionPanel/BatchActionPanel.tsx | 151 + .../DialogChangeDatasetFields.scss | 17 + .../DialogChangeDatasetFields.tsx | 128 + .../components/DisplaySettings.tsx | 52 + .../components/FieldActionsPopup.tsx | 101 + .../components/ObservedDataTable.tsx | 35 + .../components/TableTextInput.tsx | 61 + .../components/TypeSelect/TypeSelect.scss | 28 + .../components/TypeSelect/TypeSelect.tsx | 117 + .../DatasetTable/components/index.ts | 5 + .../components/DatasetTable/constants.ts | 76 + .../datasets/components/DatasetTable/types.ts | 29 + .../datasets/components/DatasetTable/utils.ts | 283 + .../DatasetTableFieldsSection.scss | 16 + .../DatasetTableFieldsSection.tsx | 69 + .../DescriptionButton/DescriptionButton.tsx | 73 + .../DialogCreateDataset.tsx | 80 + .../components/DragAndDrop/DragAndDrop.tsx | 30 + .../components/ErrorView/ErrorView.scss | 14 + .../components/ErrorView/ErrorView.tsx | 36 + .../FieldSettingsDialog.scss | 36 + .../FieldSettingsDialog.tsx | 226 + .../FilterSection/FilterSection.tsx | 97 + .../components/FilterSection/helpers.ts | 98 + .../components/FilterSection/types.ts | 3 + .../FilterSection/useFilterSection.ts | 130 + .../ParametersSection/ParametersSection.tsx | 72 + .../CopyToClipboardMenuItem.tsx | 26 + .../HeaderWithTooltip/HeaderWithTooltip.scss | 6 + .../HeaderWithTooltip/HeaderWithTooltip.tsx | 48 + .../components/ParametersSection/helpers.ts | 133 + .../useParametersSection.tsx | 66 + .../PreviewHeader/PreviewHeader.scss | 48 + .../PreviewHeader/PreviewHeader.tsx | 150 + .../PreviewHeader/__tests__/utils.test.ts | 16 + .../components/PreviewHeader/constants.ts | 1 + .../components/PreviewHeader/utils.ts | 6 + .../components/PreviewTable/PreviewTable.js | 252 + .../components/PreviewTable/PreviewTable.scss | 62 + .../components/RLSDialog/RLSDialog.scss | 9 + .../components/RLSDialog/RLSDialog.tsx | 137 + .../RelationDialog/RelationDialog.scss | 144 + .../RelationDialog/RelationDialog.tsx | 681 + .../components/RelationsMap/RelationsMap.js | 436 + .../components/RelationsMap/RelationsMap.scss | 60 + .../SelectSourcePrototypes.scss | 208 + .../SelectSourcePrototypes.tsx | 609 + .../SelectSourcePrototypes/SourcesTable.tsx | 301 + .../__tests__/utils.test.ts | 36 + .../SelectSourcePrototypes/constants.ts | 5 + .../SelectSourcePrototypes/utils.ts | 16 + .../datasets/components/Source/Source.js | 158 + .../datasets/components/Source/Source.scss | 73 + .../SourceEditorDialog.scss | 196 + .../SourceEditorDialog/SourceEditorDialog.tsx | 286 + .../components/EditorFormItem.tsx | 172 + .../SourceEditorDialog/components/Form.tsx | 109 + .../components/InputFormItem.tsx | 113 + .../components/ParamSelector.tsx | 201 + .../components/SelectFormItem.tsx | 60 + .../components/SourceError.tsx | 63 + .../components/SourceHelpTooltip.tsx | 196 + .../components/SourceSwitcher.tsx | 39 + .../SourceEditorDialog/components/index.ts | 5 + .../components/SourceEditorDialog/types.ts | 12 + .../SourceEditorDialog/utils/helpers.ts | 245 + .../SourceEditorDialog/utils/index.ts | 2 + .../SourceEditorDialog/utils/validation.ts | 81 + .../components/UserName/UserName.scss | 24 + .../datasets/components/UserName/UserName.tsx | 31 + .../units/datasets/components/Veil/Veil.scss | 16 + .../units/datasets/components/Veil/Veil.tsx | 45 + .../DatasetFieldInspector.scss | 14 + .../DatasetFieldInspector.tsx | 58 + .../datasets/components/dialogs/index.ts | 1 + .../datasets/components/hoc/AvatarDnD.js | 98 + src/ui/units/datasets/constants/datasets.ts | 49 + src/ui/units/datasets/constants/index.ts | 269 + .../Dataset/ActionPanelRightItems.tsx | 237 + .../datasets/containers/Dataset/Dataset.scss | 66 + .../datasets/containers/Dataset/Dataset.tsx | 905 + .../datasets/containers/Dataset/constants.ts | 1 + .../Dataset/getAdditionalContextMenuItems.tsx | 170 + .../containers/Dataset/useHistoryActions.tsx | 51 + .../containers/DatasetCache/DatasetCache.scss | 11 + .../containers/DatasetCache/DatasetCache.tsx | 39 + .../DatasetCache/components/CacheWarning.tsx | 24 + .../DatasetCache/components/Description.tsx | 32 + .../containers/DatasetCache/constants.ts | 1 + .../containers/DatasetEditor/DatasetEditor.js | 480 + .../DatasetEditor/DatasetEditor.scss | 8 + .../containers/DatasetEditor/utils.ts | 31 + .../containers/DatasetError/DatasetError.ts | 125 + .../DatasetFilters/DatasetFilters.tsx | 80 + .../containers/DatasetPage/DatasetPage.scss | 10 + .../containers/DatasetPage/DatasetPage.tsx | 105 + .../DatasetPage/createDatasetPageContext.ts | 12 + .../DatasetParameters/DatasetParameters.tsx | 32 + .../DatasetPreview/DatasetPreview.js | 93 + .../DatasetPreview/DatasetPreview.scss | 7 + .../DatasetSources/DatasetSources.scss | 19 + .../DatasetSources/DatasetSources.tsx | 1012 + .../DatasetTabViewer/DatasetTabViewer.tsx | 79 + .../datasets/helpers/dataset-error-helpers.ts | 111 + src/ui/units/datasets/helpers/datasets.ts | 38 + src/ui/units/datasets/helpers/mockTexts.ts | 43 + src/ui/units/datasets/helpers/utils.test.ts | 14 + src/ui/units/datasets/helpers/utils.ts | 204 + src/ui/units/datasets/helpers/validation.ts | 13 + .../units/datasets/icons/join-full-outer.svg | 6 + src/ui/units/datasets/icons/join-inner.svg | 7 + src/ui/units/datasets/icons/join-left.svg | 6 + src/ui/units/datasets/icons/join-none.svg | 6 + src/ui/units/datasets/icons/join-right.svg | 6 + .../store/actions/creators/datasetTyped.ts | 2468 ++ .../store/actions/creators/dialogs.ts | 61 + .../datasets/store/actions/creators/index.ts | 2 + .../datasets/store/actions/creators/utils.ts | 66 + .../datasets/store/actions/types/dataset.ts | 135 + src/ui/units/datasets/store/constants.ts | 139 + .../datasets/store/edit-history-middleware.ts | 46 + .../units/datasets/store/reducers/dataset.ts | 1556 + src/ui/units/datasets/store/reducers/index.ts | 28 + .../datasets/store/reducers/utils/index.ts | 56 + .../datasets/store/reducers/utils/mocks.ts | 34 + .../store/reducers/utils/utils.test.ts | 55 + .../units/datasets/store/selectors/dataset.ts | 257 + .../datasets/store/selectors/datasetTyped.ts | 4 + .../units/datasets/store/selectors/index.ts | 2 + src/ui/units/datasets/store/types/dataset.ts | 992 + src/ui/units/datasets/store/types/index.ts | 1 + src/ui/units/datasets/styles/mixins.scss | 24 + src/ui/units/datasets/styles/variables.scss | 49 + src/ui/units/datasets/typings/dataset.ts | 8 + src/ui/units/datasets/typings/redux.ts | 28 + src/ui/units/preview/components/App/App.scss | 14 + src/ui/units/preview/components/App/App.tsx | 85 + .../components/IndexPage/IndexPage.scss | 6 + .../components/IndexPage/IndexPage.tsx | 22 + .../preview/components/Preview/Preview.scss | 26 + .../preview/components/Preview/Preview.tsx | 246 + .../units/preview/hooks/useChartAutoupdate.ts | 38 + .../preview/modules/constants/constants.ts | 3 + src/ui/units/preview/modules/helpers.ts | 19 + .../units/ql/components/DND/DNDContainer.tsx | 248 + src/ui/units/ql/components/DND/DNDItem.tsx | 357 + src/ui/units/ql/components/DND/DNDLayer.tsx | 81 + .../components/Dialogs/Settings/Settings.scss | 45 + .../components/Dialogs/Settings/Settings.tsx | 115 + src/ui/units/ql/components/Grid/Grid.scss | 9 + src/ui/units/ql/components/Grid/Grid.tsx | 163 + .../ql/components/Grid/GridPane/GridPane.scss | 8 + .../ql/components/Grid/GridPane/GridPane.tsx | 30 + .../GridPane/GridPaneView/GridPaneView.scss | 57 + .../GridPane/GridPaneView/GridPaneView.tsx | 87 + .../GridPaneView/GridPaneViewContent.tsx | 53 + src/ui/units/ql/configs/chart-types.ts | 12 + src/ui/units/ql/constants/index.ts | 87 + src/ui/units/ql/containers/App/App.scss | 26 + src/ui/units/ql/containers/App/App.tsx | 92 + .../ql/containers/PaneMain/PaneMain.scss | 4 + .../units/ql/containers/PaneMain/PaneMain.tsx | 49 + .../PaneMain/ScreenEditor/ScreenEditor.scss | 85 + .../PaneMain/ScreenEditor/ScreenEditor.tsx | 333 + .../ScreenEditor/TabParams/DefaultValue.tsx | 213 + .../ScreenEditor/TabParams/OverridenValue.tsx | 35 + .../ScreenEditor/TabParams/TabParams.scss | 188 + .../ScreenEditor/TabParams/TabParams.tsx | 419 + .../PaneMain/ScreenEditor/TabParams/utils.ts | 22 + .../TabQuery/ScreenPromQL/ScreenPromQL.scss | 101 + .../TabQuery/ScreenPromQL/ScreenPromQL.tsx | 600 + .../TabQuery/ScreenSQL/ScreenSQL.scss | 8 + .../TabQuery/ScreenSQL/ScreenSQL.tsx | 304 + .../ScreenEditor/TabQuery/TabQuery.scss | 4 + .../ScreenEditor/TabQuery/TabQuery.tsx | 76 + .../containers/PanePreview/PanePreview.scss | 59 + .../ql/containers/PanePreview/PanePreview.tsx | 313 + .../PaneTablePreview/PaneTablePreview.scss | 29 + .../PaneTablePreview/PaneTablePreview.tsx | 69 + .../PaneVisualization/PaneVisualization.scss | 41 + .../PaneVisualization/PaneVisualization.tsx | 65 + src/ui/units/ql/containers/QL/QL.scss | 34 + src/ui/units/ql/containers/QL/QL.tsx | 248 + .../QL/QLActionPanel/QLActionPanel.scss | 6 + .../QL/QLActionPanel/QLActionPanel.tsx | 381 + .../QL/QLActionPanel/useQLActionPanel.ts | 177 + .../ql/containers/QL/ViewSetup/ViewSetup.scss | 57 + .../ql/containers/QL/ViewSetup/ViewSetup.tsx | 398 + src/ui/units/ql/modules/helpers.ts | 28 + src/ui/units/ql/store/actions/dialog.ts | 45 + src/ui/units/ql/store/actions/ql.ts | 1323 + src/ui/units/ql/store/reducers/index.ts | 18 + src/ui/units/ql/store/reducers/ql.ts | 992 + src/ui/units/ql/store/typings/index.ts | 1 + src/ui/units/ql/store/typings/ql.ts | 436 + src/ui/units/ql/store/utils/grid.ts | 164 + src/ui/units/ql/store/utils/monitoring.ts | 214 + src/ui/units/ql/styles/ql.scss | 3 + .../__tests__/getQlVisualization.test.ts | 62 + .../__tests__/mocks/getQlVisualization.ts | 41 + src/ui/units/ql/utils/chart-settings.ts | 24 + src/ui/units/ql/utils/connectionts.ts | 7 + src/ui/units/ql/utils/query.ts | 15 + .../getAvailableQlVisualizations.ts | 53 + .../getDefaultQlVisualization.ts | 16 + .../utils/visualization/getQlVisualization.ts | 44 + src/ui/units/ql/utils/visualization/index.ts | 3 + .../AppearanceSettings.scss | 11 + .../AppearanceSettings/AppearanceSettings.tsx | 49 + .../CreateUserForm/CreateUserForm.tsx | 124 + .../components/SectionGroup/SectionGroup.scss | 23 + .../components/SectionGroup/SectionGroup.tsx | 26 + .../UsersList/LabelsList/LabelsList.scss | 32 + .../UsersList/LabelsList/LabelsList.tsx | 92 + .../UsersList/UsersFilters/UsersFilters.scss | 29 + .../UsersList/UsersFilters/UsersFilters.tsx | 80 + .../components/UsersList/UsersList.scss | 62 + .../components/UsersList/UsersList.tsx | 325 + .../components/UsersList/constants.ts | 4 + .../components/UsersList/utils.tsx | 84 + .../AccessErrorPage/AccessErrorPage.tsx | 23 + .../service-settings/containers/App/App.scss | 8 + .../service-settings/containers/App/App.tsx | 44 + .../CreateProfilePage/CreateProfilePage.scss | 20 + .../CreateProfilePage/CreateProfilePage.tsx | 49 + .../containers/MainPage/MainPage.scss | 52 + .../containers/MainPage/MainPage.tsx | 167 + .../UserProfilePage/UserProfilePage.scss | 32 + .../UserProfilePage/UserProfilePage.tsx | 62 + .../store/actions/serviceSettings.ts | 196 + .../store/constants/serviceSettings.ts | 14 + .../store/reducers/serviceSettings.ts | 145 + .../store/selectors/serviceSettings.ts | 19 + .../store/typings/serviceSettings.ts | 18 + .../service-settings/styles/variables.scss | 1 + .../wizard/actions/__tests__/dndItems.test.ts | 91 + src/ui/units/wizard/actions/dataset.ts | 259 + src/ui/units/wizard/actions/dialog.ts | 637 + src/ui/units/wizard/actions/dialogColor.ts | 187 + src/ui/units/wizard/actions/dndItems.ts | 468 + .../units/wizard/actions/hierarchyEditor.ts | 28 + src/ui/units/wizard/actions/index.ts | 2310 ++ src/ui/units/wizard/actions/placeholder.ts | 562 + src/ui/units/wizard/actions/preview.ts | 295 + src/ui/units/wizard/actions/settings.ts | 70 + src/ui/units/wizard/actions/tooltip.ts | 37 + .../utils/__tests__/getDatasetUpdates.test.ts | 103 + .../wizard/actions/utils/getDatasetUpdates.ts | 388 + src/ui/units/wizard/actions/utils/index.ts | 3 + .../actions/utils/mutateAndValidateItem.ts | 72 + .../wizard/actions/utils/placeholders.ts | 10 + src/ui/units/wizard/actions/visualization.ts | 534 + src/ui/units/wizard/actions/widget.ts | 237 + .../wizard/components/AddField/AddField.scss | 29 + .../wizard/components/AddField/AddField.tsx | 127 + .../wizard/components/DND/DNDContainer.tsx | 246 + .../units/wizard/components/DND/DNDItem.tsx | 298 + .../wizard/components/DND/DragAndDrop.tsx | 30 + .../DatasetSelect/DatasetSelect.scss | 55 + .../DatasetSelect/DatasetSelect.tsx | 217 + .../DatasetSelect/DatasetSelectItem.scss | 104 + .../DatasetSelect/DatasetSelectItem.tsx | 149 + .../ColorSettingsContainer.scss | 10 + .../ColorSettingsContainer.tsx | 222 + .../Dialogs/DialogColor/DialogColor.scss | 50 + .../Dialogs/DialogColor/DialogColor.tsx | 293 + .../DialogColorGradient.scss | 63 + .../DialogColorGradient.tsx | 324 + .../DialogColorPalette.scss | 38 + .../DialogColorPalette/DialogColorPalette.tsx | 147 + .../PaletteWithCustomColor.scss | 11 + .../PaletteWithCustomColor.tsx | 96 + .../DialogColumnSettings.scss | 52 + .../DialogColumnSettings.tsx | 172 + .../ColumnWidthSetting.scss | 21 + .../ColumnWidthSetting/ColumnWidthSetting.tsx | 84 + .../ColumnWidthSettingsSection.tsx | 80 + .../components/FieldInfo/FieldInfo.scss | 35 + .../components/FieldInfo/FieldInfo.tsx | 37 + .../components/Subheader/Subheader.scss | 12 + .../components/Subheader/Subheader.tsx | 28 + .../hooks/useDialogColumnSettingsState.ts | 102 + .../DialogColumnSettings/utils/index.ts | 78 + .../Dialogs/DialogField/DialogField.scss | 31 + .../Dialogs/DialogField/DialogField.tsx | 709 + .../BackgroundSettings.scss | 5 + .../BackgroundSettings/BackgroundSettings.tsx | 160 + .../BackgroundSettings/hooks/helpers.tsx | 160 + .../hooks/useBackgrounColorFieldSelect.ts | 118 + .../useBackgroundColorModeRadioButtons.ts | 117 + .../hooks/useBackgroundNullModeSettings.tsx | 31 + .../hooks/useBackgroundSettings.ts | 61 + .../useBackgroundSettingsButtonColorDialog.ts | 50 + .../hooks/useBackgroundSettingsSwitch.ts | 24 + .../components/BarsSettings/BarsSettings.tsx | 210 + .../ColorControls/ColorsControl.tsx | 118 + .../GradientColorControl.scss | 10 + .../GradientColorControl.tsx | 77 + .../components/MinMaxInputs/MinMaxInputs.scss | 21 + .../components/MinMaxInputs/MinMaxInputs.tsx | 93 + .../PaletteColorControl.scss | 24 + .../PaletteColorControl.tsx | 138 + .../BarsSettings/useBarsSettings/helpers.ts | 38 + .../useBarsSettings/useBarsSettings.ts | 138 + .../ButtonColorDialog/ButtonColorDialog.scss | 12 + .../ButtonColorDialog/ButtonColorDialog.tsx | 123 + .../DialogFieldInput/DialogFieldInput.tsx | 28 + .../DialogFieldMainSection.tsx | 447 + .../DialogFieldRow/DialogFieldRow.tsx | 30 + .../DialogFieldSelect/DialogFieldSelect.scss | 7 + .../DialogFieldSelect/DialogFieldSelect.tsx | 47 + .../DialogFieldSettingTitle.scss | 4 + .../DialogFieldSettingTitle.tsx | 15 + .../SubTotalsSettings/SubTotalsSettings.tsx | 39 + .../DialogField/utils/backgroundSettings.ts | 90 + .../Dialogs/DialogField/utils/barsSettings.ts | 25 + .../Dialogs/DialogField/utils/misc.ts | 110 + .../Dialogs/DialogField/utils/subTotals.ts | 28 + .../DialogFieldInspector.tsx | 186 + .../DialogGeolayer/DialogGeolayer.scss | 19 + .../Dialogs/DialogGeolayer/DialogGeolayer.tsx | 88 + .../DialogLabelSettings.scss | 20 + .../DialogLabelSettings.tsx | 133 + .../DialogLineShapes/DialogLineShapes.scss | 93 + .../DialogLineShapes/DialogLineShapes.tsx | 186 + .../CommonSettings/CommonSettings.scss | 14 + .../CommonSettings/CommonSettings.tsx | 95 + .../components/LineSettings/LineSettings.tsx | 173 + .../components/LineWidth/LineWidth.scss | 12 + .../components/LineWidth/LineWidth.tsx | 118 + .../ShapesPalette/ShapesPalette.scss | 44 + .../ShapesPalette/ShapesPalette.tsx | 68 + .../Dialogs/DialogLineShapes/constants.ts | 13 + .../Dialogs/DialogLineShapes/i18n.ts | 3 + .../Dialogs/DialogLineShapes/types.ts | 8 + .../wizard/components/Dialogs/DialogLink.scss | 44 + .../wizard/components/Dialogs/DialogLink.tsx | 160 + .../DialogMetricColors.scss | 12 + .../DialogMetricColors/DialogMetricColors.tsx | 146 + .../Dialogs/DialogMultidataset.scss | 135 + .../components/Dialogs/DialogMultidataset.tsx | 391 + .../components/Dialogs/DialogNoRights.tsx | 38 + .../DialogPlaceholder/DialogPlaceholder.scss | 51 + .../DialogPlaceholder/DialogPlaceholder.tsx | 1086 + .../DialogPlaceholderRow.tsx | 29 + .../constants/radio-buttons.tsx | 169 + .../Dialogs/DialogPlaceholder/utils.ts | 14 + .../components/Dialogs/DialogPointsSize.scss | 37 + .../components/Dialogs/DialogPointsSize.tsx | 224 + .../Dialogs/DialogShapes/DialogShapes.scss | 26 + .../Dialogs/DialogShapes/DialogShapes.tsx | 160 + .../DialogShapesPalette.scss | 54 + .../DialogShapesPalette.tsx | 125 + .../DialogTooltipSettings.scss | 20 + .../DialogTooltipSettings.tsx | 110 + .../wizard/components/Dialogs/Dialogs.scss | 64 + .../Settings/CenterSetting/CenterSetting.scss | 10 + .../Settings/CenterSetting/CenterSetting.tsx | 68 + .../IndicatorTitleSetting.scss | 19 + .../IndicatorTitleSetting.tsx | 117 + .../Settings/LimitInput/LimitInput.scss | 11 + .../Settings/LimitInput/LimitInput.tsx | 71 + .../Settings/SettingFeed/SettingFeed.scss | 15 + .../Settings/SettingFeed/SettingFeed.tsx | 35 + .../NavigatorSubSettings.scss | 30 + .../NavigatorSubSettings.tsx | 169 + .../SettingNavigator/SettingNavigator.scss | 2 + .../SettingNavigator/SettingNavigator.tsx | 49 + .../SettingPagination/SettingPagination.tsx | 87 + .../SettingSwitcher/SettingSwitcher.scss | 12 + .../SettingSwitcher/SettingSwitcher.tsx | 93 + .../SettingTitleMode/SettingTitleMode.scss | 12 + .../SettingTitleMode/SettingTitleMode.tsx | 49 + .../components/Dialogs/Settings/Settings.scss | 44 + .../components/Dialogs/Settings/Settings.tsx | 1140 + .../Settings/ZoomSetting/ZoomSetting.scss | 10 + .../Settings/ZoomSetting/ZoomSetting.tsx | 65 + .../DialogRadioButtons.scss | 5 + .../DialogRadioButtons/DialogRadioButtons.tsx | 51 + .../components/DialogRow/DialogRow.scss | 8 + .../components/DialogRow/DialogRow.tsx | 48 + .../TitleWithTooltip/TitleWithTooltip.scss | 12 + .../TitleWithTooltip/TitleWithTooltip.tsx | 31 + .../GeolayersSelect/GeolayersSelect.scss | 113 + .../GeolayersSelect/GeolayersSelect.tsx | 340 + .../GradientPalettePreview.scss | 4 + .../GradientPalettePreview.tsx | 20 + .../HierarchyEditor/HierarchyEditor.scss | 70 + .../HierarchyEditor/HierarchyEditor.tsx | 165 + .../MinifiedPalette/MinifiedPalette.scss | 43 + .../MinifiedPalette/MinifiedPalette.tsx | 121 + .../wizard/components/Palette/Palette.scss | 25 + .../wizard/components/Palette/Palette.tsx | 109 + .../PlaceholderActionIcon.scss | 11 + .../PlaceholderActionIcon.tsx | 61 + .../components/SearchInput/SearchInput.scss | 7 + .../components/SearchInput/SearchInput.tsx | 36 + .../components/ValuesList/ValuesList.scss | 55 + .../components/ValuesList/ValuesList.tsx | 435 + ...VisualizationItemTitleWithAggregation.scss | 9 + .../VisualizationItemTitleWithAggregation.tsx | 32 + src/ui/units/wizard/constants/aggregations.ts | 14 + src/ui/units/wizard/constants/dialogColor.ts | 39 + src/ui/units/wizard/constants/formats.ts | 56 + src/ui/units/wizard/constants/grouping.ts | 11 + src/ui/units/wizard/constants/index.ts | 53 + src/ui/units/wizard/constants/paletteTypes.ts | 5 + src/ui/units/wizard/constants/placeholders.ts | 3 + src/ui/units/wizard/containers/App.scss | 24 + src/ui/units/wizard/containers/App.tsx | 74 + .../DatasetItem/DatasetItem.scss | 247 + .../DatasetItem/DatasetItem.tsx | 419 + .../FieldsContainer/FieldsContainer.tsx | 17 + .../ParametersContainer.tsx | 34 + .../SectionContainer/SectionContainer.scss | 10 + .../SectionContainer/SectionContainer.tsx | 41 + .../HierarchyEditor/HierarchyEditor.tsx | 261 + .../Wizard/SectionDataset/SectionDataset.scss | 136 + .../Wizard/SectionDataset/SectionDataset.tsx | 1193 + .../Wizard/SectionPreview/SectionPreview.scss | 44 + .../Wizard/SectionPreview/SectionPreview.tsx | 248 + .../AddField/AddField.tsx | 125 + .../AvailablePlaceholder.tsx | 79 + .../ColorsPlaceholder/ColorsPlaceholder.tsx | 209 + .../DashboardFiltersPlaceholder.tsx | 169 + .../DashboardParametersPlaceholder.tsx | 108 + .../FiltersPlaceholder/FiltersPlaceholder.tsx | 128 + .../LabelsPlaceholder/LabelsPlaceholder.tsx | 121 + .../LayerFiltersPlaceholder.tsx | 140 + .../MetricColorsPlaceholder.tsx | 60 + .../Placeholder/Placeholder.tsx | 288 + .../PlaceholdersContainer.tsx | 319 + .../SegmentsPlaceholder.tsx | 90 + .../ShapesPlaceholder/ShapesPlaceholder.tsx | 149 + .../SortPlaceholder/SortPlaceholder.tsx | 176 + .../TooltipsPlaceholder.tsx | 99 + .../VisualizationPlaceholder.tsx | 436 + .../VisualizationPlaceholder/constants.ts | 10 + .../PlaceholdersContainer/utils.ts | 8 + .../SectionVisualization.scss | 201 + .../SectionVisualization.tsx | 163 + .../VisualizationItem/VisualizationItem.scss | 567 + .../VisualizationItem/VisualizationItem.tsx | 1003 + .../CombinedChartLayerTypeSwitcher.scss | 14 + .../CombinedChartLayerTypeSwitcher.tsx | 71 + .../VisualizationLayersControl.scss | 29 + .../VisualizationLayersControl.tsx | 337 + .../VisualizationSelector.scss | 114 + .../VisualizationSelector.tsx | 254 + .../wizard/containers/Wizard/Wizard.scss | 115 + .../units/wizard/containers/Wizard/Wizard.tsx | 676 + .../WizardActionPanel/WizardActionPanel.scss | 39 + .../WizardActionPanel/WizardActionPanel.tsx | 146 + .../WizardActionPanel/useWizardActionPanel.ts | 220 + .../reducers/__tests__/mocks/common.mock.ts | 69 + .../reducers/__tests__/mocks/preview.mock.ts | 5 + .../wizard/reducers/__tests__/preview.test.ts | 270 + .../__tests__/utils/previewTestUtils.ts | 33 + src/ui/units/wizard/reducers/dataset.ts | 213 + src/ui/units/wizard/reducers/dialogColor.ts | 46 + .../units/wizard/reducers/hierarchyEditor.ts | 38 + src/ui/units/wizard/reducers/index.ts | 76 + src/ui/units/wizard/reducers/preview.ts | 568 + .../wizard/reducers/revisions/reducers.ts | 73 + src/ui/units/wizard/reducers/settings.ts | 69 + .../clearUnusedVisualizationItems.test.ts | 503 + .../getPlaceholdersWithMergedSettings.test.ts | 180 + .../__tests__/updateColorHierarchies.test.ts | 112 + .../wizard/reducers/utils/checkTableColor.ts | 5 + .../utils/clearUnusedVisualizationItems.ts | 269 + .../wizard/reducers/utils/getConfigData.ts | 43 + .../getPlaceholdersWithMergedSettings.ts | 112 + src/ui/units/wizard/reducers/utils/index.ts | 2 + .../reducers/utils/updateColorHierarchies.ts | 40 + src/ui/units/wizard/reducers/visualization.ts | 1197 + src/ui/units/wizard/reducers/widget.ts | 108 + src/ui/units/wizard/selectors/dataset.ts | 131 + src/ui/units/wizard/selectors/dialogColor.ts | 82 + src/ui/units/wizard/selectors/gradient.ts | 32 + src/ui/units/wizard/selectors/index.ts | 21 + src/ui/units/wizard/selectors/preview.ts | 100 + src/ui/units/wizard/selectors/settings.ts | 51 + .../units/wizard/selectors/visualization.ts | 85 + src/ui/units/wizard/selectors/widget.ts | 41 + src/ui/units/wizard/styles/mixins.scss | 20 + src/ui/units/wizard/styles/variables.scss | 12 + src/ui/units/wizard/typings/index.ts | 1 + src/ui/units/wizard/typings/wizard.ts | 36 + .../wizard/utils/__tests__/helpers.test.ts | 222 + .../mappers/mapServerToClientConfig.ts | 49 + .../wizard/utils/__tests__/wizard.test.ts | 80 + src/ui/units/wizard/utils/colors.ts | 16 + src/ui/units/wizard/utils/filters.ts | 32 + src/ui/units/wizard/utils/helpers.ts | 753 + .../utils/mappers/mapChartsToClientConfig.ts | 239 + .../utils/mappers/mapClientToChartsConfig.ts | 64 + src/ui/units/wizard/utils/palette.tsx | 56 + src/ui/units/wizard/utils/placeholder.ts | 125 + src/ui/units/wizard/utils/shapes.ts | 11 + src/ui/units/wizard/utils/table.ts | 20 + src/ui/units/wizard/utils/visualization.ts | 95 + src/ui/units/wizard/utils/wizard.ts | 648 + .../components/CreateEntry/CreateEntry.scss | 22 + .../components/CreateEntry/CreateEntry.tsx | 198 + .../components/CreateEntry/utils.tsx | 127 + .../CreateEntryDialog/CreateEntryDialog.tsx | 69 + .../components/CreateEntryDialog/utils.ts | 9 + .../DeleteEntryDialog/DeleteEntryDialog.tsx | 65 + .../DuplicateEntryDialog.tsx | 84 + .../EmptyWorkbook/EmptyWorkbook.scss | 10 + .../EmptyWorkbook/EmptyWorkbook.tsx | 89 + .../EmptyWorkbook/EmptyWorkbookContainer.tsx | 31 + .../components/EmptyWorkbook/utils.ts | 38 + .../components/EntryActions/EntryActions.tsx | 146 + .../components/EntryActions/utils.ts | 4 + .../RenameEntryDialog/RenameEntryDialog.scss | 5 + .../RenameEntryDialog/RenameEntryDialog.tsx | 89 + .../ChunkGroup/ChunkGroup.scss | 11 + .../ChunkGroup/ChunkGroup.tsx | 67 + .../MainTabContent/MainTabContent.scss | 107 + .../MainTabContent/MainTabContent.tsx | 188 + .../Table/WorkbookEntriesTable/Row/Row.scss | 123 + .../Table/WorkbookEntriesTable/Row/Row.tsx | 200 + .../WorkbookEntriesTable.scss | 52 + .../WorkbookEntriesTable.tsx | 327 + .../WorkbookEntriesTableTabs.tsx | 171 + .../Table/WorkbookEntriesTable/constants.ts | 17 + .../Table/WorkbookEntriesTable/types.ts | 15 + .../Table/WorkbookEntriesTable/utils.ts | 58 + .../WorkbookActions/WorkbookActions.scss | 11 + .../WorkbookActions/WorkbookActions.tsx | 253 + .../components/WorkbookActions/utils.tsx | 5 + .../WorkbookFilters/WorkbookFilters.scss | 19 + .../WorkbookFilters/WorkbookFilters.tsx | 103 + .../WorkbookMainTabContent.tsx | 309 + .../useChunkedEntries.ts | 55 + .../components/WorkbookPage/WorkbookPage.scss | 60 + .../components/WorkbookPage/WorkbookPage.tsx | 198 + .../WorkbookPage/hooks/useLayout.tsx | 220 + .../WorkbookTabContent.scss | 8 + .../WorkbookTabContent/WorkbookTabContent.tsx | 230 + .../WorkbookTabContent/useChunkedEntries.ts | 52 + .../components/WorkbookTabs/WorkbookTabs.tsx | 33 + .../components/WorkbookTabs/constants.ts | 1 + .../components/WorkbookTabs/types.ts | 10 + .../components/WorkbookTabs/utils.ts | 51 + src/ui/units/workbooks/constants/index.ts | 11 + src/ui/units/workbooks/store/actions/index.ts | 924 + .../units/workbooks/store/constants/index.ts | 88 + .../units/workbooks/store/reducers/index.ts | 14 + .../workbooks/store/reducers/workbook-page.ts | 622 + .../units/workbooks/store/selectors/index.ts | 138 + src/ui/units/workbooks/types/index.ts | 36 + src/ui/units/workbooks/utils/entry.ts | 8 + src/ui/utils/__tests__/docs.test.ts | 128 + src/ui/utils/__tests__/helpers.test.ts | 15 + src/ui/utils/__tests__/validation.test.ts | 143 + src/ui/utils/absurd.ts | 29 + .../add-chart-modeling-series.ts | 396 + src/ui/utils/chart-modeling/index.ts | 37 + src/ui/utils/collectionFiltersStorage.ts | 25 + src/ui/utils/colors.ts | 48 + src/ui/utils/connections/icons.ts | 44 + src/ui/utils/connections/index.ts | 1 + src/ui/utils/copyItems.ts | 258 + src/ui/utils/copyText.ts | 34 + src/ui/utils/custom-export-menu-item.ts | 55 + src/ui/utils/date.ts | 5 + src/ui/utils/docs/helpers.ts | 6 + src/ui/utils/docs/index.ts | 43 + src/ui/utils/embedded.ts | 30 + src/ui/utils/errorContentTypes.ts | 100 + src/ui/utils/errors/errorByCode.ts | 33 + src/ui/utils/errors/manual.ts | 1 + src/ui/utils/errors/parse.ts | 123 + src/ui/utils/getActionPanelItems.tsx | 96 + src/ui/utils/getAllEntryScopes.ts | 5 + src/ui/utils/getBasicActionPanelItems.tsx | 95 + src/ui/utils/getCurrentUserRights.ts | 13 + src/ui/utils/getOverridedTheme.ts | 11 + src/ui/utils/getRestrictedParamNames.ts | 3 + .../getRevisionsPanelEntryScopesTexts.ts | 28 + src/ui/utils/getScopeTypeIcon.ts | 22 + src/ui/utils/getTopLevelEntryScopes.ts | 5 + src/ui/utils/getTypeSelectOptions.tsx | 26 + src/ui/utils/gridStorage.ts | 65 + src/ui/utils/helpers.ts | 64 + src/ui/utils/history.tsx | 5 + src/ui/utils/icons.ts | 8 + src/ui/utils/index.ts | 20 + src/ui/utils/isEnabledFeature.ts | 9 + src/ui/utils/line-shapes.tsx | 53 + .../utils/markdown/get-render-markdown-fn.ts | 19 + src/ui/utils/markdown/get-render-yfm-fn.tsx | 21 + src/ui/utils/markdown/index.ts | 2 + src/ui/utils/markup.tsx | 21 + src/ui/utils/mermaid-sanitize.ts | 6 + src/ui/utils/migrateItemDataOnPaste.ts | 34 + src/ui/utils/mobile.ts | 25 + src/ui/utils/openModalSubscriber.ts | 19 + src/ui/utils/os.ts | 19 + src/ui/utils/palettes.tsx | 26 + src/ui/utils/readOnly.ts | 12 + src/ui/utils/revisions.ts | 30 + src/ui/utils/scrollableContainerContext.tsx | 33 + src/ui/utils/sdkRequests.ts | 43 + src/ui/utils/setEntryKey.ts | 40 + .../get-is-restricted-shared-entry.ts | 14 + .../shared-entries/get-is-shared-entry.ts | 8 + src/ui/utils/shared-entries/index.ts | 5 + src/ui/utils/stringUtils.ts | 10 + src/ui/utils/urlSearch.ts | 51 + src/ui/utils/urlUtils.ts | 55 + src/ui/utils/utils.ts | 269 + src/ui/utils/validation.ts | 69 + src/ui/utils/visualizations/line.ts | 133 + .../visualizations/placeholders/colors.ts | 81 + .../placeholders/common-measures.ts | 115 + src/ui/utils/waitOperation.ts | 105 + src/ui/utils/widgetColors.ts | 87 + src/ui/utils/widgets/colors.ts | 1 + src/ui/utils/widgets/internalMargins.ts | 67 + src/ui/utils/workbooks.ts | 10 + statoscope.config.js | 39 + tests/.eslintrc | 16 + tests/Dockerfile | 90 + tests/README.md | 103 + tests/constants/config/charts.ts | 21 + tests/constants/config/connections.ts | 11 + tests/constants/config/dash.ts | 21 + tests/constants/config/datasets.ts | 6 + tests/constants/config/index.ts | 18 + tests/constants/config/ql.ts | 15 + tests/constants/config/wizard.ts | 9 + tests/constants/constants.ts | 24 + tests/constants/mock-data.ts | 30 + tests/constants/test-entities/charts.ts | 30 + tests/constants/test-entities/connections.ts | 9 + tests/constants/test-entities/dash.ts | 12 + tests/constants/test-entities/datasets.ts | 5 + tests/constants/test-entities/ql.ts | 28 + tests/constants/test-entities/wizard.ts | 6 + tests/constants/test-entities/workbook.ts | 5 + tests/data/post-init.sh | 105 + tests/data/sales-db.sql | 38 + tests/data/superstore-sales.csv | 9801 ++++++ tests/data/us-e2e-data.sql | 105 + tests/docker-compose.base.yml | 132 + tests/docker-compose.e2e.dev.yml | 58 + tests/docker-compose.e2e.nginx.yml | 11 + tests/docker-compose.e2e.yml | 100 + tests/documentation/env_configuration.md | 13 + ...e-widget-must-be-the-same-screenshot-1.png | Bin 0 -> 39870 bytes ...idgets-without-autoheight-screenshot-1.png | Bin 0 -> 90445 bytes ...nshots-Headers-with-hints-screenshot-1.png | Bin 0 -> 150240 bytes ...ifferent-markdown-widgets-screenshot-1.png | Bin 0 -> 93978 bytes ...s-Custom-order-of-widgets-screenshot-1.png | Bin 0 -> 89324 bytes ...-Default-order-of-widgets-screenshot-1.png | Bin 0 -> 82305 bytes ...creenshots-Chart-bg-color-screenshot-1.png | Bin 0 -> 61858 bytes ...creenshots-Title-bg-color-screenshot-1.png | Bin 0 -> 70062 bytes ...eenshots-Title-text-color-screenshot-1.png | Bin 0 -> 38169 bytes ...-formula-inside-the-table-screenshot-1.png | Bin 0 -> 3854 bytes ...es-not-affect-axis-settings-screenshot.png | Bin 0 -> 10718 bytes ...n-names-in-Labels-section-screenshot-1.png | Bin 0 -> 30913 bytes ...chart-Datetime-dataLabels-screenshot-1.png | Bin 0 -> 15443 bytes ...a-chart-Markup-dataLabels-screenshot-1.png | Bin 0 -> 15189 bytes ...a-chart-String-dataLabels-screenshot-1.png | Bin 0 -> 15969 bytes ...hart-Percentage-as-labels-screenshot-1.png | Bin 0 -> 20542 bytes ...art-Html-labels-on-X-axis-screenshot-1.png | Bin 0 -> 34006 bytes ...t-Html-legend-color-field-screenshot-1.png | Bin 0 -> 65534 bytes ...art-Split-with-few-values-screenshot-1.png | Bin 0 -> 15774 bytes ...t-Split-with-single-value-screenshot-1.png | Bin 0 -> 13718 bytes ...a-chart-Stacking-disabled-screenshot-1.png | Bin 0 -> 41343 bytes ...e-two-measures-in-tooltip-screenshot-1.png | Bin 0 -> 9075 bytes ...age-formatting-in-tooltip-screenshot-1.png | Bin 0 -> 14740 bytes ...hart-Tooltip-rows-sorting-screenshot-1.png | Bin 0 -> 50096 bytes ...by-measure-field-gradient-screenshot-1.png | Bin 0 -> 10396 bytes ...-Coloring-with-hex-colors-screenshot-1.png | Bin 0 -> 11757 bytes ...chart-Datetime-dataLabels-screenshot-1.png | Bin 0 -> 10726 bytes ...x-chart-Markup-dataLabels-screenshot-1.png | Bin 0 -> 10384 bytes ...asureValues-as-dataLabels-screenshot-1.png | Bin 0 -> 7394 bytes ...x-chart-String-dataLabels-screenshot-1.png | Bin 0 -> 11087 bytes ...hart-Percentage-as-labels-screenshot-1.png | Bin 0 -> 11030 bytes ...art-Html-labels-on-X-axis-screenshot-1.png | Bin 0 -> 20285 bytes ...t-Html-legend-color-field-screenshot-1.png | Bin 0 -> 21843 bytes ...x-chart-Negative-Y-values-screenshot-1.png | Bin 0 -> 6353 bytes ...art-Split-with-few-values-screenshot-1.png | Bin 0 -> 12834 bytes ...t-Split-with-single-value-screenshot-1.png | Bin 0 -> 9900 bytes ...e-two-measures-in-tooltip-screenshot-1.png | Bin 0 -> 7644 bytes ...age-formatting-in-tooltip-screenshot-1.png | Bin 0 -> 12271 bytes ...-chart-Logarithmic-x-axis-screenshot-1.png | Bin 0 -> 5520 bytes ...c-x-axis-with-zero-values-screenshot-1.png | Bin 0 -> 4595 bytes ...chart-Rotated-axis-labels-screenshot-1.png | Bin 0 -> 7800 bytes ...y-Measure-Values-gradient-screenshot-1.png | Bin 0 -> 8817 bytes ...easure-Names-in-Y-section-screenshot-1.png | Bin 0 -> 5744 bytes ...by-measure-field-gradient-screenshot-1.png | Bin 0 -> 8775 bytes ...chart-Datetime-dataLabels-screenshot-1.png | Bin 0 -> 8855 bytes ...y-chart-Markup-dataLabels-screenshot-1.png | Bin 0 -> 9356 bytes ...asureValues-as-dataLabels-screenshot-1.png | Bin 0 -> 9173 bytes ...y-chart-String-dataLabels-screenshot-1.png | Bin 0 -> 9298 bytes ...hart-Percentage-as-labels-screenshot-1.png | Bin 0 -> 9636 bytes ...Grouping-by-Measure-Names-screenshot-1.png | Bin 0 -> 6664 bytes ...the-setting-is-turned-off-screenshot-1.png | Bin 0 -> 5838 bytes ...-chart-Sorting-by-X-field-screenshot-1.png | Bin 0 -> 5258 bytes ...e-two-measures-in-tooltip-screenshot-1.png | Bin 0 -> 8215 bytes ...age-formatting-in-tooltip-screenshot-1.png | Bin 0 -> 13187 bytes ...sitive-left-on-the-second-screenshot-1.png | Bin 0 -> 18256 bytes ...-colored-by-Measure-Names-screenshot-1.png | Bin 0 -> 14691 bytes ...s-with-prefix-and-postfix-screenshot-1.png | Bin 0 -> 15037 bytes ...s-with-prefix-and-postfix-screenshot-2.png | Bin 0 -> 15426 bytes ...single-series-column-line-screenshot-1.png | Bin 0 -> 1528 bytes ...-layers-with-single-lines-screenshot-1.png | Bin 0 -> 1318 bytes ...-visibility-of-the-legend-screenshot-1.png | Bin 0 -> 30831 bytes ...d-title-color-field-value-screenshot-1.png | Bin 0 -> 20892 bytes ...d-title-color-field-value-screenshot-1.png | Bin 0 -> 20927 bytes ...ayed-as-color-field-value-screenshot-1.png | Bin 0 -> 20016 bytes ...d-title-color-field-value-screenshot-1.png | Bin 0 -> 26894 bytes ...ed-for-the-combined-chart-screenshot-1.png | Bin 0 -> 123191 bytes ...or-different-layer-series-screenshot-1.png | Bin 0 -> 9235 bytes ...s-with-and-without-colors-screenshot-1.png | Bin 0 -> 13639 bytes ...pty-values---ignore-nulls-screenshot-1.png | Bin 0 -> 7597 bytes ...-coloring-for-null-values-screenshot-1.png | Bin 0 -> 6698 bytes ...-coloring-for-zero-values-screenshot-1.png | Bin 0 -> 6744 bytes ...t-table-Gradient-coloring-screenshot-1.png | Bin 0 -> 25360 bytes ...h-column-background-color-screenshot-1.png | Bin 0 -> 7079 bytes ...e-is-less-than-the-actual-screenshot-1.png | Bin 0 -> 13245 bytes ...Flat-table-Pinned-columns-screenshot-1.png | Bin 0 -> 157880 bytes ...Flat-table-Pinned-columns-screenshot-2.png | Bin 0 -> 157337 bytes ...Flat-table-Pinned-columns-screenshot-3.png | Bin 0 -> 83128 bytes ...er-of-rows-when-scrolling-screenshot-1.png | Bin 0 -> 21273 bytes ...er-of-rows-when-scrolling-screenshot-2.png | Bin 0 -> 25369 bytes ...during-row-virtualization-screenshot-1.png | Bin 0 -> 18858 bytes ...during-row-virtualization-screenshot-2.png | Bin 0 -> 20671 bytes ...acement-of-the-totals-row-screenshot-1.png | Bin 0 -> 49461 bytes ...-Auto-scale-from-0-to-max-screenshot-1.png | Bin 0 -> 35720 bytes ...s-titles-with-line-breaks-screenshot-1.png | Bin 0 -> 96270 bytes ...s-and-ends-on-data-points-screenshot-1.png | Bin 0 -> 52586 bytes ...te-and-time-on-the-Y-axis-screenshot-1.png | Bin 0 -> 9627 bytes ...art-The-right-Y-axis-only-screenshot-1.png | Bin 0 -> 62763 bytes ...Two-Y-axes-left-and-right-screenshot-1.png | Bin 0 -> 108127 bytes ...art-Html-labels-on-X-axis-screenshot-1.png | Bin 0 -> 48425 bytes ...t-Html-legend-color-field-screenshot-1.png | Bin 0 -> 78936 bytes ...rs-are-assigned-correctly-screenshot-1.png | Bin 0 -> 70215 bytes ...rendered-with-split-value-screenshot-1.png | Bin 0 -> 4641 bytes ...art-Split-with-few-values-screenshot-1.png | Bin 0 -> 46636 bytes ...t-Split-with-single-value-screenshot-1.png | Bin 0 -> 35103 bytes ...two-Y-axes-left-and-right-screenshot-1.png | Bin 0 -> 162748 bytes ...d-Pie-chart-Auto-coloring-screenshot-1.png | Bin 0 -> 26688 bytes ...d-Pie-chart-Auto-coloring-screenshot-2.png | Bin 0 -> 28490 bytes ...d-Pie-chart-Auto-coloring-screenshot-3.png | Bin 0 -> 39002 bytes ...d-Pie-chart-Auto-coloring-screenshot-4.png | Bin 0 -> 80282 bytes ...d-Pie-chart-Auto-coloring-screenshot-5.png | Bin 0 -> 40880 bytes ...d-Pie-chart-Auto-coloring-screenshot-6.png | Bin 0 -> 42385 bytes ...olor-palette-from-dataset-screenshot-1.png | Bin 0 -> 35110 bytes ...-a-chart-with-a-hierarchy-screenshot-1.png | Bin 0 -> 18310 bytes ...-characters-in-dataLabels-screenshot-1.png | Bin 0 -> 44754 bytes ...ro-values-with-a-gradient-screenshot-1.png | Bin 0 -> 12845 bytes ...-table-Custom-palette-bar-screenshot-1.png | Bin 0 -> 14972 bytes ...ivot-table-Two-colors-bar-screenshot-1.png | Bin 0 -> 16505 bytes ...ivot-table-Two-colors-bar-screenshot-2.png | Bin 0 -> 9915 bytes ...omplex-three-level-header-screenshot-1.png | Bin 0 -> 23663 bytes ...d-Pivot-table-Null-values-screenshot-1.png | Bin 0 -> 7033 bytes ...-header-in-the-table-head-screenshot-1.png | Bin 0 -> 17904 bytes ...-header-in-the-table-rows-screenshot-1.png | Bin 0 -> 39048 bytes ...g-rows-with-markup-values-screenshot-1.png | Bin 0 -> 18840 bytes ...ue-in-the-previous-column-screenshot-1.png | Bin 0 -> 18856 bytes ...izard-Pivot-table-No-data-screenshot-1.png | Bin 0 -> 6965 bytes ...able-Table-with-Rows-only-screenshot-1.png | Bin 0 -> 11907 bytes ...ed-columns-with-subtotals-screenshot-1.png | Bin 0 -> 23734 bytes ...ed-columns-with-subtotals-screenshot-2.png | Bin 0 -> 24064 bytes ...ble-Subtotals-for-Columns-screenshot-1.png | Bin 0 -> 24406 bytes ...-table-Subtotals-for-Rows-screenshot-1.png | Bin 0 -> 27384 bytes ...er-of-rows-when-scrolling-screenshot-1.png | Bin 0 -> 44888 bytes ...er-of-rows-when-scrolling-screenshot-2.png | Bin 0 -> 48571 bytes ...ng-and-fixed-column-width-screenshot-1.png | Bin 0 -> 83446 bytes ...acement-of-the-totals-row-screenshot-1.png | Bin 0 -> 49461 bytes ...and-max-values-for-Y-axis-screenshot-1.png | Bin 0 -> 6926 bytes ...by-measure-field-gradient-screenshot-1.png | Bin 0 -> 6932 bytes ...e-fake-title-of-the-field-screenshot-1.png | Bin 0 -> 6845 bytes ...-a-chart-with-a-hierarchy-screenshot-1.png | Bin 0 -> 8332 bytes ...-chart-Markdown-dimension-screenshot-1.png | Bin 0 -> 32724 bytes ...-when-hovering-chart-area-screenshot-1.png | Bin 0 -> 28594 bytes .../charts/export/forbiddenExport.test.ts | 260 + .../charts/preview/widget-width.test.ts | 29 + .../contentTableItemsLinks.test.ts | 35 + .../connections/clickhouse.test.ts | 29 + .../connections/postgres.test.ts | 31 + .../connections/ytsaurus.test.ts | 27 + .../dash/base/actionPanel.test.ts | 63 + .../dash/base/fixedHeader.test.ts | 133 + .../dash/base/loading.test.ts | 190 + .../dash/base/relations.test.ts | 69 + tests/opensource-suites/dash/constants.ts | 4 + .../dash/params/params.test.ts | 362 + .../dash/relations/ignore.test.ts | 114 + .../dash/relations/relationType.test.ts | 147 + .../dash/relations/relations.test.ts | 129 + .../dash/relations/relationsEmpty.test.ts | 41 + .../relations/relationsWithErrorChart.test.ts | 56 + .../dash/relations/validation.test.ts | 55 + .../dash/revisions/revisionsList.test.ts | 74 + .../revisionsListCopyDashboards.test.ts | 86 + ...evisionsListWithCreationDashboards.test.ts | 211 + ...evisionsListWithCreationSaveAsCopy.test.ts | 139 + .../dash/revisions/revisionsSwitching.test.ts | 107 + .../dash/screenshots/baseWidgets.test.ts | 59 + .../dash/screenshots/headerWidgets.test.ts | 34 + .../dash/screenshots/markdownCases.test.ts | 33 + .../dash/screenshots/mobile.test.ts | 54 + .../screenshots/widgetsBackground.test.ts | 36 + .../dash/selectors/acceptableValues.test.ts | 80 + .../dash/selectors/addSelectors.test.ts | 54 + .../autoupdateRelatedSelectors.test.ts | 417 + .../selectors/configManipulations.test.ts | 264 + .../dash/selectors/datetimeSelectors.test.ts | 45 + .../externalInfluencesOnGroup.test.ts | 273 + .../dash/selectors/global/baseActions.test.ts | 309 + .../dash/selectors/groupBaseActions.test.ts | 156 + .../dash/selectors/groupButtonActions.test.ts | 264 + .../selectorWithInvalidField.test.ts | 41 + .../dash/selectors/selectorsLabels.test.ts | 119 + .../dash/state/stateChange.test.ts | 473 + .../opensource-suites/dash/tabs/tabs.test.ts | 54 + .../dash/tabs/tabsChange.test.ts | 53 + .../dash/tabs/tabsWidgetOrder.test.ts | 128 + tests/opensource-suites/dash/toc/toc.test.ts | 34 + .../dash/toc/tocSettings.test.ts | 91 + .../dash/widgets/autoheight.test.ts | 90 + .../widgets/autoheightDynamicCases.test.ts | 145 + .../dash/widgets/katex.test.ts | 31 + .../dash/widgets/markdown.test.ts | 48 + .../dataset/base/dataset-editor.test.ts | 305 + .../dataset/base/preview.test.ts | 112 + .../dataset/base/save-button.test.ts | 133 + .../dataset/base/sources.test.ts | 149 + .../dataset/base/tabs.test.ts | 64 + .../dataset/base/unload-alert.test.ts | 99 + .../dataset/field-settings/colors.test.ts | 59 + .../dataset/filters/dataset-filters.test.ts | 120 + tests/opensource-suites/ql/common/run.test.ts | 34 + .../connections/selectSQLConnection.test.ts | 48 + .../ql/export/dataExport.test.ts | 23 + .../opensource-suites/ql/params/dates.test.ts | 47 + .../ql/params/intervals.test.ts | 50 + .../ql/visualizations/column/column.test.ts | 95 + .../flat-table/column-settings.test.ts | 60 + .../ql/visualizations/pie/labels.test.ts | 41 + .../selectVisualization.test.ts | 65 + .../ql/visualizations/treemap/basic.test.ts | 36 + tests/opensource-suites/utils.ts | 74 + .../wizard/datasets/add-dataset.test.ts | 32 + .../wizard/datasets/replace-dataset.test.ts | 44 + .../wizard/edit-history/basic.test.ts | 96 + .../wizard/fields/add-field.test.ts | 176 + .../wizard/fields/field-actions.test.ts | 109 + .../wizard/fields/parameters.test.ts | 124 + .../filters/filters-with-dialog-color.test.ts | 94 + .../tooltip-header-formatting.test.ts | 138 + tests/opensource-suites/wizard/utils.ts | 57 + .../visualizations/area/data-labels.test.ts | 105 + .../wizard/visualizations/area/export.test.ts | 50 + .../visualizations/area/hierarchy.test.ts | 56 + .../wizard/visualizations/area/html.test.ts | 74 + .../wizard/visualizations/area/split.test.ts | 53 + .../visualizations/area/stacking.test.ts | 45 + .../visualizations/area/tooltip.test.ts | 194 + .../visualizations/bar-x/coloring.test.ts | 74 + .../visualizations/bar-x/data-labels.test.ts | 121 + .../visualizations/bar-x/export.test.ts | 50 + .../visualizations/bar-x/hierarchy.test.ts | 51 + .../wizard/visualizations/bar-x/html.test.ts | 74 + .../bar-x/negative-values.test.ts | 46 + .../wizard/visualizations/bar-x/split.test.ts | 53 + .../visualizations/bar-x/tooltip.test.ts | 171 + .../bar-y/axis-settings.test.ts | 125 + .../visualizations/bar-y/coloring.test.ts | 92 + .../visualizations/bar-y/data-labels.test.ts | 146 + .../visualizations/bar-y/grouping.test.ts | 55 + .../visualizations/bar-y/hierarchy.test.ts | 51 + .../visualizations/bar-y/legend.test.ts | 51 + .../visualizations/bar-y/sorting.test.ts | 47 + .../visualizations/bar-y/tooltip.test.ts | 173 + .../visualizations/combined/axis.test.ts | 46 + .../visualizations/combined/coloring.test.ts | 45 + .../visualizations/combined/config.test.ts | 73 + .../combined/data-labels.test.ts | 62 + .../combined/hierarchies.test.ts | 117 + .../visualizations/combined/legend.test.ts | 233 + .../combined/measure-names-values.test.ts | 75 + .../visualizations/combined/navigator.test.ts | 44 + .../visualizations/combined/stacking.test.ts | 44 + .../visualizations/combined/tooltip.test.ts | 36 + .../wizard/visualizations/constants.ts | 4 + .../flat-table/coloring.test.ts | 192 + .../flat-table/column-settings.test.ts | 87 + .../flat-table/drag-n-drop.test.ts | 52 + .../visualizations/flat-table/export.test.ts | 65 + .../flat-table/formatting.test.ts | 33 + .../flat-table/linear-indicator.test.ts | 143 + .../flat-table/pinned-columns.test.ts | 73 + .../visualizations/flat-table/preview.test.ts | 76 + .../visualizations/flat-table/sorting.test.ts | 228 + .../visualizations/flat-table/totals.test.ts | 86 + .../flat-table/virtualization.test.ts | 139 + .../wizard/visualizations/line/axis.test.ts | 166 + .../wizard/visualizations/line/export.test.ts | 107 + .../visualizations/line/hierarchy.test.ts | 56 + .../wizard/visualizations/line/html.test.ts | 74 + .../visualizations/line/preview.test.ts | 78 + .../wizard/visualizations/line/shapes.test.ts | 74 + .../wizard/visualizations/line/split.test.ts | 119 + .../visualizations/line/tooltip.test.ts | 99 + .../visualizations/metric/basic.test.ts | 66 + .../visualizations/metric/color.test.ts | 68 + .../visualizations/metric/preview.test.ts | 45 + .../visualizations/metric/title.test.ts | 61 + .../wizard/visualizations/pie/basic.test.ts | 100 + .../pie/dataset-field-settings.test.ts | 96 + .../visualizations/pie/drag-n-drop.test.ts | 67 + .../wizard/visualizations/pie/export.test.ts | 51 + .../visualizations/pie/hierarchy.test.ts | 56 + .../wizard/visualizations/pie/markup.test.ts | 44 + .../pivot-table/coloring.test.ts | 129 + .../pivot-table/column-settings.test.ts | 51 + .../pivot-table/drag-n-drop.test.ts | 73 + .../visualizations/pivot-table/export.test.ts | 67 + .../pivot-table/formatting.test.ts | 35 + .../visualizations/pivot-table/header.test.ts | 150 + .../pivot-table/no-data.test.ts | 42 + .../pivot-table/old-pivot-table.test.ts | 41 + .../pivot-table/pagination.test.ts | 65 + .../pivot-table/pinned-columns.test.ts | 65 + .../pivot-table/sorting.test.ts | 236 + .../visualizations/pivot-table/totals.test.ts | 157 + .../pivot-table/virtualization.test.ts | 112 + .../scatter/axis-settings.test.ts | 61 + .../visualizations/scatter/coloring.test.ts | 67 + .../visualizations/treemap/export.test.ts | 64 + .../visualizations/treemap/hierarchy.test.ts | 56 + .../treemap/html-in-chart.test.ts | 49 + .../visualizations/treemap/tooltip.test.ts | 40 + tests/page-objects/BasePage.ts | 113 + tests/page-objects/ChartPage.ts | 317 + .../collections/CollectionsPage.ts | 119 + .../page-objects/collections/ContentTable.ts | 50 + .../collections/ContentTableRow.ts | 26 + tests/page-objects/collections/EmptyState.ts | 19 + tests/page-objects/collections/index.ts | 1 + tests/page-objects/common/ChartContainer.ts | 59 + tests/page-objects/common/DialogControl.ts | 84 + .../DialogControlPO/AppearanceInnerTitle.ts | 24 + .../common/DialogControlPO/AppearanceTitle.ts | 24 + .../DialogControlPO/DatasetFieldSelector.ts | 16 + .../common/DialogControlPO/ElementType.ts | 16 + .../common/DialogControlPO/FieldName.ts | 11 + .../DialogControlPO/ImpactTabsIdsSelector.ts | 16 + .../DialogControlPO/ImpactTypeSelector.ts | 16 + .../DialogControlPO/RequiredCheckbox.ts | 11 + .../DialogControlPO/SelectDatasetButton.ts | 21 + .../common/DialogControlPO/SourceType.ts | 16 + .../DialogControlPO/SourceTypeSelect.ts | 16 + tests/page-objects/common/DialogParameter.ts | 71 + tests/page-objects/common/Revisions.ts | 206 + tests/page-objects/common/Visualization.ts | 4 + .../common/abstract/CheckboxElementPO.ts | 7 + .../page-objects/common/abstract/ElementPO.ts | 36 + .../common/abstract/RadioGroupElementPO.ts | 7 + .../common/abstract/SelectElementPO.ts | 50 + .../common/abstract/TextInputElementPO.ts | 7 + tests/page-objects/common/abstract/types.ts | 11 + tests/page-objects/common/utils.ts | 10 + .../connections/ConnectionsPage.ts | 260 + tests/page-objects/constants/base.ts | 1 + tests/page-objects/constants/chartkit.ts | 13 + .../constants/common-selectors.ts | 19 + tests/page-objects/constants/common-urls.ts | 5 + .../page-objects/dashboard/ChartkitControl.ts | 53 + tests/page-objects/dashboard/DashTabs.ts | 32 + tests/page-objects/dashboard/DashboardPage.ts | 1161 + .../dashboard/DashboardSettings.ts | 61 + tests/page-objects/dashboard/Description.ts | 47 + tests/page-objects/dashboard/FixedHeader.ts | 42 + .../page-objects/dashboard/TableOfContent.ts | 42 + .../controlActions/ControlActions.ts | 414 + .../dashboard/controlActions/constants.ts | 8 + .../dataset/DatasetConnectionSection.ts | 108 + .../dataset/DatasetFieldsTable.ts | 75 + tests/page-objects/dataset/DatasetPage.ts | 294 + .../page-objects/dataset/DatasetTabSection.ts | 57 + tests/page-objects/dataset/constants.ts | 7 + tests/page-objects/dataset/utils.ts | 5 + tests/page-objects/ql/PreviewTable.ts | 61 + tests/page-objects/ql/QLPage.ts | 279 + tests/page-objects/types.ts | 7 + tests/page-objects/wizard/BarsSettings.ts | 83 + tests/page-objects/wizard/ChartKit.ts | 300 + tests/page-objects/wizard/ChartSettings.ts | 161 + tests/page-objects/wizard/ColorDialog.ts | 86 + tests/page-objects/wizard/ColumnSettings.ts | 81 + tests/page-objects/wizard/DatasetSelector.ts | 64 + .../page-objects/wizard/DialogMultidataset.ts | 46 + tests/page-objects/wizard/Field.ts | 3 + tests/page-objects/wizard/FieldEditor.ts | 59 + tests/page-objects/wizard/FilterEditor.ts | 122 + tests/page-objects/wizard/HierarchyEditor.ts | 87 + .../wizard/LabelsSettingsDialog.ts | 44 + tests/page-objects/wizard/LinkDialog.ts | 29 + .../page-objects/wizard/MetricColorsDialog.ts | 72 + .../page-objects/wizard/NavigationMinimal.ts | 39 + tests/page-objects/wizard/ParameterEditor.ts | 41 + .../page-objects/wizard/PlaceholderDialog.ts | 98 + .../wizard/SectionVisualization.ts | 412 + tests/page-objects/wizard/ShapeDialog.ts | 49 + .../wizard/VisualizationItemDialog.ts | 147 + tests/page-objects/wizard/WizardPage.ts | 269 + .../workbook/ActionsMoreDropdown.ts | 30 + .../workbook/CreateEntryButton.ts | 32 + .../workbook/DialogCollectionStructure.ts | 32 + .../workbook/DialogCreateEntry.ts | 33 + .../workbook/DialogDeleteWorkbook.ts | 21 + tests/page-objects/workbook/DialogWorkbook.ts | 26 + .../page-objects/workbook/EditEntityButton.ts | 19 + tests/page-objects/workbook/Filters.ts | 21 + .../workbook/NavigationMinimalPopup.ts | 62 + tests/page-objects/workbook/Workbook.ts | 122 + tests/playwright.config.ts | 132 + tests/suites/charts/comments/comments.test.ts | 63 + tests/suites/charts/export/export.test.ts | 65 + .../suites/charts/inspector/inspector.test.ts | 34 + .../suites/charts/navigator/navigator.test.ts | 23 + .../charts/render/tooManyRowsChart.test.ts | 25 + .../suites/charts/shareLink/shareLink.test.ts | 21 + .../charts/tooltip-in-wizard-charts.test.ts | 29 + .../connections/check-s3-button.test.ts | 25 + tests/suites/dash/base/actionPanel.test.ts | 58 + tests/suites/dash/base/autoRefresh.test.ts | 53 + .../base/globalAndChartsDefaultParams.test.ts | 152 + tests/suites/dash/base/loading.test.ts | 197 + tests/suites/dash/base/relations.test.ts | 81 + .../connections/dashWithConnections.test.ts | 38 + .../dashWithoutConnections.test.ts | 39 + .../dash/connections/ignoreConnection.test.ts | 46 + .../loadingConnectionsModal.test.ts | 37 + tests/suites/dash/constants.ts | 4 + .../suites/dash/dash-meta/description.test.ts | 36 + tests/suites/dash/helpers.ts | 122 + tests/suites/dash/mobile/mobileDash.test.ts | 181 + tests/suites/dash/params/editorParams.test.ts | 85 + tests/suites/dash/params/params.test.ts | 462 + .../revisionsListCopyDashboards.test.ts | 79 + .../revisionsListReadyDashboards.test.ts | 220 + ...evisionsListWithCreationDashboards.test.ts | 212 + ...evisionsListWithCreationSaveAsCopy.test.ts | 112 + .../dash/selectors/acceptableValues.test.ts | 71 + .../dash/selectors/addSelectors.test.ts | 54 + .../dash/selectors/chartInnerControls.test.ts | 174 + .../dash/selectors/datetimeSelectors.test.ts | 31 + .../selectors/editorSelectorChange.test.ts | 51 + .../withGlobalParams.test.ts | 129 + .../withoutGlobalParams.test.ts | 147 + .../withGlobalParams.test.ts | 136 + .../withoutGlobalParams.test.ts | 155 + .../dash/selectors/selectorsLabels.test.ts | 156 + tests/suites/dash/state/stateChange.test.ts | 361 + .../dash/state/stateExternalSelector.test.ts | 48 + .../dash/statistics/collectChartStats.test.ts | 22 + .../dash/statistics/collectDashStats.test.ts | 22 + tests/suites/dash/tabs/tabs.test.ts | 50 + tests/suites/dash/tabs/tabsChange.test.ts | 59 + .../suites/dash/tabs/tabsWidgetOrder.test.ts | 122 + tests/suites/dash/toc/toc.test.ts | 27 + tests/suites/dash/toc/tocSettings.test.ts | 96 + tests/suites/dash/widgets/autoheight.test.ts | 129 + .../suites/dash/widgets/customTooltip.test.ts | 38 + tests/suites/dash/widgets/markdown.test.ts | 35 + tests/suites/dash/widgets/menu.test.ts | 25 + .../suites/dash/widgets/renderCharts.test.ts | 33 + tests/suites/dataset/autoCreateYT.test.ts | 99 + tests/suites/dataset/avatars/avatars.test.ts | 29 + .../dataset/avatars/avatarsHandle.test.ts | 23 + .../dataset/avatars/avatarsRelations.test.ts | 62 + tests/suites/dataset/checkDeletedConn.test.ts | 19 + .../checkTransitionFromExistToNew.test.ts | 29 + tests/suites/dataset/history.test.ts | 748 + .../dataset/navigate-from-creation.test.ts | 58 + .../dataset/parameters/addParameter.test.ts | 48 + .../parameters/deleteParameter.test.ts | 36 + .../parameters/duplicateParameter.test.ts | 48 + .../dataset/parameters/editParameter.test.ts | 44 + .../parameterDialogValidation.test.ts | 111 + .../dataset/parameters/parametersTab.test.ts | 46 + .../relationsWithValidationError.test.ts | 69 + tests/suites/dataset/revisions.test.ts | 123 + .../suites/dataset/sourceEditorDialog.test.ts | 130 + .../dataset-sources-listing.test.ts | 307 + tests/suites/dataset/sources-list/helpers.ts | 26 + .../suites/dataset/sources-list/mock-data.ts | 91 + tests/suites/dataset/tabsHandling.test.ts | 83 + .../suites/editor/common/initialLoad.test.ts | 31 + .../suites/preview/comments/comments.test.ts | 25 + .../editor-actions/resetButton.test.ts | 153 + .../preview/tooltip/customTooltip.test.ts | 41 + .../sql/action-panel/qlRevisions.test.ts | 238 + tests/suites/sql/chart-actions/render.test.ts | 114 + tests/suites/sql/chart-actions/saving.test.ts | 148 + tests/suites/sql/common/loadChart.test.ts | 19 + .../connections/selectSQLConnection.test.ts | 46 + tests/suites/sql/export/dataExport.test.ts | 31 + tests/suites/sql/params/base.test.ts | 44 + tests/suites/sql/params/intervals.test.ts | 27 + .../sql/visualizations/flatTable.test.ts | 139 + .../selectVisualization.test.ts | 75 + .../action-panel/revisionsControls.test.ts | 29 + .../action-panel/wizardRevisions.test.ts | 221 + .../wizard/chart-actions/checkMenu.test.ts | 28 + .../wizard/chart-actions/saveChart.test.ts | 102 + .../wizard/chart-settings/grouping.test.ts | 74 + .../wizard/chart-settings/navigator.test.ts | 291 + .../wizard/chart-settings/pagination.test.ts | 69 + .../wizard/colors/changeColorScheme.test.ts | 121 + .../colors/coloredByMeasureNames.test.ts | 189 + .../colors/colorizingByMeasureValues.test.ts | 52 + .../colors/combineColorsWithShapes.test.ts | 78 + .../colors/gradientColorsDialog.test.ts | 40 + .../wizard/colum-settings/columnWidth.test.ts | 117 + .../colorize-combined-chart.test.ts | 292 + .../combined-chart-tooltip.test.ts | 77 + .../hierarchies-combined-chart.test.ts | 120 + .../wizard/combined-chart/labels.test.ts | 64 + .../layers-and-multidatasets.test.ts | 142 + .../measure-names-values.test.ts | 103 + .../suites/wizard/common/initialLoad.test.ts | 19 + tests/suites/wizard/constants.ts | 20 + .../wizard/data-types/dateGrouping.test.ts | 282 + .../suites/wizard/datasets/addDataset.test.ts | 28 + .../wizard/datasets/datasetActions.test.ts | 67 + .../wizard/datasets/datasetPopup.test.ts | 30 + .../wizard/datasets/replaceDataset.test.ts | 195 + .../wizard/datasets/requestDataset.test.ts | 44 + .../wizard/dialog-field/barsSettings.test.ts | 253 + tests/suites/wizard/export/flatTable.test.ts | 100 + tests/suites/wizard/export/line-chart.test.ts | 51 + tests/suites/wizard/export/pivotTable.test.ts | 52 + .../suites/wizard/export/scatterChart.test.ts | 116 + .../wizard/fields/dragAndDropField.test.ts | 110 + .../wizard/fields/duplicateField.test.ts | 43 + tests/suites/wizard/fields/editField.test.ts | 112 + .../fields/editVisualizationField.test.ts | 48 + .../wizard/fields/noneExistingDsField.test.ts | 39 + .../suites/wizard/fields/removeField.test.ts | 118 + .../suites/wizard/fields/searchField.test.ts | 62 + .../wizard/filters/dashboardFilters.test.ts | 292 + .../dashboardFiltersWithOperator.test.ts | 27 + .../wizard/filters/filtersWithDate.test.ts | 171 + .../filters/filtersWithMultiDataset.test.ts | 48 + .../wizard/filters/layersFilters.test.ts | 76 + .../wizard/filters/obligatoryFilters.test.ts | 66 + .../wizard/filters/replaceFilter.test.ts | 47 + .../wizard/flatTable/pagination.test.ts | 225 + tests/suites/wizard/flatTable/sort.test.ts | 52 + .../addDatasetToGeolayerChart.test.ts | 30 + .../wizard/geolayer/createGeolayers.test.ts | 90 + .../wizard/geolayer/geopointAddColor.test.ts | 97 + .../geolayer/geopoints/addLabel.test.ts | 32 + .../geolayer/geopoints/addMeasure.test.ts | 22 + .../geolayer/geopoints/addTooltip.test.ts | 35 + .../wizard/geolayer/removeGeolayers.test.ts | 31 + .../wizard/geolayer/renameGeolayers.test.ts | 46 + .../wizard/hierarchy/createHierarchy.test.ts | 99 + .../wizard/hierarchy/deleteHierarchy.test.ts | 66 + .../suites/wizard/hierarchy/drillDown.test.ts | 88 + .../hierarchy/drillDownInBigDataset.test.ts | 45 + .../wizard/hierarchy/drillDownReset.test.ts | 73 + .../wizard/hierarchy/editHierarchy.test.ts | 103 + tests/suites/wizard/labels/axisLabels.test.ts | 44 + .../wizard/labels/dataLabelsPosition.test.ts | 53 + tests/suites/wizard/labels/editLabel.test.ts | 89 + .../suites/wizard/labels/formatLabels.test.ts | 158 + tests/suites/wizard/links/dialogLinks.test.ts | 73 + tests/suites/wizard/metric/metric.test.ts | 53 + .../wizard/metric/metricCustomization.test.ts | 149 + .../wizard/navigation/navigation.test.ts | 88 + .../wizard/parameters/deleteParameter.test.ts | 40 + .../wizard/parameters/editParameter.test.ts | 234 + .../wizard/parameters/urlParameters.test.ts | 124 + .../wizard/pivot-table/formatting.test.ts | 47 + .../suites/wizard/pivot-table/header.test.ts | 139 + .../wizard/pivot-table/markdown.test.ts | 40 + .../wizard/pivot-table/mocks/headerMock.ts | 65 + .../wizard/pivot-table/pagination.test.ts | 153 + .../placeholder-settings/autoScale.test.ts | 150 + .../placeholder-settings/axisMode.test.ts | 230 + .../placeholder-settings/axisType.test.ts | 103 + .../placeholder-settings/connectNulls.test.ts | 155 + .../placeholders/general.placeholders.test.ts | 134 + .../placeholders/labelsPlaceholder.test.ts | 63 + .../placeholders/shapesPlaceholder.test.ts | 154 + .../placeholders/sortPlaceholder.test.ts | 38 + .../placeholders/tooltipsPlaceholder.test.ts | 65 + tests/suites/wizard/preview/apiRun.test.ts | 74 + .../suites/wizard/preview/dataExport.test.ts | 59 + .../shapes/combineShapesWithColors.test.ts | 216 + .../shapes/measureNamesShapesActions.test.ts | 241 + .../wizard/shapes/shapesWithoutColors.test.ts | 57 + .../sort/sortPlaceholderActions.test.ts | 407 + tests/suites/wizard/sort/treeSort.test.ts | 35 + .../visualizationsList.test.ts | 36 + tests/tsconfig.json | 11 + tests/types/config/charts.ts | 15 + tests/types/config/connections.ts | 8 + tests/types/config/dash.ts | 22 + tests/types/config/datasets.ts | 11 + tests/types/config/index.ts | 16 + tests/types/config/ql.ts | 12 + tests/types/config/wizard.ts | 6 + tests/utils/constants.ts | 210 + tests/utils/helpers.ts | 142 + tests/utils/index.ts | 342 + tests/utils/playwright/constants.ts | 19 + .../datalens/datalens-test-setup.ts | 116 + tests/utils/playwright/datalens/setup-e2e.ts | 9 + tests/utils/playwright/datalens/utils.ts | 177 + .../utils/playwright/globalTestDefinition.ts | 46 + tests/utils/playwright/types.ts | 98 + tests/utils/playwright/utils.ts | 56 + tsconfig.jest.json | 29 + tsconfig.json | 9 + workflow.json | 7 + 5528 files changed, 466293 insertions(+) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .gitignore create mode 100644 .lintstagedrc.json create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .prettierrc.js create mode 100644 .stylelintrc create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 api/server/app-env.ts create mode 100644 api/server/callbacks.ts create mode 100644 api/server/charts-engine.ts create mode 100644 api/server/components.ts create mode 100644 api/server/configs.ts create mode 100644 api/server/constants.ts create mode 100644 api/server/controllers.ts create mode 100644 api/server/dev-env.ts create mode 100644 api/server/expresskit.ts create mode 100644 api/server/local-dev.ts create mode 100644 api/server/middlewares.ts create mode 100644 api/server/plugins.ts create mode 100644 api/server/registry.ts create mode 100644 api/server/types.ts create mode 100644 api/server/utils.ts create mode 100644 api/ui/components/Collapse.ts create mode 100644 api/ui/components/ViewError.ts create mode 100644 api/ui/constants/common.ts create mode 100644 api/ui/constants/yfm.ts create mode 100644 api/ui/hocs.ts create mode 100644 api/ui/hooks.ts create mode 100644 api/ui/libs/logger.ts create mode 100644 api/ui/libs/metrica.ts create mode 100644 api/ui/libs/oldSdk.ts create mode 100644 api/ui/libs/sdk.ts create mode 100644 api/ui/tsconfig.json create mode 100644 api/ui/typings.ts create mode 100644 api/ui/utils/absurd.ts create mode 100644 api/ui/utils/common.ts create mode 100644 app-builder.config.ts create mode 100644 deploy/nginx/entrypoint.sh create mode 100644 deploy/nginx/nginx.conf create mode 100644 deploy/supervisor/supervisord.conf create mode 100644 dev-nginx/nginx.example.conf create mode 100644 dev/env/opensource/development.env create mode 100644 dist/deploy-public/robots/external/production/robots.txt create mode 100644 dist/public/favicon.ico create mode 100644 dist/public/os-favicon.ico create mode 100644 dist/public/robots.txt create mode 100644 jest.config.ts create mode 100644 jest/base-config.ts create mode 100644 jest/mappers/ignore-node-modules-mapper.ts create mode 100644 jest/mappers/moduleNameMappers.ts create mode 100644 jest/mocks/globals.mock.js create mode 100644 jest/mocks/index.ts create mode 100644 jest/mocks/style.mock.js create mode 100644 jest/setup-files/index.ts create mode 100644 jest/setup-files/testing-library.setup-after.ts create mode 100644 jest/setup-files/testing-library.setup.ts create mode 100644 jest/transformers/image-transformer.js create mode 100644 jest/transformers/index.ts create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 scripts/ci/lint-typecheck/features.sh create mode 100644 scripts/ci/lint-typecheck/helpers/check-features.ts create mode 100644 scripts/ci/statoscope-comment.jora create mode 100644 scripts/e2e/us-dump.sh create mode 100644 scripts/maintenance/utils/http-requests.js create mode 100644 scripts/maintenance/utils/logger.js create mode 100644 scripts/maintenance/utils/uitls.js create mode 100644 scripts/setup-dev-env.js create mode 100644 secrets/.keep create mode 100644 src/@types/chartkit-dl.d.ts create mode 100644 src/@types/express.d.ts create mode 100644 src/@types/global.d.ts create mode 100644 src/@types/highcharts-preparers.d.ts create mode 100644 src/@types/json-fn.d.ts create mode 100644 src/@types/nodekit.d.ts create mode 100644 src/@types/svg.d.ts create mode 100644 src/@types/window.d.ts create mode 100644 src/i18n-keysets/.arcignore create mode 100644 src/i18n-keysets/.gitignore create mode 100644 src/i18n-keysets/auth.dialog-change-password/context.json create mode 100644 src/i18n-keysets/auth.dialog-change-password/en.json create mode 100644 src/i18n-keysets/auth.dialog-change-password/keyset.json create mode 100644 src/i18n-keysets/auth.dialog-change-password/ru.json create mode 100644 src/i18n-keysets/auth.dialog-change-user-role/context.json create mode 100644 src/i18n-keysets/auth.dialog-change-user-role/en.json create mode 100644 src/i18n-keysets/auth.dialog-change-user-role/keyset.json create mode 100644 src/i18n-keysets/auth.dialog-change-user-role/ru.json create mode 100644 src/i18n-keysets/auth.dialog-delete-user/context.json create mode 100644 src/i18n-keysets/auth.dialog-delete-user/en.json create mode 100644 src/i18n-keysets/auth.dialog-delete-user/keyset.json create mode 100644 src/i18n-keysets/auth.dialog-delete-user/ru.json create mode 100644 src/i18n-keysets/auth.dialog-edit-profile/context.json create mode 100644 src/i18n-keysets/auth.dialog-edit-profile/en.json create mode 100644 src/i18n-keysets/auth.dialog-edit-profile/keyset.json create mode 100644 src/i18n-keysets/auth.dialog-edit-profile/ru.json create mode 100644 src/i18n-keysets/auth.form-controls/context.json create mode 100644 src/i18n-keysets/auth.form-controls/en.json create mode 100644 src/i18n-keysets/auth.form-controls/keyset.json create mode 100644 src/i18n-keysets/auth.form-controls/ru.json create mode 100644 src/i18n-keysets/auth.sign-in/context.json create mode 100644 src/i18n-keysets/auth.sign-in/en.json create mode 100644 src/i18n-keysets/auth.sign-in/keyset.json create mode 100644 src/i18n-keysets/auth.sign-in/ru.json create mode 100644 src/i18n-keysets/auth.sign-up/context.json create mode 100644 src/i18n-keysets/auth.sign-up/en.json create mode 100644 src/i18n-keysets/auth.sign-up/keyset.json create mode 100644 src/i18n-keysets/auth.sign-up/ru.json create mode 100644 src/i18n-keysets/auth.user-form-validation/context.json create mode 100644 src/i18n-keysets/auth.user-form-validation/en.json create mode 100644 src/i18n-keysets/auth.user-form-validation/keyset.json create mode 100644 src/i18n-keysets/auth.user-form-validation/ru.json create mode 100644 src/i18n-keysets/auth.user-profile.view/context.json create mode 100644 src/i18n-keysets/auth.user-profile.view/en.json create mode 100644 src/i18n-keysets/auth.user-profile.view/keyset.json create mode 100644 src/i18n-keysets/auth.user-profile.view/ru.json create mode 100644 src/i18n-keysets/chartkit.button-spin-input/context.json create mode 100644 src/i18n-keysets/chartkit.button-spin-input/en.json create mode 100644 src/i18n-keysets/chartkit.button-spin-input/keyset.json create mode 100644 src/i18n-keysets/chartkit.button-spin-input/ru.json create mode 100644 src/i18n-keysets/chartkit.control.items/context.json create mode 100644 src/i18n-keysets/chartkit.control.items/en.json create mode 100644 src/i18n-keysets/chartkit.control.items/keyset.json create mode 100644 src/i18n-keysets/chartkit.control.items/ru.json create mode 100644 src/i18n-keysets/chartkit.control/context.json create mode 100644 src/i18n-keysets/chartkit.control/en.json create mode 100644 src/i18n-keysets/chartkit.control/keyset.json create mode 100644 src/i18n-keysets/chartkit.control/ru.json create mode 100644 src/i18n-keysets/chartkit.custom-error/context.json create mode 100644 src/i18n-keysets/chartkit.custom-error/en.json create mode 100644 src/i18n-keysets/chartkit.custom-error/keyset.json create mode 100644 src/i18n-keysets/chartkit.custom-error/ru.json create mode 100644 src/i18n-keysets/chartkit.data-provider/context.json create mode 100644 src/i18n-keysets/chartkit.data-provider/en.json create mode 100644 src/i18n-keysets/chartkit.data-provider/keyset.json create mode 100644 src/i18n-keysets/chartkit.data-provider/ru.json create mode 100644 src/i18n-keysets/chartkit.error-details/context.json create mode 100644 src/i18n-keysets/chartkit.error-details/en.json create mode 100644 src/i18n-keysets/chartkit.error-details/keyset.json create mode 100644 src/i18n-keysets/chartkit.error-details/ru.json create mode 100644 src/i18n-keysets/chartkit.insights/context.json create mode 100644 src/i18n-keysets/chartkit.insights/en.json create mode 100644 src/i18n-keysets/chartkit.insights/keyset.json create mode 100644 src/i18n-keysets/chartkit.insights/ru.json create mode 100644 src/i18n-keysets/chartkit.menu.download-dialog/context.json create mode 100644 src/i18n-keysets/chartkit.menu.download-dialog/en.json create mode 100644 src/i18n-keysets/chartkit.menu.download-dialog/keyset.json create mode 100644 src/i18n-keysets/chartkit.menu.download-dialog/ru.json create mode 100644 src/i18n-keysets/chartkit.menu.export/context.json create mode 100644 src/i18n-keysets/chartkit.menu.export/en.json create mode 100644 src/i18n-keysets/chartkit.menu.export/keyset.json create mode 100644 src/i18n-keysets/chartkit.menu.export/ru.json create mode 100644 src/i18n-keysets/chartkit.menu.inspector/context.json create mode 100644 src/i18n-keysets/chartkit.menu.inspector/en.json create mode 100644 src/i18n-keysets/chartkit.menu.inspector/keyset.json create mode 100644 src/i18n-keysets/chartkit.menu.inspector/ru.json create mode 100644 src/i18n-keysets/chartkit.menu/context.json create mode 100644 src/i18n-keysets/chartkit.menu/en.json create mode 100644 src/i18n-keysets/chartkit.menu/keyset.json create mode 100644 src/i18n-keysets/chartkit.menu/ru.json create mode 100644 src/i18n-keysets/chartkit/context.json create mode 100644 src/i18n-keysets/chartkit/en.json create mode 100644 src/i18n-keysets/chartkit/keyset.json create mode 100644 src/i18n-keysets/chartkit/ru.json create mode 100644 src/i18n-keysets/collections/context.json create mode 100644 src/i18n-keysets/collections/en.json create mode 100644 src/i18n-keysets/collections/keyset.json create mode 100644 src/i18n-keysets/collections/ru.json create mode 100644 src/i18n-keysets/common.components/context.json create mode 100644 src/i18n-keysets/common.components/en.json create mode 100644 src/i18n-keysets/common.components/keyset.json create mode 100644 src/i18n-keysets/common.components/ru.json create mode 100644 src/i18n-keysets/common.data-types/context.json create mode 100644 src/i18n-keysets/common.data-types/en.json create mode 100644 src/i18n-keysets/common.data-types/keyset.json create mode 100644 src/i18n-keysets/common.data-types/ru.json create mode 100644 src/i18n-keysets/common.errors/context.json create mode 100644 src/i18n-keysets/common.errors/en.json create mode 100644 src/i18n-keysets/common.errors/keyset.json create mode 100644 src/i18n-keysets/common.errors/ru.json create mode 100644 src/i18n-keysets/common.read-only/context.json create mode 100644 src/i18n-keysets/common.read-only/en.json create mode 100644 src/i18n-keysets/common.read-only/keyset.json create mode 100644 src/i18n-keysets/common.read-only/ru.json create mode 100644 src/i18n-keysets/component.action-panel.view/context.json create mode 100644 src/i18n-keysets/component.action-panel.view/en.json create mode 100644 src/i18n-keysets/component.action-panel.view/keyset.json create mode 100644 src/i18n-keysets/component.action-panel.view/ru.json create mode 100644 src/i18n-keysets/component.aside-header-settings.view/context.json create mode 100644 src/i18n-keysets/component.aside-header-settings.view/en.json create mode 100644 src/i18n-keysets/component.aside-header-settings.view/keyset.json create mode 100644 src/i18n-keysets/component.aside-header-settings.view/ru.json create mode 100644 src/i18n-keysets/component.aside-header.view/context.json create mode 100644 src/i18n-keysets/component.aside-header.view/en.json create mode 100644 src/i18n-keysets/component.aside-header.view/keyset.json create mode 100644 src/i18n-keysets/component.aside-header.view/ru.json create mode 100644 src/i18n-keysets/component.chart-modeling-settings/context.json create mode 100644 src/i18n-keysets/component.chart-modeling-settings/en.json create mode 100644 src/i18n-keysets/component.chart-modeling-settings/keyset.json create mode 100644 src/i18n-keysets/component.chart-modeling-settings/ru.json create mode 100644 src/i18n-keysets/component.chart-save-controls/context.json create mode 100644 src/i18n-keysets/component.chart-save-controls/en.json create mode 100644 src/i18n-keysets/component.chart-save-controls/keyset.json create mode 100644 src/i18n-keysets/component.chart-save-controls/ru.json create mode 100644 src/i18n-keysets/component.chartkit-alerts.view/context.json create mode 100644 src/i18n-keysets/component.chartkit-alerts.view/en.json create mode 100644 src/i18n-keysets/component.chartkit-alerts.view/keyset.json create mode 100644 src/i18n-keysets/component.chartkit-alerts.view/ru.json create mode 100644 src/i18n-keysets/component.chartkit-error.codes/context.json create mode 100644 src/i18n-keysets/component.chartkit-error.codes/en.json create mode 100644 src/i18n-keysets/component.chartkit-error.codes/keyset.json create mode 100644 src/i18n-keysets/component.chartkit-error.codes/ru.json create mode 100644 src/i18n-keysets/component.chartkit-error.view/context.json create mode 100644 src/i18n-keysets/component.chartkit-error.view/en.json create mode 100644 src/i18n-keysets/component.chartkit-error.view/keyset.json create mode 100644 src/i18n-keysets/component.chartkit-error.view/ru.json create mode 100644 src/i18n-keysets/component.collection-breadcrumbs/context.json create mode 100644 src/i18n-keysets/component.collection-breadcrumbs/en.json create mode 100644 src/i18n-keysets/component.collection-breadcrumbs/keyset.json create mode 100644 src/i18n-keysets/component.collection-breadcrumbs/ru.json create mode 100644 src/i18n-keysets/component.collection-filters/context.json create mode 100644 src/i18n-keysets/component.collection-filters/en.json create mode 100644 src/i18n-keysets/component.collection-filters/keyset.json create mode 100644 src/i18n-keysets/component.collection-filters/ru.json create mode 100644 src/i18n-keysets/component.collections-structure/context.json create mode 100644 src/i18n-keysets/component.collections-structure/en.json create mode 100644 src/i18n-keysets/component.collections-structure/keyset.json create mode 100644 src/i18n-keysets/component.collections-structure/ru.json create mode 100644 src/i18n-keysets/component.color-palette-editor/context.json create mode 100644 src/i18n-keysets/component.color-palette-editor/en.json create mode 100644 src/i18n-keysets/component.color-palette-editor/keyset.json create mode 100644 src/i18n-keysets/component.color-palette-editor/ru.json create mode 100644 src/i18n-keysets/component.copy-entry-to-workbook-dialog/context.json create mode 100644 src/i18n-keysets/component.copy-entry-to-workbook-dialog/en.json create mode 100644 src/i18n-keysets/component.copy-entry-to-workbook-dialog/keyset.json create mode 100644 src/i18n-keysets/component.copy-entry-to-workbook-dialog/ru.json create mode 100644 src/i18n-keysets/component.dashkit.view/context.json create mode 100644 src/i18n-keysets/component.dashkit.view/en.json create mode 100644 src/i18n-keysets/component.dashkit.view/keyset.json create mode 100644 src/i18n-keysets/component.dashkit.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/context.json create mode 100644 src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/en.json create mode 100644 src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-aliases.view/context.json create mode 100644 src/i18n-keysets/component.dialog-aliases.view/en.json create mode 100644 src/i18n-keysets/component.dialog-aliases.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-aliases.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-collection-no-create-permission.view/context.json create mode 100644 src/i18n-keysets/component.dialog-collection-no-create-permission.view/en.json create mode 100644 src/i18n-keysets/component.dialog-collection-no-create-permission.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-collection-no-create-permission.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-copy-entry.view/context.json create mode 100644 src/i18n-keysets/component.dialog-copy-entry.view/en.json create mode 100644 src/i18n-keysets/component.dialog-copy-entry.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-copy-entry.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-copy-workbook.view/context.json create mode 100644 src/i18n-keysets/component.dialog-copy-workbook.view/en.json create mode 100644 src/i18n-keysets/component.dialog-copy-workbook.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-copy-workbook.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-create-dashboard.view/context.json create mode 100644 src/i18n-keysets/component.dialog-create-dashboard.view/en.json create mode 100644 src/i18n-keysets/component.dialog-create-dashboard.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-create-dashboard.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-create-editor-chart.view/context.json create mode 100644 src/i18n-keysets/component.dialog-create-editor-chart.view/en.json create mode 100644 src/i18n-keysets/component.dialog-create-editor-chart.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-create-editor-chart.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-create-folder.view/context.json create mode 100644 src/i18n-keysets/component.dialog-create-folder.view/en.json create mode 100644 src/i18n-keysets/component.dialog-create-folder.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-create-folder.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-create-workbook-entry.view/context.json create mode 100644 src/i18n-keysets/component.dialog-create-workbook-entry.view/en.json create mode 100644 src/i18n-keysets/component.dialog-create-workbook-entry.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-create-workbook-entry.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-dash-meta/context.json create mode 100644 src/i18n-keysets/component.dialog-dash-meta/en.json create mode 100644 src/i18n-keysets/component.dialog-dash-meta/keyset.json create mode 100644 src/i18n-keysets/component.dialog-dash-meta/ru.json create mode 100644 src/i18n-keysets/component.dialog-delete-entry.view/context.json create mode 100644 src/i18n-keysets/component.dialog-delete-entry.view/en.json create mode 100644 src/i18n-keysets/component.dialog-delete-entry.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-delete-entry.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-edit-favorites-alias.view/context.json create mode 100644 src/i18n-keysets/component.dialog-edit-favorites-alias.view/en.json create mode 100644 src/i18n-keysets/component.dialog-edit-favorites-alias.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-edit-favorites-alias.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-edit-warning.view/context.json create mode 100644 src/i18n-keysets/component.dialog-edit-warning.view/en.json create mode 100644 src/i18n-keysets/component.dialog-edit-warning.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-edit-warning.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-entry-description/context.json create mode 100644 src/i18n-keysets/component.dialog-entry-description/en.json create mode 100644 src/i18n-keysets/component.dialog-entry-description/keyset.json create mode 100644 src/i18n-keysets/component.dialog-entry-description/ru.json create mode 100644 src/i18n-keysets/component.dialog-move-entry.view/context.json create mode 100644 src/i18n-keysets/component.dialog-move-entry.view/en.json create mode 100644 src/i18n-keysets/component.dialog-move-entry.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-move-entry.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-parameter/context.json create mode 100644 src/i18n-keysets/component.dialog-parameter/en.json create mode 100644 src/i18n-keysets/component.dialog-parameter/keyset.json create mode 100644 src/i18n-keysets/component.dialog-parameter/ru.json create mode 100644 src/i18n-keysets/component.dialog-related-entities.view/context.json create mode 100644 src/i18n-keysets/component.dialog-related-entities.view/en.json create mode 100644 src/i18n-keysets/component.dialog-related-entities.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-related-entities.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-relations.view/context.json create mode 100644 src/i18n-keysets/component.dialog-relations.view/en.json create mode 100644 src/i18n-keysets/component.dialog-relations.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-relations.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-rename-entry.view/context.json create mode 100644 src/i18n-keysets/component.dialog-rename-entry.view/en.json create mode 100644 src/i18n-keysets/component.dialog-rename-entry.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-rename-entry.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-revisions-diff.view/context.json create mode 100644 src/i18n-keysets/component.dialog-revisions-diff.view/en.json create mode 100644 src/i18n-keysets/component.dialog-revisions-diff.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-revisions-diff.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-revisions.view/context.json create mode 100644 src/i18n-keysets/component.dialog-revisions.view/en.json create mode 100644 src/i18n-keysets/component.dialog-revisions.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-revisions.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-save-as-new-entry.view/context.json create mode 100644 src/i18n-keysets/component.dialog-save-as-new-entry.view/en.json create mode 100644 src/i18n-keysets/component.dialog-save-as-new-entry.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-save-as-new-entry.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-save-widget.view/context.json create mode 100644 src/i18n-keysets/component.dialog-save-widget.view/en.json create mode 100644 src/i18n-keysets/component.dialog-save-widget.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-save-widget.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-select-shared-entry.view/context.json create mode 100644 src/i18n-keysets/component.dialog-select-shared-entry.view/en.json create mode 100644 src/i18n-keysets/component.dialog-select-shared-entry.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-select-shared-entry.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-set-actual-confirm.view/context.json create mode 100644 src/i18n-keysets/component.dialog-set-actual-confirm.view/en.json create mode 100644 src/i18n-keysets/component.dialog-set-actual-confirm.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-set-actual-confirm.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-bindings.view/context.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-bindings.view/en.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-bindings.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-bindings.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-permissions.view/context.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-permissions.view/en.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-permissions.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-permissions.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-unbind.view/context.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-unbind.view/en.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-unbind.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-shared-entry-unbind.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-shared-related-entities.view/context.json create mode 100644 src/i18n-keysets/component.dialog-shared-related-entities.view/en.json create mode 100644 src/i18n-keysets/component.dialog-shared-related-entities.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-shared-related-entities.view/ru.json create mode 100644 src/i18n-keysets/component.dialog-switch-public.view/context.json create mode 100644 src/i18n-keysets/component.dialog-switch-public.view/en.json create mode 100644 src/i18n-keysets/component.dialog-switch-public.view/keyset.json create mode 100644 src/i18n-keysets/component.dialog-switch-public.view/ru.json create mode 100644 src/i18n-keysets/component.dl-dataset-field-list.view/context.json create mode 100644 src/i18n-keysets/component.dl-dataset-field-list.view/en.json create mode 100644 src/i18n-keysets/component.dl-dataset-field-list.view/keyset.json create mode 100644 src/i18n-keysets/component.dl-dataset-field-list.view/ru.json create mode 100644 src/i18n-keysets/component.dl-dialog-confirm.view/context.json create mode 100644 src/i18n-keysets/component.dl-dialog-confirm.view/en.json create mode 100644 src/i18n-keysets/component.dl-dialog-confirm.view/keyset.json create mode 100644 src/i18n-keysets/component.dl-dialog-confirm.view/ru.json create mode 100644 src/i18n-keysets/component.dl-dialog-filter.view/context.json create mode 100644 src/i18n-keysets/component.dl-dialog-filter.view/en.json create mode 100644 src/i18n-keysets/component.dl-dialog-filter.view/keyset.json create mode 100644 src/i18n-keysets/component.dl-dialog-filter.view/ru.json create mode 100644 src/i18n-keysets/component.dl-dialog-ql-parameter.view/context.json create mode 100644 src/i18n-keysets/component.dl-dialog-ql-parameter.view/en.json create mode 100644 src/i18n-keysets/component.dl-dialog-ql-parameter.view/keyset.json create mode 100644 src/i18n-keysets/component.dl-dialog-ql-parameter.view/ru.json create mode 100644 src/i18n-keysets/component.dl-dialog-warning.view/context.json create mode 100644 src/i18n-keysets/component.dl-dialog-warning.view/en.json create mode 100644 src/i18n-keysets/component.dl-dialog-warning.view/keyset.json create mode 100644 src/i18n-keysets/component.dl-dialog-warning.view/ru.json create mode 100644 src/i18n-keysets/component.dl-field-editor.view/context.json create mode 100644 src/i18n-keysets/component.dl-field-editor.view/en.json create mode 100644 src/i18n-keysets/component.dl-field-editor.view/keyset.json create mode 100644 src/i18n-keysets/component.dl-field-editor.view/ru.json create mode 100644 src/i18n-keysets/component.editor-console.view/context.json create mode 100644 src/i18n-keysets/component.editor-console.view/en.json create mode 100644 src/i18n-keysets/component.editor-console.view/keyset.json create mode 100644 src/i18n-keysets/component.editor-console.view/ru.json create mode 100644 src/i18n-keysets/component.editor-controls/context.json create mode 100644 src/i18n-keysets/component.editor-controls/en.json create mode 100644 src/i18n-keysets/component.editor-controls/keyset.json create mode 100644 src/i18n-keysets/component.editor-controls/ru.json create mode 100644 src/i18n-keysets/component.editor-search.view/context.json create mode 100644 src/i18n-keysets/component.editor-search.view/en.json create mode 100644 src/i18n-keysets/component.editor-search.view/keyset.json create mode 100644 src/i18n-keysets/component.editor-search.view/ru.json create mode 100644 src/i18n-keysets/component.entity-row.view/context.json create mode 100644 src/i18n-keysets/component.entity-row.view/en.json create mode 100644 src/i18n-keysets/component.entity-row.view/keyset.json create mode 100644 src/i18n-keysets/component.entity-row.view/ru.json create mode 100644 src/i18n-keysets/component.entry-context-menu.view/context.json create mode 100644 src/i18n-keysets/component.entry-context-menu.view/en.json create mode 100644 src/i18n-keysets/component.entry-context-menu.view/keyset.json create mode 100644 src/i18n-keysets/component.entry-context-menu.view/ru.json create mode 100644 src/i18n-keysets/component.entry-label.view/context.json create mode 100644 src/i18n-keysets/component.entry-label.view/en.json create mode 100644 src/i18n-keysets/component.entry-label.view/keyset.json create mode 100644 src/i18n-keysets/component.entry-label.view/ru.json create mode 100644 src/i18n-keysets/component.entry-row.view/context.json create mode 100644 src/i18n-keysets/component.entry-row.view/en.json create mode 100644 src/i18n-keysets/component.entry-row.view/keyset.json create mode 100644 src/i18n-keysets/component.entry-row.view/ru.json create mode 100644 src/i18n-keysets/component.entry-suggest.view/context.json create mode 100644 src/i18n-keysets/component.entry-suggest.view/en.json create mode 100644 src/i18n-keysets/component.entry-suggest.view/keyset.json create mode 100644 src/i18n-keysets/component.entry-suggest.view/ru.json create mode 100644 src/i18n-keysets/component.error-content.view/context.json create mode 100644 src/i18n-keysets/component.error-content.view/en.json create mode 100644 src/i18n-keysets/component.error-content.view/keyset.json create mode 100644 src/i18n-keysets/component.error-content.view/ru.json create mode 100644 src/i18n-keysets/component.error-dialog.view/context.json create mode 100644 src/i18n-keysets/component.error-dialog.view/en.json create mode 100644 src/i18n-keysets/component.error-dialog.view/keyset.json create mode 100644 src/i18n-keysets/component.error-dialog.view/ru.json create mode 100644 src/i18n-keysets/component.expandable-panel.view/context.json create mode 100644 src/i18n-keysets/component.expandable-panel.view/en.json create mode 100644 src/i18n-keysets/component.expandable-panel.view/keyset.json create mode 100644 src/i18n-keysets/component.expandable-panel.view/ru.json create mode 100644 src/i18n-keysets/component.field-editor.view/context.json create mode 100644 src/i18n-keysets/component.field-editor.view/en.json create mode 100644 src/i18n-keysets/component.field-editor.view/keyset.json create mode 100644 src/i18n-keysets/component.field-editor.view/ru.json create mode 100644 src/i18n-keysets/component.formatted-value.view/context.json create mode 100644 src/i18n-keysets/component.formatted-value.view/en.json create mode 100644 src/i18n-keysets/component.formatted-value.view/keyset.json create mode 100644 src/i18n-keysets/component.formatted-value.view/ru.json create mode 100644 src/i18n-keysets/component.iam-access-dialog/context.json create mode 100644 src/i18n-keysets/component.iam-access-dialog/en.json create mode 100644 src/i18n-keysets/component.iam-access-dialog/keyset.json create mode 100644 src/i18n-keysets/component.iam-access-dialog/ru.json create mode 100644 src/i18n-keysets/component.inaccessible-on-mobile-inset.view/context.json create mode 100644 src/i18n-keysets/component.inaccessible-on-mobile-inset.view/en.json create mode 100644 src/i18n-keysets/component.inaccessible-on-mobile-inset.view/keyset.json create mode 100644 src/i18n-keysets/component.inaccessible-on-mobile-inset.view/ru.json create mode 100644 src/i18n-keysets/component.list-with-menu.view/context.json create mode 100644 src/i18n-keysets/component.list-with-menu.view/en.json create mode 100644 src/i18n-keysets/component.list-with-menu.view/keyset.json create mode 100644 src/i18n-keysets/component.list-with-menu.view/ru.json create mode 100644 src/i18n-keysets/component.migrate-to-workbook-dialog/context.json create mode 100644 src/i18n-keysets/component.migrate-to-workbook-dialog/en.json create mode 100644 src/i18n-keysets/component.migrate-to-workbook-dialog/keyset.json create mode 100644 src/i18n-keysets/component.migrate-to-workbook-dialog/ru.json create mode 100644 src/i18n-keysets/component.monaco.view/context.json create mode 100644 src/i18n-keysets/component.monaco.view/en.json create mode 100644 src/i18n-keysets/component.monaco.view/keyset.json create mode 100644 src/i18n-keysets/component.monaco.view/ru.json create mode 100644 src/i18n-keysets/component.navigation-prompt/context.json create mode 100644 src/i18n-keysets/component.navigation-prompt/en.json create mode 100644 src/i18n-keysets/component.navigation-prompt/keyset.json create mode 100644 src/i18n-keysets/component.navigation-prompt/ru.json create mode 100644 src/i18n-keysets/component.navigation.view/context.json create mode 100644 src/i18n-keysets/component.navigation.view/en.json create mode 100644 src/i18n-keysets/component.navigation.view/keyset.json create mode 100644 src/i18n-keysets/component.navigation.view/ru.json create mode 100644 src/i18n-keysets/component.need-reset-dialog.view/context.json create mode 100644 src/i18n-keysets/component.need-reset-dialog.view/en.json create mode 100644 src/i18n-keysets/component.need-reset-dialog.view/keyset.json create mode 100644 src/i18n-keysets/component.need-reset-dialog.view/ru.json create mode 100644 src/i18n-keysets/component.number-field-formatting.view/context.json create mode 100644 src/i18n-keysets/component.number-field-formatting.view/en.json create mode 100644 src/i18n-keysets/component.number-field-formatting.view/keyset.json create mode 100644 src/i18n-keysets/component.number-field-formatting.view/ru.json create mode 100644 src/i18n-keysets/component.operations/context.json create mode 100644 src/i18n-keysets/component.operations/en.json create mode 100644 src/i18n-keysets/component.operations/keyset.json create mode 100644 src/i18n-keysets/component.operations/ru.json create mode 100644 src/i18n-keysets/component.path-select.view/context.json create mode 100644 src/i18n-keysets/component.path-select.view/en.json create mode 100644 src/i18n-keysets/component.path-select.view/keyset.json create mode 100644 src/i18n-keysets/component.path-select.view/ru.json create mode 100644 src/i18n-keysets/component.permission-select.view/context.json create mode 100644 src/i18n-keysets/component.permission-select.view/en.json create mode 100644 src/i18n-keysets/component.permission-select.view/keyset.json create mode 100644 src/i18n-keysets/component.permission-select.view/ru.json create mode 100644 src/i18n-keysets/component.relative-dates-picker.view/context.json create mode 100644 src/i18n-keysets/component.relative-dates-picker.view/en.json create mode 100644 src/i18n-keysets/component.relative-dates-picker.view/keyset.json create mode 100644 src/i18n-keysets/component.relative-dates-picker.view/ru.json create mode 100644 src/i18n-keysets/component.revisions-panel.view/context.json create mode 100644 src/i18n-keysets/component.revisions-panel.view/en.json create mode 100644 src/i18n-keysets/component.revisions-panel.view/keyset.json create mode 100644 src/i18n-keysets/component.revisions-panel.view/ru.json create mode 100644 src/i18n-keysets/component.select-migration-to-workbook-dialog/context.json create mode 100644 src/i18n-keysets/component.select-migration-to-workbook-dialog/en.json create mode 100644 src/i18n-keysets/component.select-migration-to-workbook-dialog/keyset.json create mode 100644 src/i18n-keysets/component.select-migration-to-workbook-dialog/ru.json create mode 100644 src/i18n-keysets/component.toaster.view/context.json create mode 100644 src/i18n-keysets/component.toaster.view/en.json create mode 100644 src/i18n-keysets/component.toaster.view/keyset.json create mode 100644 src/i18n-keysets/component.toaster.view/ru.json create mode 100644 src/i18n-keysets/component.user-menu.view/context.json create mode 100644 src/i18n-keysets/component.user-menu.view/en.json create mode 100644 src/i18n-keysets/component.user-menu.view/keyset.json create mode 100644 src/i18n-keysets/component.user-menu.view/ru.json create mode 100644 src/i18n-keysets/component.users-suggest.view/context.json create mode 100644 src/i18n-keysets/component.users-suggest.view/en.json create mode 100644 src/i18n-keysets/component.users-suggest.view/keyset.json create mode 100644 src/i18n-keysets/component.users-suggest.view/ru.json create mode 100644 src/i18n-keysets/component.view-error.view/context.json create mode 100644 src/i18n-keysets/component.view-error.view/en.json create mode 100644 src/i18n-keysets/component.view-error.view/keyset.json create mode 100644 src/i18n-keysets/component.view-error.view/ru.json create mode 100644 src/i18n-keysets/component.workbook-export-dialog.view/context.json create mode 100644 src/i18n-keysets/component.workbook-export-dialog.view/en.json create mode 100644 src/i18n-keysets/component.workbook-export-dialog.view/keyset.json create mode 100644 src/i18n-keysets/component.workbook-export-dialog.view/ru.json create mode 100644 src/i18n-keysets/component.workbook-export.notifications/context.json create mode 100644 src/i18n-keysets/component.workbook-export.notifications/en.json create mode 100644 src/i18n-keysets/component.workbook-export.notifications/keyset.json create mode 100644 src/i18n-keysets/component.workbook-export.notifications/ru.json create mode 100644 src/i18n-keysets/component.workbook-import-view.view/context.json create mode 100644 src/i18n-keysets/component.workbook-import-view.view/en.json create mode 100644 src/i18n-keysets/component.workbook-import-view.view/keyset.json create mode 100644 src/i18n-keysets/component.workbook-import-view.view/ru.json create mode 100644 src/i18n-keysets/component.workbook.navigation.view/context.json create mode 100644 src/i18n-keysets/component.workbook.navigation.view/en.json create mode 100644 src/i18n-keysets/component.workbook.navigation.view/keyset.json create mode 100644 src/i18n-keysets/component.workbook.navigation.view/ru.json create mode 100644 src/i18n-keysets/component.wysiwyg-editor.view/context.json create mode 100644 src/i18n-keysets/component.wysiwyg-editor.view/en.json create mode 100644 src/i18n-keysets/component.wysiwyg-editor.view/keyset.json create mode 100644 src/i18n-keysets/component.wysiwyg-editor.view/ru.json create mode 100644 src/i18n-keysets/components.common.Datepicker/context.json create mode 100644 src/i18n-keysets/components.common.Datepicker/en.json create mode 100644 src/i18n-keysets/components.common.Datepicker/keyset.json create mode 100644 src/i18n-keysets/components.common.Datepicker/ru.json create mode 100644 src/i18n-keysets/components.common.SimpleDatepicker/context.json create mode 100644 src/i18n-keysets/components.common.SimpleDatepicker/en.json create mode 100644 src/i18n-keysets/components.common.SimpleDatepicker/keyset.json create mode 100644 src/i18n-keysets/components.common.SimpleDatepicker/ru.json create mode 100644 src/i18n-keysets/components.common.YCSelect/context.json create mode 100644 src/i18n-keysets/components.common.YCSelect/en.json create mode 100644 src/i18n-keysets/components.common.YCSelect/keyset.json create mode 100644 src/i18n-keysets/components.common.YCSelect/ru.json create mode 100644 src/i18n-keysets/connections.connectors-list.view/context.json create mode 100644 src/i18n-keysets/connections.connectors-list.view/en.json create mode 100644 src/i18n-keysets/connections.connectors-list.view/keyset.json create mode 100644 src/i18n-keysets/connections.connectors-list.view/ru.json create mode 100644 src/i18n-keysets/connections.file.view/context.json create mode 100644 src/i18n-keysets/connections.file.view/en.json create mode 100644 src/i18n-keysets/connections.file.view/keyset.json create mode 100644 src/i18n-keysets/connections.file.view/ru.json create mode 100644 src/i18n-keysets/connections.form/context.json create mode 100644 src/i18n-keysets/connections.form/en.json create mode 100644 src/i18n-keysets/connections.form/keyset.json create mode 100644 src/i18n-keysets/connections.form/ru.json create mode 100644 src/i18n-keysets/connections.gsheet.view/context.json create mode 100644 src/i18n-keysets/connections.gsheet.view/en.json create mode 100644 src/i18n-keysets/connections.gsheet.view/keyset.json create mode 100644 src/i18n-keysets/connections.gsheet.view/ru.json create mode 100644 src/i18n-keysets/connections.yadocs.view/context.json create mode 100644 src/i18n-keysets/connections.yadocs.view/en.json create mode 100644 src/i18n-keysets/connections.yadocs.view/keyset.json create mode 100644 src/i18n-keysets/connections.yadocs.view/ru.json create mode 100644 src/i18n-keysets/dash.action-panel.view/context.json create mode 100644 src/i18n-keysets/dash.action-panel.view/en.json create mode 100644 src/i18n-keysets/dash.action-panel.view/keyset.json create mode 100644 src/i18n-keysets/dash.action-panel.view/ru.json create mode 100644 src/i18n-keysets/dash.chartkit-menu.view/context.json create mode 100644 src/i18n-keysets/dash.chartkit-menu.view/en.json create mode 100644 src/i18n-keysets/dash.chartkit-menu.view/keyset.json create mode 100644 src/i18n-keysets/dash.chartkit-menu.view/ru.json create mode 100644 src/i18n-keysets/dash.control-dialog.edit/context.json create mode 100644 src/i18n-keysets/dash.control-dialog.edit/en.json create mode 100644 src/i18n-keysets/dash.control-dialog.edit/keyset.json create mode 100644 src/i18n-keysets/dash.control-dialog.edit/ru.json create mode 100644 src/i18n-keysets/dash.dashkit-control.error/context.json create mode 100644 src/i18n-keysets/dash.dashkit-control.error/en.json create mode 100644 src/i18n-keysets/dash.dashkit-control.error/keyset.json create mode 100644 src/i18n-keysets/dash.dashkit-control.error/ru.json create mode 100644 src/i18n-keysets/dash.dashkit-plugin-common.view/context.json create mode 100644 src/i18n-keysets/dash.dashkit-plugin-common.view/en.json create mode 100644 src/i18n-keysets/dash.dashkit-plugin-common.view/keyset.json create mode 100644 src/i18n-keysets/dash.dashkit-plugin-common.view/ru.json create mode 100644 src/i18n-keysets/dash.dashkit-plugin-control.view/context.json create mode 100644 src/i18n-keysets/dash.dashkit-plugin-control.view/en.json create mode 100644 src/i18n-keysets/dash.dashkit-plugin-control.view/keyset.json create mode 100644 src/i18n-keysets/dash.dashkit-plugin-control.view/ru.json create mode 100644 src/i18n-keysets/dash.dialogs-common.edit/context.json create mode 100644 src/i18n-keysets/dash.dialogs-common.edit/en.json create mode 100644 src/i18n-keysets/dash.dialogs-common.edit/keyset.json create mode 100644 src/i18n-keysets/dash.dialogs-common.edit/ru.json create mode 100644 src/i18n-keysets/dash.edit-query-dialog/context.json create mode 100644 src/i18n-keysets/dash.edit-query-dialog/en.json create mode 100644 src/i18n-keysets/dash.edit-query-dialog/keyset.json create mode 100644 src/i18n-keysets/dash.edit-query-dialog/ru.json create mode 100644 src/i18n-keysets/dash.empty-state.view/context.json create mode 100644 src/i18n-keysets/dash.empty-state.view/en.json create mode 100644 src/i18n-keysets/dash.empty-state.view/keyset.json create mode 100644 src/i18n-keysets/dash.empty-state.view/ru.json create mode 100644 src/i18n-keysets/dash.error.view/context.json create mode 100644 src/i18n-keysets/dash.error.view/en.json create mode 100644 src/i18n-keysets/dash.error.view/keyset.json create mode 100644 src/i18n-keysets/dash.error.view/ru.json create mode 100644 src/i18n-keysets/dash.extended-settings-dialog.edit/context.json create mode 100644 src/i18n-keysets/dash.extended-settings-dialog.edit/en.json create mode 100644 src/i18n-keysets/dash.extended-settings-dialog.edit/keyset.json create mode 100644 src/i18n-keysets/dash.extended-settings-dialog.edit/ru.json create mode 100644 src/i18n-keysets/dash.group-controls-dialog.edit/context.json create mode 100644 src/i18n-keysets/dash.group-controls-dialog.edit/en.json create mode 100644 src/i18n-keysets/dash.group-controls-dialog.edit/keyset.json create mode 100644 src/i18n-keysets/dash.group-controls-dialog.edit/ru.json create mode 100644 src/i18n-keysets/dash.image-dialog.edit/context.json create mode 100644 src/i18n-keysets/dash.image-dialog.edit/en.json create mode 100644 src/i18n-keysets/dash.image-dialog.edit/keyset.json create mode 100644 src/i18n-keysets/dash.image-dialog.edit/ru.json create mode 100644 src/i18n-keysets/dash.main.view/context.json create mode 100644 src/i18n-keysets/dash.main.view/en.json create mode 100644 src/i18n-keysets/dash.main.view/keyset.json create mode 100644 src/i18n-keysets/dash.main.view/ru.json create mode 100644 src/i18n-keysets/dash.navigation-input.edit/context.json create mode 100644 src/i18n-keysets/dash.navigation-input.edit/en.json create mode 100644 src/i18n-keysets/dash.navigation-input.edit/keyset.json create mode 100644 src/i18n-keysets/dash.navigation-input.edit/ru.json create mode 100644 src/i18n-keysets/dash.palette-background/context.json create mode 100644 src/i18n-keysets/dash.palette-background/en.json create mode 100644 src/i18n-keysets/dash.palette-background/keyset.json create mode 100644 src/i18n-keysets/dash.palette-background/ru.json create mode 100644 src/i18n-keysets/dash.params-button-dialog.view/context.json create mode 100644 src/i18n-keysets/dash.params-button-dialog.view/en.json create mode 100644 src/i18n-keysets/dash.params-button-dialog.view/keyset.json create mode 100644 src/i18n-keysets/dash.params-button-dialog.view/ru.json create mode 100644 src/i18n-keysets/dash.params-setting.view/context.json create mode 100644 src/i18n-keysets/dash.params-setting.view/en.json create mode 100644 src/i18n-keysets/dash.params-setting.view/keyset.json create mode 100644 src/i18n-keysets/dash.params-setting.view/ru.json create mode 100644 src/i18n-keysets/dash.popup-widgets-order.view/context.json create mode 100644 src/i18n-keysets/dash.popup-widgets-order.view/en.json create mode 100644 src/i18n-keysets/dash.popup-widgets-order.view/keyset.json create mode 100644 src/i18n-keysets/dash.popup-widgets-order.view/ru.json create mode 100644 src/i18n-keysets/dash.settings-dialog.edit/context.json create mode 100644 src/i18n-keysets/dash.settings-dialog.edit/en.json create mode 100644 src/i18n-keysets/dash.settings-dialog.edit/keyset.json create mode 100644 src/i18n-keysets/dash.settings-dialog.edit/ru.json create mode 100644 src/i18n-keysets/dash.store.view/context.json create mode 100644 src/i18n-keysets/dash.store.view/en.json create mode 100644 src/i18n-keysets/dash.store.view/keyset.json create mode 100644 src/i18n-keysets/dash.store.view/ru.json create mode 100644 src/i18n-keysets/dash.table-of-content.view/context.json create mode 100644 src/i18n-keysets/dash.table-of-content.view/en.json create mode 100644 src/i18n-keysets/dash.table-of-content.view/keyset.json create mode 100644 src/i18n-keysets/dash.table-of-content.view/ru.json create mode 100644 src/i18n-keysets/dash.tabs-dialog.edit/context.json create mode 100644 src/i18n-keysets/dash.tabs-dialog.edit/en.json create mode 100644 src/i18n-keysets/dash.tabs-dialog.edit/keyset.json create mode 100644 src/i18n-keysets/dash.tabs-dialog.edit/ru.json create mode 100644 src/i18n-keysets/dash.text-dialog.edit/context.json create mode 100644 src/i18n-keysets/dash.text-dialog.edit/en.json create mode 100644 src/i18n-keysets/dash.text-dialog.edit/keyset.json create mode 100644 src/i18n-keysets/dash.text-dialog.edit/ru.json create mode 100644 src/i18n-keysets/dash.title-dialog.edit/context.json create mode 100644 src/i18n-keysets/dash.title-dialog.edit/en.json create mode 100644 src/i18n-keysets/dash.title-dialog.edit/keyset.json create mode 100644 src/i18n-keysets/dash.title-dialog.edit/ru.json create mode 100644 src/i18n-keysets/dash.widget-dialog.edit/context.json create mode 100644 src/i18n-keysets/dash.widget-dialog.edit/en.json create mode 100644 src/i18n-keysets/dash.widget-dialog.edit/keyset.json create mode 100644 src/i18n-keysets/dash.widget-dialog.edit/ru.json create mode 100644 src/i18n-keysets/datalens.landing.error/context.json create mode 100644 src/i18n-keysets/datalens.landing.error/en.json create mode 100644 src/i18n-keysets/datalens.landing.error/keyset.json create mode 100644 src/i18n-keysets/datalens.landing.error/ru.json create mode 100644 src/i18n-keysets/dataset.dataset-creation.create/context.json create mode 100644 src/i18n-keysets/dataset.dataset-creation.create/en.json create mode 100644 src/i18n-keysets/dataset.dataset-creation.create/keyset.json create mode 100644 src/i18n-keysets/dataset.dataset-creation.create/ru.json create mode 100644 src/i18n-keysets/dataset.dataset-editor.modify/context.json create mode 100644 src/i18n-keysets/dataset.dataset-editor.modify/en.json create mode 100644 src/i18n-keysets/dataset.dataset-editor.modify/keyset.json create mode 100644 src/i18n-keysets/dataset.dataset-editor.modify/ru.json create mode 100644 src/i18n-keysets/dataset.field-settings-dialog.modify/context.json create mode 100644 src/i18n-keysets/dataset.field-settings-dialog.modify/en.json create mode 100644 src/i18n-keysets/dataset.field-settings-dialog.modify/keyset.json create mode 100644 src/i18n-keysets/dataset.field-settings-dialog.modify/ru.json create mode 100644 src/i18n-keysets/dataset.filters-tab.modify/context.json create mode 100644 src/i18n-keysets/dataset.filters-tab.modify/en.json create mode 100644 src/i18n-keysets/dataset.filters-tab.modify/keyset.json create mode 100644 src/i18n-keysets/dataset.filters-tab.modify/ru.json create mode 100644 src/i18n-keysets/dataset.notifications.view/context.json create mode 100644 src/i18n-keysets/dataset.notifications.view/en.json create mode 100644 src/i18n-keysets/dataset.notifications.view/keyset.json create mode 100644 src/i18n-keysets/dataset.notifications.view/ru.json create mode 100644 src/i18n-keysets/dataset.parameters-tab.modify/context.json create mode 100644 src/i18n-keysets/dataset.parameters-tab.modify/en.json create mode 100644 src/i18n-keysets/dataset.parameters-tab.modify/keyset.json create mode 100644 src/i18n-keysets/dataset.parameters-tab.modify/ru.json create mode 100644 src/i18n-keysets/dataset.rls.modify/context.json create mode 100644 src/i18n-keysets/dataset.rls.modify/en.json create mode 100644 src/i18n-keysets/dataset.rls.modify/keyset.json create mode 100644 src/i18n-keysets/dataset.rls.modify/ru.json create mode 100644 src/i18n-keysets/dataset.sources-tab.modify/context.json create mode 100644 src/i18n-keysets/dataset.sources-tab.modify/en.json create mode 100644 src/i18n-keysets/dataset.sources-tab.modify/keyset.json create mode 100644 src/i18n-keysets/dataset.sources-tab.modify/ru.json create mode 100644 src/i18n-keysets/dataset.validation/context.json create mode 100644 src/i18n-keysets/dataset.validation/en.json create mode 100644 src/i18n-keysets/dataset.validation/keyset.json create mode 100644 src/i18n-keysets/dataset.validation/ru.json create mode 100644 src/i18n-keysets/dl-dialog-settings/context.json create mode 100644 src/i18n-keysets/dl-dialog-settings/en.json create mode 100644 src/i18n-keysets/dl-dialog-settings/keyset.json create mode 100644 src/i18n-keysets/dl-dialog-settings/ru.json create mode 100644 src/i18n-keysets/editor.button-save.view/context.json create mode 100644 src/i18n-keysets/editor.button-save.view/en.json create mode 100644 src/i18n-keysets/editor.button-save.view/keyset.json create mode 100644 src/i18n-keysets/editor.button-save.view/ru.json create mode 100644 src/i18n-keysets/editor.common.view/context.json create mode 100644 src/i18n-keysets/editor.common.view/en.json create mode 100644 src/i18n-keysets/editor.common.view/keyset.json create mode 100644 src/i18n-keysets/editor.common.view/ru.json create mode 100644 src/i18n-keysets/editor.dialog-confirm-warning.view/context.json create mode 100644 src/i18n-keysets/editor.dialog-confirm-warning.view/en.json create mode 100644 src/i18n-keysets/editor.dialog-confirm-warning.view/keyset.json create mode 100644 src/i18n-keysets/editor.dialog-confirm-warning.view/ru.json create mode 100644 src/i18n-keysets/editor.dialog-no-edit-rights.view/context.json create mode 100644 src/i18n-keysets/editor.dialog-no-edit-rights.view/en.json create mode 100644 src/i18n-keysets/editor.dialog-no-edit-rights.view/keyset.json create mode 100644 src/i18n-keysets/editor.dialog-no-edit-rights.view/ru.json create mode 100644 src/i18n-keysets/editor.docs.view/context.json create mode 100644 src/i18n-keysets/editor.docs.view/en.json create mode 100644 src/i18n-keysets/editor.docs.view/keyset.json create mode 100644 src/i18n-keysets/editor.docs.view/ru.json create mode 100644 src/i18n-keysets/editor.editor-page-error.view/context.json create mode 100644 src/i18n-keysets/editor.editor-page-error.view/en.json create mode 100644 src/i18n-keysets/editor.editor-page-error.view/keyset.json create mode 100644 src/i18n-keysets/editor.editor-page-error.view/ru.json create mode 100644 src/i18n-keysets/editor.fetch.view/context.json create mode 100644 src/i18n-keysets/editor.fetch.view/en.json create mode 100644 src/i18n-keysets/editor.fetch.view/keyset.json create mode 100644 src/i18n-keysets/editor.fetch.view/ru.json create mode 100644 src/i18n-keysets/editor.grid-scheme-select.view/context.json create mode 100644 src/i18n-keysets/editor.grid-scheme-select.view/en.json create mode 100644 src/i18n-keysets/editor.grid-scheme-select.view/keyset.json create mode 100644 src/i18n-keysets/editor.grid-scheme-select.view/ru.json create mode 100644 src/i18n-keysets/exports.enable-data-export-settings/context.json create mode 100644 src/i18n-keysets/exports.enable-data-export-settings/en.json create mode 100644 src/i18n-keysets/exports.enable-data-export-settings/keyset.json create mode 100644 src/i18n-keysets/exports.enable-data-export-settings/ru.json create mode 100644 src/i18n-keysets/main.service-settings.view/context.json create mode 100644 src/i18n-keysets/main.service-settings.view/en.json create mode 100644 src/i18n-keysets/main.service-settings.view/keyset.json create mode 100644 src/i18n-keysets/main.service-settings.view/ru.json create mode 100644 src/i18n-keysets/new-workbooks.empty-table/context.json create mode 100644 src/i18n-keysets/new-workbooks.empty-table/en.json create mode 100644 src/i18n-keysets/new-workbooks.empty-table/keyset.json create mode 100644 src/i18n-keysets/new-workbooks.empty-table/ru.json create mode 100644 src/i18n-keysets/new-workbooks.table-filters/context.json create mode 100644 src/i18n-keysets/new-workbooks.table-filters/en.json create mode 100644 src/i18n-keysets/new-workbooks.table-filters/keyset.json create mode 100644 src/i18n-keysets/new-workbooks.table-filters/ru.json create mode 100644 src/i18n-keysets/new-workbooks/context.json create mode 100644 src/i18n-keysets/new-workbooks/en.json create mode 100644 src/i18n-keysets/new-workbooks/keyset.json create mode 100644 src/i18n-keysets/new-workbooks/ru.json create mode 100644 src/i18n-keysets/preview/context.json create mode 100644 src/i18n-keysets/preview/en.json create mode 100644 src/i18n-keysets/preview/keyset.json create mode 100644 src/i18n-keysets/preview/ru.json create mode 100644 src/i18n-keysets/public.dash.error/context.json create mode 100644 src/i18n-keysets/public.dash.error/en.json create mode 100644 src/i18n-keysets/public.dash.error/keyset.json create mode 100644 src/i18n-keysets/public.dash.error/ru.json create mode 100644 src/i18n-keysets/public.dash.footer/context.json create mode 100644 src/i18n-keysets/public.dash.footer/en.json create mode 100644 src/i18n-keysets/public.dash.footer/keyset.json create mode 100644 src/i18n-keysets/public.dash.footer/ru.json create mode 100644 src/i18n-keysets/revisions.dialog-confirm/context.json create mode 100644 src/i18n-keysets/revisions.dialog-confirm/en.json create mode 100644 src/i18n-keysets/revisions.dialog-confirm/keyset.json create mode 100644 src/i18n-keysets/revisions.dialog-confirm/ru.json create mode 100644 src/i18n-keysets/service-settings.create-user.view/context.json create mode 100644 src/i18n-keysets/service-settings.create-user.view/en.json create mode 100644 src/i18n-keysets/service-settings.create-user.view/keyset.json create mode 100644 src/i18n-keysets/service-settings.create-user.view/ru.json create mode 100644 src/i18n-keysets/service-settings.main.view/context.json create mode 100644 src/i18n-keysets/service-settings.main.view/en.json create mode 100644 src/i18n-keysets/service-settings.main.view/keyset.json create mode 100644 src/i18n-keysets/service-settings.main.view/ru.json create mode 100644 src/i18n-keysets/service-settings.users-list.view/context.json create mode 100644 src/i18n-keysets/service-settings.users-list.view/en.json create mode 100644 src/i18n-keysets/service-settings.users-list.view/keyset.json create mode 100644 src/i18n-keysets/service-settings.users-list.view/ru.json create mode 100644 src/i18n-keysets/shared-entry/context.json create mode 100644 src/i18n-keysets/shared-entry/en.json create mode 100644 src/i18n-keysets/shared-entry/keyset.json create mode 100644 src/i18n-keysets/shared-entry/ru.json create mode 100644 src/i18n-keysets/sql/context.json create mode 100644 src/i18n-keysets/sql/en.json create mode 100644 src/i18n-keysets/sql/keyset.json create mode 100644 src/i18n-keysets/sql/ru.json create mode 100644 src/i18n-keysets/validation/context.json create mode 100644 src/i18n-keysets/validation/en.json create mode 100644 src/i18n-keysets/validation/keyset.json create mode 100644 src/i18n-keysets/validation/ru.json create mode 100644 src/i18n-keysets/wizard.hierarchy-editor/context.json create mode 100644 src/i18n-keysets/wizard.hierarchy-editor/en.json create mode 100644 src/i18n-keysets/wizard.hierarchy-editor/keyset.json create mode 100644 src/i18n-keysets/wizard.hierarchy-editor/ru.json create mode 100644 src/i18n-keysets/wizard.palette/context.json create mode 100644 src/i18n-keysets/wizard.palette/en.json create mode 100644 src/i18n-keysets/wizard.palette/keyset.json create mode 100644 src/i18n-keysets/wizard.palette/ru.json create mode 100644 src/i18n-keysets/wizard.prepares/context.json create mode 100644 src/i18n-keysets/wizard.prepares/en.json create mode 100644 src/i18n-keysets/wizard.prepares/keyset.json create mode 100644 src/i18n-keysets/wizard.prepares/ru.json create mode 100644 src/i18n-keysets/wizard/context.json create mode 100644 src/i18n-keysets/wizard/en.json create mode 100644 src/i18n-keysets/wizard/keyset.json create mode 100644 src/i18n-keysets/wizard/ru.json create mode 100644 src/i18n/constants.ts create mode 100644 src/i18n/index.ts create mode 100644 src/i18n/prepare-keysets/constants.ts create mode 100644 src/i18n/prepare-keysets/index.ts create mode 100644 src/i18n/prepare-keysets/prepare-keysets.ts create mode 100644 src/i18n/prepare-keysets/utils.ts create mode 100644 src/i18n/read-keysets.ts create mode 100644 src/i18n/tsconfig.json create mode 100644 src/i18n/types.ts create mode 100644 src/i18n/utils.ts create mode 100644 src/server/.eslintrc create mode 100644 src/server/app-env.ts create mode 100644 src/server/callbacks/index.ts create mode 100644 src/server/callbacks/page-error.ts create mode 100644 src/server/components/api-docs/constants.ts create mode 100644 src/server/components/api-docs/index.ts create mode 100644 src/server/components/api-docs/types.ts create mode 100644 src/server/components/app-layout/app-layout-settings.ts create mode 100644 src/server/components/app-layout/plugins/layout/index.ts create mode 100644 src/server/components/auth/callbacks/auth-layout.ts create mode 100644 src/server/components/auth/callbacks/index.ts create mode 100644 src/server/components/auth/middlewares/auth/api-auth.ts create mode 100644 src/server/components/auth/middlewares/auth/constants.ts create mode 100644 src/server/components/auth/middlewares/auth/index.ts create mode 100644 src/server/components/auth/middlewares/auth/ui-auth.ts create mode 100644 src/server/components/auth/routes.ts create mode 100644 src/server/components/auth/types/token.ts create mode 100644 src/server/components/auth/types/user.ts create mode 100644 src/server/components/axios.ts create mode 100644 src/server/components/cache-client.ts create mode 100644 src/server/components/chart-editor-developer-mode-access-check.ts create mode 100644 src/server/components/charts-engine/components/__tests__/chart-validator.test.ts create mode 100644 src/server/components/charts-engine/components/__tests__/mocks/chart-validator.ts create mode 100644 src/server/components/charts-engine/components/__tests__/source-alias.test.ts create mode 100644 src/server/components/charts-engine/components/__tests__/utils.test.ts create mode 100644 src/server/components/charts-engine/components/chart-generator.ts create mode 100644 src/server/components/charts-engine/components/chart-validator.ts create mode 100644 src/server/components/charts-engine/components/error-handler.ts create mode 100644 src/server/components/charts-engine/components/markdown.ts create mode 100644 src/server/components/charts-engine/components/processor/chart-api-context.ts create mode 100644 src/server/components/charts-engine/components/processor/comments-fetcher.ts create mode 100644 src/server/components/charts-engine/components/processor/console.ts create mode 100644 src/server/components/charts-engine/components/processor/control-builder.ts create mode 100644 src/server/components/charts-engine/components/processor/data-fetcher.ts create mode 100644 src/server/components/charts-engine/components/processor/hooks.ts create mode 100644 src/server/components/charts-engine/components/processor/index.ts create mode 100644 src/server/components/charts-engine/components/processor/paramsUtils.ts create mode 100644 src/server/components/charts-engine/components/processor/source-alias.ts create mode 100644 src/server/components/charts-engine/components/processor/sources.ts create mode 100644 src/server/components/charts-engine/components/processor/stack-trace-prepaper.ts create mode 100644 src/server/components/charts-engine/components/processor/types.ts create mode 100644 src/server/components/charts-engine/components/processor/utils.ts create mode 100644 src/server/components/charts-engine/components/processor/worker-chart-builder.ts create mode 100644 src/server/components/charts-engine/components/request/axios.ts create mode 100644 src/server/components/charts-engine/components/request/index.ts create mode 100644 src/server/components/charts-engine/components/storage/base.ts create mode 100644 src/server/components/charts-engine/components/storage/index.ts create mode 100644 src/server/components/charts-engine/components/storage/types.ts create mode 100644 src/server/components/charts-engine/components/storage/united-storage/index.ts create mode 100644 src/server/components/charts-engine/components/storage/united-storage/provider.ts create mode 100644 src/server/components/charts-engine/components/utils.ts create mode 100644 src/server/components/charts-engine/components/wizard-worker/index.ts create mode 100644 src/server/components/charts-engine/components/wizard-worker/types.ts create mode 100644 src/server/components/charts-engine/components/wizard-worker/utils.ts create mode 100644 src/server/components/charts-engine/constants/index.ts create mode 100644 src/server/components/charts-engine/controllers/charts.ts create mode 100644 src/server/components/charts-engine/controllers/config.ts create mode 100644 src/server/components/charts-engine/controllers/csvConverter.ts create mode 100644 src/server/components/charts-engine/controllers/embedded-entry.ts create mode 100644 src/server/components/charts-engine/controllers/embeds.ts create mode 100644 src/server/components/charts-engine/controllers/export.ts create mode 100644 src/server/components/charts-engine/controllers/markdown.ts create mode 100644 src/server/components/charts-engine/controllers/run.ts create mode 100644 src/server/components/charts-engine/controllers/utils.ts create mode 100644 src/server/components/charts-engine/index.ts create mode 100644 src/server/components/charts-engine/runners/common.ts create mode 100644 src/server/components/charts-engine/runners/constants.ts create mode 100644 src/server/components/charts-engine/runners/control.ts create mode 100644 src/server/components/charts-engine/runners/index.ts create mode 100644 src/server/components/charts-engine/runners/utils.ts create mode 100644 src/server/components/charts-engine/runners/wizard.ts create mode 100644 src/server/components/charts-engine/runners/worker.ts create mode 100644 src/server/components/charts-engine/types.ts create mode 100644 src/server/components/features/features-list/AsideHeaderEnabled.ts create mode 100644 src/server/components/features/features-list/AuthUpdateWithTimeout.ts create mode 100644 src/server/components/features/features-list/ChartEditorDeveloperModeCheck.ts create mode 100644 src/server/components/features/features-list/ChartModeling.ts create mode 100644 src/server/components/features/features-list/ChartkitAlerts.ts create mode 100644 src/server/components/features/features-list/CollectionsAccessEnabled.ts create mode 100644 src/server/components/features/features-list/CollectionsE2ETestsMode.ts create mode 100644 src/server/components/features/features-list/CollectionsEnabled.ts create mode 100644 src/server/components/features/features-list/Comments.ts create mode 100644 src/server/components/features/features-list/ConnectionBasedControl.ts create mode 100644 src/server/components/features/features-list/CopyEntriesToWorkbook.ts create mode 100644 src/server/components/features/features-list/DashBoardSupportDescription.ts create mode 100644 src/server/components/features/features-list/DashBoardWidgetParamsStrictValidation.ts create mode 100644 src/server/components/features/features-list/DashServerMigrationEnable.ts create mode 100644 src/server/components/features/features-list/DashServerValidationEnabled.ts create mode 100644 src/server/components/features/features-list/DatasetsRLS.ts create mode 100644 src/server/components/features/features-list/DisableFnAndHtml.ts create mode 100644 src/server/components/features/features-list/EnableAutocreateDataset.ts create mode 100644 src/server/components/features/features-list/EnableChartEditorDocs.ts create mode 100644 src/server/components/features/features-list/EnableCommonChartDashSettings.ts create mode 100644 src/server/components/features/features-list/EnableCustomDashMargins.ts create mode 100644 src/server/components/features/features-list/EnableCustomMonitoring.ts create mode 100644 src/server/components/features/features-list/EnableDashAutoFocus.ts create mode 100644 src/server/components/features/features-list/EnableDashChartStat.ts create mode 100644 src/server/components/features/features-list/EnableDashColorPickersByTheme.ts create mode 100644 src/server/components/features/features-list/EnableDatasetEarlyInvalidationCache.ts create mode 100644 src/server/components/features/features-list/EnableEmbedsInDialogShare.ts create mode 100644 src/server/components/features/features-list/EnableEntryMenuItemShare.ts create mode 100644 src/server/components/features/features-list/EnableExportSettings.ts create mode 100644 src/server/components/features/features-list/EnableExportWorkbookFile.ts create mode 100644 src/server/components/features/features-list/EnableFooter.ts create mode 100644 src/server/components/features/features-list/EnableMobileFixedHeader.ts create mode 100644 src/server/components/features/features-list/EnableMobileHeader.ts create mode 100644 src/server/components/features/features-list/EnableNewDashSettings.ts create mode 100644 src/server/components/features/features-list/EnableNewServiceSettings.ts create mode 100644 src/server/components/features/features-list/EnablePublishEntry.ts create mode 100644 src/server/components/features/features-list/EnableRLSV2.ts create mode 100644 src/server/components/features/features-list/EnableSaveAsEditorScript.ts create mode 100644 src/server/components/features/features-list/EnableServerlessEditor.ts create mode 100644 src/server/components/features/features-list/EnableSharedEntries.ts create mode 100644 src/server/components/features/features-list/EnableUpdatingDsSettingsByAction.ts create mode 100644 src/server/components/features/features-list/EntryMenuItemCopy.ts create mode 100644 src/server/components/features/features-list/EntryMenuItemMove.ts create mode 100644 src/server/components/features/features-list/ExternalSelectors.ts create mode 100644 src/server/components/features/features-list/FetchDocumentation.ts create mode 100644 src/server/components/features/features-list/FieldEditorDocSection.ts create mode 100644 src/server/components/features/features-list/FunnelChart.ts create mode 100644 src/server/components/features/features-list/GravityChartsForBarYAndScatter.ts create mode 100644 src/server/components/features/features-list/GravityChartsForLineAreaAndBarX.ts create mode 100644 src/server/components/features/features-list/GravityChartsForPieAndTreemap.ts create mode 100644 src/server/components/features/features-list/HideMultitenant.ts create mode 100644 src/server/components/features/features-list/HolidaysOnChart.ts create mode 100644 src/server/components/features/features-list/MassRemoveCollectionsWorkbooks.ts create mode 100644 src/server/components/features/features-list/MenuItemsFlatView.ts create mode 100644 src/server/components/features/features-list/MultiDatasets.ts create mode 100644 src/server/components/features/features-list/MultipleColorsInVisualization.ts create mode 100644 src/server/components/features/features-list/NewTableWidgetForCE.ts create mode 100644 src/server/components/features/features-list/NoJsonFn.ts create mode 100644 src/server/components/features/features-list/QLMonitoring.ts create mode 100644 src/server/components/features/features-list/QLPrometheus.ts create mode 100644 src/server/components/features/features-list/QlAutoExecuteMonitoringChart.ts create mode 100644 src/server/components/features/features-list/ReadOnlyMode.ts create mode 100644 src/server/components/features/features-list/RevisionsListNoLimit.ts create mode 100644 src/server/components/features/features-list/ShouldCheckEditorAccess.ts create mode 100644 src/server/components/features/features-list/ShowChartsEngineDebugInfo.ts create mode 100644 src/server/components/features/features-list/ShowCreateEntryWithMenu.ts create mode 100644 src/server/components/features/features-list/ShowInspectorDetails.ts create mode 100644 src/server/components/features/features-list/UsDynamicMasterToken.ts create mode 100644 src/server/components/features/features-list/UsDynamicMasterTokenInProxy.ts create mode 100644 src/server/components/features/features-list/UseChartsEngineLogin.ts create mode 100644 src/server/components/features/features-list/UseChartsEngineResponseConfig.ts create mode 100644 src/server/components/features/features-list/UseComponentHeader.ts create mode 100644 src/server/components/features/features-list/UseConfigurableChartkit.ts create mode 100644 src/server/components/features/features-list/UsePublicDistincts.ts create mode 100644 src/server/components/features/features-list/UseSourceAlias.ts create mode 100644 src/server/components/features/features-list/UseYqlFolderKey.ts create mode 100644 src/server/components/features/features-list/XlsxChartExportEnabled.ts create mode 100644 src/server/components/features/features-list/XlsxFilesEnabled.ts create mode 100644 src/server/components/features/features-list/index.ts create mode 100644 src/server/components/features/index.ts create mode 100644 src/server/components/features/utils.ts create mode 100644 src/server/components/gateway-auth-helpers/auth-args-middleware.ts create mode 100644 src/server/components/gateway-auth-helpers/bi-auth-helpers.ts create mode 100644 src/server/components/gateway-auth-helpers/index.ts create mode 100644 src/server/components/gateway-auth-helpers/us-auth-helpers.ts create mode 100644 src/server/components/index.ts create mode 100644 src/server/components/layout/index.ts create mode 100644 src/server/components/layout/landing-layout.ts create mode 100644 src/server/components/layout/opensource-layout-config.ts create mode 100644 src/server/components/layout/utils.ts create mode 100644 src/server/components/metrika-data-formatter.js create mode 100644 src/server/components/public-api/config/index.ts create mode 100644 src/server/components/public-api/config/v1.ts create mode 100644 src/server/components/public-api/constants/action-names.ts create mode 100644 src/server/components/public-api/constants/common.ts create mode 100644 src/server/components/public-api/constants/index.ts create mode 100644 src/server/components/public-api/index.ts create mode 100644 src/server/components/public-api/types.ts create mode 100644 src/server/components/public-api/utils/index.ts create mode 100644 src/server/components/public-api/utils/init-public-api-swagger.ts create mode 100644 src/server/components/public-api/utils/parse-public-api-version-header.ts create mode 100644 src/server/components/public-api/utils/pick-additional-public-api-headers.ts create mode 100644 src/server/components/public-api/utils/prepare-public-api-base-config.ts create mode 100644 src/server/components/public-api/utils/register-action-to-open-api.ts create mode 100644 src/server/components/resolve-entry-by-link.ts create mode 100644 src/server/components/sdk/dash.ts create mode 100644 src/server/components/sdk/index.ts create mode 100644 src/server/components/sdk/us.ts create mode 100644 src/server/components/workbook-transfer/charts.ts create mode 100644 src/server/components/workbook-transfer/create-transfer-notifications.ts create mode 100644 src/server/components/workbook-transfer/dash.ts create mode 100644 src/server/configs/common.ts create mode 100644 src/server/configs/opensource/common.ts create mode 100644 src/server/configs/opensource/development.ts create mode 100644 src/server/configs/secrets.js create mode 100644 src/server/configs/shared/control-dash-chart-template.ts create mode 100644 src/server/configs/shared/datalens-chart-template.ts create mode 100644 src/server/configs/shared/ql-chart-template.ts create mode 100644 src/server/configs/sources-secrets.json create mode 100644 src/server/constants/axios.ts create mode 100644 src/server/constants/index.ts create mode 100644 src/server/constants/public-api.ts create mode 100644 src/server/controllers/api.ts create mode 100644 src/server/controllers/dl-main.ts create mode 100644 src/server/controllers/index.ts create mode 100644 src/server/controllers/navigate.ts create mode 100644 src/server/controllers/navigation.ts create mode 100644 src/server/controllers/ping.ts create mode 100644 src/server/controllers/public-api/constants.ts create mode 100644 src/server/controllers/public-api/index.ts create mode 100644 src/server/controllers/public-api/utils.ts create mode 100644 src/server/controllers/stats.js create mode 100644 src/server/controllers/utils/handle-entry-redirect.ts create mode 100644 src/server/controllers/workbook-transfer.ts create mode 100644 src/server/controllers/xlsx-converter.ts create mode 100644 src/server/expresskit.ts create mode 100644 src/server/index.ts create mode 100644 src/server/lib/validation/index.ts create mode 100644 src/server/lib/validation/types.ts create mode 100644 src/server/local-dev.ts create mode 100644 src/server/middlewares/app-layout.ts create mode 100644 src/server/middlewares/before-auth-defaults.ts create mode 100644 src/server/middlewares/connector-icons.ts create mode 100644 src/server/middlewares/ctx.ts create mode 100644 src/server/middlewares/index.ts create mode 100644 src/server/middlewares/patch-logger.ts create mode 100644 src/server/middlewares/scr-requests.ts create mode 100644 src/server/middlewares/server-feature-with-bounded-context.ts create mode 100644 src/server/middlewares/tenant-settings.ts create mode 100644 src/server/middlewares/x-dl-context.ts create mode 100644 src/server/modes/charts/init-charts-engine.ts create mode 100644 src/server/modes/charts/plugins/constants/middleware-urls.ts create mode 100644 src/server/modes/charts/plugins/constants/misc.ts create mode 100644 src/server/modes/charts/plugins/control/constants/misc.ts create mode 100644 src/server/modes/charts/plugins/control/controls/index.ts create mode 100644 src/server/modes/charts/plugins/control/helpers/misc.ts create mode 100644 src/server/modes/charts/plugins/control/index.ts create mode 100644 src/server/modes/charts/plugins/control/js/helpers/dataset/process-dataset-content.ts create mode 100644 src/server/modes/charts/plugins/control/js/helpers/dataset/process-fields.ts create mode 100644 src/server/modes/charts/plugins/control/js/helpers/process-content.ts create mode 100644 src/server/modes/charts/plugins/control/js/helpers/typed-query/process-typed-query-content.ts create mode 100644 src/server/modes/charts/plugins/control/js/helpers/typed-query/process-typed-query-parameters.ts create mode 100644 src/server/modes/charts/plugins/control/js/index.ts create mode 100644 src/server/modes/charts/plugins/control/js/types.ts create mode 100644 src/server/modes/charts/plugins/control/types.ts create mode 100644 src/server/modes/charts/plugins/control/url/constants.ts create mode 100644 src/server/modes/charts/plugins/control/url/distincts/build-distincts-body.ts create mode 100644 src/server/modes/charts/plugins/control/url/distincts/index.ts create mode 100644 src/server/modes/charts/plugins/control/url/fields/index.ts create mode 100644 src/server/modes/charts/plugins/control/url/index.ts create mode 100644 src/server/modes/charts/plugins/control/url/typed-query/index.ts create mode 100644 src/server/modes/charts/plugins/control/url/types.ts create mode 100644 src/server/modes/charts/plugins/dash-api.ts create mode 100644 src/server/modes/charts/plugins/data-api-json-schema.ts create mode 100644 src/server/modes/charts/plugins/datalens/chart-editor-module.ts create mode 100644 src/server/modes/charts/plugins/datalens/config/constants.ts create mode 100644 src/server/modes/charts/plugins/datalens/config/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/config/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/gravity-charts/utils/dataLabels.ts create mode 100644 src/server/modes/charts/plugins/datalens/gravity-charts/utils/format.ts create mode 100644 src/server/modes/charts/plugins/datalens/gravity-charts/utils/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/gravity-charts/utils/range-slider.ts create mode 100644 src/server/modes/charts/plugins/datalens/gravity-charts/utils/tooltip.ts create mode 100644 src/server/modes/charts/plugins/datalens/highcharts.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/constants/errors.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/__tests__/combined-chart/mergeResultForCombinedCharts.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/__tests__/mocks/totals/getMergedTotals.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/__tests__/totals/getMergedTotals.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/colors/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/utils.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/layer-chart/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/layer-chart/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/misc/prepare-single-result.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/notifications/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/totals/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/helpers/totals/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/js-v1.5-private.ts create mode 100644 src/server/modes/charts/plugins/datalens/js/js.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/__tests__/common.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/area/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/area/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/__tests__/table-head-generator.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/constants/misc.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/__tests__/color.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/__tests__/mocks/color.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/misc.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/sort.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/totals.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/table-head-generator.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/table-rows-generator.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/mocks/prepareBarX.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/mocks/prepareHighchartsBarX.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/prepareBarX.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/prepareHighchartsBarX.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-x/gravity-charts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-x/highcharts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-x/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-x/prepare-bar-x.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-x/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-y/__tests__/bar-y.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-y/gravity-charts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-y/highcharts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-y/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/bar-y/prepare-bar-y-data.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/constants.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/__tests__/flat-table.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/__tests__/mocks/flat-table.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/background-settings/get-background-colors-map-by-continuous-column.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/background-settings/get-flat-table-background-styles.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/footer/getFooter.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/mocks/getFooter.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-flat-table-background-styles.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/footer/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/footer/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/misc.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/funnel/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/geopoint-with-cluster.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/geopoint/__tests__/mocks/geopoints.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/geopoint/__tests__/prepareGeopoint.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/geopoint/constants.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/geopoint/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/geopoint/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/geopolygon.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/heatmap.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/action-params.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/axis/addAxisFormatter.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/axis/addAxisFormatting.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/axis/data-labels.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/axis/get-axis-formatting.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/axis/getAxisType.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/axis/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/axis/isAxisLabelDateFormat.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/backgroundSettings/misc.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/barsSettings/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/barsSettings/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/colors.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/columnSettings.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/common.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/get-formatted-value.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/highcharts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/layers.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/legend.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/segments.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/helpers/visualizations.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line-time/__tests__/line-time.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line-time/__tests__/mocks/line-time.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line-time/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/__tests__/line.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/__tests__/mocks/bar.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/__tests__/mocks/line.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/gravity-charts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/__tests__/getSegmentsYAxis.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/__tests__/getSortedLineKeys.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/axis/getLayerPlaceholderWithItems.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/axis/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/color-helpers/colorizeByGradient.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/color-helpers/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/getSortedCategories.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/getSortedLineKeys.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/getXAxisValue.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/__tests__/helpers.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/getColoredLineLegendTitle.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/helpers.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToDimensionColoredLines.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToLines.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToMeasureColoredLines.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/prepareLines.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentName.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsIndexInOrder.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsList.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsMap.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsYAxis.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/helpers/utils.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/highcharts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/prepare-line-data.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/line/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/metric/__tests__/metric.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/metric/__tests__/mocks/metric.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/metric/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/metric/utils.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/metric/variants/basic.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/metric/variants/markup.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/__tests__/mocks/old-pivot-table.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/__tests__/old-pivot-table.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/old-pivot-table.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/highcharts.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/mocks/pie.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/pie.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/pie/gravity-charts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/pie/highcharts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/pie/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/pie/prepare-pie-data.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/pie/utils.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/polyline.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/scatter/__tests__/mocks/scatter.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/scatter/__tests__/scatter.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/scatter/gravity-charts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/scatter/helpers/shape.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/scatter/helpers/tooltip.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/scatter/highcharts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/scatter/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/scatter/prepare-scatter.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/scatter/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/treemap/gravity-charts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/treemap/highcharts.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/treemap/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/preparers/utils.ts create mode 100644 src/server/modes/charts/plugins/datalens/private-module.ts create mode 100644 src/server/modes/charts/plugins/datalens/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/__tests__/default-request/buildTotalRequestFields.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/__tests__/default-request/mocks/buildTotalRequests.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/constants.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/default-request.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getAnnotations.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getRegularFields.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getTotalsForPivot.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/common.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getAnnotations.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getRegularFields.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getTotalsForPivot.mock.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/totals.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/build-sources.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/helpers/__tests__/filters.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/helpers/__tests__/parameters.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/helpers/fields.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/helpers/filters.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/helpers/index.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/helpers/misc.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/helpers/parameters.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/helpers/table-settings.ts create mode 100644 src/server/modes/charts/plugins/datalens/url/types.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/__tests__/color-helpers.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/__tests__/geo-helpers.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/__tests__/get-gradient-stops.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/__tests__/misc-helpers.test.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/axis-helpers.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/color-helpers.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/config-helpers.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/constants.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/export-helpers.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/geo-helpers.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/get-gradient-stops.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/hierarchy-helpers.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/markup-helpers.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/misc-helpers.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/pivot-table/totals.ts create mode 100644 src/server/modes/charts/plugins/datalens/utils/shape-helpers.ts create mode 100644 src/server/modes/charts/plugins/dataset/v2.ts create mode 100644 src/server/modes/charts/plugins/helpers/color-palettes.ts create mode 100644 src/server/modes/charts/plugins/helpers/misc.ts create mode 100644 src/server/modes/charts/plugins/logins-blacklist.ts create mode 100644 src/server/modes/charts/plugins/ql/index.ts create mode 100644 src/server/modes/charts/plugins/ql/module/chart-editor-module.ts create mode 100644 src/server/modes/charts/plugins/ql/module/config/build-chart-config.ts create mode 100644 src/server/modes/charts/plugins/ql/module/config/index.ts create mode 100644 src/server/modes/charts/plugins/ql/module/highcharts.ts create mode 100644 src/server/modes/charts/plugins/ql/module/js/build-graph.ts create mode 100644 src/server/modes/charts/plugins/ql/module/library-config/build-library-config.ts create mode 100644 src/server/modes/charts/plugins/ql/module/library-config/index.ts create mode 100644 src/server/modes/charts/plugins/ql/module/private-module.ts create mode 100644 src/server/modes/charts/plugins/ql/module/url/build-sources.ts create mode 100644 src/server/modes/charts/plugins/ql/module/url/index.ts create mode 100644 src/server/modes/charts/plugins/ql/module/url/types.ts create mode 100644 src/server/modes/charts/plugins/ql/preparers/line-time.ts create mode 100644 src/server/modes/charts/plugins/ql/preparers/line.ts create mode 100644 src/server/modes/charts/plugins/ql/preparers/metric.ts create mode 100644 src/server/modes/charts/plugins/ql/preparers/pie.ts create mode 100644 src/server/modes/charts/plugins/ql/preparers/preview-table.ts create mode 100644 src/server/modes/charts/plugins/ql/preparers/table.ts create mode 100644 src/server/modes/charts/plugins/ql/ql-chart-runner.ts create mode 100644 src/server/modes/charts/plugins/ql/types.ts create mode 100644 src/server/modes/charts/plugins/ql/utils/__tests__/misc-helpers.test.ts create mode 100644 src/server/modes/charts/plugins/ql/utils/autofill-helpers.ts create mode 100644 src/server/modes/charts/plugins/ql/utils/colors.ts create mode 100644 src/server/modes/charts/plugins/ql/utils/connection.ts create mode 100644 src/server/modes/charts/plugins/ql/utils/constants.ts create mode 100644 src/server/modes/charts/plugins/ql/utils/identify-params.ts create mode 100644 src/server/modes/charts/plugins/ql/utils/migrate-helpers.ts create mode 100644 src/server/modes/charts/plugins/ql/utils/misc-helpers.ts create mode 100644 src/server/modes/charts/plugins/ql/utils/value-helpers.ts create mode 100644 src/server/modes/charts/plugins/ql/utils/visualization-utils.ts create mode 100644 src/server/modes/charts/plugins/ql/worker.ts create mode 100644 src/server/modes/charts/plugins/ql/yagr.ts create mode 100644 src/server/modes/charts/plugins/request-with-dataset/index.ts create mode 100644 src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/charts-with-dataset.ts create mode 100644 src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/controls-with-dataset.ts create mode 100644 src/server/modes/charts/plugins/request-with-dataset/request-dataset.ts create mode 100644 src/server/modes/charts/plugins/runner-key-adapter.ts create mode 100644 src/server/modes/charts/plugins/types.ts create mode 100644 src/server/modes/charts/telemetry.ts create mode 100644 src/server/modes/opensource/app.ts create mode 100644 src/server/modes/opensource/middlewares/index.ts create mode 100644 src/server/modes/opensource/middlewares/subrequest-headers.ts create mode 100644 src/server/modes/opensource/routes.ts create mode 100644 src/server/nodekit.ts create mode 100644 src/server/registry/index.ts create mode 100644 src/server/registry/units/common/auth-functions-map.ts create mode 100644 src/server/registry/units/common/functions-map/index.ts create mode 100644 src/server/registry/units/common/functions-map/on-embeds-controller-before-response.ts create mode 100644 src/server/registry/units/common/functions-map/on-embeds-controller-start.ts create mode 100644 src/server/registry/units/common/index.ts create mode 100644 src/server/registry/units/common/register.ts create mode 100644 src/server/registry/utils/register-app-plugins.ts create mode 100644 src/server/tsconfig.json create mode 100644 src/server/types/app-layout.ts create mode 100644 src/server/types/connections.ts create mode 100644 src/server/types/controllers.ts create mode 100644 src/server/types/gateway.ts create mode 100644 src/server/types/xlsxConverter.ts create mode 100644 src/server/utils/axios.ts create mode 100644 src/server/utils/env-utils.ts create mode 100644 src/server/utils/gateway.ts create mode 100644 src/server/utils/index.ts create mode 100644 src/server/utils/language.ts create mode 100644 src/server/utils/redis.ts create mode 100644 src/server/utils/routes.ts create mode 100644 src/server/utils/schema-validate.ts create mode 100644 src/shared/__tests__/.eslintrc create mode 100644 src/shared/__tests__/types.test.ts create mode 100644 src/shared/__tests__/utils.test.ts create mode 100644 src/shared/components/auth/constants/cookie.ts create mode 100644 src/shared/components/auth/constants/role.ts create mode 100644 src/shared/components/auth/constants/token.ts create mode 100644 src/shared/components/auth/constants/url.ts create mode 100644 src/shared/components/auth/types/layout.ts create mode 100644 src/shared/components/auth/utils.ts create mode 100644 src/shared/components/public-api/utils/index.ts create mode 100644 src/shared/constants/api-v2.ts create mode 100644 src/shared/constants/chart-modeling.ts create mode 100644 src/shared/constants/chartkit-handlers.ts create mode 100644 src/shared/constants/charts.ts create mode 100644 src/shared/constants/collections.ts create mode 100644 src/shared/constants/colors/common/classic-20.ts create mode 100644 src/shared/constants/colors/common/datalens.ts create mode 100644 src/shared/constants/colors/common/default-20.ts create mode 100644 src/shared/constants/colors/common/emerald-20.ts create mode 100644 src/shared/constants/colors/common/golden-20.ts create mode 100644 src/shared/constants/colors/common/neutral-20.ts create mode 100644 src/shared/constants/colors/common/oceanic-20.ts create mode 100644 src/shared/constants/colors/common/traffic-light-9.ts create mode 100644 src/shared/constants/colors/index.ts create mode 100644 src/shared/constants/colors/tableau/blue-red-12.ts create mode 100644 src/shared/constants/colors/tableau/blue-red-6.ts create mode 100644 src/shared/constants/colors/tableau/color-blind-10.ts create mode 100644 src/shared/constants/colors/tableau/gray-5.ts create mode 100644 src/shared/constants/colors/tableau/green-orange-12.ts create mode 100644 src/shared/constants/colors/tableau/green-orange-6.ts create mode 100644 src/shared/constants/colors/tableau/purpure-gray-12.ts create mode 100644 src/shared/constants/colors/tableau/purpure-gray-6.ts create mode 100644 src/shared/constants/colors/tableau/tableau-10-light.ts create mode 100644 src/shared/constants/colors/tableau/tableau-10-medium.ts create mode 100644 src/shared/constants/colors/tableau/tableau-10.ts create mode 100644 src/shared/constants/colors/tableau/tableau-20.ts create mode 100644 src/shared/constants/colors/tableau/traffic-light.ts create mode 100644 src/shared/constants/colors/types.ts create mode 100644 src/shared/constants/common.ts create mode 100644 src/shared/constants/connections.ts create mode 100644 src/shared/constants/cookie.ts create mode 100644 src/shared/constants/dash.ts create mode 100644 src/shared/constants/datasets.ts create mode 100644 src/shared/constants/datepicker/relative-datepicker.ts create mode 100644 src/shared/constants/entry.ts create mode 100644 src/shared/constants/error-codes.ts create mode 100644 src/shared/constants/error/index.ts create mode 100644 src/shared/constants/exports.ts create mode 100644 src/shared/constants/gradients/default.ts create mode 100644 src/shared/constants/gradients/index.ts create mode 100644 src/shared/constants/gradients/three-point-gradients.ts create mode 100644 src/shared/constants/gradients/three-point/blue-gray-red.ts create mode 100644 src/shared/constants/gradients/three-point/blue-yellow-red.ts create mode 100644 src/shared/constants/gradients/three-point/orange-blue-green.ts create mode 100644 src/shared/constants/gradients/three-point/orange-gray-blue.ts create mode 100644 src/shared/constants/gradients/three-point/orange-violet-blue.ts create mode 100644 src/shared/constants/gradients/three-point/pink-gray-green.ts create mode 100644 src/shared/constants/gradients/three-point/red-orange-green.ts create mode 100644 src/shared/constants/gradients/three-point/traffic-light.ts create mode 100644 src/shared/constants/gradients/three-point/violet-blue-green.ts create mode 100644 src/shared/constants/gradients/three-point/yellow-green-blue.ts create mode 100644 src/shared/constants/gradients/two-point-gradients.ts create mode 100644 src/shared/constants/gradients/two-point/blue.ts create mode 100644 src/shared/constants/gradients/two-point/cyan.ts create mode 100644 src/shared/constants/gradients/two-point/golden.ts create mode 100644 src/shared/constants/gradients/two-point/gray.ts create mode 100644 src/shared/constants/gradients/two-point/green-blue.ts create mode 100644 src/shared/constants/gradients/two-point/green.ts create mode 100644 src/shared/constants/gradients/two-point/oceanic.ts create mode 100644 src/shared/constants/gradients/two-point/orange-yellow.ts create mode 100644 src/shared/constants/gradients/two-point/red-blue.ts create mode 100644 src/shared/constants/gradients/two-point/red.ts create mode 100644 src/shared/constants/gradients/two-point/violet-orange.ts create mode 100644 src/shared/constants/gradients/two-point/violet.ts create mode 100644 src/shared/constants/gradients/two-point/yellow.ts create mode 100644 src/shared/constants/header.ts create mode 100644 src/shared/constants/iam/index.ts create mode 100644 src/shared/constants/index.ts create mode 100644 src/shared/constants/markdown.ts create mode 100644 src/shared/constants/markup.ts create mode 100644 src/shared/constants/meta-manager/index.ts create mode 100644 src/shared/constants/navigation.ts create mode 100644 src/shared/constants/old-schema.ts create mode 100644 src/shared/constants/operations.ts create mode 100644 src/shared/constants/placeholder.ts create mode 100644 src/shared/constants/print.ts create mode 100644 src/shared/constants/qa/action-panel.ts create mode 100644 src/shared/constants/qa/chart.ts create mode 100644 src/shared/constants/qa/collections.ts create mode 100644 src/shared/constants/qa/components.ts create mode 100644 src/shared/constants/qa/connections.ts create mode 100644 src/shared/constants/qa/control.ts create mode 100644 src/shared/constants/qa/dash.ts create mode 100644 src/shared/constants/qa/datasets.ts create mode 100644 src/shared/constants/qa/dl-navigation.ts create mode 100644 src/shared/constants/qa/edit-history.ts create mode 100644 src/shared/constants/qa/field-editor.ts create mode 100644 src/shared/constants/qa/index.ts create mode 100644 src/shared/constants/qa/ql.ts create mode 100644 src/shared/constants/qa/settings.ts create mode 100644 src/shared/constants/qa/shared-entries.ts create mode 100644 src/shared/constants/qa/sign-in.ts create mode 100644 src/shared/constants/qa/uikit.ts create mode 100644 src/shared/constants/qa/visualization.ts create mode 100644 src/shared/constants/qa/wizard.ts create mode 100644 src/shared/constants/qa/workbooks.ts create mode 100644 src/shared/constants/ql.ts create mode 100644 src/shared/constants/shapes/index.ts create mode 100644 src/shared/constants/shapes/lines/index.ts create mode 100644 src/shared/constants/shapes/points/index.ts create mode 100644 src/shared/constants/themes.ts create mode 100644 src/shared/constants/timeout.ts create mode 100644 src/shared/constants/ui-sandbox.ts create mode 100644 src/shared/constants/url.ts create mode 100644 src/shared/constants/user.ts create mode 100644 src/shared/constants/visualization.ts create mode 100644 src/shared/constants/widgets.ts create mode 100644 src/shared/constants/wizard.ts create mode 100644 src/shared/constants/workbook-transfer.ts create mode 100644 src/shared/constants/workbooks.ts create mode 100644 src/shared/constants/yfm.ts create mode 100644 src/shared/endpoints/constants/index.ts create mode 100644 src/shared/endpoints/constants/opensource.ts create mode 100644 src/shared/endpoints/index.ts create mode 100644 src/shared/endpoints/schema/index.ts create mode 100644 src/shared/endpoints/schema/opensource.ts create mode 100644 src/shared/endpoints/sources/index.ts create mode 100644 src/shared/endpoints/sources/opensource.ts create mode 100644 src/shared/endpoints/sources/types.ts create mode 100644 src/shared/endpoints/types.ts create mode 100644 src/shared/endpoints/utils.ts create mode 100644 src/shared/index.ts create mode 100644 src/shared/modules/__tests__/charts-shared.test.ts create mode 100644 src/shared/modules/__tests__/helpers.test.ts create mode 100644 src/shared/modules/bi-api/index.ts create mode 100644 src/shared/modules/bi-api/mappers/index.ts create mode 100644 src/shared/modules/bi-api/mappers/v2.ts create mode 100644 src/shared/modules/charts-shared.ts create mode 100644 src/shared/modules/colors/common-helpers.ts create mode 100644 src/shared/modules/colors/distincts-helpers.ts create mode 100644 src/shared/modules/config/ql/__tests__/mapQlConfigToLatestVersion.test.ts create mode 100644 src/shared/modules/config/ql/__tests__/v1.test.ts create mode 100644 src/shared/modules/config/ql/__tests__/v2.test.ts create mode 100644 src/shared/modules/config/ql/index.ts create mode 100644 src/shared/modules/config/ql/mapQlConfigToLatestVersion.ts create mode 100644 src/shared/modules/config/ql/v1/mapUndefinedConfigToV1.ts create mode 100644 src/shared/modules/config/ql/v2/mapV1ConfigToV2.ts create mode 100644 src/shared/modules/config/ql/v3/mapV2ConfigToV3.ts create mode 100644 src/shared/modules/config/ql/v4/mapV3ConfigToV4.ts create mode 100644 src/shared/modules/config/ql/v5/mapV4ConfigToV5.ts create mode 100644 src/shared/modules/config/ql/v6/mapV5ConfigToV6.ts create mode 100644 src/shared/modules/config/ql/v7/mapV6ConfigToV7.ts create mode 100644 src/shared/modules/config/utils.ts create mode 100644 src/shared/modules/config/wizard/__test__/mapChartsConfigToLatestVersion.test.ts create mode 100644 src/shared/modules/config/wizard/__test__/v4.test.ts create mode 100644 src/shared/modules/config/wizard/__test__/v8.test.ts create mode 100644 src/shared/modules/config/wizard/index.ts create mode 100644 src/shared/modules/config/wizard/mapChartsConfigToLatestVersion.ts create mode 100644 src/shared/modules/config/wizard/mocks/v4.mock.ts create mode 100644 src/shared/modules/config/wizard/v1/mapV1ConfigToV2.ts create mode 100644 src/shared/modules/config/wizard/v10/mapV10ConfigToV11.ts create mode 100644 src/shared/modules/config/wizard/v11/mapV11ConfigToV12.ts create mode 100644 src/shared/modules/config/wizard/v12/mapV12ConfigToV13.ts create mode 100644 src/shared/modules/config/wizard/v13/mapV13ConfigToV14.ts create mode 100644 src/shared/modules/config/wizard/v14/mapV14ConfigToV15.ts create mode 100644 src/shared/modules/config/wizard/v2/mapV2ConfigToV3.ts create mode 100644 src/shared/modules/config/wizard/v3/mapV3ConfigToV4.ts create mode 100644 src/shared/modules/config/wizard/v4/mapV4ConfigToV5.ts create mode 100644 src/shared/modules/config/wizard/v5/mapV5ConfigToV6.ts create mode 100644 src/shared/modules/config/wizard/v6/mapV6ConfigToV7.ts create mode 100644 src/shared/modules/config/wizard/v7/mapV7ConfigToV8.ts create mode 100644 src/shared/modules/config/wizard/v8/mapV8ConfigToV9.ts create mode 100644 src/shared/modules/config/wizard/v9/mapV9ConfigToV10.ts create mode 100644 src/shared/modules/control/typed-query-helpers.ts create mode 100644 src/shared/modules/dash-scheme-converter.ts create mode 100644 src/shared/modules/entry.ts create mode 100644 src/shared/modules/fields.ts create mode 100644 src/shared/modules/format-units/formatUnit.test.ts create mode 100644 src/shared/modules/format-units/formatUnit.ts create mode 100644 src/shared/modules/format-units/i18n/en.json create mode 100644 src/shared/modules/format-units/i18n/i18n.ts create mode 100644 src/shared/modules/format-units/i18n/ru.json create mode 100644 src/shared/modules/format-units/index.ts create mode 100644 src/shared/modules/format-units/types.ts create mode 100644 src/shared/modules/helpers.ts create mode 100644 src/shared/modules/index.ts create mode 100644 src/shared/modules/language.ts create mode 100644 src/shared/modules/markdown/emoji-defs.ts create mode 100644 src/shared/modules/markdown/markdown-plugins/unify-terms.ts create mode 100644 src/shared/modules/markdown/markdown.ts create mode 100644 src/shared/modules/markup.ts create mode 100644 src/shared/modules/qa-helpers.ts create mode 100644 src/shared/modules/ql-helpers.ts create mode 100644 src/shared/modules/sleep.ts create mode 100644 src/shared/modules/slugify.ts create mode 100644 src/shared/modules/tenant.ts create mode 100644 src/shared/modules/transliterate.ts create mode 100644 src/shared/modules/typed-query-api/helpers/parameters.ts create mode 100644 src/shared/modules/typed-query-api/index.ts create mode 100644 src/shared/modules/typed-query-api/mappers/parameters.ts create mode 100644 src/shared/modules/url.ts create mode 100644 src/shared/modules/user.ts create mode 100644 src/shared/modules/visualization/placeholder.ts create mode 100644 src/shared/modules/wizard-helpers.ts create mode 100644 src/shared/modules/wizard/axis-mode.ts create mode 100644 src/shared/modules/wizard/axis-settings.ts create mode 100644 src/shared/modules/wizard/config.ts create mode 100644 src/shared/modules/wizard/index.ts create mode 100644 src/shared/modules/wizard/tooltip.ts create mode 100644 src/shared/old-schema/charts.ts create mode 100644 src/shared/old-schema/index.js create mode 100644 src/shared/schema/auth-schema/auth/actions.ts create mode 100644 src/shared/schema/auth-schema/auth/index.ts create mode 100644 src/shared/schema/auth-schema/auth/types.ts create mode 100644 src/shared/schema/auth-schema/index.ts create mode 100644 src/shared/schema/auth/actions.ts create mode 100644 src/shared/schema/auth/index.ts create mode 100644 src/shared/schema/auth/types/common.ts create mode 100644 src/shared/schema/auth/types/users.ts create mode 100644 src/shared/schema/bi/actions/connections.ts create mode 100644 src/shared/schema/bi/actions/datasets.ts create mode 100644 src/shared/schema/bi/actions/index.ts create mode 100644 src/shared/schema/bi/actions/oauth.ts create mode 100644 src/shared/schema/bi/helpers/datasets.ts create mode 100644 src/shared/schema/bi/helpers/index.ts create mode 100644 src/shared/schema/bi/index.ts create mode 100644 src/shared/schema/bi/schemas/connections.ts create mode 100644 src/shared/schema/bi/schemas/datasets.ts create mode 100644 src/shared/schema/bi/schemas/index.ts create mode 100644 src/shared/schema/bi/types/common.ts create mode 100644 src/shared/schema/bi/types/connections.ts create mode 100644 src/shared/schema/bi/types/datasets.ts create mode 100644 src/shared/schema/bi/types/forms.ts create mode 100644 src/shared/schema/bi/types/index.ts create mode 100644 src/shared/schema/bi/types/oauth.ts create mode 100644 src/shared/schema/extensions/actions/iam-access-dialog.ts create mode 100644 src/shared/schema/extensions/actions/index.ts create mode 100644 src/shared/schema/extensions/index.ts create mode 100644 src/shared/schema/extensions/types/iam-access-dialog.ts create mode 100644 src/shared/schema/extensions/types/index.ts create mode 100644 src/shared/schema/gateway-utils.ts create mode 100644 src/shared/schema/googleapis/actions.ts create mode 100644 src/shared/schema/index.ts create mode 100644 src/shared/schema/meta-manager/actions/export/cancel-workbook-export.ts create mode 100644 src/shared/schema/meta-manager/actions/export/get-workbook-export-result.ts create mode 100644 src/shared/schema/meta-manager/actions/export/get-workbook-export-status.ts create mode 100644 src/shared/schema/meta-manager/actions/export/index.ts create mode 100644 src/shared/schema/meta-manager/actions/export/start-workbook-export.ts create mode 100644 src/shared/schema/meta-manager/actions/import/get-workbook-import-status.ts create mode 100644 src/shared/schema/meta-manager/actions/import/index.ts create mode 100644 src/shared/schema/meta-manager/actions/import/start-workbook-import.ts create mode 100644 src/shared/schema/meta-manager/actions/index.ts create mode 100644 src/shared/schema/meta-manager/index.ts create mode 100644 src/shared/schema/meta-manager/schemas/common/index.ts create mode 100644 src/shared/schema/meta-manager/schemas/export/cancel-workbook-export.ts create mode 100644 src/shared/schema/meta-manager/schemas/export/get-workbook-export-result.ts create mode 100644 src/shared/schema/meta-manager/schemas/export/get-workbook-export-status.ts create mode 100644 src/shared/schema/meta-manager/schemas/export/index.ts create mode 100644 src/shared/schema/meta-manager/schemas/export/start-workbook-export.ts create mode 100644 src/shared/schema/meta-manager/schemas/import/get-workbook-import-status.ts create mode 100644 src/shared/schema/meta-manager/schemas/import/index.ts create mode 100644 src/shared/schema/meta-manager/schemas/import/start-workbook-import.ts create mode 100644 src/shared/schema/meta-manager/schemas/index.ts create mode 100644 src/shared/schema/meta-manager/types/common.ts create mode 100644 src/shared/schema/meta-manager/types/export.ts create mode 100644 src/shared/schema/meta-manager/types/import.ts create mode 100644 src/shared/schema/meta-manager/types/index.ts create mode 100644 src/shared/schema/mix/actions/dash/create-dashboard-v1.ts create mode 100644 src/shared/schema/mix/actions/dash/delete-dashboard.ts create mode 100644 src/shared/schema/mix/actions/dash/get-dashboard-v1.ts create mode 100644 src/shared/schema/mix/actions/dash/index.ts create mode 100644 src/shared/schema/mix/actions/dash/update-dashboard-v1.ts create mode 100644 src/shared/schema/mix/actions/editor.ts create mode 100644 src/shared/schema/mix/actions/entries.ts create mode 100644 src/shared/schema/mix/actions/index.ts create mode 100644 src/shared/schema/mix/actions/markdown.ts create mode 100644 src/shared/schema/mix/actions/navigation.ts create mode 100644 src/shared/schema/mix/actions/ql/create-ql-chart.ts create mode 100644 src/shared/schema/mix/actions/ql/delete-ql-chart.ts create mode 100644 src/shared/schema/mix/actions/ql/get-ql-chart.ts create mode 100644 src/shared/schema/mix/actions/ql/index.ts create mode 100644 src/shared/schema/mix/actions/ql/update-ql-chart.ts create mode 100644 src/shared/schema/mix/actions/wizard/create-wizard-chart.ts create mode 100644 src/shared/schema/mix/actions/wizard/delete-wizard-chart.ts create mode 100644 src/shared/schema/mix/actions/wizard/get-wizard-chart.ts create mode 100644 src/shared/schema/mix/actions/wizard/index.ts create mode 100644 src/shared/schema/mix/actions/wizard/update-wizard-chart.ts create mode 100644 src/shared/schema/mix/helpers/__tests__/editor.test.ts create mode 100644 src/shared/schema/mix/helpers/dash/index.ts create mode 100644 src/shared/schema/mix/helpers/dash/migrate-dash-to-v1.ts create mode 100644 src/shared/schema/mix/helpers/dash/normalize-dash-data.ts create mode 100644 src/shared/schema/mix/helpers/editor/index.ts create mode 100644 src/shared/schema/mix/helpers/editor/validation.ts create mode 100644 src/shared/schema/mix/helpers/entries.ts create mode 100644 src/shared/schema/mix/helpers/index.ts create mode 100644 src/shared/schema/mix/helpers/validation.ts create mode 100644 src/shared/schema/mix/index.ts create mode 100644 src/shared/schema/mix/schemas/dash/create-dashboard-v1.ts create mode 100644 src/shared/schema/mix/schemas/dash/dash-v1.ts create mode 100644 src/shared/schema/mix/schemas/dash/delete-dashboard.ts create mode 100644 src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts create mode 100644 src/shared/schema/mix/schemas/dash/update-dashboard-v1.ts create mode 100644 src/shared/schema/mix/schemas/ql.ts create mode 100644 src/shared/schema/mix/schemas/wizard.ts create mode 100644 src/shared/schema/mix/types/dash/index.ts create mode 100644 src/shared/schema/mix/types/entries.ts create mode 100644 src/shared/schema/mix/types/index.ts create mode 100644 src/shared/schema/mix/types/markdown.ts create mode 100644 src/shared/schema/mix/types/navigation.ts create mode 100644 src/shared/schema/mix/types/ql.ts create mode 100644 src/shared/schema/mix/types/wizard.ts create mode 100644 src/shared/schema/stat-api/types/index.ts create mode 100644 src/shared/schema/types.ts create mode 100644 src/shared/schema/us-private/actions/entries.ts create mode 100644 src/shared/schema/us-private/actions/index.ts create mode 100644 src/shared/schema/us-private/actions/tenant.ts create mode 100644 src/shared/schema/us-private/index.ts create mode 100644 src/shared/schema/us-private/types/entries.ts create mode 100644 src/shared/schema/us-private/types/index.ts create mode 100644 src/shared/schema/us/actions/collections/create-collection.ts create mode 100644 src/shared/schema/us/actions/collections/delete-collection.ts create mode 100644 src/shared/schema/us/actions/collections/delete-collections.ts create mode 100644 src/shared/schema/us/actions/collections/get-collection-breadcrumbs.ts create mode 100644 src/shared/schema/us/actions/collections/get-collection.ts create mode 100644 src/shared/schema/us/actions/collections/get-collections-by-ids.ts create mode 100644 src/shared/schema/us/actions/collections/get-root-collection-permissions.ts create mode 100644 src/shared/schema/us/actions/collections/get-structure-items.ts create mode 100644 src/shared/schema/us/actions/collections/index.ts create mode 100644 src/shared/schema/us/actions/collections/move-collection.ts create mode 100644 src/shared/schema/us/actions/collections/move-collections.ts create mode 100644 src/shared/schema/us/actions/collections/update-collection.ts create mode 100644 src/shared/schema/us/actions/color-palettes.ts create mode 100644 src/shared/schema/us/actions/editor.ts create mode 100644 src/shared/schema/us/actions/embeds/create-embed.ts create mode 100644 src/shared/schema/us/actions/embeds/delete-embed.ts create mode 100644 src/shared/schema/us/actions/embeds/index.ts create mode 100644 src/shared/schema/us/actions/embeds/list-embeds.ts create mode 100644 src/shared/schema/us/actions/entries/create-entry.ts create mode 100644 src/shared/schema/us/actions/entries/get-entries-relations.ts create mode 100644 src/shared/schema/us/actions/entries/get-entries.ts create mode 100644 src/shared/schema/us/actions/entries/get-entry.ts create mode 100644 src/shared/schema/us/actions/entries/index.ts create mode 100644 src/shared/schema/us/actions/entries/list-directory.ts create mode 100644 src/shared/schema/us/actions/entries/update-entry.ts create mode 100644 src/shared/schema/us/actions/favorites.ts create mode 100644 src/shared/schema/us/actions/index.ts create mode 100644 src/shared/schema/us/actions/locks.ts create mode 100644 src/shared/schema/us/actions/operations.ts create mode 100644 src/shared/schema/us/actions/presets.ts create mode 100644 src/shared/schema/us/actions/private.ts create mode 100644 src/shared/schema/us/actions/state.ts create mode 100644 src/shared/schema/us/actions/template.ts create mode 100644 src/shared/schema/us/actions/tenant.ts create mode 100644 src/shared/schema/us/actions/user.ts create mode 100644 src/shared/schema/us/actions/workbooks/copy-workbook.ts create mode 100644 src/shared/schema/us/actions/workbooks/create-workbook.ts create mode 100644 src/shared/schema/us/actions/workbooks/delete-workbook.ts create mode 100644 src/shared/schema/us/actions/workbooks/delete-workbooks.ts create mode 100644 src/shared/schema/us/actions/workbooks/get-workbook-entries.ts create mode 100644 src/shared/schema/us/actions/workbooks/get-workbook-shared-entries.ts create mode 100644 src/shared/schema/us/actions/workbooks/get-workbook.ts create mode 100644 src/shared/schema/us/actions/workbooks/get-workbooks-by-ids.ts create mode 100644 src/shared/schema/us/actions/workbooks/get-workbooks-list.ts create mode 100644 src/shared/schema/us/actions/workbooks/index.ts create mode 100644 src/shared/schema/us/actions/workbooks/migrate-entries-to-workbook-by-copy.ts create mode 100644 src/shared/schema/us/actions/workbooks/migrate-entries-to-workbook-by-transfer.ts create mode 100644 src/shared/schema/us/actions/workbooks/move-workbook.ts create mode 100644 src/shared/schema/us/actions/workbooks/move-workbooks.ts create mode 100644 src/shared/schema/us/actions/workbooks/update-workbook.ts create mode 100644 src/shared/schema/us/index.ts create mode 100644 src/shared/schema/us/schemas/collections/get-collections-by-ids.ts create mode 100644 src/shared/schema/us/schemas/collections/index.ts create mode 100644 src/shared/schema/us/schemas/embeds/create-embed.ts create mode 100644 src/shared/schema/us/schemas/embeds/delete-embed.ts create mode 100644 src/shared/schema/us/schemas/embeds/embed.ts create mode 100644 src/shared/schema/us/schemas/embeds/index.ts create mode 100644 src/shared/schema/us/schemas/embeds/list-embeds.ts create mode 100644 src/shared/schema/us/schemas/entries/get-entries.ts create mode 100644 src/shared/schema/us/schemas/entries/list-directory.ts create mode 100644 src/shared/schema/us/schemas/operation/index.ts create mode 100644 src/shared/schema/us/schemas/workbooks/get-workbook-entries.ts create mode 100644 src/shared/schema/us/schemas/workbooks/get-workbooks-by-ids.ts create mode 100644 src/shared/schema/us/schemas/workbooks/index.ts create mode 100644 src/shared/schema/us/types/access-bindings.ts create mode 100644 src/shared/schema/us/types/collections.ts create mode 100644 src/shared/schema/us/types/color-palettes.ts create mode 100644 src/shared/schema/us/types/common.ts create mode 100644 src/shared/schema/us/types/editor.ts create mode 100644 src/shared/schema/us/types/embeds.ts create mode 100644 src/shared/schema/us/types/entries.ts create mode 100644 src/shared/schema/us/types/favorites.ts create mode 100644 src/shared/schema/us/types/fields.ts create mode 100644 src/shared/schema/us/types/index.ts create mode 100644 src/shared/schema/us/types/locks.ts create mode 100644 src/shared/schema/us/types/operations.ts create mode 100644 src/shared/schema/us/types/presets.ts create mode 100644 src/shared/schema/us/types/private.ts create mode 100644 src/shared/schema/us/types/sort.ts create mode 100644 src/shared/schema/us/types/state.ts create mode 100644 src/shared/schema/us/types/template.ts create mode 100644 src/shared/schema/us/types/tenant.ts create mode 100644 src/shared/schema/us/types/workbooks.ts create mode 100644 src/shared/schema/utils.ts create mode 100644 src/shared/server/axios.ts create mode 100644 src/shared/types/bi-api/index.ts create mode 100644 src/shared/types/bi-api/v2.ts create mode 100644 src/shared/types/chartkit/common.ts create mode 100644 src/shared/types/chartkit/dl-chartkit.ts create mode 100644 src/shared/types/chartkit/gravity-charts/index.ts create mode 100644 src/shared/types/chartkit/gravity-charts/scatter.ts create mode 100644 src/shared/types/chartkit/index.ts create mode 100644 src/shared/types/chartkit/table.ts create mode 100644 src/shared/types/charts.ts create mode 100644 src/shared/types/color-palettes.ts create mode 100644 src/shared/types/common-update.ts create mode 100644 src/shared/types/common.ts create mode 100644 src/shared/types/config/ql/index.ts create mode 100644 src/shared/types/config/ql/v1.ts create mode 100644 src/shared/types/config/ql/v2.ts create mode 100644 src/shared/types/config/ql/v3.ts create mode 100644 src/shared/types/config/ql/v4.ts create mode 100644 src/shared/types/config/ql/v5.ts create mode 100644 src/shared/types/config/ql/v6.ts create mode 100644 src/shared/types/config/ql/v7.ts create mode 100644 src/shared/types/config/wizard/index.ts create mode 100644 src/shared/types/config/wizard/v10.ts create mode 100644 src/shared/types/config/wizard/v11.ts create mode 100644 src/shared/types/config/wizard/v12.ts create mode 100644 src/shared/types/config/wizard/v13.ts create mode 100644 src/shared/types/config/wizard/v14.ts create mode 100644 src/shared/types/config/wizard/v15.ts create mode 100644 src/shared/types/config/wizard/v2.ts create mode 100644 src/shared/types/config/wizard/v3.ts create mode 100644 src/shared/types/config/wizard/v4.ts create mode 100644 src/shared/types/config/wizard/v5.ts create mode 100644 src/shared/types/config/wizard/v6.ts create mode 100644 src/shared/types/config/wizard/v7.ts create mode 100644 src/shared/types/config/wizard/v8.ts create mode 100644 src/shared/types/config/wizard/v9.ts create mode 100644 src/shared/types/configs/index.ts create mode 100644 src/shared/types/connections.ts create mode 100644 src/shared/types/dash.ts create mode 100644 src/shared/types/dataset.ts create mode 100644 src/shared/types/entry.ts create mode 100644 src/shared/types/feature.ts create mode 100644 src/shared/types/formatting.ts create mode 100644 src/shared/types/index.ts create mode 100644 src/shared/types/language.ts create mode 100644 src/shared/types/menu.ts create mode 100644 src/shared/types/meta-manager/index.ts create mode 100644 src/shared/types/metrica.ts create mode 100644 src/shared/types/notification.ts create mode 100644 src/shared/types/permissions.ts create mode 100644 src/shared/types/ql/common.ts create mode 100644 src/shared/types/ql/versions.ts create mode 100644 src/shared/types/typed-query-api/index.ts create mode 100644 src/shared/types/typed-query-api/v1.ts create mode 100644 src/shared/types/ui-sandbox.ts create mode 100644 src/shared/types/utility-types.ts create mode 100644 src/shared/types/widget.ts create mode 100644 src/shared/types/wizard/background-settings.ts create mode 100644 src/shared/types/wizard/bars.ts create mode 100644 src/shared/types/wizard/column.ts create mode 100644 src/shared/types/wizard/export.ts create mode 100644 src/shared/types/wizard/field.ts create mode 100644 src/shared/types/wizard/index.ts create mode 100644 src/shared/types/wizard/misc.ts create mode 100644 src/shared/types/wizard/placeholder.ts create mode 100644 src/shared/types/wizard/sub-totals.ts create mode 100644 src/shared/types/wizard/versions.ts create mode 100644 src/shared/utils/__tests__/date-time.test.ts create mode 100644 src/shared/utils/array/assert-non-empty-array.ts create mode 100644 src/shared/utils/array/index.ts create mode 100644 src/shared/utils/dash.ts create mode 100644 src/shared/utils/dataset.ts create mode 100644 src/shared/utils/date-time.ts create mode 100644 src/shared/utils/entry.ts create mode 100644 src/shared/utils/feature.ts create mode 100644 src/shared/utils/functions-registry.ts create mode 100644 src/shared/utils/index.ts create mode 100644 src/shared/utils/makeFunctionTemplate.ts create mode 100644 src/shared/utils/manualError.ts create mode 100644 src/shared/utils/markdown.ts create mode 100644 src/shared/utils/markup.ts create mode 100644 src/shared/utils/openapi/index.ts create mode 100644 src/shared/utils/palettes.ts create mode 100644 src/shared/utils/ui-sandbox.ts create mode 100644 src/shared/utils/visualization-check.ts create mode 100644 src/shared/utils/wizard.ts create mode 100644 src/shared/zod-schemas/dash.ts create mode 100644 src/shared/zod-schemas/entry.ts create mode 100644 src/shared/zod-schemas/permissions.ts create mode 100644 src/shared/zod-schemas/shared-entry-permissions.ts create mode 100644 src/ui/.ci-eslintrc create mode 100644 src/ui/.eslintrc create mode 100644 src/ui/assets/icons/alert.svg create mode 100644 src/ui/assets/icons/arr-reverse.svg create mode 100644 src/ui/assets/icons/arr-right.svg create mode 100644 src/ui/assets/icons/arrow-left.svg create mode 100644 src/ui/assets/icons/arrow-right.svg create mode 100644 src/ui/assets/icons/avatar-middle.png create mode 100644 src/ui/assets/icons/branded-question.svg create mode 100644 src/ui/assets/icons/broken.svg create mode 100644 src/ui/assets/icons/browse.svg create mode 100644 src/ui/assets/icons/calendar-2.svg create mode 100644 src/ui/assets/icons/chart-monitoringql.svg create mode 100644 src/ui/assets/icons/chart-promql.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-area-100p.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-area.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-bar-100p.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-bar.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-column-100p.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-column.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-donut.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-flat-table.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-geolayers.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-geopoint.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-geopolygon.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-heatmap.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-lines.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-metric.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-pie.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-pivot.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-scatter.svg create mode 100644 src/ui/assets/icons/charts-visualisations/vis-treemap.svg create mode 100644 src/ui/assets/icons/check-folder.svg create mode 100644 src/ui/assets/icons/choveryt.svg create mode 100644 src/ui/assets/icons/clickhouse.svg create mode 100644 src/ui/assets/icons/collections/collection-colored-big-dark.svg create mode 100644 src/ui/assets/icons/collections/collection-colored-big.svg create mode 100644 src/ui/assets/icons/collections/collection-colored-dark.svg create mode 100644 src/ui/assets/icons/collections/collection-colored.svg create mode 100644 src/ui/assets/icons/collections/collection.svg create mode 100644 src/ui/assets/icons/collections/grid.svg create mode 100644 src/ui/assets/icons/collections/workbook-colored.svg create mode 100644 src/ui/assets/icons/collections/workbook.svg create mode 100644 src/ui/assets/icons/connected.svg create mode 100644 src/ui/assets/icons/connections/appmetrica.svg create mode 100644 src/ui/assets/icons/connections/choveryt.svg create mode 100644 src/ui/assets/icons/connections/chydb.svg create mode 100644 src/ui/assets/icons/connections/clickhouse.svg create mode 100644 src/ui/assets/icons/connections/csv.svg create mode 100644 src/ui/assets/icons/connections/greenplum.svg create mode 100644 src/ui/assets/icons/connections/metrica.svg create mode 100644 src/ui/assets/icons/connections/monitoring.svg create mode 100644 src/ui/assets/icons/connections/mssql.svg create mode 100644 src/ui/assets/icons/connections/mysql.svg create mode 100644 src/ui/assets/icons/connections/oracle.svg create mode 100644 src/ui/assets/icons/connections/postgres.svg create mode 100644 src/ui/assets/icons/connections/undefined.svg create mode 100644 src/ui/assets/icons/connections/ydb.svg create mode 100644 src/ui/assets/icons/connector.svg create mode 100644 src/ui/assets/icons/copy-xmark.svg create mode 100644 src/ui/assets/icons/cross.svg create mode 100644 src/ui/assets/icons/csv.svg create mode 100644 src/ui/assets/icons/dimensions.svg create mode 100644 src/ui/assets/icons/dl-auth-logo-dark.svg create mode 100644 src/ui/assets/icons/dl-auth-logo-light.svg create mode 100644 src/ui/assets/icons/error-close.svg create mode 100644 src/ui/assets/icons/error.svg create mode 100644 src/ui/assets/icons/file-diff.svg create mode 100644 src/ui/assets/icons/filter-tooltip.svg create mode 100644 src/ui/assets/icons/folder-selection.svg create mode 100644 src/ui/assets/icons/folder.svg create mode 100644 src/ui/assets/icons/funnel-clear.svg create mode 100644 src/ui/assets/icons/gradients/blue-gray-red.svg create mode 100644 src/ui/assets/icons/gradients/blue-yellow-red.svg create mode 100644 src/ui/assets/icons/gradients/blue.svg create mode 100644 src/ui/assets/icons/gradients/cyan.svg create mode 100644 src/ui/assets/icons/gradients/golden.svg create mode 100644 src/ui/assets/icons/gradients/gray.svg create mode 100644 src/ui/assets/icons/gradients/green-blue.svg create mode 100644 src/ui/assets/icons/gradients/green.svg create mode 100644 src/ui/assets/icons/gradients/oceanic.svg create mode 100644 src/ui/assets/icons/gradients/orange-blue-green.svg create mode 100644 src/ui/assets/icons/gradients/orange-gray-blue.svg create mode 100644 src/ui/assets/icons/gradients/orange-violet-blue.svg create mode 100644 src/ui/assets/icons/gradients/orange-yellow.svg create mode 100644 src/ui/assets/icons/gradients/pink-gray-green.svg create mode 100644 src/ui/assets/icons/gradients/red-blue.svg create mode 100644 src/ui/assets/icons/gradients/red-orange-green.svg create mode 100644 src/ui/assets/icons/gradients/red.svg create mode 100644 src/ui/assets/icons/gradients/traffic-light.svg create mode 100644 src/ui/assets/icons/gradients/violet-blue-green.svg create mode 100644 src/ui/assets/icons/gradients/violet-orange.svg create mode 100644 src/ui/assets/icons/gradients/violet.svg create mode 100644 src/ui/assets/icons/gradients/yellow-green-blue.svg create mode 100644 src/ui/assets/icons/gradients/yellow.svg create mode 100644 src/ui/assets/icons/greenplum.svg create mode 100644 src/ui/assets/icons/header-nav.svg create mode 100644 src/ui/assets/icons/header-star-active.svg create mode 100644 src/ui/assets/icons/header-star-inactive.svg create mode 100644 src/ui/assets/icons/id-square.svg create mode 100644 src/ui/assets/icons/info.svg create mode 100644 src/ui/assets/icons/legacy-wizard.svg create mode 100644 src/ui/assets/icons/line-cap-butt.svg create mode 100644 src/ui/assets/icons/line-cap-round.svg create mode 100644 src/ui/assets/icons/line-join-miter.svg create mode 100644 src/ui/assets/icons/line-join-round.svg create mode 100644 src/ui/assets/icons/lock.svg create mode 100644 src/ui/assets/icons/logo-ya-dl.svg create mode 100644 src/ui/assets/icons/menu.svg create mode 100644 src/ui/assets/icons/monitoring.svg create mode 100644 src/ui/assets/icons/mono-collection.svg create mode 100644 src/ui/assets/icons/mono-service-settings.svg create mode 100644 src/ui/assets/icons/mssql.svg create mode 100644 src/ui/assets/icons/mysql.svg create mode 100644 src/ui/assets/icons/okay.svg create mode 100644 src/ui/assets/icons/os-logo.svg create mode 100644 src/ui/assets/icons/palettes/datalens-classic20.svg create mode 100644 src/ui/assets/icons/palettes/datalens-default20.svg create mode 100644 src/ui/assets/icons/palettes/datalens-neo20.svg create mode 100644 src/ui/assets/icons/palettes/emerald-20.svg create mode 100644 src/ui/assets/icons/palettes/golden-20.svg create mode 100644 src/ui/assets/icons/palettes/neutral-20.svg create mode 100644 src/ui/assets/icons/palettes/oceanic-20.svg create mode 100644 src/ui/assets/icons/palettes/traffic-light-9.svg create mode 100644 src/ui/assets/icons/parameter-section.svg create mode 100644 src/ui/assets/icons/placeholder-tooltip.svg create mode 100644 src/ui/assets/icons/plus.svg create mode 100644 src/ui/assets/icons/postgres.svg create mode 100644 src/ui/assets/icons/preview-bottom.svg create mode 100644 src/ui/assets/icons/preview-side.svg create mode 100644 src/ui/assets/icons/product.svg create mode 100644 src/ui/assets/icons/public-filled.svg create mode 100644 src/ui/assets/icons/ql-chart.svg create mode 100644 src/ui/assets/icons/relations-alias.svg create mode 100644 src/ui/assets/icons/relations-any.svg create mode 100644 src/ui/assets/icons/relations-controls.svg create mode 100644 src/ui/assets/icons/relations-no-data.svg create mode 100644 src/ui/assets/icons/relations-no-link.svg create mode 100644 src/ui/assets/icons/relations.svg create mode 100644 src/ui/assets/icons/relative-mode.svg create mode 100644 src/ui/assets/icons/search-clear.svg create mode 100644 src/ui/assets/icons/shapes.svg create mode 100644 src/ui/assets/icons/sort-asc.svg create mode 100644 src/ui/assets/icons/sort-desc.svg create mode 100644 src/ui/assets/icons/source-table.svg create mode 100644 src/ui/assets/icons/sync.svg create mode 100644 src/ui/assets/icons/tick.svg create mode 100644 src/ui/assets/icons/toast-attention.svg create mode 100644 src/ui/assets/icons/toggle-preview.svg create mode 100644 src/ui/assets/icons/undefined.svg create mode 100644 src/ui/assets/icons/vis-area-100p.svg create mode 100644 src/ui/assets/icons/vis-area.svg create mode 100644 src/ui/assets/icons/vis-bar-100p.svg create mode 100644 src/ui/assets/icons/vis-bar.svg create mode 100644 src/ui/assets/icons/vis-column-100p.svg create mode 100644 src/ui/assets/icons/vis-column.svg create mode 100644 src/ui/assets/icons/vis-combined.svg create mode 100644 src/ui/assets/icons/vis-donut.svg create mode 100644 src/ui/assets/icons/vis-flat-table.svg create mode 100644 src/ui/assets/icons/vis-funnel.svg create mode 100644 src/ui/assets/icons/vis-geolayers.svg create mode 100644 src/ui/assets/icons/vis-geopoint.svg create mode 100644 src/ui/assets/icons/vis-geopolygon.svg create mode 100644 src/ui/assets/icons/vis-heatmap.svg create mode 100644 src/ui/assets/icons/vis-lines.svg create mode 100644 src/ui/assets/icons/vis-metric.svg create mode 100644 src/ui/assets/icons/vis-pie.svg create mode 100644 src/ui/assets/icons/vis-pivot.svg create mode 100644 src/ui/assets/icons/vis-scatter.svg create mode 100644 src/ui/assets/icons/vis-treemap.svg create mode 100644 src/ui/assets/icons/visualization.svg create mode 100644 src/ui/assets/icons/warning-colored.svg create mode 100644 src/ui/assets/icons/yt.svg create mode 100644 src/ui/assets/images/dl-auth-background-dark.jpg create mode 100644 src/ui/assets/images/dl-auth-background-light.jpg create mode 100644 src/ui/assets/images/illustration/dark/403.svg create mode 100644 src/ui/assets/images/illustration/dark/404.svg create mode 100644 src/ui/assets/images/illustration/dark/500.svg create mode 100644 src/ui/assets/images/illustration/dark/bad_request.svg create mode 100644 src/ui/assets/images/illustration/dark/empty_directory.svg create mode 100644 src/ui/assets/images/illustration/dark/empty_state.svg create mode 100644 src/ui/assets/images/illustration/dark/no_accounts.svg create mode 100644 src/ui/assets/images/illustration/dark/no_permission.svg create mode 100644 src/ui/assets/images/illustration/dark/nothing_found.svg create mode 100644 src/ui/assets/images/illustration/dark/project.svg create mode 100644 src/ui/assets/images/illustration/dark/success_operation.svg create mode 100644 src/ui/assets/images/illustration/light/403.svg create mode 100644 src/ui/assets/images/illustration/light/404.svg create mode 100644 src/ui/assets/images/illustration/light/500.svg create mode 100644 src/ui/assets/images/illustration/light/bad_request.svg create mode 100644 src/ui/assets/images/illustration/light/empty_directory.svg create mode 100644 src/ui/assets/images/illustration/light/empty_state.svg create mode 100644 src/ui/assets/images/illustration/light/no_accounts.svg create mode 100644 src/ui/assets/images/illustration/light/no_permission.svg create mode 100644 src/ui/assets/images/illustration/light/nothing_found.svg create mode 100644 src/ui/assets/images/illustration/light/project.svg create mode 100644 src/ui/assets/images/illustration/light/success_operation.svg create mode 100644 src/ui/components/AccessRights/AccessRightsUrlOpen.tsx create mode 100644 src/ui/components/ActionPanel/ActionPanel.scss create mode 100644 src/ui/components/ActionPanel/ActionPanel.tsx create mode 100644 src/ui/components/ActionPanel/ActionPanelHelpers.ts create mode 100644 src/ui/components/ActionPanel/components/ChartSaveControls/ChartSaveControl.scss create mode 100644 src/ui/components/ActionPanel/components/ChartSaveControls/ChartSaveControl.tsx create mode 100644 src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveButton/SaveButton.scss create mode 100644 src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveButton/SaveButton.tsx create mode 100644 src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveDropdown/SaveDropdown.scss create mode 100644 src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveDropdown/SaveDropdown.tsx create mode 100644 src/ui/components/ActionPanel/components/ChartSaveControls/types.ts create mode 100644 src/ui/components/ActionPanel/components/ChartSaveControls/useAdditionalItems.tsx create mode 100644 src/ui/components/ActionPanel/components/EntryPanel/EntryPanel.scss create mode 100644 src/ui/components/ActionPanel/components/EntryPanel/EntryPanel.tsx create mode 100644 src/ui/components/ActionPanel/index.ts create mode 100644 src/ui/components/ActionPanel/utils.ts create mode 100644 src/ui/components/AdaptiveDialog/AdaptiveDialog.tsx create mode 100644 src/ui/components/AnimateBlock/AnimateBlock.scss create mode 100644 src/ui/components/AnimateBlock/AnimateBlock.tsx create mode 100644 src/ui/components/AnimateBlock/index.ts create mode 100644 src/ui/components/AsideHeaderAdapter/AsideHeaderAdapter.scss create mode 100644 src/ui/components/AsideHeaderAdapter/AsideHeaderAdapter.tsx create mode 100644 src/ui/components/AsideHeaderAdapter/LogoText/LogoText.scss create mode 100644 src/ui/components/AsideHeaderAdapter/LogoText/LogoText.tsx create mode 100644 src/ui/components/AsideHeaderAdapter/Settings/Appearance/HighcontrastSettings.tsx create mode 100644 src/ui/components/AsideHeaderAdapter/Settings/Appearance/ThemeSettings.tsx create mode 100644 src/ui/components/AsideHeaderAdapter/Settings/ItemField/ItemField.tsx create mode 100644 src/ui/components/AsideHeaderAdapter/Settings/LanguageSettings.tsx create mode 100644 src/ui/components/AsideHeaderAdapter/Settings/Settings.tsx create mode 100644 src/ui/components/AsideHeaderAdapter/Settings/types.ts create mode 100644 src/ui/components/AsideHeaderAdapter/Settings/utils.ts create mode 100644 src/ui/components/AsideHeaderAdapter/VersionDialog/VersionDialog.tsx create mode 100644 src/ui/components/AsideHeaderAdapter/constants.ts create mode 100644 src/ui/components/AsideHeaderAdapter/helpers.ts create mode 100644 src/ui/components/AsideHeaderAdapter/index.ts create mode 100644 src/ui/components/AsyncImage/AsyncImage.tsx create mode 100644 src/ui/components/AutogeneratedPaletteIcon/AutogeneratedPaletteIcon.scss create mode 100644 src/ui/components/AutogeneratedPaletteIcon/AutogeneratedPaletteIcon.tsx create mode 100644 src/ui/components/BetaMark/BetaMark.scss create mode 100644 src/ui/components/BetaMark/BetaMark.tsx create mode 100644 src/ui/components/Breadcrumbs/CollectionsStructureBreadcrumbs/CollectionsStructureBreadcrumbs.scss create mode 100644 src/ui/components/Breadcrumbs/CollectionsStructureBreadcrumbs/CollectionsStructureBreadcrumbs.tsx create mode 100644 src/ui/components/BreadcrumbsItemLink/BreadcrumbsItemLink.tsx create mode 100644 src/ui/components/ButtonAttach/ButtonAttach.scss create mode 100644 src/ui/components/ButtonAttach/ButtonAttach.tsx create mode 100644 src/ui/components/ChartModelingSettings/ChartModelingSettings.scss create mode 100644 src/ui/components/ChartModelingSettings/ChartModelingSettings.tsx create mode 100644 src/ui/components/ChartModelingSettings/components/ShapeSelect/ShapeSelect.tsx create mode 100644 src/ui/components/ChartModelingSettings/components/SmothSettings.tsx create mode 100644 src/ui/components/ChartModelingSettings/components/TrendSettings.tsx create mode 100644 src/ui/components/ChartModelingSettings/constants.ts create mode 100644 src/ui/components/ChartModelingSettings/i18n.ts create mode 100644 src/ui/components/ChartModelingSettings/utils.ts create mode 100644 src/ui/components/Collapse/Collapse.scss create mode 100644 src/ui/components/Collapse/Collapse.tsx create mode 100644 src/ui/components/Collapse/types.ts create mode 100644 src/ui/components/CollectionFilters/CollectionFilters.scss create mode 100644 src/ui/components/CollectionFilters/CollectionFilters.tsx create mode 100644 src/ui/components/CollectionFilters/index.ts create mode 100644 src/ui/components/CollectionIcon/CollectionIcon.tsx create mode 100644 src/ui/components/CollectionsStructure/CollectionDialog/CollectionDialog.scss create mode 100644 src/ui/components/CollectionsStructure/CollectionDialog/CollectionDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/CollectionDialog/index.ts create mode 100644 src/ui/components/CollectionsStructure/CollectionNoCreatePermissionDialog/CollectionNoCreatePermissionDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/CollectionNoCreatePermissionDialog/index.ts create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/CollectionStructureDialog.scss create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/CollectionStructureDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/CreateEntityDialog/CreateEntityDialog.scss create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/CreateEntityDialog/CreateEntityDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/NewTitleDialog/NewTitleDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/StructureItemSelect/Item/Item.scss create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/StructureItemSelect/Item/Item.tsx create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/StructureItemSelect/Item/index.ts create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/StructureItemSelect/StructureItemSelect.scss create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/StructureItemSelect/StructureItemSelect.tsx create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/StructureItemSelect/index.ts create mode 100644 src/ui/components/CollectionsStructure/CollectionStructureDialog/index.ts create mode 100644 src/ui/components/CollectionsStructure/CopyEntriesDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/CopyWorkbookDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/CreateCollectionDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/CreateEntryInWorkbookDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/CreateWorkbookDialog/CreateWorkbookDialog.scss create mode 100644 src/ui/components/CollectionsStructure/CreateWorkbookDialog/CreateWorkbookDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/CreateWorkbookDialog/ImportFileField/ImportFileField.scss create mode 100644 src/ui/components/CollectionsStructure/CreateWorkbookDialog/ImportFileField/ImportFileField.tsx create mode 100644 src/ui/components/CollectionsStructure/CreateWorkbookDialog/ImportWorkbookView/ImportWorkbookView.scss create mode 100644 src/ui/components/CollectionsStructure/CreateWorkbookDialog/ImportWorkbookView/ImportWorkbookView.tsx create mode 100644 src/ui/components/CollectionsStructure/CreateWorkbookDialog/utils.ts create mode 100644 src/ui/components/CollectionsStructure/DeleteCollectionDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/DeleteCollectionsWorkbooksContent/DeleteCollectionsWorkbooksContent.scss create mode 100644 src/ui/components/CollectionsStructure/DeleteCollectionsWorkbooksContent/DeleteCollectionsWorkbooksContent.tsx create mode 100644 src/ui/components/CollectionsStructure/DeleteCollectionsWorkbooksContent/index.ts create mode 100644 src/ui/components/CollectionsStructure/DeleteCollectionsWorkbooksDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/DeleteDialog/DeleteDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/DeleteDialog/index.ts create mode 100644 src/ui/components/CollectionsStructure/DeleteWorkbookDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/EditCollectionDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/EditSharedEntryDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/EditWorkbookDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/ExportWorkbookDialog/ExportInfo/ExportInfo.tsx create mode 100644 src/ui/components/CollectionsStructure/ExportWorkbookDialog/ExportWorkbookDialog.scss create mode 100644 src/ui/components/CollectionsStructure/ExportWorkbookDialog/ExportWorkbookDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/ExportWorkbookDialog/utils.ts create mode 100644 src/ui/components/CollectionsStructure/MigrateEntryToWorkbookDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/MoveCollectionDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/MoveCollectionsWorkbooksDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/MoveSharedEntryDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/MoveWorkbookDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/WorkbookDialog/WorkbookDialog.scss create mode 100644 src/ui/components/CollectionsStructure/WorkbookDialog/WorkbookDialog.tsx create mode 100644 src/ui/components/CollectionsStructure/WorkbookDialog/index.ts create mode 100644 src/ui/components/CollectionsStructure/WorkbookDialog/types.ts create mode 100644 src/ui/components/CollectionsStructure/components/EntriesNotificationCut/EntriesNotificationCut.scss create mode 100644 src/ui/components/CollectionsStructure/components/EntriesNotificationCut/EntriesNotificationCut.tsx create mode 100644 src/ui/components/CollectionsStructure/components/EntriesNotificationCut/helpers.ts create mode 100644 src/ui/components/CollectionsStructure/components/EntriesNotificationCut/types.ts create mode 100644 src/ui/components/CollectionsStructure/hooks/useCollectionEntityDialogState.ts create mode 100644 src/ui/components/CollectionsStructure/hooks/useCollectionStructureDialogState.ts create mode 100644 src/ui/components/CollectionsStructure/hooks/useNotificationsAndDetails.ts create mode 100644 src/ui/components/CollectionsStructure/index.ts create mode 100644 src/ui/components/CollectionsStructure/types.ts create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorPaletteCharkitPreview/ColorPaletteChartkitPreview.tsx create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorPaletteCharkitPreview/types.ts create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorPaletteCharkitPreview/utils.ts create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorPaletteEditorContainer.scss create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorPaletteEditorContainer.tsx create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorPalettesCard/ColorPalettesCard.scss create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorPalettesCard/ColorPalettesCard.tsx create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorPalettesCard/DefaultPaletteSelect/DefaultPaletteSelect.scss create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorPalettesCard/DefaultPaletteSelect/DefaultPaletteSelect.tsx create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorTextInput/ColorTextInput.scss create mode 100644 src/ui/components/ColorPaletteEditorContainer/ColorTextInput/ColorTextInput.tsx create mode 100644 src/ui/components/ColorPaletteEditorContainer/GradientPaletteEditor/GradientPaletteEditor.scss create mode 100644 src/ui/components/ColorPaletteEditorContainer/GradientPaletteEditor/GradientPaletteEditor.tsx create mode 100644 src/ui/components/ColorPaletteEditorContainer/PaletteEditor/PaletteEditor.scss create mode 100644 src/ui/components/ColorPaletteEditorContainer/PaletteEditor/PaletteEditor.tsx create mode 100644 src/ui/components/ColorPaletteSelect/ColorPaletteSelect.scss create mode 100644 src/ui/components/ColorPaletteSelect/ColorPaletteSelect.tsx create mode 100644 src/ui/components/ColorPickerInput/ColorPickerInput.scss create mode 100644 src/ui/components/ColorPickerInput/ColorPickerInput.tsx create mode 100644 src/ui/components/ColorPickerInput/utils.ts create mode 100644 src/ui/components/ColorValueItem/ColorValueItem.scss create mode 100644 src/ui/components/ColorValueItem/ColorValueItem.tsx create mode 100644 src/ui/components/ConnectorIcon/ConnectorIcon.scss create mode 100644 src/ui/components/ConnectorIcon/ConnectorIcon.tsx create mode 100644 src/ui/components/ControlComponents/BackButton/BackButton.scss create mode 100644 src/ui/components/ControlComponents/BackButton/BackButton.tsx create mode 100644 src/ui/components/ControlComponents/Dialog/Dialog.scss create mode 100644 src/ui/components/ControlComponents/Dialog/Dialog.tsx create mode 100644 src/ui/components/ControlComponents/Sections/AppearanceSection/AppearanceSection.scss create mode 100644 src/ui/components/ControlComponents/Sections/AppearanceSection/AppearanceSection.tsx create mode 100644 src/ui/components/ControlComponents/Sections/AppearanceSection/Rows/ColorAccentRow/ColorAccentRow.tsx create mode 100644 src/ui/components/ControlComponents/Sections/AppearanceSection/Rows/HintRow/HintRow.tsx create mode 100644 src/ui/components/ControlComponents/Sections/AppearanceSection/Rows/InnerTitleRow/InnerTitleRow.tsx create mode 100644 src/ui/components/ControlComponents/Sections/AppearanceSection/Rows/TitlePlacementRow/TitlePlacementRow.tsx create mode 100644 src/ui/components/ControlComponents/Sections/AppearanceSection/Rows/TitleRow/TitleRow.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/CommonGroupSettingsSection.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ConnectionSettings/ConnectionSettings.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ConnectionSettings/components/ConnectionSelector/ConnectionSelector.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ConnectionSettings/components/ConnectionSelector/helpers.ts create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ConnectionSettings/components/QueryTypeControl/QueryTypeControl.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ConnectionSettings/components/QueryTypeControl/components/EditLabelControl/EditLabelControl.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ConnectionSettings/components/QueryTypeControl/components/EditQueryControl/EditQueryControl.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ConnectionSettings/components/QueryTypeControl/helpers.ts create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ConnectionSettings/helpers/get-distincts-by-typed-query.ts create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/DatasetSelectorSettings/DatasetSelectorSettings.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/EntrySelector/EntrySelector.scss create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/EntrySelector/EntrySelector.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ExternalSelectorSettings/ExternalSelectorSettings.scss create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ExternalSelectorSettings/ExternalSelectorSettings.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ImpactTypeSelect/CurrentTabOption/CurrentTabOption.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ImpactTypeSelect/ImpactTypeSelect.scss create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ImpactTypeSelect/ImpactTypeSelect.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ImpactTypeSelect/SelectedTabsOption/SelectedTabsOption.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ImpactTypeSelect/constants.ts create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ImpactTypeSelect/helpers.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ImpactTypeSelect/useTabVisibilityValidation.ts create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/InputTypeSelector/InputTypeSelector.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/ParameterNameInput/ParameterNameInput.tsx create mode 100644 src/ui/components/ControlComponents/Sections/CommonSettingsSection/helpers/input-type-select.tsx create mode 100644 src/ui/components/ControlComponents/Sections/Date/Acceptable/Acceptable.scss create mode 100644 src/ui/components/ControlComponents/Sections/Date/Acceptable/Acceptable.tsx create mode 100644 src/ui/components/ControlComponents/Sections/Date/Default/Default.scss create mode 100644 src/ui/components/ControlComponents/Sections/Date/Default/Default.tsx create mode 100644 src/ui/components/ControlComponents/Sections/OperationSelector/OperationSelector.tsx create mode 100644 src/ui/components/ControlComponents/Sections/ParametersSection/ParametersSection.scss create mode 100644 src/ui/components/ControlComponents/Sections/ParametersSection/ParametersSection.tsx create mode 100644 src/ui/components/ControlComponents/Sections/Switchers/Button/Button.scss create mode 100644 src/ui/components/ControlComponents/Sections/Switchers/Button/Button.tsx create mode 100644 src/ui/components/ControlComponents/Sections/Switchers/DatasetField/DatasetField.scss create mode 100644 src/ui/components/ControlComponents/Sections/Switchers/DatasetField/DatasetField.tsx create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ListValueControl/Acceptable/Acceptable.tsx create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ListValueControl/Acceptable/SelectorValuesDialogBody.scss create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ListValueControl/Acceptable/SelectorValuesDialogBody.tsx create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ListValueControl/ListValueControl.tsx create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ListValueControl/MultiselectableCheckbox.tsx create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ListValueControl/ValueSelector/DynamicValueSelect.tsx create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ListValueControl/ValueSelector/StaticValueSelect.tsx create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ListValueControl/ValueSelector/hooks.ts create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ListValueControl/ValueSelector/types.ts create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ListValueControl/ValueSelector/utils.ts create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/RequiredValueCheckbox/RequiredValueCheckbox.tsx create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ValueSelector.scss create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/ValueSelector.tsx create mode 100644 src/ui/components/ControlComponents/Sections/ValueSelector/helpers/get-distincts-by-dataset-field.ts create mode 100644 src/ui/components/ControlComponents/Sections/Wrapper/Wrapper.scss create mode 100644 src/ui/components/ControlComponents/Sections/Wrapper/Wrapper.tsx create mode 100644 src/ui/components/ControlComponents/SelectorPreview/SelectorPreview.tsx create mode 100644 src/ui/components/ControlComponents/SelectorTypeSelect/SelectorTypeSelect.tsx create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/CopyEntriesToWorkbookDialog.scss create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/CopyEntriesToWorkbookDialog.tsx create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/components/Body/Body.scss create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/components/Body/Body.tsx create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/components/Body/index.ts create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/components/EntriesGroup/EntriesGroup.scss create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/components/EntriesGroup/EntriesGroup.tsx create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/components/EntriesGroup/index.ts create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/components/EntryRow/EntryRow.scss create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/components/EntryRow/EntryRow.tsx create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/components/EntryRow/index.ts create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/index.ts create mode 100644 src/ui/components/CopyEntriesToWorkbookDialog/utils.ts create mode 100644 src/ui/components/DNDPane/DNDItem/DNDItem.tsx create mode 100644 src/ui/components/DNDPane/DNDPane.scss create mode 100644 src/ui/components/DNDPane/DNDPane.tsx create mode 100644 src/ui/components/DashKit/DashKit.scss create mode 100644 src/ui/components/DashKit/DashKit.ts create mode 100644 src/ui/components/DashKit/constants.ts create mode 100644 src/ui/components/DashKit/context/WidgetContext.tsx create mode 100644 src/ui/components/DashKit/helpers.tsx create mode 100644 src/ui/components/DashKit/plugins/Control/Control.scss create mode 100644 src/ui/components/DashKit/plugins/Control/Control.tsx create mode 100644 src/ui/components/DashKit/plugins/Control/ControlItems/ControlItemSelect.scss create mode 100644 src/ui/components/DashKit/plugins/Control/ControlItems/ControlItemSelect.tsx create mode 100644 src/ui/components/DashKit/plugins/Control/Error/Error.scss create mode 100644 src/ui/components/DashKit/plugins/Control/Error/Error.tsx create mode 100644 src/ui/components/DashKit/plugins/Control/constants.ts create mode 100644 src/ui/components/DashKit/plugins/Control/prerenderMiddleware.js create mode 100644 src/ui/components/DashKit/plugins/Control/types.ts create mode 100644 src/ui/components/DashKit/plugins/Control/utils.ts create mode 100644 src/ui/components/DashKit/plugins/DebugInfoTool/DebugInfoTool.scss create mode 100644 src/ui/components/DashKit/plugins/DebugInfoTool/DebugInfoTool.tsx create mode 100644 src/ui/components/DashKit/plugins/GroupControl/Control/Control.tsx create mode 100644 src/ui/components/DashKit/plugins/GroupControl/Control/store/reducer.ts create mode 100644 src/ui/components/DashKit/plugins/GroupControl/Control/store/types.ts create mode 100644 src/ui/components/DashKit/plugins/GroupControl/GroupControl.scss create mode 100644 src/ui/components/DashKit/plugins/GroupControl/GroupControl.tsx create mode 100644 src/ui/components/DashKit/plugins/GroupControl/types.ts create mode 100644 src/ui/components/DashKit/plugins/GroupControl/utils.ts create mode 100644 src/ui/components/DashKit/plugins/Image/Image.scss create mode 100644 src/ui/components/DashKit/plugins/Image/Image.tsx create mode 100644 src/ui/components/DashKit/plugins/RendererWrapper/RendererWrapper.scss create mode 100644 src/ui/components/DashKit/plugins/RendererWrapper/RendererWrapper.tsx create mode 100644 src/ui/components/DashKit/plugins/Text/Text.scss create mode 100644 src/ui/components/DashKit/plugins/Text/Text.tsx create mode 100644 src/ui/components/DashKit/plugins/Title/AnchorLink/AnchorLink.tsx create mode 100644 src/ui/components/DashKit/plugins/Title/Title.scss create mode 100644 src/ui/components/DashKit/plugins/Title/Title.tsx create mode 100644 src/ui/components/DashKit/plugins/Title/constants.ts create mode 100644 src/ui/components/DashKit/plugins/Title/utils.ts create mode 100644 src/ui/components/DashKit/plugins/Widget/WidgetPlugin.tsx create mode 100644 src/ui/components/DashKit/plugins/Widget/components/Footer.tsx create mode 100644 src/ui/components/DashKit/plugins/Widget/components/Header.tsx create mode 100644 src/ui/components/DashKit/plugins/Widget/components/helpers.ts create mode 100644 src/ui/components/DashKit/plugins/Widget/store/actions.ts create mode 100644 src/ui/components/DashKit/plugins/Widget/store/reducer.ts create mode 100644 src/ui/components/DashKit/plugins/Widget/types.ts create mode 100644 src/ui/components/DashKit/plugins/mixins.scss create mode 100644 src/ui/components/DashKit/plugins/types.ts create mode 100644 src/ui/components/DashKit/utils.ts create mode 100644 src/ui/components/DataTypeIcon/DataTypeIcon.scss create mode 100644 src/ui/components/DataTypeIcon/DataTypeIcon.tsx create mode 100644 src/ui/components/DatasetFieldList/DatasetFieldList.scss create mode 100644 src/ui/components/DatasetFieldList/DatasetFieldList.tsx create mode 100644 src/ui/components/DebouncedInput/DebouncedInput.tsx create mode 100644 src/ui/components/DebouncedInput/index.ts create mode 100644 src/ui/components/DialogAddSharedEntryFromLink/DialogAddSharedEntryFromLink.tsx create mode 100644 src/ui/components/DialogChartWidget/DialogChartWidget.scss create mode 100644 src/ui/components/DialogChartWidget/DialogChartWidget.tsx create mode 100644 src/ui/components/DialogCommon/DialogCommon.scss create mode 100644 src/ui/components/DialogCommon/DialogCommon.tsx create mode 100644 src/ui/components/DialogConfirm/DialogConfirm.tsx create mode 100644 src/ui/components/DialogDefault/DialogDefault.tsx create mode 100644 src/ui/components/DialogEditItem/DialogEditItem.tsx create mode 100644 src/ui/components/DialogEntryDescription/DialogEntryDescription.scss create mode 100644 src/ui/components/DialogEntryDescription/DialogEntryDescription.tsx create mode 100644 src/ui/components/DialogEntryDescription/DialogEntryDescriptionWrapper.tsx create mode 100644 src/ui/components/DialogEntryDescription/EntryDescriptionButton.tsx create mode 100644 src/ui/components/DialogEntryDescription/index.ts create mode 100644 src/ui/components/DialogErrorWithTabs/DebugInfo/DebugInfo.scss create mode 100644 src/ui/components/DialogErrorWithTabs/DebugInfo/DebugInfo.tsx create mode 100644 src/ui/components/DialogErrorWithTabs/DialogErrorWithTabs.scss create mode 100644 src/ui/components/DialogErrorWithTabs/DialogErrorWithTabs.tsx create mode 100644 src/ui/components/DialogErrorWithTabs/ErrorText/ErrorText.scss create mode 100644 src/ui/components/DialogErrorWithTabs/ErrorText/ErrorText.tsx create mode 100644 src/ui/components/DialogErrorWithTabs/Tabs/DatabaseRequestTab/DatabaseRequestTab.tsx create mode 100644 src/ui/components/DialogErrorWithTabs/Tabs/DatabaseResponseTab/DatabaseResponseTab.tsx create mode 100644 src/ui/components/DialogErrorWithTabs/Tabs/DebugTab/DebugTab.tsx create mode 100644 src/ui/components/DialogErrorWithTabs/Tabs/DebugTab/utils.test.ts create mode 100644 src/ui/components/DialogErrorWithTabs/Tabs/DebugTab/utils.ts create mode 100644 src/ui/components/DialogErrorWithTabs/Tabs/DocumentationTab/DocumentationTab.scss create mode 100644 src/ui/components/DialogErrorWithTabs/Tabs/DocumentationTab/DocumentationTab.tsx create mode 100644 src/ui/components/DialogExternalControl/DialogExternalControl.scss create mode 100644 src/ui/components/DialogExternalControl/DialogExternalControl.tsx create mode 100644 src/ui/components/DialogFieldEditor/DialogFieldEditor.scss create mode 100644 src/ui/components/DialogFieldEditor/DialogFieldEditor.tsx create mode 100644 src/ui/components/DialogFilter/BooleanFilter/BooleanFilter.scss create mode 100644 src/ui/components/DialogFilter/BooleanFilter/BooleanFilter.tsx create mode 100644 src/ui/components/DialogFilter/DateFilter/DateFilter.tsx create mode 100644 src/ui/components/DialogFilter/DialogFilter.scss create mode 100644 src/ui/components/DialogFilter/DialogFilter.tsx create mode 100644 src/ui/components/DialogFilter/InputFilter/InputFilter.scss create mode 100644 src/ui/components/DialogFilter/InputFilter/InputFilter.tsx create mode 100644 src/ui/components/DialogFilter/SelectFilter/SelectFilter.scss create mode 100644 src/ui/components/DialogFilter/SelectFilter/SelectFilter.tsx create mode 100644 src/ui/components/DialogFilter/constants.ts create mode 100644 src/ui/components/DialogFilter/typings.ts create mode 100644 src/ui/components/DialogFilter/utils.ts create mode 100644 src/ui/components/DialogGroupControl/DialogGroupControl.scss create mode 100644 src/ui/components/DialogGroupControl/DialogGroupControl.tsx create mode 100644 src/ui/components/DialogGroupControl/GroupControlBody/GroupControlBody.tsx create mode 100644 src/ui/components/DialogGroupControl/GroupControlBody/SimilarSelectorsBlock/SimilarSelectorsBlock.tsx create mode 100644 src/ui/components/DialogGroupControl/GroupControlFooter/GroupControlFooter.tsx create mode 100644 src/ui/components/DialogGroupControl/GroupControlSidebar/GroupControlSidebar.tsx create mode 100644 src/ui/components/DialogGroupControl/GroupControlSidebar/TabItemWrapper/TabItemWrapper.scss create mode 100644 src/ui/components/DialogGroupControl/GroupControlSidebar/TabItemWrapper/TabItemWrapper.tsx create mode 100644 src/ui/components/DialogGroupControl/GroupExtendedSettings/ControlPlacementRow.tsx create mode 100644 src/ui/components/DialogGroupControl/GroupExtendedSettings/GroupExtendedSettings.scss create mode 100644 src/ui/components/DialogGroupControl/GroupExtendedSettings/GroupExtendedSettings.tsx create mode 100644 src/ui/components/DialogGroupControl/hooks/useSimilarSelectorsActions.ts create mode 100644 src/ui/components/DialogImageWidget/DialogImageWidget.scss create mode 100644 src/ui/components/DialogImageWidget/DialogImageWidget.tsx create mode 100644 src/ui/components/DialogImageWidget/index.ts create mode 100644 src/ui/components/DialogManager/DialogManager.ts create mode 100644 src/ui/components/DialogManager/DialogManagerContainer.tsx create mode 100644 src/ui/components/DialogNoRights/DialogNoRights.scss create mode 100644 src/ui/components/DialogNoRights/DialogNoRights.tsx create mode 100644 src/ui/components/DialogParameter/DefaultValueSection.tsx create mode 100644 src/ui/components/DialogParameter/DialogParameter.scss create mode 100644 src/ui/components/DialogParameter/DialogParameter.tsx create mode 100644 src/ui/components/DialogParameter/TemplateSection.tsx create mode 100644 src/ui/components/DialogParameter/helpers.tsx create mode 100644 src/ui/components/DialogParameter/useParameterForm.ts create mode 100644 src/ui/components/DialogParameter/useParameterTooltipMarkdown.tsx create mode 100644 src/ui/components/DialogParameter/useValidationErrors.ts create mode 100644 src/ui/components/DialogQLParameter/DialogQLParameter.scss create mode 100644 src/ui/components/DialogQLParameter/DialogQLParameter.tsx create mode 100644 src/ui/components/DialogRelatedEntities/DialogRelatedEntities.scss create mode 100644 src/ui/components/DialogRelatedEntities/DialogRelatedEntities.tsx create mode 100644 src/ui/components/DialogRelatedEntities/constants.ts create mode 100644 src/ui/components/DialogRelations/DialogRelations.scss create mode 100644 src/ui/components/DialogRelations/DialogRelations.tsx create mode 100644 src/ui/components/DialogRelations/components/Content/Content.scss create mode 100644 src/ui/components/DialogRelations/components/Content/Content.tsx create mode 100644 src/ui/components/DialogRelations/components/Content/Row.tsx create mode 100644 src/ui/components/DialogRelations/components/Content/helpers.tsx create mode 100644 src/ui/components/DialogRelations/components/Content/types.ts create mode 100644 src/ui/components/DialogRelations/components/DialogAliases/DialogAliases.scss create mode 100644 src/ui/components/DialogRelations/components/DialogAliases/DialogAliases.tsx create mode 100644 src/ui/components/DialogRelations/components/DialogAliases/components/AddAliases/AddAliases.scss create mode 100644 src/ui/components/DialogRelations/components/DialogAliases/components/AddAliases/AddAliases.tsx create mode 100644 src/ui/components/DialogRelations/components/DialogAliases/components/AddAliases/helpers.ts create mode 100644 src/ui/components/DialogRelations/components/DialogAliases/components/AliasesDetail/AliasesDetail.scss create mode 100644 src/ui/components/DialogRelations/components/DialogAliases/components/AliasesDetail/AliasesDetail.tsx create mode 100644 src/ui/components/DialogRelations/components/DialogAliases/components/AliasesList/AliasesInvalidList.tsx create mode 100644 src/ui/components/DialogRelations/components/DialogAliases/components/AliasesList/AliasesList.scss create mode 100644 src/ui/components/DialogRelations/components/DialogAliases/components/AliasesList/AliasesList.tsx create mode 100644 src/ui/components/DialogRelations/components/Filters/Filters.scss create mode 100644 src/ui/components/DialogRelations/components/Filters/Filters.tsx create mode 100644 src/ui/components/DialogRelations/constants.ts create mode 100644 src/ui/components/DialogRelations/helpers.tsx create mode 100644 src/ui/components/DialogRelations/hooks/helpers.ts create mode 100644 src/ui/components/DialogRelations/hooks/helpersChart.ts create mode 100644 src/ui/components/DialogRelations/hooks/helpersControls.ts create mode 100644 src/ui/components/DialogRelations/hooks/helpersDatasets.ts create mode 100644 src/ui/components/DialogRelations/hooks/useFilteredRelations.tsx create mode 100644 src/ui/components/DialogRelations/hooks/useRelations.tsx create mode 100644 src/ui/components/DialogRelations/test/helpers.test.ts create mode 100644 src/ui/components/DialogRelations/types.ts create mode 100644 src/ui/components/DialogSelectSharedEntry/DialogSelectSharedEntry.scss create mode 100644 src/ui/components/DialogSelectSharedEntry/DialogSelectSharedEntry.tsx create mode 100644 src/ui/components/DialogSharedEntryBindings/DialogSharedEntryBindings.scss create mode 100644 src/ui/components/DialogSharedEntryBindings/DialogSharedEntryBindings.tsx create mode 100644 src/ui/components/DialogSharedEntryBindings/components/DeleteAlert.tsx create mode 100644 src/ui/components/DialogSharedEntryBindings/components/DirectionControl.tsx create mode 100644 src/ui/components/DialogSharedEntryBindings/components/Relations.tsx create mode 100644 src/ui/components/DialogSharedEntryBindings/components/SharedBindingsFooter.tsx create mode 100644 src/ui/components/DialogSharedEntryBindings/components/SharedBindingsHeader.tsx create mode 100644 src/ui/components/DialogSharedEntryBindings/constants.ts create mode 100644 src/ui/components/DialogSharedEntryBindings/hooks/useSharedEntryBindings.ts create mode 100644 src/ui/components/DialogSharedEntryBindings/types.ts create mode 100644 src/ui/components/DialogSharedEntryBindings/utils.ts create mode 100644 src/ui/components/DialogSharedEntryPermissions/DialogSharedEntryPermissions.scss create mode 100644 src/ui/components/DialogSharedEntryPermissions/DialogSharedEntryPermissions.tsx create mode 100644 src/ui/components/DialogSharedEntryPermissions/components/PermissionButton/PermissionButton.scss create mode 100644 src/ui/components/DialogSharedEntryPermissions/components/PermissionButton/PermissionButton.tsx create mode 100644 src/ui/components/DialogSharedEntryUnbind/DialogSharedEntryUnbind.scss create mode 100644 src/ui/components/DialogSharedEntryUnbind/DialogSharedEntryUnbind.tsx create mode 100644 src/ui/components/DialogSharedRelatedEntities/DialogSharedRelatedEntities.scss create mode 100644 src/ui/components/DialogSharedRelatedEntities/DialogSharedRelatedEntities.tsx create mode 100644 src/ui/components/DialogSuccessWithAction/DialogSuccessWithAction.scss create mode 100644 src/ui/components/DialogSuccessWithAction/DialogSuccessWithAction.tsx create mode 100644 src/ui/components/DialogTextWidget/DialogTextWidget.scss create mode 100644 src/ui/components/DialogTextWidget/DialogTextWidget.tsx create mode 100644 src/ui/components/DialogTextWidget/index.tsx create mode 100644 src/ui/components/DialogTitleWidget/DialogTitleWidget.scss create mode 100644 src/ui/components/DialogTitleWidget/DialogTitleWidget.tsx create mode 100644 src/ui/components/DialogTitleWidget/useColorSettings.ts create mode 100644 src/ui/components/DialogWarning/DialogWarning.tsx create mode 100644 src/ui/components/DragAndDrop/DragAndDrop.tsx create mode 100644 src/ui/components/DropdownAction/DropdownAction.scss create mode 100644 src/ui/components/DropdownAction/DropdownAction.tsx create mode 100644 src/ui/components/DropdownNavigation/DropdownNavigation.js create mode 100644 src/ui/components/DropdownNavigation/DropdownNavigation.scss create mode 100644 src/ui/components/EditableText/EditableText.tsx create mode 100644 src/ui/components/EntitiesList/EntitiesList.scss create mode 100644 src/ui/components/EntitiesList/EntitiesList.tsx create mode 100644 src/ui/components/EntitiesList/types.ts create mode 100644 src/ui/components/EntityIcon/EntityIcon.scss create mode 100644 src/ui/components/EntityIcon/EntityIcon.tsx create mode 100644 src/ui/components/EntityLink/EntityLink.scss create mode 100644 src/ui/components/EntityLink/EntityLink.tsx create mode 100644 src/ui/components/EntityRow/EntityRow.scss create mode 100644 src/ui/components/EntityRow/EntityRow.tsx create mode 100644 src/ui/components/EntriesList/EntriesList.scss create mode 100644 src/ui/components/EntriesList/EntriesList.tsx create mode 100644 src/ui/components/EntriesList/Item/Item.tsx create mode 100644 src/ui/components/EntriesList/types.ts create mode 100644 src/ui/components/EntryBreadcrumbs/EntryBreadcrumbs.scss create mode 100644 src/ui/components/EntryBreadcrumbs/EntryBreadcrumbs.tsx create mode 100644 src/ui/components/EntryBreadcrumbs/helpers.ts create mode 100644 src/ui/components/EntryContextMenu/EntryContextMenu.tsx create mode 100644 src/ui/components/EntryContextMenu/EntryContextMenuBase/EntryContextMenuBase.scss create mode 100644 src/ui/components/EntryContextMenu/EntryContextMenuBase/EntryContextMenuBase.tsx create mode 100644 src/ui/components/EntryContextMenu/constants.ts create mode 100644 src/ui/components/EntryContextMenu/helpers.ts create mode 100644 src/ui/components/EntryContextMenu/index.ts create mode 100644 src/ui/components/EntryContextMenu/test/withConfiguredEntryContextMenu.test.ts create mode 100644 src/ui/components/EntryContextMenu/types.ts create mode 100644 src/ui/components/EntryContextMenu/utils.ts create mode 100644 src/ui/components/EntryContextMenu/withConfiguredEntryContextMenu/helpers.ts create mode 100644 src/ui/components/EntryContextMenu/withConfiguredEntryContextMenu/withConfiguredEntryContextMenu.tsx create mode 100644 src/ui/components/EntryDialogues/DialogAccess/DialogAccess.tsx create mode 100644 src/ui/components/EntryDialogues/DialogAccessDescription/DialogAccessDescription.scss create mode 100644 src/ui/components/EntryDialogues/DialogAccessDescription/DialogAccessDescription.tsx create mode 100644 src/ui/components/EntryDialogues/DialogCopyEntry/DialogCopyEntry.tsx create mode 100644 src/ui/components/EntryDialogues/DialogCopyEntry/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogCreateDashboard/DialogCreateDashboard.tsx create mode 100644 src/ui/components/EntryDialogues/DialogCreateDashboard/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogCreateEditorChart/DialogCreateEditorChart.tsx create mode 100644 src/ui/components/EntryDialogues/DialogCreateEditorChart/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogCreateFolder/DialogCreateFolder.tsx create mode 100644 src/ui/components/EntryDialogues/DialogCreateFolder/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogCreateQLChart/DialogCreateQLChart.tsx create mode 100644 src/ui/components/EntryDialogues/DialogCreateQLChart/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogCreateWizardChart/DialogCreateWizardChart.tsx create mode 100644 src/ui/components/EntryDialogues/DialogCreateWizardChart/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogCreateWorkbookEntry/DialogCreateWorkbookEntry.scss create mode 100644 src/ui/components/EntryDialogues/DialogCreateWorkbookEntry/DialogCreateWorkbookEntry.tsx create mode 100644 src/ui/components/EntryDialogues/DialogDeleteEntry/DialogDeleteEntry.scss create mode 100644 src/ui/components/EntryDialogues/DialogDeleteEntry/DialogDeleteEntry.tsx create mode 100644 src/ui/components/EntryDialogues/DialogDeleteEntry/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogEditFavoritesAlias/DialogEditFavoritesAlias.scss create mode 100644 src/ui/components/EntryDialogues/DialogEditFavoritesAlias/DialogEditFavoritesAlias.tsx create mode 100644 src/ui/components/EntryDialogues/DialogEditFavoritesAlias/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogEditWarning/DialogEditWarning.scss create mode 100644 src/ui/components/EntryDialogues/DialogEditWarning/DialogEditWarning.tsx create mode 100644 src/ui/components/EntryDialogues/DialogEntrySaveAsNew/DialogEntrySaveAsNew.tsx create mode 100644 src/ui/components/EntryDialogues/DialogEntrySaveAsNew/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogMakeActualConfirm/DialogMakeActualConfirm.scss create mode 100644 src/ui/components/EntryDialogues/DialogMakeActualConfirm/DialogMakeActualConfirm.tsx create mode 100644 src/ui/components/EntryDialogues/DialogMigrateToWorkbook/DialogMigrateToWorkbook.tsx create mode 100644 src/ui/components/EntryDialogues/DialogMigrateToWorkbook/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogMoveEntry/DialogMoveEntry.tsx create mode 100644 src/ui/components/EntryDialogues/DialogMoveEntry/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogRenameEntry/DialogRenameEntry.tsx create mode 100644 src/ui/components/EntryDialogues/DialogRenameEntry/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogShareEntry/DialogShareEntry.tsx create mode 100644 src/ui/components/EntryDialogues/DialogShareEntry/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/AlertTooltip/AlertTooltip.scss create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/AlertTooltip/AlertTooltip.tsx create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/AuthorSection/AuthorSection.scss create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/AuthorSection/AuthorSection.tsx create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/ContentError/ContentError.scss create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/ContentError/ContentError.tsx create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/ContentLoader/ContentLoader.scss create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/ContentLoader/ContentLoader.tsx create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/CurrentEntrySection/CurrentEntrySection.scss create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/CurrentEntrySection/CurrentEntrySection.tsx create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/DialogAlert/DialogAlert.scss create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/DialogAlert/DialogAlert.tsx create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/DialogPublic.scss create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/DialogPublic.tsx create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/RelationsList/RelationsList.scss create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/RelationsList/RelationsList.tsx create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/SharedLink/SharedLink.scss create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/SharedLink/SharedLink.tsx create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/constants.ts create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/helpers.ts create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/types.ts create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogPublic/useDialogPublicState.ts create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/DialogSwitchPublic.tsx create mode 100644 src/ui/components/EntryDialogues/DialogSwitchPublic/index.ts create mode 100644 src/ui/components/EntryDialogues/DialogUnlock/DialogUnlock.tsx create mode 100644 src/ui/components/EntryDialogues/DialogUnlock/index.ts create mode 100644 src/ui/components/EntryDialogues/EntryDialogBase/EntryDialogBase.scss create mode 100644 src/ui/components/EntryDialogues/EntryDialogBase/EntryDialogBase.tsx create mode 100644 src/ui/components/EntryDialogues/EntryDialogues.tsx create mode 100644 src/ui/components/EntryDialogues/constants.ts create mode 100644 src/ui/components/EntryDialogues/index.ts create mode 100644 src/ui/components/EntryDialogues/types.ts create mode 100644 src/ui/components/EntryDialogues/utils.ts create mode 100644 src/ui/components/EntryIcon/EntryIcon.tsx create mode 100644 src/ui/components/EntryRow/EntryRow.scss create mode 100644 src/ui/components/EntryRow/EntryRow.tsx create mode 100644 src/ui/components/EntrySuggest/EntrySuggest.scss create mode 100644 src/ui/components/EntrySuggest/EntrySuggest.tsx create mode 100644 src/ui/components/EntrySuggest/types.ts create mode 100644 src/ui/components/EntryTitle/EntryTitle.scss create mode 100644 src/ui/components/EntryTitle/EntryTitle.tsx create mode 100644 src/ui/components/EntryTitle/index.ts create mode 100644 src/ui/components/EntryTitle/types.ts create mode 100644 src/ui/components/EntryTitle/utils.ts create mode 100644 src/ui/components/ErrorBoundary/ErrorBoundary.tsx create mode 100644 src/ui/components/ErrorContent/DebugInfo/DebugInfo.scss create mode 100644 src/ui/components/ErrorContent/DebugInfo/DebugInfo.tsx create mode 100644 src/ui/components/ErrorContent/ErrorContent.scss create mode 100644 src/ui/components/ErrorContent/ErrorContent.tsx create mode 100644 src/ui/components/ErrorPage/ErrorPage.tsx create mode 100644 src/ui/components/ErrorPage/withErrorPage.tsx create mode 100644 src/ui/components/ExpandablePanel/ExpandablePanel.scss create mode 100644 src/ui/components/ExpandablePanel/ExpandablePanel.tsx create mode 100644 src/ui/components/FieldEditor/FieldEditor.scss create mode 100644 src/ui/components/FieldEditor/FieldEditor.tsx create mode 100644 src/ui/components/FieldEditor/components/DocSection.tsx create mode 100644 src/ui/components/FieldEditor/components/FormulaSection.tsx create mode 100644 src/ui/components/FieldEditor/components/NameHeader.tsx create mode 100644 src/ui/components/FieldEditor/components/Settings.tsx create mode 100644 src/ui/components/FieldEditor/components/SourceSection.tsx create mode 100644 src/ui/components/FieldEditor/components/helpers.ts create mode 100644 src/ui/components/FieldEditor/constants.ts create mode 100644 src/ui/components/FieldEditor/index.tsx create mode 100644 src/ui/components/FieldEditor/typings.ts create mode 100644 src/ui/components/FieldEditor/utils.ts create mode 100644 src/ui/components/FieldWrapper/FieldWrapper.scss create mode 100644 src/ui/components/FieldWrapper/FieldWrapper.tsx create mode 100644 src/ui/components/FloatMenu/FloatMenu.scss create mode 100644 src/ui/components/FloatMenu/FloatMenu.tsx create mode 100644 src/ui/components/FormSection/FormSection.scss create mode 100644 src/ui/components/FormSection/FormSection.tsx create mode 100644 src/ui/components/FormattedValue/FormattedValue.tsx create mode 100644 src/ui/components/IamAccessDialog/AccessList/AccessList.scss create mode 100644 src/ui/components/IamAccessDialog/AccessList/AccessList.tsx create mode 100644 src/ui/components/IamAccessDialog/AccessList/ActionPanel/ActionPanel.scss create mode 100644 src/ui/components/IamAccessDialog/AccessList/ActionPanel/ActionPanel.tsx create mode 100644 src/ui/components/IamAccessDialog/AccessList/Block/Block.scss create mode 100644 src/ui/components/IamAccessDialog/AccessList/Block/Block.tsx create mode 100644 src/ui/components/IamAccessDialog/AccessList/DirectAccessesTable/Controls/Controls.scss create mode 100644 src/ui/components/IamAccessDialog/AccessList/DirectAccessesTable/Controls/Controls.tsx create mode 100644 src/ui/components/IamAccessDialog/AccessList/DirectAccessesTable/DirectAccessesTable.scss create mode 100644 src/ui/components/IamAccessDialog/AccessList/DirectAccessesTable/DirectAccessesTable.tsx create mode 100644 src/ui/components/IamAccessDialog/AccessList/DirectAccessesTable/utils.ts create mode 100644 src/ui/components/IamAccessDialog/AccessList/Filters/Filters.scss create mode 100644 src/ui/components/IamAccessDialog/AccessList/Filters/Filters.tsx create mode 100644 src/ui/components/IamAccessDialog/AccessList/InheritedAccessesTable/InheritedAccessesTable.scss create mode 100644 src/ui/components/IamAccessDialog/AccessList/InheritedAccessesTable/InheritedAccessesTable.tsx create mode 100644 src/ui/components/IamAccessDialog/AddSubjects/ActionPanel/ActionPanel.scss create mode 100644 src/ui/components/IamAccessDialog/AddSubjects/ActionPanel/ActionPanel.tsx create mode 100644 src/ui/components/IamAccessDialog/AddSubjects/AddSubjects.scss create mode 100644 src/ui/components/IamAccessDialog/AddSubjects/AddSubjects.tsx create mode 100644 src/ui/components/IamAccessDialog/AddSubjects/SubjectsList/SubjectsList.scss create mode 100644 src/ui/components/IamAccessDialog/AddSubjects/SubjectsList/SubjectsList.tsx create mode 100644 src/ui/components/IamAccessDialog/IamAccessDialog.tsx create mode 100644 src/ui/components/IamAccessDialog/IamAccessDialogComponent.tsx create mode 100644 src/ui/components/IamAccessDialog/ResourceInfo/ResourceInfo.scss create mode 100644 src/ui/components/IamAccessDialog/ResourceInfo/ResourceInfo.tsx create mode 100644 src/ui/components/IamAccessDialog/constants.ts create mode 100644 src/ui/components/IamAccessDialog/index.ts create mode 100644 src/ui/components/IamAccessDialog/utils.ts create mode 100644 src/ui/components/IconById/IconById.tsx create mode 100644 src/ui/components/Illustration/Illustration.scss create mode 100644 src/ui/components/Illustration/Illustration.tsx create mode 100644 src/ui/components/Illustration/context.ts create mode 100644 src/ui/components/Illustration/getIllustrationStore.ts create mode 100644 src/ui/components/Illustration/types.ts create mode 100644 src/ui/components/Illustration/utils.tsx create mode 100644 src/ui/components/InaccessibleOnMobileInset/InaccessibleOnMobileInset.js create mode 100644 src/ui/components/InaccessibleOnMobileInset/InaccessibleOnMobileInset.scss create mode 100644 src/ui/components/Interpolate/Interpolate.tsx create mode 100644 src/ui/components/Interpolate/README.md create mode 100644 src/ui/components/Interpolate/index.ts create mode 100644 src/ui/components/Interpolate/utils.tsx create mode 100644 src/ui/components/InterpolatedText/InterpolatedText.tsx create mode 100644 src/ui/components/LandingPage/LandingPage.scss create mode 100644 src/ui/components/LandingPage/LandingPage.tsx create mode 100644 src/ui/components/ListWithMenu/ListWithMenu.scss create mode 100644 src/ui/components/ListWithMenu/ListWithMenu.tsx create mode 100644 src/ui/components/ListWithRemove/ListWithRemove.scss create mode 100644 src/ui/components/ListWithRemove/ListWithRemove.tsx create mode 100644 src/ui/components/LocationChange/LocationChange.tsx create mode 100644 src/ui/components/MarkdownControl/MarkdownControl.scss create mode 100644 src/ui/components/MarkdownControl/MarkdownControl.tsx create mode 100644 src/ui/components/MarkdownHelpPopover/Content.tsx create mode 100644 src/ui/components/MarkdownHelpPopover/MarkdownHelpPopover.scss create mode 100644 src/ui/components/MarkdownHelpPopover/MarkdownHelpPopover.tsx create mode 100644 src/ui/components/Markup/Markup.tsx create mode 100644 src/ui/components/Markup/components/MarkupTooltip/MarkupTooltip.tsx create mode 100644 src/ui/components/Markup/components/UserInfo/UserInfo.tsx create mode 100644 src/ui/components/Markup/components/index.ts create mode 100644 src/ui/components/Markup/index.ts create mode 100644 src/ui/components/Markup/types.ts create mode 100644 src/ui/components/Markup/utils.test.ts create mode 100644 src/ui/components/Markup/utils.ts create mode 100644 src/ui/components/MigrateToWorkbookDialog/MigrateToWorkbookDialog.scss create mode 100644 src/ui/components/MigrateToWorkbookDialog/MigrateToWorkbookDialog.tsx create mode 100644 src/ui/components/MigrateToWorkbookDialog/components/Body/Body.scss create mode 100644 src/ui/components/MigrateToWorkbookDialog/components/Body/Body.tsx create mode 100644 src/ui/components/MigrateToWorkbookDialog/components/Body/index.ts create mode 100644 src/ui/components/MigrateToWorkbookDialog/components/EntriesGroup/EntriesGroup.scss create mode 100644 src/ui/components/MigrateToWorkbookDialog/components/EntriesGroup/EntriesGroup.tsx create mode 100644 src/ui/components/MigrateToWorkbookDialog/components/EntriesGroup/index.ts create mode 100644 src/ui/components/MigrateToWorkbookDialog/components/EntryRow/EntryRow.scss create mode 100644 src/ui/components/MigrateToWorkbookDialog/components/EntryRow/EntryRow.tsx create mode 100644 src/ui/components/MigrateToWorkbookDialog/components/EntryRow/index.ts create mode 100644 src/ui/components/MigrateToWorkbookDialog/index.ts create mode 100644 src/ui/components/MobileHeader/MobileHeader.scss create mode 100644 src/ui/components/MobileHeader/MobileHeader.tsx create mode 100644 src/ui/components/MobileHeader/MobileHeaderComponent/BurgerMenuFooter/BurgerMenuFooter.tsx create mode 100644 src/ui/components/MobileHeader/MobileHeaderComponent/MobileHeaderComponent.tsx create mode 100644 src/ui/components/MobileHeader/MobileHeaderComponent/UserPanel/UserPanel.scss create mode 100644 src/ui/components/MobileHeader/MobileHeaderComponent/UserPanel/UserPanel.tsx create mode 100644 src/ui/components/MobileHeader/constants.ts create mode 100644 src/ui/components/MobileTocToggle/MobileTocToggle.scss create mode 100644 src/ui/components/MobileTocToggle/MobileTocToggle.tsx create mode 100644 src/ui/components/Monaco/LazyMonaco.scss create mode 100644 src/ui/components/Monaco/LazyMonaco.tsx create mode 100644 src/ui/components/Monaco/Monaco.scss create mode 100644 src/ui/components/Monaco/Monaco.tsx create mode 100644 src/ui/components/Navigation/Base/NavigationBase.js create mode 100644 src/ui/components/Navigation/Base/NavigationBase.scss create mode 100644 src/ui/components/Navigation/Base/configure.ts create mode 100644 src/ui/components/Navigation/Base/types.ts create mode 100644 src/ui/components/Navigation/Base/utils.ts create mode 100644 src/ui/components/Navigation/Core/CreateEntry/CreateEntry.scss create mode 100644 src/ui/components/Navigation/Core/CreateEntry/CreateEntry.tsx create mode 100644 src/ui/components/Navigation/Core/CreateEntry/getCreateEntryItems.tsx create mode 100644 src/ui/components/Navigation/Core/EntryContextButton/EntryContextButton.tsx create mode 100644 src/ui/components/Navigation/Core/ErrorView/ErrorView.scss create mode 100644 src/ui/components/Navigation/Core/ErrorView/ErrorView.tsx create mode 100644 src/ui/components/Navigation/Core/FavoritesNameWithAliasItem/FavoritesNameWithAliasItem.scss create mode 100644 src/ui/components/Navigation/Core/FavoritesNameWithAliasItem/FavoritesNameWithAliasItem.tsx create mode 100644 src/ui/components/Navigation/Core/NavigationBreadcrumbs/BreadcrumbMenu.scss create mode 100644 src/ui/components/Navigation/Core/NavigationBreadcrumbs/BreadcrumbMenu.tsx create mode 100644 src/ui/components/Navigation/Core/NavigationBreadcrumbs/NavigationBreadcrumbs.scss create mode 100644 src/ui/components/Navigation/Core/NavigationBreadcrumbs/NavigationBreadcrumbs.tsx create mode 100644 src/ui/components/Navigation/Core/NavigationEntries.js create mode 100644 src/ui/components/Navigation/Core/NavigationEntries.scss create mode 100644 src/ui/components/Navigation/Core/NavigationInline.js create mode 100644 src/ui/components/Navigation/Core/NavigationInline.scss create mode 100644 src/ui/components/Navigation/Core/NavigationMinimal.js create mode 100644 src/ui/components/Navigation/Core/NavigationMinimal.scss create mode 100644 src/ui/components/Navigation/Core/NavigationModal.js create mode 100644 src/ui/components/Navigation/Core/NavigationModal.scss create mode 100644 src/ui/components/Navigation/Core/OrderSelect/OrderSelect.tsx create mode 100644 src/ui/components/Navigation/Core/SearchInput/SearchInput.tsx create mode 100644 src/ui/components/Navigation/Core/Sidebar/Sidebar.js create mode 100644 src/ui/components/Navigation/Core/Sidebar/Sidebar.scss create mode 100644 src/ui/components/Navigation/Core/TableView/BatchDialog/BatchDialog.tsx create mode 100644 src/ui/components/Navigation/Core/TableView/List/List.tsx create mode 100644 src/ui/components/Navigation/Core/TableView/Row/Row.tsx create mode 100644 src/ui/components/Navigation/Core/TableView/Row/RowHeader.tsx create mode 100644 src/ui/components/Navigation/Core/TableView/TableView.scss create mode 100644 src/ui/components/Navigation/Core/TableView/TableView.tsx create mode 100644 src/ui/components/Navigation/Core/TableView/types.ts create mode 100644 src/ui/components/Navigation/Core/TableView/useBatchSelect.ts create mode 100644 src/ui/components/Navigation/NavigationMinimal.d.ts create mode 100644 src/ui/components/Navigation/NavigationMinimal.js create mode 100644 src/ui/components/Navigation/NavigationModal.d.ts create mode 100644 src/ui/components/Navigation/NavigationModal.js create mode 100644 src/ui/components/Navigation/PlaceSelect/PlaceSelect.js create mode 100644 src/ui/components/Navigation/PlaceSelect/PlaceSelect.scss create mode 100644 src/ui/components/Navigation/components/BatchPanel/BatchPanel.tsx create mode 100644 src/ui/components/Navigation/components/EmptyState/EmptyState.scss create mode 100644 src/ui/components/Navigation/components/EmptyState/EmptyState.tsx create mode 100644 src/ui/components/Navigation/components/EmptyState/utils.ts create mode 100644 src/ui/components/Navigation/constants.ts create mode 100644 src/ui/components/Navigation/dialogs/BatchMove/AccessError/AccessError.scss create mode 100644 src/ui/components/Navigation/dialogs/BatchMove/AccessError/AccessError.tsx create mode 100644 src/ui/components/Navigation/dialogs/BatchMove/BatchMove.tsx create mode 100644 src/ui/components/Navigation/dialogs/BatchMove/MoveError/MoveError.scss create mode 100644 src/ui/components/Navigation/dialogs/BatchMove/MoveError/MoveError.tsx create mode 100644 src/ui/components/Navigation/dialogs/BatchMove/SelectDestination/SelectDestination.scss create mode 100644 src/ui/components/Navigation/dialogs/BatchMove/SelectDestination/SelectDestination.tsx create mode 100644 src/ui/components/Navigation/dialogs/BatchMove/types.ts create mode 100644 src/ui/components/Navigation/hoc/resolveNavigationPath.js create mode 100644 src/ui/components/Navigation/index.ts create mode 100644 src/ui/components/Navigation/settings.ts create mode 100644 src/ui/components/Navigation/types.ts create mode 100644 src/ui/components/Navigation/util.ts create mode 100644 src/ui/components/NavigationPrompt/NavigationPrompt.tsx create mode 100644 src/ui/components/NavigationTabs/NavigationTabs.tsx create mode 100644 src/ui/components/NumberFormatSettings/NumberFormatSettings.tsx create mode 100644 src/ui/components/NumberFormatSettings/NumberInput/NumberInput.scss create mode 100644 src/ui/components/NumberFormatSettings/NumberInput/NumberInput.tsx create mode 100644 src/ui/components/NumberInputWithSlider/NumberInputWithSlider.scss create mode 100644 src/ui/components/NumberInputWithSlider/NumberInputWithSlider.tsx create mode 100644 src/ui/components/OpenDialogNeedReset/OpenDialogNeedReset.tsx create mode 100644 src/ui/components/OrderBySelect/OrderBySelect.tsx create mode 100644 src/ui/components/OrderBySelect/constants.ts create mode 100644 src/ui/components/OrderBySelect/index.tsx create mode 100644 src/ui/components/OrderBySelect/types.ts create mode 100644 src/ui/components/PageTitle/PageTitle.tsx create mode 100644 src/ui/components/PageTitle/index.ts create mode 100644 src/ui/components/PageTitle/usePageTitle.ts create mode 100644 src/ui/components/PaletteIcon/PaletteIcon.tsx create mode 100644 src/ui/components/PaletteItem/PaletteItem.scss create mode 100644 src/ui/components/PaletteItem/PaletteItem.tsx create mode 100644 src/ui/components/PathSelect/PathSelect.scss create mode 100644 src/ui/components/PathSelect/PathSelect.tsx create mode 100644 src/ui/components/Picture/Picture.tsx create mode 100644 src/ui/components/PlaceholderIllustration/PlaceholderIllustration.tsx create mode 100644 src/ui/components/PlaceholderIllustration/types.ts create mode 100644 src/ui/components/Progress/DialogCounterProgress.scss create mode 100644 src/ui/components/Progress/DialogCounterProgress.tsx create mode 100644 src/ui/components/ProgressBar/ProgressBar.scss create mode 100644 src/ui/components/ProgressBar/ProgressBar.tsx create mode 100644 src/ui/components/RelativeDatesPicker/RelativeDatesPicker.scss create mode 100644 src/ui/components/RelativeDatesPicker/RelativeDatesPicker.tsx create mode 100644 src/ui/components/RelativeDatesPicker/constants.ts create mode 100644 src/ui/components/RelativeDatesPicker/types.ts create mode 100644 src/ui/components/RelativeDatesPicker/utils.ts create mode 100644 src/ui/components/RevisionStatusPoint/RevisionStatusPoint.scss create mode 100644 src/ui/components/RevisionStatusPoint/RevisionStatusPoint.tsx create mode 100644 src/ui/components/Revisions/Revisions.scss create mode 100644 src/ui/components/Revisions/Revisions.tsx create mode 100644 src/ui/components/Revisions/RevisionsList/RevisionsList.scss create mode 100644 src/ui/components/Revisions/RevisionsList/RevisionsList.tsx create mode 100644 src/ui/components/Revisions/helpers.ts create mode 100644 src/ui/components/Revisions/types.ts create mode 100644 src/ui/components/RevisionsPanel/RevisionsPanel.scss create mode 100644 src/ui/components/RevisionsPanel/RevisionsPanel.tsx create mode 100644 src/ui/components/RevisionsPanel/components/RevisionsControls.tsx create mode 100644 src/ui/components/RevisionsPanel/utils.ts create mode 100644 src/ui/components/ScrollableWithShadow/ScrollableWithShadow.scss create mode 100644 src/ui/components/ScrollableWithShadow/ScrollableWithShadow.tsx create mode 100644 src/ui/components/SectionWrapper/SectionWrapper.scss create mode 100644 src/ui/components/SectionWrapper/SectionWrapper.tsx create mode 100644 src/ui/components/Select/constants/SelectStyles.ts create mode 100644 src/ui/components/Select/hooks/useSelectAsyncFetch/CustomLoader.scss create mode 100644 src/ui/components/Select/hooks/useSelectAsyncFetch/README.md create mode 100644 src/ui/components/Select/hooks/useSelectAsyncFetch/useIntersection.ts create mode 100644 src/ui/components/Select/hooks/useSelectAsyncFetch/useSelectAsyncFetch.tsx create mode 100644 src/ui/components/Select/hooks/useSelectInfinityFetch/types.ts create mode 100644 src/ui/components/Select/hooks/useSelectInfinityFetch/useSelectInfinityFetch.ts create mode 100644 src/ui/components/Select/hooks/useSelectOptionsEnhancer.tsx create mode 100644 src/ui/components/Select/hooks/useSelectRenderEmptyOptions/useSelectRenderEmptyOptions.tsx create mode 100644 src/ui/components/Select/hooks/useSelectRenderError.tsx create mode 100644 src/ui/components/Select/hooks/useSelectRenderFilter/ListControls.scss create mode 100644 src/ui/components/Select/hooks/useSelectRenderFilter/useSelectRenderFilter.tsx create mode 100644 src/ui/components/Select/styles/SelectUtil.scss create mode 100644 src/ui/components/Select/wrappers/SelectAsync.tsx create mode 100644 src/ui/components/Select/wrappers/SelectFeatured.tsx create mode 100644 src/ui/components/Select/wrappers/SelectFeaturedAsync.tsx create mode 100644 src/ui/components/SelectComponents/components/SelectOptionWithIcon/SelectOptionWithIcon.scss create mode 100644 src/ui/components/SelectComponents/components/SelectOptionWithIcon/SelectOptionWithIcon.tsx create mode 100644 src/ui/components/SelectComponents/index.ts create mode 100644 src/ui/components/SelectMigrationToWorkbookDialog/Body/Body.scss create mode 100644 src/ui/components/SelectMigrationToWorkbookDialog/Body/Body.tsx create mode 100644 src/ui/components/SelectMigrationToWorkbookDialog/SelectMigrationToWorkbookDialog.scss create mode 100644 src/ui/components/SelectMigrationToWorkbookDialog/SelectMigrationToWorkbookDialog.tsx create mode 100644 src/ui/components/SelectMigrationToWorkbookDialog/constants.tsx create mode 100644 src/ui/components/SelectMigrationToWorkbookDialog/index.ts create mode 100644 src/ui/components/SharedBindingsList/ListSearch.tsx create mode 100644 src/ui/components/SharedBindingsList/SharedBindingsList.scss create mode 100644 src/ui/components/SharedBindingsList/SharedBindingsList.tsx create mode 100644 src/ui/components/SharedEntryIcon/SharedEntryIcon.scss create mode 100644 src/ui/components/SharedEntryIcon/SharedEntryIcon.tsx create mode 100644 src/ui/components/SlugifyUrl/SlugifyUrl.tsx create mode 100644 src/ui/components/SlugifyUrl/index.ts create mode 100644 src/ui/components/SmartLoader/SmartLoader.scss create mode 100644 src/ui/components/SmartLoader/SmartLoader.tsx create mode 100644 src/ui/components/TabMenu/TabMenu.scss create mode 100644 src/ui/components/TabMenu/TabMenu.tsx create mode 100644 src/ui/components/TabMenu/types.ts create mode 100644 src/ui/components/Tabs/Tabs.scss create mode 100644 src/ui/components/Tabs/Tabs.tsx create mode 100644 src/ui/components/Tabs/withDatalensSpecific.tsx create mode 100644 src/ui/components/TagInput/TagInput.scss create mode 100644 src/ui/components/TagInput/TagInput.tsx create mode 100644 src/ui/components/TemplateTextPaper/TemplateTextPaper.scss create mode 100644 src/ui/components/TemplateTextPaper/TemplateTextPaper.tsx create mode 100644 src/ui/components/UserAvatar/UserAvatarById.tsx create mode 100644 src/ui/components/UserMenu/UserAvatar.tsx create mode 100644 src/ui/components/UserMenu/UserMenu.scss create mode 100644 src/ui/components/UserMenu/UserMenu.tsx create mode 100644 src/ui/components/UsersSuggest/UsersSuggest.scss create mode 100644 src/ui/components/UsersSuggest/UsersSuggest.tsx create mode 100644 src/ui/components/UsersSuggest/types.ts create mode 100644 src/ui/components/ValuesList/ValuesList.scss create mode 100644 src/ui/components/ValuesList/ValuesList.tsx create mode 100644 src/ui/components/Veil/Veil.scss create mode 100644 src/ui/components/Veil/Veil.tsx create mode 100644 src/ui/components/ViewAsync/ViewAsync.scss create mode 100644 src/ui/components/ViewAsync/ViewAsync.tsx create mode 100644 src/ui/components/ViewError/ViewError.scss create mode 100644 src/ui/components/ViewError/ViewError.tsx create mode 100644 src/ui/components/ViewLoader/ViewLoader.scss create mode 100644 src/ui/components/ViewLoader/ViewLoader.tsx create mode 100644 src/ui/components/WidgetRoundingsInput/WidgetRoundingsInput.scss create mode 100644 src/ui/components/WidgetRoundingsInput/WidgetRoundingsInput.tsx create mode 100644 src/ui/components/Widgets/Chart/Chart.scss create mode 100644 src/ui/components/Widgets/Chart/Chart.tsx create mode 100644 src/ui/components/Widgets/Chart/ChartAlert.tsx create mode 100644 src/ui/components/Widgets/Chart/ChartSelector.tsx create mode 100644 src/ui/components/Widgets/Chart/ChartWidget.scss create mode 100644 src/ui/components/Widgets/Chart/ChartWidget.tsx create mode 100644 src/ui/components/Widgets/Chart/ChartWidgetWithProvider.tsx create mode 100644 src/ui/components/Widgets/Chart/components/ChartInfoIcon.scss create mode 100644 src/ui/components/Widgets/Chart/components/ChartInfoIcon.tsx create mode 100644 src/ui/components/Widgets/Chart/components/Content.tsx create mode 100644 src/ui/components/Widgets/Chart/components/Header.tsx create mode 100644 src/ui/components/Widgets/Chart/components/WidgetFooter.tsx create mode 100644 src/ui/components/Widgets/Chart/components/WidgetHeader.scss create mode 100644 src/ui/components/Widgets/Chart/components/WidgetHeader.tsx create mode 100644 src/ui/components/Widgets/Chart/helpers/helpers.ts create mode 100644 src/ui/components/Widgets/Chart/helpers/yandex-map.ts create mode 100644 src/ui/components/Widgets/Chart/hooks/useAutoHeightResizeObserver.ts create mode 100644 src/ui/components/Widgets/Chart/hooks/useChartActivities.tsx create mode 100644 src/ui/components/Widgets/Chart/hooks/useIntersectionObserver.ts create mode 100644 src/ui/components/Widgets/Chart/hooks/useLoadingChart.ts create mode 100644 src/ui/components/Widgets/Chart/hooks/useLoadingChartSelector.tsx create mode 100644 src/ui/components/Widgets/Chart/hooks/useLoadingChartWidget.ts create mode 100644 src/ui/components/Widgets/Chart/hooks/useMemoCallback.ts create mode 100644 src/ui/components/Widgets/Chart/store/reducer.ts create mode 100644 src/ui/components/Widgets/Chart/store/types.ts create mode 100644 src/ui/components/Widgets/Chart/types.ts create mode 100644 src/ui/components/Widgets/Chart/utils-chart-alert.ts create mode 100644 src/ui/components/Widgets/Chart/utils.ts create mode 100644 src/ui/components/WorkbookIcon/WorkbookIcon.scss create mode 100644 src/ui/components/WorkbookIcon/WorkbookIcon.tsx create mode 100644 src/ui/components/WorkbookNavigationMinimal/WorkbookNavigationMinimal.scss create mode 100644 src/ui/components/WorkbookNavigationMinimal/WorkbookNavigationMinimal.tsx create mode 100644 src/ui/components/WorkbookNavigationMinimal/components/ListWithSharedEntries.tsx create mode 100644 src/ui/components/WorkbookNavigationMinimal/constants.ts create mode 100644 src/ui/components/WorkbookNavigationMinimal/types.ts create mode 100644 src/ui/components/WysiwygEditor/MarkdownEditorDefault.tsx create mode 100644 src/ui/components/WysiwygEditor/WysiwygEditor.scss create mode 100644 src/ui/components/WysiwygEditor/WysiwygEditor.tsx create mode 100644 src/ui/components/WysiwygEditor/mixins.scss create mode 100644 src/ui/components/WysiwygEditor/preset.ts create mode 100644 src/ui/components/WysiwygEditor/variables.scss create mode 100644 src/ui/components/YfmWrapper/YfmWrapper.tsx create mode 100644 src/ui/components/YfmWrapper/YfmWrapperContent.scss create mode 100644 src/ui/components/YfmWrapper/YfmWrapperContent.tsx create mode 100644 src/ui/components/common/Datepicker/Calendar/Calendar.js create mode 100644 src/ui/components/common/Datepicker/Calendar/Calendar.scss create mode 100644 src/ui/components/common/Datepicker/Datepicker.d.ts create mode 100644 src/ui/components/common/Datepicker/Datepicker.js create mode 100644 src/ui/components/common/Datepicker/Datepicker.scss create mode 100644 src/ui/components/common/Datepicker/Month/Month.js create mode 100644 src/ui/components/common/Datepicker/Month/Month.scss create mode 100644 src/ui/components/common/Datepicker/Months/Months.js create mode 100644 src/ui/components/common/Datepicker/Months/Months.scss create mode 100644 src/ui/components/common/Datepicker/PopupContent/PopupContent.js create mode 100644 src/ui/components/common/Datepicker/PopupContent/PopupContent.scss create mode 100644 src/ui/components/common/Datepicker/Presets/Presets.js create mode 100644 src/ui/components/common/Datepicker/Presets/Presets.scss create mode 100644 src/ui/components/common/Datepicker/Quarters/Quarters.js create mode 100644 src/ui/components/common/Datepicker/Quarters/Quarters.scss create mode 100644 src/ui/components/common/Datepicker/YearSwitcher/YearSwitcher.js create mode 100644 src/ui/components/common/Datepicker/YearSwitcher/YearSwitcher.scss create mode 100644 src/ui/components/common/Datepicker/Years/Years.js create mode 100644 src/ui/components/common/Datepicker/Years/Years.scss create mode 100644 src/ui/components/common/Datepicker/constants.js create mode 100644 src/ui/components/common/Datepicker/index.tsx create mode 100644 src/ui/components/common/Datepicker/utils.js create mode 100644 src/ui/components/common/DatepickerControl/DatepickerControl.tsx create mode 100644 src/ui/components/common/RangeInputPicker/RangeInputPicker.scss create mode 100644 src/ui/components/common/RangeInputPicker/RangeInputPicker.tsx create mode 100644 src/ui/components/common/RangeInputPicker/constants.ts create mode 100644 src/ui/components/common/RangeInputPicker/index.ts create mode 100644 src/ui/components/common/RangeInputPicker/types.ts create mode 100644 src/ui/components/common/RangeInputPicker/utils.ts create mode 100644 src/ui/components/common/YCSelect/Item/Item.js create mode 100644 src/ui/components/common/YCSelect/Item/Item.scss create mode 100644 src/ui/components/common/YCSelect/Items/Items.js create mode 100644 src/ui/components/common/YCSelect/Items/Items.scss create mode 100644 src/ui/components/common/YCSelect/Search/Search.js create mode 100644 src/ui/components/common/YCSelect/Search/Search.scss create mode 100644 src/ui/components/common/YCSelect/YCSelect.d.ts create mode 100644 src/ui/components/common/YCSelect/YCSelect.js create mode 100644 src/ui/components/common/YCSelect/YCSelect.scss create mode 100644 src/ui/components/common/YCSelect/constants.js create mode 100644 src/ui/components/common/YCSelect/index.tsx create mode 100644 src/ui/components/common/YCSelect/isBothEmpty.ts create mode 100644 src/ui/components/common/YCSelect/utils.js create mode 100644 src/ui/components/common/styles/mixins.scss create mode 100644 src/ui/configs/common/icons.ts create mode 100644 src/ui/configs/index.ts create mode 100644 src/ui/constants/common.ts create mode 100644 src/ui/constants/connections.ts create mode 100644 src/ui/constants/dialogs.ts create mode 100644 src/ui/constants/embedded.ts create mode 100644 src/ui/constants/index.ts create mode 100644 src/ui/constants/misc.ts create mode 100644 src/ui/constants/mobile.ts create mode 100644 src/ui/constants/navigation.ts create mode 100644 src/ui/constants/operations.ts create mode 100644 src/ui/constants/visualizations/combined-chart.ts create mode 100644 src/ui/constants/visualizations/donut.ts create mode 100644 src/ui/constants/visualizations/flatTable.ts create mode 100644 src/ui/constants/visualizations/funnel.ts create mode 100644 src/ui/constants/visualizations/geolayer.ts create mode 100644 src/ui/constants/visualizations/geopoint.ts create mode 100644 src/ui/constants/visualizations/geopolygon.ts create mode 100644 src/ui/constants/visualizations/heatmap.ts create mode 100644 src/ui/constants/visualizations/index.ts create mode 100644 src/ui/constants/visualizations/line.ts create mode 100644 src/ui/constants/visualizations/metric.ts create mode 100644 src/ui/constants/visualizations/pie.ts create mode 100644 src/ui/constants/visualizations/pivotTable.ts create mode 100644 src/ui/constants/visualizations/polyline.ts create mode 100644 src/ui/constants/visualizations/scatter.ts create mode 100644 src/ui/constants/visualizations/treemap.ts create mode 100644 src/ui/constants/visualizations/utils.ts create mode 100644 src/ui/constants/workbooks.ts create mode 100644 src/ui/constants/yfm.ts create mode 100644 src/ui/datalens/helpers.ts create mode 100644 src/ui/datalens/hooks.ts create mode 100644 src/ui/datalens/index.tsx create mode 100644 src/ui/datalens/pages/AuthPage/AuthPage.tsx create mode 100644 src/ui/datalens/pages/CollectionsNavigationPage/CollectionsNavigationPage.tsx create mode 100644 src/ui/datalens/pages/ConnectionsPage/ConnectionsPage.tsx create mode 100644 src/ui/datalens/pages/DashAndWizardQLPages/DashAndWizardQLPages.tsx create mode 100644 src/ui/datalens/pages/DashPage/DashPage.tsx create mode 100644 src/ui/datalens/pages/DatasetPage/DatasetPage.tsx create mode 100644 src/ui/datalens/pages/FallbackPage/FallbackPage.scss create mode 100644 src/ui/datalens/pages/FallbackPage/FallbackPage.tsx create mode 100644 src/ui/datalens/pages/LandingPage/LandingPage.tsx create mode 100644 src/ui/datalens/pages/MainPage/MainPage.tsx create mode 100644 src/ui/datalens/pages/OwnUserProfilePage/OwnUserProfilePage.scss create mode 100644 src/ui/datalens/pages/OwnUserProfilePage/OwnUserProfilePage.tsx create mode 100644 src/ui/datalens/pages/PreviewPage/PreviewPage.tsx create mode 100644 src/ui/datalens/pages/QLPage/QLPage.tsx create mode 100644 src/ui/datalens/pages/ServiceSettingsPage/ServiceSettingsPage.tsx create mode 100644 src/ui/datalens/pages/WizardPage/WizardPage.tsx create mode 100644 src/ui/datalens/render.ts create mode 100644 src/ui/entries/dl-main.tsx create mode 100644 src/ui/fonts/inter-cyrillic-ext.woff2 create mode 100644 src/ui/fonts/inter-cyrillic.woff2 create mode 100644 src/ui/fonts/inter-greek.woff2 create mode 100644 src/ui/fonts/inter-latin-ext.woff2 create mode 100644 src/ui/fonts/inter-latin.woff2 create mode 100644 src/ui/fonts/inter-vietnamese.woff2 create mode 100644 src/ui/fonts/inter.css create mode 100644 src/ui/hoc/index.ts create mode 100644 src/ui/hoc/withEnabledFeature.tsx create mode 100644 src/ui/hoc/withHiddenUnmount.tsx create mode 100644 src/ui/hoc/withHotkeysContext.tsx create mode 100644 src/ui/hoc/withInaccessibleOnMobile.tsx create mode 100644 src/ui/hoc/withPromiseOpen.js create mode 100644 src/ui/hooks/index.ts create mode 100644 src/ui/hooks/useBeforeLoad.ts create mode 100644 src/ui/hooks/useBindHotkey.ts create mode 100644 src/ui/hooks/useChangedProp.ts create mode 100644 src/ui/hooks/useEditHistoryActions.ts create mode 100644 src/ui/hooks/useEffectOnce.ts create mode 100644 src/ui/hooks/useEnterClick.ts create mode 100644 src/ui/hooks/useHover.ts create mode 100644 src/ui/hooks/useLocalStorageFallback.ts create mode 100644 src/ui/hooks/useMarkdown.tsx create mode 100644 src/ui/hooks/useMermaidTheme.ts create mode 100644 src/ui/hooks/useMountedState.ts create mode 100644 src/ui/hooks/useOutsideClick.ts create mode 100644 src/ui/hooks/usePrevious.ts create mode 100644 src/ui/hooks/useRefMounted.js create mode 100644 src/ui/hooks/useSetState.js create mode 100644 src/ui/hooks/useShallowEqualSelector.ts create mode 100644 src/ui/hooks/useSyncedState.ts create mode 100644 src/ui/hooks/useTitle.ts create mode 100644 src/ui/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/ChartKit.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/ChartKit.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/ChartKitAdapter.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/ChartKitTooltip/ChartKitTooltip.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/ChartKitTooltip/ChartKitTooltip.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Error/Error.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Error/Error.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/IconRenderer.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/CirclePointIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/DashDotLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/DashLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/DiamondPointIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/DotLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/LongDashDotDotLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/LongDashDotLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/LongDashLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/ShortDashDotDotLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/ShortDashDotLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/ShortDashLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/ShortDotLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/SolidLineIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/SquarePointIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/TriangleDownPointIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/Icons/TrianglePointIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/constants.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/renderTooltipIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/DashDotLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/DashLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/DotLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/LongDashDotDotLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/LongDashDotLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/LongDashLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/ShortDashDotDotLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/ShortDashDotLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/ShortDashLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/ShortDotLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/SolidLineIcon.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/template-icons/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/utils.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Loader/Loader.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Loader/Loader.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/TooltipWithComments/TooltipWithComments.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/TooltipWithComments/TooltipWithComments.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/TooltipWithComments/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/Widget.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/AlertWidget/Alert.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/AlertWidget/AlertWidget.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/AlertWidget/utils.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Markdown/Markdown.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Markdown/Markdown.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/Bar/Bar.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/Bar/Bar.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/Bar/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/Bar/utils.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/Paginator/Paginator.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/Paginator/Paginator.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/Table.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/Table.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/WrappedHTMLNode.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/__tests__/Paginator.test.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/__tests__/action-params.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/__tests__/bar-utils.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/__tests__/misc.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/__tests__/valueFormatter.test.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/utils/action-params.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/utils/event-handlers.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/utils/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/utils/misc.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/utils/render.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/utils/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/Table/utils/validation.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/constants.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/Widget/components/index.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/components/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/action-params-handlers.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/apply-hc-handlers.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/chartkit-adapter.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/chartkitAdapter.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/constants.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/getRandomCKId.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/gravity-charts/chart-to-table.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/gravity-charts/chartkit-adapter.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/gravity-charts/comments.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/gravity-charts/event-handlers.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/gravity-charts/holidays.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/gravity-charts/utils.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/helpers.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/tooltip.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/utils.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/utils.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/helpers/wait-for-content.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/init.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/chartkit-custom-error/chartkit-custom-error.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/comments/drawing.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/config.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/handleLegendItemClick.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/options.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/addShowInNavigatorToSeries.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/addShowInNavigatorToSeries.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/buildNavigatorFallback.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/buildNavigatorFallback.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/calculatePrecision.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/calculatePrecision.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/concatStrings.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/getChartKitFormattedValue.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/getFormatOptionsFromLine.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/getFormatOptionsFromLine.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/getXAxisThresholdValue.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/getXAxisThresholdValue.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/isSafari.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/isTooltipShared.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/isTooltipShared.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/localStorage.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/mergeArrayWithObject.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/numberFormat.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/numberFormat.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/setNavigatorDefaultPeriod.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/config/utils/setNavigatorDefaultPeriod.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/graph.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/graph.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/prepare-data.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/tooltip/helpers.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/tooltip/tooltip.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/tooltip/tooltip.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/tooltip/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/graph/types/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/highcharts/colors.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/highcharts/helpers/__tests__/calculateClosestPointManually.test.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/highcharts/helpers/calcucalteClosestPointManually.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/highcharts/helpers/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/highcharts/highcharts.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/i18n/i18n.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/i18n/keysets/en.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/i18n/keysets/ru.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/map/map.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/map/options.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/map/tooltip/tooltip.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/map/tooltip/tooltip.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/perfomance.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/helpers/fetch-script.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/helpers/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/helpers/number-format.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/Canvas.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/DataConverter.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/Heatmap.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/TileUrlsGenerator.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/gridmap/Gridmap.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/gridmap/index.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/gridmap/utils/hexagonGrid.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/gridmap/utils/squareGrid.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/Polygonmap.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/colorLegend.css create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/colorLegend.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/colorize.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/colorize.test.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/defaultBalloonContent.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/defaultFilter.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/defaultFilter.test.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/defaultMapper.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/defaultMapper.test.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/defaultOnClick.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/defaultOnMouseEnter.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/defaultOnMouseLeave.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/inside.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/inside.test.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/normalizeFeature.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/components/polygonmap/utils/normalizeFeature.test.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/modules/modules.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/yandex-map.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/modules/yandex-map/yandex-map.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/AdvancedChart/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/AdvancedChart/renderer/AdvancedChartWidget.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/AdvancedChart/renderer/AdvancedChartWidget.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/AdvancedChart/renderer/components/Tooltip/Tooltip.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/AdvancedChart/renderer/components/Tooltip/Tooltip.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/AdvancedChart/renderer/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/AdvancedChart/renderer/utils/common.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/AdvancedChart/renderer/utils/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/AdvancedChart/renderer/utils/validation.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/AdvancedChart/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/HighchartsMap/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/HighchartsMap/renderer/HighchartsMapComponent.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/HighchartsMap/renderer/HighchartsMapWidget.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/HighchartsMap/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Markup/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Markup/renderer/MarkupWidget.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Markup/renderer/MarkupWidget.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Markup/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Metric/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Metric/renderer/MetricTile.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Metric/renderer/MetricTile.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Metric/renderer/MetricWidget.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Metric/renderer/MetricWidget.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Metric/renderer/metricHelpers.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Metric/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/TableWidget.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/TableWidget.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/BarCell/BarCell.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/DiffCell/DiffCell.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/HtmlCell/HtmlCell.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/MarkupCell/MarkupCell.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/SortIcon/SortIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/BackgroundTable.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/Table.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/Table.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/TableBody.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/TableFooter.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/TableHead.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/cell-width.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/usePreparedTableData.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/Table/utils.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/TableTitleView/TableTitleView.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/TreeCell/TreeCell.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/components/TreeCell/TreeCell.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/utils/action-params.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/utils/drill-down.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/utils/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/utils/math.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/utils/migrate-to-old-format.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/utils/renderer.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/renderer/utils/tree.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/Table/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/Legend/Icons/EyeSlashIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/Legend/Icons/SizeIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/Legend/Layer/Layer.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/Legend/Layer/Layer.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/Legend/Legend.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/Legend/Legend.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/YandexMapComponent.js create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/YandexMapComponent.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/YandexMapWidget.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/events.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/renderer/utils.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/YandexMap/types.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/chart-storage.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/components/StyledSplitPane/StyledSplitPane.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/components/StyledSplitPane/StyledSplitPane.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/components/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/components/withSplitPane/WithSplitPane.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/components/withSplitPane/withSplitPane.tsx create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/plugins/index.ts create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/theme.scss create mode 100644 src/ui/libs/DatalensChartkit/ChartKit/types.ts create mode 100644 src/ui/libs/DatalensChartkit/DatalensChartKitOld.js create mode 100644 src/ui/libs/DatalensChartkit/Error/Error.scss create mode 100644 src/ui/libs/DatalensChartkit/Error/Error.tsx create mode 100644 src/ui/libs/DatalensChartkit/Error/getAdditionalChartkitErrorContent.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/ChartKitBase.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/ChartKitBase.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Chart/Chart.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/ChartkitError/ChartkitError.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Content/store/reducer.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Content/store/types.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Control/Control.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/Header.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/Header.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/ChartsInsights/ChartsInsights.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/ChartsInsights/components/ChartsInsightsIcon/ChartsInsightsIcon.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/ChartsInsights/components/ChartsInsightsIcon/ChartsInsightsIcon.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/ChartsInsights/components/ChartsInsightsIcon/getIconData.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/ChartsInsights/components/TooltipContent/TooltipContent.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/ChartsInsights/components/TooltipContent/TooltipContent.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/ChartsInsights/helpers/getIconLevel.test.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/ChartsInsights/helpers/getIconLevel.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/ButtonSpin/ButtonSpin.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/ButtonSpin/ButtonSpin.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/ButtonSpinInput/ButtonSpinInput.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/ButtonSpinInput/ButtonSpinInput.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/ChartsModal/ChartsModal.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/ChartsModal/ChartsModal.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/ChartsModalMenu/ChartsModalMenu.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/ChartsModalMenu/ChartsModalMenu.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/DownloadCsv/DownloadCsv.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/DownloadCsv/DownloadCsv.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Export/CsvExport/CsvExport.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Export/Export.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Export/ToastContent/ToastContent.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Export/ToastContent/ToastContent.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Export/types.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Export/utils.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/Inspector.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/Inspector.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/SourceMeta/Datalens.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/Sources/Source/Source.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/Sources/SourceError/SourceError.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/Sources/SourceSuccess/SourceSuccess.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/Sources/Sources.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/Sources/Sources.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/Timings/Timings.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/Timings/Timings.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Inspector/types.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Modal/Body/Body.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Modal/Body/Body.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Modal/Context/Context.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Modal/Footer/Footer.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Modal/Footer/Footer.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Modal/Header/Header.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Modal/Header/Header.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Modal/Modal.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/Modal/Modal.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/PopupMessage/PopupMessage.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/PopupMessage/PopupMessage.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/constants.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Items/helpers.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Menu.js create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/Menu/Menu.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/MenuWithErrorBoundary/MenuWithErrorBoundary.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Header/components/index.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Loader/Loader.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/components/Loader/Loader.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/helpers.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitBase/types.ts create mode 100644 src/ui/libs/DatalensChartkit/components/ChartKitIcon/ChartKitIcon.js create mode 100644 src/ui/libs/DatalensChartkit/components/Control/Control.scss create mode 100644 src/ui/libs/DatalensChartkit/components/Control/Control.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/Control/Items/Items.js create mode 100644 src/ui/libs/DatalensChartkit/components/Control/Items/Items.scss create mode 100644 src/ui/libs/DatalensChartkit/components/Control/Items/withWrapForControls.js create mode 100644 src/ui/libs/DatalensChartkit/components/Control/types.ts create mode 100644 src/ui/libs/DatalensChartkit/components/DeferredInitializer/DeferredInitializer.scss create mode 100644 src/ui/libs/DatalensChartkit/components/DeferredInitializer/DeferredInitializer.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/Drill/Drill.scss create mode 100644 src/ui/libs/DatalensChartkit/components/Drill/Drill.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/Error/Error.scss create mode 100644 src/ui/libs/DatalensChartkit/components/Error/Error.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/Error/More/More.js create mode 100644 src/ui/libs/DatalensChartkit/components/Error/More/More.scss create mode 100644 src/ui/libs/DatalensChartkit/components/ErrorBoundary/ErrorBoundary.js create mode 100644 src/ui/libs/DatalensChartkit/components/ErrorBoundary/withErrorBoundary.js create mode 100644 src/ui/libs/DatalensChartkit/components/Palette/Attach/Attach.tsx create mode 100644 src/ui/libs/DatalensChartkit/components/Palette/ColorItem/ColorItem.js create mode 100644 src/ui/libs/DatalensChartkit/components/Palette/ColorItem/ColorItem.scss create mode 100644 src/ui/libs/DatalensChartkit/components/Palette/DraggableModalHOC/DraggableModalHOC.js create mode 100644 src/ui/libs/DatalensChartkit/components/Palette/DraggableModalHOC/DraggableModalHOC.scss create mode 100644 src/ui/libs/DatalensChartkit/components/Palette/PaletteEditor/PaletteEditor.js create mode 100644 src/ui/libs/DatalensChartkit/components/Palette/PaletteSettings/PaletteSettings.js create mode 100644 src/ui/libs/DatalensChartkit/components/Palette/PaletteSettings/PaletteSettings.scss create mode 100644 src/ui/libs/DatalensChartkit/components/SideMarkdown/SideMarkdown.scss create mode 100644 src/ui/libs/DatalensChartkit/components/SideMarkdown/SideMarkdown.tsx create mode 100644 src/ui/libs/DatalensChartkit/extensions/alert.js create mode 100644 src/ui/libs/DatalensChartkit/extensions/control.js create mode 100644 src/ui/libs/DatalensChartkit/extensions/markdown.js create mode 100644 src/ui/libs/DatalensChartkit/extensions/table.js create mode 100644 src/ui/libs/DatalensChartkit/helpers/convert-data-to-table.ts create mode 100644 src/ui/libs/DatalensChartkit/helpers/helpers.ts create mode 100644 src/ui/libs/DatalensChartkit/index.ts create mode 100644 src/ui/libs/DatalensChartkit/menu/Menu.tsx create mode 100644 src/ui/libs/DatalensChartkit/menu/MenuItems.tsx create mode 100644 src/ui/libs/DatalensChartkit/menu/chart-modeling.tsx create mode 100644 src/ui/libs/DatalensChartkit/menu/constants.ts create mode 100644 src/ui/libs/DatalensChartkit/menu/helpers.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/axios/axios.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/axios/axiosConcurrency.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/constants/constants.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/__tests__/ui-sandbox.test.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/__tests__/utils.test.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/chartkit-handlers.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/get-graph/get-graph.js create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/get-graph/prepare-config.js create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/get-graph/prepare-data.js create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/helpers/getChartsInsightsData.test.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/helpers/getChartsInsightsData.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/helpers/index.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/index.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/node.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/types.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/ui-sandbox-runtime.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/ui-sandbox.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/utils.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/charts/wizard.js create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/example.js create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/helpers.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/data-provider/withDataProvider.tsx create mode 100644 src/ui/libs/DatalensChartkit/modules/datalens-chartkit-custom-error/datalens-chartkit-custom-error.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/export/export.js create mode 100644 src/ui/libs/DatalensChartkit/modules/extensions-manager/extensions-manager.jsx create mode 100644 src/ui/libs/DatalensChartkit/modules/html-generator/__tests__/index.test.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/html-generator/constants.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/html-generator/index.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/html-generator/utils.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/localStorage.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/menu/menu.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/navigation.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/perfomance.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/settings/settings.js create mode 100644 src/ui/libs/DatalensChartkit/modules/table.ts create mode 100644 src/ui/libs/DatalensChartkit/modules/uri/uri.js create mode 100644 src/ui/libs/DatalensChartkit/styles/theme.scss create mode 100644 src/ui/libs/DatalensChartkit/types/common.ts create mode 100644 src/ui/libs/DatalensChartkit/types/control.ts create mode 100644 src/ui/libs/DatalensChartkit/types/dataProvider.ts create mode 100644 src/ui/libs/DatalensChartkit/types/index.ts create mode 100644 src/ui/libs/DatalensChartkit/types/menu.ts create mode 100644 src/ui/libs/DatalensChartkit/types/widget.ts create mode 100644 src/ui/libs/auth/refreshToken.ts create mode 100644 src/ui/libs/axios/axios.ts create mode 100644 src/ui/libs/axios/interceptors.ts create mode 100644 src/ui/libs/datasetHelper/index.ts create mode 100644 src/ui/libs/datasetSdk/index.js create mode 100644 src/ui/libs/google-analytics/index.ts create mode 100644 src/ui/libs/index.ts create mode 100644 src/ui/libs/logger/index.ts create mode 100644 src/ui/libs/metrica/index.ts create mode 100644 src/ui/libs/monaco/constants.ts create mode 100644 src/ui/libs/monaco/dl-lang/constants.ts create mode 100644 src/ui/libs/monaco/dl-lang/index.ts create mode 100644 src/ui/libs/monaco/index.ts create mode 100644 src/ui/libs/monaco/load-monaco-core.ts create mode 100644 src/ui/libs/monaco/ql-lang/constants.ts create mode 100644 src/ui/libs/monaco/ql-lang/index.ts create mode 100644 src/ui/libs/monaco/theme-mappers.ts create mode 100644 src/ui/libs/monaco/types.ts create mode 100644 src/ui/libs/monaco/utils.ts create mode 100644 src/ui/libs/navigateHelper/index.js create mode 100644 src/ui/libs/schematic-sdk/decorator.ts create mode 100644 src/ui/libs/schematic-sdk/index.ts create mode 100644 src/ui/libs/schematic-sdk/parse-error.ts create mode 100644 src/ui/libs/sdk/index.ts create mode 100644 src/ui/libs/sdk/types.ts create mode 100644 src/ui/libs/userSettings/index.ts create mode 100644 src/ui/modules/index.ts create mode 100644 src/ui/modules/markdownProvider.ts create mode 100644 src/ui/registry/components/DefaultEmpty.tsx create mode 100644 src/ui/registry/functions/example-function.ts create mode 100644 src/ui/registry/index.ts create mode 100644 src/ui/registry/libs/index.ts create mode 100644 src/ui/registry/libs/registryLibsPlugins.ts create mode 100644 src/ui/registry/libs/schematic-sdk/index.ts create mode 100644 src/ui/registry/units/auth/components-map.ts create mode 100644 src/ui/registry/units/auth/functions-map.ts create mode 100644 src/ui/registry/units/auth/index.ts create mode 100644 src/ui/registry/units/auth/register.tsx create mode 100644 src/ui/registry/units/auth/types/components/AdditionalProfileSections.ts create mode 100644 src/ui/registry/units/auth/types/components/AdditionalUsersFilters.ts create mode 100644 src/ui/registry/units/auth/types/components/Signin.ts create mode 100644 src/ui/registry/units/auth/types/functions/getAdditionalAddProfileFields.ts create mode 100644 src/ui/registry/units/auth/types/functions/getAdditionalProfileFields.ts create mode 100644 src/ui/registry/units/auth/types/functions/getUsersListColumns.ts create mode 100644 src/ui/registry/units/auth/types/functions/getUsersRoles.ts create mode 100644 src/ui/registry/units/chart/components-map.tsx create mode 100644 src/ui/registry/units/chart/functions-map.ts create mode 100644 src/ui/registry/units/chart/index.ts create mode 100644 src/ui/registry/units/chart/register.tsx create mode 100644 src/ui/registry/units/chart/types/components/AlertDialog.ts create mode 100644 src/ui/registry/units/chart/types/components/ChartActionPanelButton.ts create mode 100644 src/ui/registry/units/chart/types/functions/get-chartkit-holidays.ts create mode 100644 src/ui/registry/units/chart/types/functions/getChartkitMenuByType.ts create mode 100644 src/ui/registry/units/chart/types/functions/getDefaultChartMenu.ts create mode 100644 src/ui/registry/units/chart/types/functions/getPanePreviewChartMenu.ts create mode 100644 src/ui/registry/units/chart/types/functions/getWizardChartMenu.ts create mode 100644 src/ui/registry/units/collections/components-map.tsx create mode 100644 src/ui/registry/units/collections/functionts-map.ts create mode 100644 src/ui/registry/units/collections/index.ts create mode 100644 src/ui/registry/units/collections/register.ts create mode 100644 src/ui/registry/units/collections/types/customizeEmptyPlaceholder.ts create mode 100644 src/ui/registry/units/collections/types/customizeNoCreatePermissionDialog.ts create mode 100644 src/ui/registry/units/collections/types/index.ts create mode 100644 src/ui/registry/units/collections/types/useCreateWorkbookDialogHandlers.ts create mode 100644 src/ui/registry/units/common/components-map.tsx create mode 100644 src/ui/registry/units/common/components/Example/Example.tsx create mode 100644 src/ui/registry/units/common/constants/components.ts create mode 100644 src/ui/registry/units/common/constants/functions.ts create mode 100644 src/ui/registry/units/common/functions-map.ts create mode 100644 src/ui/registry/units/common/index.tsx create mode 100644 src/ui/registry/units/common/register.tsx create mode 100644 src/ui/registry/units/common/types/components/AIChatSupportForm.ts create mode 100644 src/ui/registry/units/common/types/components/AccessRights.ts create mode 100644 src/ui/registry/units/common/types/components/AccessRightsUrlOpen.ts create mode 100644 src/ui/registry/units/common/types/components/AclSubject.ts create mode 100644 src/ui/registry/units/common/types/components/AclSubjectSuggest.ts create mode 100644 src/ui/registry/units/common/types/components/ActionPanelEntrySelect.ts create mode 100644 src/ui/registry/units/common/types/components/AsideHeaderAdapter.ts create mode 100644 src/ui/registry/units/common/types/components/ButtonFavorite.ts create mode 100644 src/ui/registry/units/common/types/components/DialogAddParticipants.ts create mode 100644 src/ui/registry/units/common/types/components/DialogEntryDescription.ts create mode 100644 src/ui/registry/units/common/types/components/DialogImageWidgetLinkHint.ts create mode 100644 src/ui/registry/units/common/types/components/DialogRelatedEntitiesRadioHint.ts create mode 100644 src/ui/registry/units/common/types/components/DialogShare.ts create mode 100644 src/ui/registry/units/common/types/components/DownloadScreenshot.tsx create mode 100644 src/ui/registry/units/common/types/components/EntryBreadcrumbs.ts create mode 100644 src/ui/registry/units/common/types/components/IamAccessDialog.ts create mode 100644 src/ui/registry/units/common/types/components/LoginById.ts create mode 100644 src/ui/registry/units/common/types/components/MarkdownControl.ts create mode 100644 src/ui/registry/units/common/types/components/MarkupShareLink.ts create mode 100644 src/ui/registry/units/common/types/components/MarkupUserInfo.ts create mode 100644 src/ui/registry/units/common/types/components/MobileHeaderComponent.ts create mode 100644 src/ui/registry/units/common/types/components/OAuthTokenButton.ts create mode 100644 src/ui/registry/units/common/types/components/PlaceholderIllustrationImage.ts create mode 100644 src/ui/registry/units/common/types/components/RangeInputPicker.ts create mode 100644 src/ui/registry/units/common/types/components/ReportButton.ts create mode 100644 src/ui/registry/units/common/types/components/UserAvatarById.ts create mode 100644 src/ui/registry/units/common/types/components/YfmWrapper.ts create mode 100644 src/ui/registry/units/common/types/functions/GetUsersById.ts create mode 100644 src/ui/registry/units/common/types/functions/fetchDocumentation.ts create mode 100644 src/ui/registry/units/common/types/functions/fetchFunctionsDocumentation.ts create mode 100644 src/ui/registry/units/common/types/functions/getAdditionalChartkitErrorContent.ts create mode 100644 src/ui/registry/units/common/types/functions/getBasicActionPanelItems.ts create mode 100644 src/ui/registry/units/common/types/functions/getFunctionsDocumentation.ts create mode 100644 src/ui/registry/units/common/types/functions/getGloballyEntrySettings.ts create mode 100644 src/ui/registry/units/common/types/functions/getHeaderWithoutHelpCenterErrorContentTypes.ts create mode 100644 src/ui/registry/units/common/types/functions/getHeaderWithoutNavigationErrorContentTypes.ts create mode 100644 src/ui/registry/units/common/types/functions/getIllustrationStore.ts create mode 100644 src/ui/registry/units/common/types/functions/getImageNameFromErrorContentType.ts create mode 100644 src/ui/registry/units/common/types/functions/getLoginById.ts create mode 100644 src/ui/registry/units/common/types/functions/getLogoIcon.ts create mode 100644 src/ui/registry/units/common/types/functions/getNotAuthenticatedErrorContentTypes.ts create mode 100644 src/ui/registry/units/common/types/functions/getShouldShowAIAssistant.ts create mode 100644 src/ui/registry/units/common/types/functions/getUIEntryRoute.ts create mode 100644 src/ui/registry/units/common/types/functions/isValidLogoUrl.ts create mode 100644 src/ui/registry/units/common/types/functions/openDialogOrganizationInvite.ts create mode 100644 src/ui/registry/units/common/types/functions/openDialogOrganizationInviteUsers.ts create mode 100644 src/ui/registry/units/common/types/functions/renderDialogRelatedEntitiesAlertHint.ts create mode 100644 src/ui/registry/units/common/types/functions/resolveUsersByIds.ts create mode 100644 src/ui/registry/units/common/types/functions/setEntryKey.ts create mode 100644 src/ui/registry/units/common/types/functions/useSubjectsListId.ts create mode 100644 src/ui/registry/units/connections/components-map.tsx create mode 100644 src/ui/registry/units/connections/functions-map.ts create mode 100644 src/ui/registry/units/connections/index.ts create mode 100644 src/ui/registry/units/connections/register.tsx create mode 100644 src/ui/registry/units/connections/types/CreateEditorChartButtonProps.ts create mode 100644 src/ui/registry/units/connections/types/PreparedRowItem.ts create mode 100644 src/ui/registry/units/connections/types/beforeConnectorFormUnmount.ts create mode 100644 src/ui/registry/units/connections/types/getFakeEntry.ts create mode 100644 src/ui/registry/units/connections/types/getMockedForm.ts create mode 100644 src/ui/registry/units/connections/types/getNewConnectionDestination.ts create mode 100644 src/ui/registry/units/connections/types/getRenderConnectionSettingsPopup.ts create mode 100644 src/ui/registry/units/dash/components-map.tsx create mode 100644 src/ui/registry/units/dash/functions-map.ts create mode 100644 src/ui/registry/units/dash/index.ts create mode 100644 src/ui/registry/units/dash/register.tsx create mode 100644 src/ui/registry/units/dash/types/BeforeCloseDialogItem.ts create mode 100644 src/ui/registry/units/dash/types/BeforeOpenDialogItem.ts create mode 100644 src/ui/registry/units/dash/types/DashActionPanelAdditionalButtons.ts create mode 100644 src/ui/registry/units/dash/types/DialogDashOtherSettingsPrepend.ts create mode 100644 src/ui/registry/units/dash/types/GetExtendedItemData.ts create mode 100644 src/ui/registry/units/datasets/components-map.tsx create mode 100644 src/ui/registry/units/datasets/functions-map.ts create mode 100644 src/ui/registry/units/datasets/index.ts create mode 100644 src/ui/registry/units/datasets/register.tsx create mode 100644 src/ui/registry/units/datasets/types/CreateDashboardButton.ts create mode 100644 src/ui/registry/units/datasets/types/functions/getRenderDatasetSettingsPopup.ts create mode 100644 src/ui/registry/units/datasets/types/functions/renderRLSDialog.ts create mode 100644 src/ui/registry/units/docs/functions-map.ts create mode 100644 src/ui/registry/units/docs/index.ts create mode 100644 src/ui/registry/units/docs/register.tsx create mode 100644 src/ui/registry/units/editor/components-map.tsx create mode 100644 src/ui/registry/units/editor/constants/components.ts create mode 100644 src/ui/registry/units/editor/constants/functions.ts create mode 100644 src/ui/registry/units/editor/functions-map.ts create mode 100644 src/ui/registry/units/editor/index.ts create mode 100644 src/ui/registry/units/editor/types/components/ActionPanelButton.ts create mode 100644 src/ui/registry/units/editor/types/functions/fetchEditorDocumentation.ts create mode 100644 src/ui/registry/units/editor/types/functions/getEditorTemplates.ts create mode 100644 src/ui/registry/units/fieldEditor/components-map.tsx create mode 100644 src/ui/registry/units/fieldEditor/index.ts create mode 100644 src/ui/registry/units/main/components-map.tsx create mode 100644 src/ui/registry/units/main/index.ts create mode 100644 src/ui/registry/units/main/register.tsx create mode 100644 src/ui/registry/units/preview/components-map.tsx create mode 100644 src/ui/registry/units/preview/index.ts create mode 100644 src/ui/registry/units/preview/register.tsx create mode 100644 src/ui/registry/units/public/components-map.tsx create mode 100644 src/ui/registry/units/public/index.ts create mode 100644 src/ui/registry/units/public/register.tsx create mode 100644 src/ui/registry/units/public/types/components/Header.ts create mode 100644 src/ui/registry/units/ql/components-map.tsx create mode 100644 src/ui/registry/units/ql/functions-map.ts create mode 100644 src/ui/registry/units/ql/index.ts create mode 100644 src/ui/registry/units/ql/register.tsx create mode 100644 src/ui/registry/units/wizard/components-map.tsx create mode 100644 src/ui/registry/units/wizard/index.ts create mode 100644 src/ui/registry/units/wizard/register.tsx create mode 100644 src/ui/registry/units/wizard/types/components/WizardActionPanelExtension.ts create mode 100644 src/ui/registry/units/workbooks/components-map.tsx create mode 100644 src/ui/registry/units/workbooks/functions-map.ts create mode 100644 src/ui/registry/units/workbooks/index.ts create mode 100644 src/ui/registry/units/workbooks/register.tsx create mode 100644 src/ui/registry/units/workbooks/types/components/workbookTableRowExtendedContent.ts create mode 100644 src/ui/registry/units/workbooks/types/functions/checkWbCreateEntryButtonVisibility.ts create mode 100644 src/ui/registry/units/workbooks/types/functions/getWorkbookEntryUrl.ts create mode 100644 src/ui/registry/utils/components-registry.tsx create mode 100644 src/ui/registry/utils/register-app-plugins.tsx create mode 100644 src/ui/store/actions/asideHeader/index.ts create mode 100644 src/ui/store/actions/asideHeader/navigation.ts create mode 100644 src/ui/store/actions/chartWidget.ts create mode 100644 src/ui/store/actions/collectionsStructure/common.ts create mode 100644 src/ui/store/actions/collectionsStructure/export.ts create mode 100644 src/ui/store/actions/collectionsStructure/index.ts create mode 100644 src/ui/store/actions/collectionsStructure/showCollectionEntityErrorToast.ts create mode 100644 src/ui/store/actions/collectionsStructure/templates.ts create mode 100644 src/ui/store/actions/colorPaletteEditor.ts create mode 100644 src/ui/store/actions/controlDialog/controlDialog.ts create mode 100644 src/ui/store/actions/controlDialog/helpers.ts create mode 100644 src/ui/store/actions/copyEntriesToWorkbook.ts create mode 100644 src/ui/store/actions/dialog.ts create mode 100644 src/ui/store/actions/editHistory.ts create mode 100644 src/ui/store/actions/entryContent.ts create mode 100644 src/ui/store/actions/iamAccessDialog.ts create mode 100644 src/ui/store/actions/landing.ts create mode 100644 src/ui/store/actions/localStorageFallback.ts create mode 100644 src/ui/store/actions/migrationToWorkbook.ts create mode 100644 src/ui/store/actions/openDialogTypes.ts create mode 100644 src/ui/store/actions/toaster.ts create mode 100644 src/ui/store/actions/user.ts create mode 100644 src/ui/store/actions/usersByIds.ts create mode 100644 src/ui/store/configure.ts create mode 100644 src/ui/store/constants/collectionsStructure.ts create mode 100644 src/ui/store/constants/controlDialog.ts create mode 100644 src/ui/store/constants/copyEntriesToWorkbook.ts create mode 100644 src/ui/store/constants/editHistory.ts create mode 100644 src/ui/store/constants/iamAccessDialog.ts create mode 100644 src/ui/store/constants/migrationToWorkbook.ts create mode 100644 src/ui/store/index.ts create mode 100644 src/ui/store/reducer-registry.ts create mode 100644 src/ui/store/reducers/asideHeader.ts create mode 100644 src/ui/store/reducers/collectionsStructure.ts create mode 100644 src/ui/store/reducers/colorPaletteEditor.ts create mode 100644 src/ui/store/reducers/controlDialog/controlDialog.ts create mode 100644 src/ui/store/reducers/controlDialog/helpers.ts create mode 100644 src/ui/store/reducers/copyEntriesToWorkbook.ts create mode 100644 src/ui/store/reducers/dialog.ts create mode 100644 src/ui/store/reducers/editHistory.ts create mode 100644 src/ui/store/reducers/entryContent.ts create mode 100644 src/ui/store/reducers/iamAccessDialog.ts create mode 100644 src/ui/store/reducers/index.tsx create mode 100644 src/ui/store/reducers/landing.ts create mode 100644 src/ui/store/reducers/localStorageFallback.ts create mode 100644 src/ui/store/reducers/migrationToWorkbook.ts create mode 100644 src/ui/store/reducers/user.ts create mode 100644 src/ui/store/selectors/asideHeader.ts create mode 100644 src/ui/store/selectors/collectionsStructure.ts create mode 100644 src/ui/store/selectors/colorPaletteEditor.ts create mode 100644 src/ui/store/selectors/controlDialog.ts create mode 100644 src/ui/store/selectors/copyEntriesToWorkbook.ts create mode 100644 src/ui/store/selectors/editHistory.ts create mode 100644 src/ui/store/selectors/entryContent.ts create mode 100644 src/ui/store/selectors/iamAccessDialog.ts create mode 100644 src/ui/store/selectors/landing.ts create mode 100644 src/ui/store/selectors/localStorageFallback.ts create mode 100644 src/ui/store/selectors/migrationToWorkbook.ts create mode 100644 src/ui/store/selectors/user.ts create mode 100644 src/ui/store/toolkit/chart-modeling/actions.ts create mode 100644 src/ui/store/toolkit/chart-modeling/reducer.ts create mode 100644 src/ui/store/toolkit/chart-modeling/selectors.ts create mode 100644 src/ui/store/toolkit/chart-modeling/types.ts create mode 100644 src/ui/store/toolkit/chartkit/api.ts create mode 100644 src/ui/store/toolkit/chartkit/types.ts create mode 100644 src/ui/store/toolkit/index.ts create mode 100644 src/ui/store/typings/asideHeader.ts create mode 100644 src/ui/store/typings/controlDialog.ts create mode 100644 src/ui/store/typings/entryContent.ts create mode 100644 src/ui/store/typings/iamAccessDialog.ts create mode 100644 src/ui/store/typings/user.ts create mode 100644 src/ui/store/utils/asideHeader.ts create mode 100644 src/ui/store/utils/collectionStructure.ts create mode 100644 src/ui/store/utils/controlDialog.ts create mode 100644 src/ui/store/utils/jdp.ts create mode 100644 src/ui/store/utils/user.ts create mode 100644 src/ui/styles/base.scss create mode 100644 src/ui/styles/dash.scss create mode 100644 src/ui/styles/dl-monaco.scss create mode 100644 src/ui/styles/min-width.scss create mode 100644 src/ui/styles/mixins.scss create mode 100644 src/ui/styles/overrides.scss create mode 100644 src/ui/styles/preview.scss create mode 100644 src/ui/styles/split-pane-resizer.scss create mode 100644 src/ui/styles/theme.scss create mode 100644 src/ui/styles/variables.scss create mode 100644 src/ui/tsconfig.json create mode 100644 src/ui/typings/common.ts create mode 100644 src/ui/typings/components.ts create mode 100644 src/ui/typings/error.ts create mode 100644 src/ui/typings/index.ts create mode 100644 src/ui/units/auth/components/AuthPage/AuthPage.scss create mode 100644 src/ui/units/auth/components/AuthPage/AuthPage.tsx create mode 100644 src/ui/units/auth/components/AuthPage/useAuthPageInit.ts create mode 100644 src/ui/units/auth/components/ChangePasswordDialog/ChangePasswordDialog.scss create mode 100644 src/ui/units/auth/components/ChangePasswordDialog/ChangePasswordDialog.tsx create mode 100644 src/ui/units/auth/components/ChangeUserRoleDialog/ChangeUserRoleDialog.tsx create mode 100644 src/ui/units/auth/components/DeleteUserDialog/DeleteUserDialog.tsx create mode 100644 src/ui/units/auth/components/EditUserProfileDialog/EditUserProfileDialog.tsx create mode 100644 src/ui/units/auth/components/LoginById/LoginById.scss create mode 100644 src/ui/units/auth/components/LoginById/LoginById.tsx create mode 100644 src/ui/units/auth/components/LoginById/utils.ts create mode 100644 src/ui/units/auth/components/Logout/Logout.tsx create mode 100644 src/ui/units/auth/components/Reload/Reload.tsx create mode 100644 src/ui/units/auth/components/Signin/Signin.scss create mode 100644 src/ui/units/auth/components/Signin/Signin.tsx create mode 100644 src/ui/units/auth/components/Signin/components/Login.tsx create mode 100644 src/ui/units/auth/components/Signin/components/Password.tsx create mode 100644 src/ui/units/auth/components/Signup/Signup.scss create mode 100644 src/ui/units/auth/components/Signup/Signup.tsx create mode 100644 src/ui/units/auth/components/Signup/components/Back.tsx create mode 100644 src/ui/units/auth/components/UserProfile/UserProfile.tsx create mode 100644 src/ui/units/auth/components/UserRoleLabel/UserRoleLabel.tsx create mode 100644 src/ui/units/auth/components/formControls/CustomRow.tsx create mode 100644 src/ui/units/auth/components/formControls/Email.tsx create mode 100644 src/ui/units/auth/components/formControls/FirstName.tsx create mode 100644 src/ui/units/auth/components/formControls/GeneratePasswordButton.tsx create mode 100644 src/ui/units/auth/components/formControls/LastName.tsx create mode 100644 src/ui/units/auth/components/formControls/Login.tsx create mode 100644 src/ui/units/auth/components/formControls/Password.tsx create mode 100644 src/ui/units/auth/components/formControls/RepeatPassword.tsx create mode 100644 src/ui/units/auth/components/formControls/Roles.tsx create mode 100644 src/ui/units/auth/components/formControls/RowButton.tsx create mode 100644 src/ui/units/auth/components/formControls/formControls.scss create mode 100644 src/ui/units/auth/components/formControls/test/generatePassword.test.ts create mode 100644 src/ui/units/auth/components/formControls/types.ts create mode 100644 src/ui/units/auth/components/formControls/utils.ts create mode 100644 src/ui/units/auth/constants/errors.ts create mode 100644 src/ui/units/auth/constants/routes.ts create mode 100644 src/ui/units/auth/containers/UserProfile/UserProfile.tsx create mode 100644 src/ui/units/auth/hooks/useClearReloadedQuery.ts create mode 100644 src/ui/units/auth/hooks/useUserById.ts create mode 100644 src/ui/units/auth/store/actions/common.ts create mode 100644 src/ui/units/auth/store/actions/index.ts create mode 100644 src/ui/units/auth/store/actions/logout.ts create mode 100644 src/ui/units/auth/store/actions/signin.ts create mode 100644 src/ui/units/auth/store/actions/userInfoForm.ts create mode 100644 src/ui/units/auth/store/actions/userProfile.ts create mode 100644 src/ui/units/auth/store/actions/usersByIds.ts create mode 100644 src/ui/units/auth/store/constants/common.ts create mode 100644 src/ui/units/auth/store/constants/signin.ts create mode 100644 src/ui/units/auth/store/constants/userInfoForm.ts create mode 100644 src/ui/units/auth/store/constants/userProfile.ts create mode 100644 src/ui/units/auth/store/constants/usersByIds.ts create mode 100644 src/ui/units/auth/store/reducers/common.ts create mode 100644 src/ui/units/auth/store/reducers/index.ts create mode 100644 src/ui/units/auth/store/reducers/signin.ts create mode 100644 src/ui/units/auth/store/reducers/userInfoForm.ts create mode 100644 src/ui/units/auth/store/reducers/userProfile.ts create mode 100644 src/ui/units/auth/store/reducers/usersByIds.ts create mode 100644 src/ui/units/auth/store/selectors/common.ts create mode 100644 src/ui/units/auth/store/selectors/signin.ts create mode 100644 src/ui/units/auth/store/selectors/userInfoForm.ts create mode 100644 src/ui/units/auth/store/selectors/userProfile.ts create mode 100644 src/ui/units/auth/store/selectors/usersByIds.ts create mode 100644 src/ui/units/auth/store/typings/userInfoForm.ts create mode 100644 src/ui/units/auth/store/typings/usersByIds.ts create mode 100644 src/ui/units/auth/utils/fields.ts create mode 100644 src/ui/units/auth/utils/getUsersRoles.ts create mode 100644 src/ui/units/auth/utils/userProfile.ts create mode 100644 src/ui/units/auth/utils/validation.ts create mode 100644 src/ui/units/collections-navigation/components/CollectionBreadcrumbs/CollectionBreadcrumbs.scss create mode 100644 src/ui/units/collections-navigation/components/CollectionBreadcrumbs/CollectionBreadcrumbs.tsx create mode 100644 src/ui/units/collections-navigation/components/CollectionBreadcrumbs/index.ts create mode 100644 src/ui/units/collections-navigation/components/CollectionBreadcrumbs/utils.ts create mode 100644 src/ui/units/collections-navigation/components/CollectionsNavigationApp/CollectionsNavigationApp.tsx create mode 100644 src/ui/units/collections-navigation/components/CollectionsNavigationApp/index.ts create mode 100644 src/ui/units/collections-navigation/components/CollectionsNavigationLayout/CollectionsNavigationLayout.scss create mode 100644 src/ui/units/collections-navigation/components/CollectionsNavigationLayout/CollectionsNavigationLayout.tsx create mode 100644 src/ui/units/collections-navigation/components/CollectionsNavigationLayout/index.ts create mode 100644 src/ui/units/collections-navigation/constants/index.ts create mode 100644 src/ui/units/collections-navigation/contexts/LayoutContext.ts create mode 100644 src/ui/units/collections-navigation/store/actions/index.tsx create mode 100644 src/ui/units/collections-navigation/store/constants/index.ts create mode 100644 src/ui/units/collections-navigation/store/reducers/index.ts create mode 100644 src/ui/units/collections-navigation/store/selectors/index.tsx create mode 100644 src/ui/units/collections-navigation/utils/index.ts create mode 100644 src/ui/units/collections/components/CollectionActions/CollectionActions.scss create mode 100644 src/ui/units/collections/components/CollectionActions/CollectionActions.tsx create mode 100644 src/ui/units/collections/components/CollectionActions/components/SharedEntryNotice/SharedEntryNotice.scss create mode 100644 src/ui/units/collections/components/CollectionActions/components/SharedEntryNotice/SharedEntryNotice.tsx create mode 100644 src/ui/units/collections/components/CollectionActions/index.ts create mode 100644 src/ui/units/collections/components/CollectionActions/utils.tsx create mode 100644 src/ui/units/collections/components/CollectionBatchPanel/CollectionBatchPanel.tsx create mode 100644 src/ui/units/collections/components/CollectionContent/CollectionContent.scss create mode 100644 src/ui/units/collections/components/CollectionContent/CollectionContent.tsx create mode 100644 src/ui/units/collections/components/CollectionContent/hooks/index.ts create mode 100644 src/ui/units/collections/components/CollectionContent/hooks/useActions.tsx create mode 100644 src/ui/units/collections/components/CollectionContent/index.ts create mode 100644 src/ui/units/collections/components/CollectionContent/utils.ts create mode 100644 src/ui/units/collections/components/CollectionContentGrid/CollectionContentGrid.scss create mode 100644 src/ui/units/collections/components/CollectionContentGrid/CollectionContentGrid.tsx create mode 100644 src/ui/units/collections/components/CollectionContentGrid/CollectionItemIcon.tsx create mode 100644 src/ui/units/collections/components/CollectionContentGrid/index.ts create mode 100644 src/ui/units/collections/components/CollectionContentTable/CollectionContentTable.scss create mode 100644 src/ui/units/collections/components/CollectionContentTable/CollectionContentTable.tsx create mode 100644 src/ui/units/collections/components/CollectionContentTable/TableComponents/CollectionCheckboxCell.tsx create mode 100644 src/ui/units/collections/components/CollectionContentTable/TableComponents/CollectionLinkRow.tsx create mode 100644 src/ui/units/collections/components/CollectionContentTable/TableComponents/CollectionTitleCell.tsx create mode 100644 src/ui/units/collections/components/CollectionContentTable/helpers.ts create mode 100644 src/ui/units/collections/components/CollectionContentTable/index.ts create mode 100644 src/ui/units/collections/components/CollectionPage/CollectionPage.scss create mode 100644 src/ui/units/collections/components/CollectionPage/CollectionPage.tsx create mode 100644 src/ui/units/collections/components/CollectionPage/hooks/index.ts create mode 100644 src/ui/units/collections/components/CollectionPage/hooks/useCreateWorkbookDialogHandlers.ts create mode 100644 src/ui/units/collections/components/CollectionPage/hooks/useData.ts create mode 100644 src/ui/units/collections/components/CollectionPage/hooks/useFilters.ts create mode 100644 src/ui/units/collections/components/CollectionPage/hooks/useLayout.tsx create mode 100644 src/ui/units/collections/components/CollectionPage/hooks/useOpenCreateWorkbookDialog.ts create mode 100644 src/ui/units/collections/components/CollectionPage/hooks/useSelection.ts create mode 100644 src/ui/units/collections/components/CollectionPage/hooks/useViewMode.ts create mode 100644 src/ui/units/collections/components/CollectionPage/index.ts create mode 100644 src/ui/units/collections/components/CollectionPage/utils.ts create mode 100644 src/ui/units/collections/components/constants.ts create mode 100644 src/ui/units/collections/components/helpers.ts create mode 100644 src/ui/units/collections/components/types.ts create mode 100644 src/ui/units/collections/hooks/useRefreshPageAfterImport.ts create mode 100644 src/ui/units/collections/store/actions/index.tsx create mode 100644 src/ui/units/collections/store/constants/index.ts create mode 100644 src/ui/units/collections/store/reducers/index.ts create mode 100644 src/ui/units/collections/store/selectors/index.tsx create mode 100644 src/ui/units/connections/assets/icons/file-item.svg create mode 100644 src/ui/units/connections/assets/icons/gauth-btn-dark-en.svg create mode 100644 src/ui/units/connections/assets/icons/gauth-btn-dark-ru.svg create mode 100644 src/ui/units/connections/assets/icons/gauth-btn-light-en.svg create mode 100644 src/ui/units/connections/assets/icons/gauth-btn-light-ru.svg create mode 100644 src/ui/units/connections/components/ConnectorForm/ConnectorForm.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/ConnectorForm.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/ErrorActions/ErrorActions.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/ErrorActions/ErrorActions.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/FormActions/CheckParamsButton/CheckParamsButton.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/FormActions/CheckParamsButton/CheckParamsButton.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/FormActions/FormActions.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/FormActions/SubmitButton/SubmitButton.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/FormActions/selectors.ts create mode 100644 src/ui/units/connections/components/ConnectorForm/FormActions/utils.ts create mode 100644 src/ui/units/connections/components/ConnectorForm/FormRow/FormItem.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/FormRow/FormRow.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/FormRow/FormRow.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/FormRow/PreparedRowItem.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/Checkbox/Checkbox.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/Datepicker/Datepicker.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/components/Datepicker/Datepicker.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/Description/Description.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/components/Description/Description.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/FileInput/FileInput.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/FileInput/utils.test.ts create mode 100644 src/ui/units/connections/components/ConnectorForm/components/FileInput/utils.ts create mode 100644 src/ui/units/connections/components/ConnectorForm/components/Input/Input.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/KeyValue/KeyValue.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/components/KeyValue/KeyValue.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/KeyValue/hooks.ts create mode 100644 src/ui/units/connections/components/ConnectorForm/components/KeyValue/types.ts create mode 100644 src/ui/units/connections/components/ConnectorForm/components/Label/Label.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/components/Label/Label.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/MarkdownItem/MarkdownItem.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/PlainText/PlainText.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/components/PlainText/PlainText.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/RadioButton/RadioButton.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/components/RadioButton/RadioButton.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/RadioGroup/RadioGroup.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/components/RadioGroup/RadioGroup.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/Select/Select.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/components/Select/Select.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/components/index.ts create mode 100644 src/ui/units/connections/components/ConnectorForm/components/withControlWrap/withControlWrap.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/components/withControlWrap/withControlWrap.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/preparedRows/CacheTtlRow/CacheTtlRow.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/preparedRows/CollapseRow/CollapseRow.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/preparedRows/CollapseRow/CollapseRow.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/preparedRows/EarlyInvalidationCacheRow/EarlyInvalidationCacheRow.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/preparedRows/EarlyInvalidationCacheRow/EarlyInvalidationCacheRow.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/preparedRows/RawSQLLevelRow/RawSQLLevelRow.scss create mode 100644 src/ui/units/connections/components/ConnectorForm/preparedRows/RawSQLLevelRow/RawSQLLevelRow.tsx create mode 100644 src/ui/units/connections/components/ConnectorForm/preparedRows/index.ts create mode 100644 src/ui/units/connections/components/ConnectorForm/utils/__tests__/getCollapsesToExpand.test.ts create mode 100644 src/ui/units/connections/components/ConnectorForm/utils/index.ts create mode 100644 src/ui/units/connections/components/ConnectorsList/ConnectorsList.scss create mode 100644 src/ui/units/connections/components/ConnectorsList/ConnectorsList.tsx create mode 100644 src/ui/units/connections/components/ConnectorsList/ListItem.tsx create mode 100644 src/ui/units/connections/components/ConnectorsList/utils/index.ts create mode 100644 src/ui/units/connections/components/ErrorView/ErrorView.scss create mode 100644 src/ui/units/connections/components/ErrorView/ErrorView.tsx create mode 100644 src/ui/units/connections/components/FormTitle/FormTitle.scss create mode 100644 src/ui/units/connections/components/FormTitle/FormTitle.tsx create mode 100644 src/ui/units/connections/components/Page/ConnPanelActions.scss create mode 100644 src/ui/units/connections/components/Page/ConnPanelActions.tsx create mode 100644 src/ui/units/connections/components/Page/Page.scss create mode 100644 src/ui/units/connections/components/Page/Page.tsx create mode 100644 src/ui/units/connections/components/Page/components/ConnSettings.tsx create mode 100644 src/ui/units/connections/components/Page/components/CreateDatasetButton.tsx create mode 100644 src/ui/units/connections/components/Page/components/CreateQlChartButton.tsx create mode 100644 src/ui/units/connections/components/Page/components/DescriptionButton.tsx create mode 100644 src/ui/units/connections/components/Page/components/S3BasedConnButton.tsx create mode 100644 src/ui/units/connections/components/Page/components/UnloadConfirmation.tsx create mode 100644 src/ui/units/connections/components/Page/components/index.ts create mode 100644 src/ui/units/connections/components/Page/constants.ts create mode 100644 src/ui/units/connections/components/Page/filterContextMenuItems.ts create mode 100644 src/ui/units/connections/components/Page/useAdditionalContextMenuItems.tsx create mode 100644 src/ui/units/connections/components/Page/useApiErrors.ts create mode 100644 src/ui/units/connections/components/Page/utils.test.ts create mode 100644 src/ui/units/connections/components/Page/utils.ts create mode 100644 src/ui/units/connections/components/Router/Router.tsx create mode 100644 src/ui/units/connections/components/WrappedLoader/WrappedLoader.scss create mode 100644 src/ui/units/connections/components/WrappedLoader/WrappedLoader.tsx create mode 100644 src/ui/units/connections/components/custom-forms/ChOverYT/ChOverYT.scss create mode 100644 src/ui/units/connections/components/custom-forms/ChOverYT/ChOverYT.tsx create mode 100644 src/ui/units/connections/components/custom-forms/ChOverYT/ChoicePage.tsx create mode 100644 src/ui/units/connections/components/custom-forms/ChOverYT/ConnectorCard.tsx create mode 100644 src/ui/units/connections/components/custom-forms/ChOverYT/Notification.tsx create mode 100644 src/ui/units/connections/components/custom-forms/ChOverYT/constants.ts create mode 100644 src/ui/units/connections/components/custom-forms/File/File.scss create mode 100644 src/ui/units/connections/components/custom-forms/File/File.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/AddFileButton.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/FilesList.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/ListItem/FileItem.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/ListItem/ListItem.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/ListItem/SourceItem.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/ReplaceSourceButton.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/Workspace/ColumnFilter.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/Workspace/FileSettings.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/Workspace/TypeSelect.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/Workspace/Workspace.tsx create mode 100644 src/ui/units/connections/components/custom-forms/File/components/index.ts create mode 100644 src/ui/units/connections/components/custom-forms/File/context.ts create mode 100644 src/ui/units/connections/components/custom-forms/File/types.ts create mode 100644 src/ui/units/connections/components/custom-forms/File/useHandlers.ts create mode 100644 src/ui/units/connections/components/custom-forms/File/utils.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/GSheetsV2.scss create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/GSheetsV2.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/AddSection.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/AdditionalTitleContent/GAuthButton.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/AdditionalTitleContent/index.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/GSheetListItemView/GSheetSourceInfoView.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/GSheetListItemView/GSheetSourceView.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/GSheetListItemView/UploadedGSheetView.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/GSheetListItemView/index.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/SheetsList.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/Workspace/EmptyWorkspace.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/Workspace/GSheetSourceInfoWorkspace.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/Workspace/GSheetSourceWorkspace.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/Workspace/GSheetWorkspace.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/Workspace/Workspace.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/Workspace/utils.ts create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/components/index.ts create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/containers/ActionBarContainer.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/containers/SheetsListContainer.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/containers/WorkspaceContainer.tsx create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/containers/index.ts create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/containers/useGSheetDialogs.ts create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/containers/useGoogleAuth.ts create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/containers/utils.ts create mode 100644 src/ui/units/connections/components/custom-forms/GSheetsV2/types.ts create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/Yadocs.scss create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/Yadocs.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/components/AdditionalTitleContent.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/components/DialogAddDocument/DialogAddDocument.scss create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/components/DialogAddDocument/DialogAddDocument.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/components/DocsList.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/components/Workspace.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/components/YadocListItemView/UploadedYadocView.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/components/YadocListItemView/YadocSourceInfoView.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/components/YadocListItemView/YadocSourceView.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/components/YadocListItemView/index.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/components/index.ts create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/containers/ActionBarContainer.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/containers/DocsListContainer.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/containers/WorkspaceContainer.tsx create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/containers/index.ts create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/containers/useYadocsDialogs.ts create mode 100644 src/ui/units/connections/components/custom-forms/Yadocs/types.ts create mode 100644 src/ui/units/connections/components/custom-forms/components/ButtonLogout/ButtonLogout.scss create mode 100644 src/ui/units/connections/components/custom-forms/components/ButtonLogout/ButtonLogout.tsx create mode 100644 src/ui/units/connections/components/custom-forms/components/ColumnFilter/ColumnFilter.scss create mode 100644 src/ui/units/connections/components/custom-forms/components/ColumnFilter/ColumnFilter.tsx create mode 100644 src/ui/units/connections/components/custom-forms/components/ColumnsHeaderSwitcher/ColumnsHeaderSwitcher.scss create mode 100644 src/ui/units/connections/components/custom-forms/components/ColumnsHeaderSwitcher/ColumnsHeaderSwitcher.tsx create mode 100644 src/ui/units/connections/components/custom-forms/components/DialogWithInput/DialogWithInput.tsx create mode 100644 src/ui/units/connections/components/custom-forms/components/DialogWithInput/index.ts create mode 100644 src/ui/units/connections/components/custom-forms/components/DialogWithInput/types.ts create mode 100644 src/ui/units/connections/components/custom-forms/components/ListItem/ActionDeleteView.tsx create mode 100644 src/ui/units/connections/components/custom-forms/components/ListItem/ActionErrorView.tsx create mode 100644 src/ui/units/connections/components/custom-forms/components/ListItem/ActionMoreView.tsx create mode 100644 src/ui/units/connections/components/custom-forms/components/ListItem/ListItem.scss create mode 100644 src/ui/units/connections/components/custom-forms/components/ListItem/ListItem.tsx create mode 100644 src/ui/units/connections/components/custom-forms/components/ListItem/ListItemActions.tsx create mode 100644 src/ui/units/connections/components/custom-forms/components/ListItem/types.ts create mode 100644 src/ui/units/connections/components/custom-forms/components/index.ts create mode 100644 src/ui/units/connections/components/custom-forms/hooks/useFileSourceTableWidgetData.ts create mode 100644 src/ui/units/connections/components/custom-forms/index.ts create mode 100644 src/ui/units/connections/components/custom-forms/utils/render.scss create mode 100644 src/ui/units/connections/components/custom-forms/utils/render.tsx create mode 100644 src/ui/units/connections/components/dialogs/Confirm/Confirm.tsx create mode 100644 src/ui/units/connections/components/dialogs/Confirm/index.ts create mode 100644 src/ui/units/connections/components/dialogs/Confirm/types.ts create mode 100644 src/ui/units/connections/components/dialogs/CreateConnection/CreateConnection.tsx create mode 100644 src/ui/units/connections/components/dialogs/CreateConnectionInWbOrCollection/CreateConnectionInWbOrCollection.tsx create mode 100644 src/ui/units/connections/components/dialogs/S3Source/DialogS3Source.scss create mode 100644 src/ui/units/connections/components/dialogs/S3Source/DialogS3Source.tsx create mode 100644 src/ui/units/connections/components/dialogs/S3Source/ListHeader.tsx create mode 100644 src/ui/units/connections/components/dialogs/S3Source/ListItem.tsx create mode 100644 src/ui/units/connections/components/dialogs/S3Source/index.ts create mode 100644 src/ui/units/connections/components/dialogs/S3Source/types.ts create mode 100644 src/ui/units/connections/components/dialogs/S3Source/utils.ts create mode 100644 src/ui/units/connections/components/dialogs/index.ts create mode 100644 src/ui/units/connections/components/hooks/index.ts create mode 100644 src/ui/units/connections/components/hooks/useCreationHandler/index.ts create mode 100644 src/ui/units/connections/components/hooks/useValidateForm.ts create mode 100644 src/ui/units/connections/components/index.ts create mode 100644 src/ui/units/connections/components/mixins.scss create mode 100644 src/ui/units/connections/constants/form.ts create mode 100644 src/ui/units/connections/constants/index.ts create mode 100644 src/ui/units/connections/constants/validation.ts create mode 100644 src/ui/units/connections/index.tsx create mode 100644 src/ui/units/connections/store/actions/api.ts create mode 100644 src/ui/units/connections/store/actions/base.ts create mode 100644 src/ui/units/connections/store/actions/file.ts create mode 100644 src/ui/units/connections/store/actions/gsheet/index.ts create mode 100644 src/ui/units/connections/store/actions/gsheet/misc-actions.ts create mode 100644 src/ui/units/connections/store/actions/gsheet/utils.ts create mode 100644 src/ui/units/connections/store/actions/index.ts create mode 100644 src/ui/units/connections/store/actions/s3-based.ts create mode 100644 src/ui/units/connections/store/actions/technotes.md create mode 100644 src/ui/units/connections/store/actions/yadoc/index.ts create mode 100644 src/ui/units/connections/store/actions/yadoc/misc-actions.ts create mode 100644 src/ui/units/connections/store/actions/yadoc/utils.ts create mode 100644 src/ui/units/connections/store/constants.ts create mode 100644 src/ui/units/connections/store/index.ts create mode 100644 src/ui/units/connections/store/reducers/index.ts create mode 100644 src/ui/units/connections/store/selectors/connection-data.ts create mode 100644 src/ui/units/connections/store/selectors/file.ts create mode 100644 src/ui/units/connections/store/selectors/form.ts create mode 100644 src/ui/units/connections/store/selectors/gsheet.ts create mode 100644 src/ui/units/connections/store/selectors/index.ts create mode 100644 src/ui/units/connections/store/selectors/initial-form.ts create mode 100644 src/ui/units/connections/store/selectors/inner-form.ts create mode 100644 src/ui/units/connections/store/selectors/yadocs.ts create mode 100644 src/ui/units/connections/store/typings/file.ts create mode 100644 src/ui/units/connections/store/typings/gsheet.ts create mode 100644 src/ui/units/connections/store/typings/index.ts create mode 100644 src/ui/units/connections/store/typings/s3-based.ts create mode 100644 src/ui/units/connections/store/typings/yadocs.ts create mode 100644 src/ui/units/connections/store/utils/__tests__/connectors.test.ts create mode 100644 src/ui/units/connections/store/utils/__tests__/forms.test.ts create mode 100644 src/ui/units/connections/store/utils/connectors.ts create mode 100644 src/ui/units/connections/store/utils/entry.ts create mode 100644 src/ui/units/connections/store/utils/file.ts create mode 100644 src/ui/units/connections/store/utils/forms.ts create mode 100644 src/ui/units/connections/store/utils/index.ts create mode 100644 src/ui/units/connections/store/utils/validation.ts create mode 100644 src/ui/units/connections/typings/common.ts create mode 100644 src/ui/units/connections/typings/form.ts create mode 100644 src/ui/units/connections/typings/index.ts create mode 100644 src/ui/units/connections/utils/__tests__/common.test.ts create mode 100644 src/ui/units/connections/utils/__tests__/connectors.test.ts create mode 100644 src/ui/units/connections/utils/__tests__/entry.test.ts create mode 100644 src/ui/units/connections/utils/common.ts create mode 100644 src/ui/units/connections/utils/connectors.ts create mode 100644 src/ui/units/connections/utils/entry.ts create mode 100644 src/ui/units/connections/utils/gsheet.ts create mode 100644 src/ui/units/connections/utils/i18n.ts create mode 100644 src/ui/units/connections/utils/index.ts create mode 100644 src/ui/units/connections/utils/validation.ts create mode 100644 src/ui/units/connections/utils/yadocs.ts create mode 100644 src/ui/units/dash/components/DashActionPanel/DashActionPanel.scss create mode 100644 src/ui/units/dash/components/DashActionPanel/DashActionPanel.tsx create mode 100644 src/ui/units/dash/components/DashActionPanel/DashActionPanelMobile.scss create mode 100644 src/ui/units/dash/components/DashActionPanel/DashActionPanelMobile.tsx create mode 100644 src/ui/units/dash/components/DashActionPanel/Description/Description.tsx create mode 100644 src/ui/units/dash/components/DashActionPanel/EditControls/EditControls.tsx create mode 100644 src/ui/units/dash/components/DashActionPanel/SaveDropDown/SaveDropDown.tsx create mode 100644 src/ui/units/dash/components/DashActionPanel/ViewControls/ViewControls.tsx create mode 100644 src/ui/units/dash/components/DashActionPanel/helpers.ts create mode 100644 src/ui/units/dash/components/EmptyState/EmptyState.scss create mode 100644 src/ui/units/dash/components/EmptyState/EmptyState.tsx create mode 100644 src/ui/units/dash/components/GlobalSelectorIcon/GlobalSelectorIcon.scss create mode 100644 src/ui/units/dash/components/GlobalSelectorIcon/GlobalSelectorIcon.tsx create mode 100644 src/ui/units/dash/components/Loader/Loader.scss create mode 100644 src/ui/units/dash/components/Loader/Loader.tsx create mode 100644 src/ui/units/dash/components/NavigationInput/InputLink/InputLink.js create mode 100644 src/ui/units/dash/components/NavigationInput/InputLink/InputLink.scss create mode 100644 src/ui/units/dash/components/NavigationInput/NavigationInput.js create mode 100644 src/ui/units/dash/components/NavigationInput/NavigationInput.scss create mode 100644 src/ui/units/dash/components/ParamsSettings/ParamsRow.tsx create mode 100644 src/ui/units/dash/components/ParamsSettings/ParamsSettings.scss create mode 100644 src/ui/units/dash/components/ParamsSettings/ParamsSettings.tsx create mode 100644 src/ui/units/dash/components/ParamsSettings/helpers.ts create mode 100644 src/ui/units/dash/components/ParamsSettings/test/validateParamTitle.test.ts create mode 100644 src/ui/units/dash/components/ParamsSettings/types.ts create mode 100644 src/ui/units/dash/components/ShareButton/ShareButton.scss create mode 100644 src/ui/units/dash/components/ShareButton/ShareButton.tsx create mode 100644 src/ui/units/dash/containers/App/App.scss create mode 100644 src/ui/units/dash/containers/App/App.tsx create mode 100644 src/ui/units/dash/containers/Body/Body.scss create mode 100644 src/ui/units/dash/containers/Body/Body.tsx create mode 100644 src/ui/units/dash/containers/Body/LazyBody.tsx create mode 100644 src/ui/units/dash/containers/Body/components/Content/Content.tsx create mode 100644 src/ui/units/dash/containers/Body/components/Content/hooks/useCopiedData.ts create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/DashkitWrapper.tsx create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/hooks/useConfig.ts create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/hooks/useDashKitContext.ts create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/hooks/useDashkitRef.ts create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/hooks/useDataProviderContext.ts create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/hooks/useHashStates.ts create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/hooks/useOverlay/OverlayControls.scss create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/hooks/useOverlay/getGearGlobalIcon.tsx create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/hooks/useOverlay/helpers.ts create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/hooks/useOverlay/useOverlay.tsx create mode 100644 src/ui/units/dash/containers/Body/components/DashkitWrapper/hooks/usePreparedCopyItemOptions.ts create mode 100644 src/ui/units/dash/containers/Body/context.tsx create mode 100644 src/ui/units/dash/containers/Dash/Dash.tsx create mode 100644 src/ui/units/dash/containers/DashError/DashError.tsx create mode 100644 src/ui/units/dash/containers/DashHotkes/DashHotkeys.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/DialogEditQuery/DialogEditQuery.scss create mode 100644 src/ui/units/dash/containers/Dialogs/DialogEditQuery/DialogEditQuery.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/DialogEditQuery/QueryEditor/QueryEditor.scss create mode 100644 src/ui/units/dash/containers/Dialogs/DialogEditQuery/QueryEditor/QueryEditor.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/DialogEditQuery/QueryError/QueryError.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/DialogEditQuery/helpers/validate-query.ts create mode 100644 src/ui/units/dash/containers/Dialogs/DialogParams/DialogParams.scss create mode 100644 src/ui/units/dash/containers/Dialogs/DialogParams/DialogParams.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Dialogs.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/Settings.scss create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/Settings.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/SettingsDrawer.scss create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/SettingsDrawer.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/components/AutoRefresh.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/components/Display.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/components/LoadPriority.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/components/MaxConnection.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/components/OtherSettings.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/components/Params.scss create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/components/Params.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/components/Row.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/components/Title.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/useDashSettings.ts create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/useGlobalParams.ts create mode 100644 src/ui/units/dash/containers/Dialogs/Settings/utils.ts create mode 100644 src/ui/units/dash/containers/Dialogs/Tabs/EditedTabItem.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Tabs/PopupWidgetsOrder/PopupWidgetsOrder.scss create mode 100644 src/ui/units/dash/containers/Dialogs/Tabs/PopupWidgetsOrder/PopupWidgetsOrder.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Tabs/PopupWidgetsOrder/helpers.ts create mode 100644 src/ui/units/dash/containers/Dialogs/Tabs/PopupWidgetsOrder/test/helpers.test.ts create mode 100644 src/ui/units/dash/containers/Dialogs/Tabs/TabItem.scss create mode 100644 src/ui/units/dash/containers/Dialogs/Tabs/TabItem.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/Tabs/Tabs.scss create mode 100644 src/ui/units/dash/containers/Dialogs/Tabs/Tabs.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/__tests__/utils.test.ts create mode 100644 src/ui/units/dash/containers/Dialogs/components/ColorInputsGroup/ColorInputsGroup.scss create mode 100644 src/ui/units/dash/containers/Dialogs/components/ColorInputsGroup/ColorInputsGroup.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/components/ColorPalette/ColorItem/ColorItem.scss create mode 100644 src/ui/units/dash/containers/Dialogs/components/ColorPalette/ColorItem/ColorItem.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/components/ColorPalette/ColorPalette.scss create mode 100644 src/ui/units/dash/containers/Dialogs/components/ColorPalette/ColorPalette.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/components/ColorPicker/ColorPicker.scss create mode 100644 src/ui/units/dash/containers/Dialogs/components/ColorPicker/ColorPicker.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/components/ColorPickerInputWithPreset/ColorPickerInputWithPreset.scss create mode 100644 src/ui/units/dash/containers/Dialogs/components/ColorPickerInputWithPreset/ColorPickerInputWithPreset.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/components/InternalMarginsToggler/InternalMarginsToggler.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/components/PaletteBackground/PaletteBackground.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/components/PaletteText/PaletteText.tsx create mode 100644 src/ui/units/dash/containers/Dialogs/constants.ts create mode 100644 src/ui/units/dash/containers/Dialogs/utils.ts create mode 100644 src/ui/units/dash/containers/FixedHeader/FixedHeader.scss create mode 100644 src/ui/units/dash/containers/FixedHeader/FixedHeader.tsx create mode 100644 src/ui/units/dash/containers/FixedHeader/FixedHeaderMobile.scss create mode 100644 src/ui/units/dash/containers/FixedHeader/FixedHeaderMobile.tsx create mode 100644 src/ui/units/dash/containers/Header/Header.tsx create mode 100644 src/ui/units/dash/containers/MobileFloatMenu/MobileFloatMenu.tsx create mode 100644 src/ui/units/dash/containers/TableOfContent/TableOfContent.scss create mode 100644 src/ui/units/dash/containers/TableOfContent/TableOfContent.tsx create mode 100644 src/ui/units/dash/containers/TableOfContent/helpers.ts create mode 100644 src/ui/units/dash/containers/Tabs/Tabs.scss create mode 100644 src/ui/units/dash/containers/Tabs/Tabs.tsx create mode 100644 src/ui/units/dash/contexts/WidgetMetaContext.tsx create mode 100644 src/ui/units/dash/hooks/useIframeFeatures.tsx create mode 100644 src/ui/units/dash/modules/constants.ts create mode 100644 src/ui/units/dash/modules/helpers.ts create mode 100644 src/ui/units/dash/modules/markdownProvider.ts create mode 100644 src/ui/units/dash/modules/postMessage.ts create mode 100644 src/ui/units/dash/modules/pushStats.ts create mode 100644 src/ui/units/dash/store/actions/__tests__/helpers.test.ts create mode 100644 src/ui/units/dash/store/actions/base/__tests__/helpers.test.ts create mode 100644 src/ui/units/dash/store/actions/base/actions.ts create mode 100644 src/ui/units/dash/store/actions/base/helpers.ts create mode 100644 src/ui/units/dash/store/actions/copy-and-paste/helpers.ts create mode 100644 src/ui/units/dash/store/actions/dash.ts create mode 100644 src/ui/units/dash/store/actions/dashTyped.ts create mode 100644 src/ui/units/dash/store/actions/dialogs/actions.ts create mode 100644 src/ui/units/dash/store/actions/dialogs/dialog-edit-query.ts create mode 100644 src/ui/units/dash/store/actions/helpers.ts create mode 100644 src/ui/units/dash/store/actions/index.ts create mode 100644 src/ui/units/dash/store/actions/relations/actions.ts create mode 100644 src/ui/units/dash/store/constants/dashActionTypes.ts create mode 100644 src/ui/units/dash/store/constants/index.ts create mode 100644 src/ui/units/dash/store/reducers/dash.js create mode 100644 src/ui/units/dash/store/reducers/dashHelpers.ts create mode 100644 src/ui/units/dash/store/reducers/dashTypedReducer.ts create mode 100644 src/ui/units/dash/store/selectors/dash.js create mode 100644 src/ui/units/dash/store/selectors/dashTypedSelectors.ts create mode 100644 src/ui/units/dash/store/typings/dash.ts create mode 100644 src/ui/units/dash/store/utils.ts create mode 100644 src/ui/units/dash/styles/dash.scss create mode 100644 src/ui/units/dash/typings/context.ts create mode 100644 src/ui/units/dash/typings/dash.ts create mode 100644 src/ui/units/dash/typings/entry.ts create mode 100644 src/ui/units/dash/utils/__tests__/selectors.test.ts create mode 100644 src/ui/units/dash/utils/colors.ts create mode 100644 src/ui/units/dash/utils/context.ts create mode 100644 src/ui/units/dash/utils/customEvents.ts create mode 100644 src/ui/units/dash/utils/dashkitProps.ts create mode 100644 src/ui/units/dash/utils/getTabId.ts create mode 100644 src/ui/units/dash/utils/scrollUtils.ts create mode 100644 src/ui/units/dash/utils/selectors.ts create mode 100644 src/ui/units/dash/utils/url.ts create mode 100644 src/ui/units/datasets/components/Avatar/Avatar.scss create mode 100644 src/ui/units/datasets/components/Avatar/Avatar.tsx create mode 100644 src/ui/units/datasets/components/ColorsDialog/ColorsDialog.scss create mode 100644 src/ui/units/datasets/components/ColorsDialog/ColorsDialog.tsx create mode 100644 src/ui/units/datasets/components/ContainerLoader/ContainerLoader.scss create mode 100644 src/ui/units/datasets/components/ContainerLoader/ContainerLoader.tsx create mode 100644 src/ui/units/datasets/components/DatasetCacheGrid/DatasetCacheGrid.scss create mode 100644 src/ui/units/datasets/components/DatasetCacheGrid/DatasetCacheGrid.tsx create mode 100644 src/ui/units/datasets/components/DatasetCacheGrid/components/CacheModeSelector.tsx create mode 100644 src/ui/units/datasets/components/DatasetCacheGrid/components/CacheParameterRow.tsx create mode 100644 src/ui/units/datasets/components/DatasetCacheGrid/components/FormulaContent.tsx create mode 100644 src/ui/units/datasets/components/DatasetCacheGrid/components/LastResultRow.tsx create mode 100644 src/ui/units/datasets/components/DatasetCacheGrid/components/Row.tsx create mode 100644 src/ui/units/datasets/components/DatasetCacheGrid/components/SqlContent.tsx create mode 100644 src/ui/units/datasets/components/DatasetPanel/DatasetPanel.scss create mode 100644 src/ui/units/datasets/components/DatasetPanel/DatasetPanel.tsx create mode 100644 src/ui/units/datasets/components/DatasetPanel/helpers.ts create mode 100644 src/ui/units/datasets/components/DatasetRouter/DatasetRouter.scss create mode 100644 src/ui/units/datasets/components/DatasetRouter/DatasetRouter.tsx create mode 100644 src/ui/units/datasets/components/DatasetRouter/UnloadConfirmation.tsx create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/DatasetTabFieldList.scss create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/DatasetTabFieldList.tsx create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/components/FieldRow/FieldRow.scss create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/components/FieldRow/FieldRow.tsx create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/components/TitleColumn/TitleColumn.scss create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/components/TitleColumn/TitleColumn.tsx create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/components/TypeColumn/TypeColumn.scss create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/components/TypeColumn/TypeColumn.tsx create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/components/ValidationColumn/ValidationColumn.scss create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/components/ValidationColumn/ValidationColumn.tsx create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/components/ValueColumn/ValueColumn.scss create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/components/ValueColumn/ValueColumn.tsx create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/constants.ts create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/helpers.tsx create mode 100644 src/ui/units/datasets/components/DatasetTabFieldList/types.ts create mode 100644 src/ui/units/datasets/components/DatasetTabSection/DatasetTabSection.scss create mode 100644 src/ui/units/datasets/components/DatasetTabSection/DatasetTabSection.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/DatasetTable.scss create mode 100644 src/ui/units/datasets/components/DatasetTable/DatasetTable.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/Aggregation.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/Cast.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/Description.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/FieldSettings.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/Hidden.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/Id.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/IndexColumn.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/More.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/Rls.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/Source.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/Title.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/columns/index.ts create mode 100644 src/ui/units/datasets/components/DatasetTable/components/AggregationSelect/AggregationSelect.scss create mode 100644 src/ui/units/datasets/components/DatasetTable/components/AggregationSelect/AggregationSelect.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/components/BatchActionPanel/BatchActionPanel.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/components/BatchActionPanel/components/DialogChangeDatasetFields/DialogChangeDatasetFields.scss create mode 100644 src/ui/units/datasets/components/DatasetTable/components/BatchActionPanel/components/DialogChangeDatasetFields/DialogChangeDatasetFields.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/components/DisplaySettings.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/components/FieldActionsPopup.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/components/ObservedDataTable.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/components/TableTextInput.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/components/TypeSelect/TypeSelect.scss create mode 100644 src/ui/units/datasets/components/DatasetTable/components/TypeSelect/TypeSelect.tsx create mode 100644 src/ui/units/datasets/components/DatasetTable/components/index.ts create mode 100644 src/ui/units/datasets/components/DatasetTable/constants.ts create mode 100644 src/ui/units/datasets/components/DatasetTable/types.ts create mode 100644 src/ui/units/datasets/components/DatasetTable/utils.ts create mode 100644 src/ui/units/datasets/components/DatasetTableFieldsSection/DatasetTableFieldsSection.scss create mode 100644 src/ui/units/datasets/components/DatasetTableFieldsSection/DatasetTableFieldsSection.tsx create mode 100644 src/ui/units/datasets/components/DescriptionButton/DescriptionButton.tsx create mode 100644 src/ui/units/datasets/components/DialogCreateDataset/DialogCreateDataset.tsx create mode 100644 src/ui/units/datasets/components/DragAndDrop/DragAndDrop.tsx create mode 100644 src/ui/units/datasets/components/ErrorView/ErrorView.scss create mode 100644 src/ui/units/datasets/components/ErrorView/ErrorView.tsx create mode 100644 src/ui/units/datasets/components/FieldSettingsDialog/FieldSettingsDialog.scss create mode 100644 src/ui/units/datasets/components/FieldSettingsDialog/FieldSettingsDialog.tsx create mode 100644 src/ui/units/datasets/components/FilterSection/FilterSection.tsx create mode 100644 src/ui/units/datasets/components/FilterSection/helpers.ts create mode 100644 src/ui/units/datasets/components/FilterSection/types.ts create mode 100644 src/ui/units/datasets/components/FilterSection/useFilterSection.ts create mode 100644 src/ui/units/datasets/components/ParametersSection/ParametersSection.tsx create mode 100644 src/ui/units/datasets/components/ParametersSection/components/CopyToClipboard/CopyToClipboardMenuItem.tsx create mode 100644 src/ui/units/datasets/components/ParametersSection/components/HeaderWithTooltip/HeaderWithTooltip.scss create mode 100644 src/ui/units/datasets/components/ParametersSection/components/HeaderWithTooltip/HeaderWithTooltip.tsx create mode 100644 src/ui/units/datasets/components/ParametersSection/helpers.ts create mode 100644 src/ui/units/datasets/components/ParametersSection/useParametersSection.tsx create mode 100644 src/ui/units/datasets/components/PreviewHeader/PreviewHeader.scss create mode 100644 src/ui/units/datasets/components/PreviewHeader/PreviewHeader.tsx create mode 100644 src/ui/units/datasets/components/PreviewHeader/__tests__/utils.test.ts create mode 100644 src/ui/units/datasets/components/PreviewHeader/constants.ts create mode 100644 src/ui/units/datasets/components/PreviewHeader/utils.ts create mode 100644 src/ui/units/datasets/components/PreviewTable/PreviewTable.js create mode 100644 src/ui/units/datasets/components/PreviewTable/PreviewTable.scss create mode 100644 src/ui/units/datasets/components/RLSDialog/RLSDialog.scss create mode 100644 src/ui/units/datasets/components/RLSDialog/RLSDialog.tsx create mode 100644 src/ui/units/datasets/components/RelationDialog/RelationDialog.scss create mode 100644 src/ui/units/datasets/components/RelationDialog/RelationDialog.tsx create mode 100644 src/ui/units/datasets/components/RelationsMap/RelationsMap.js create mode 100644 src/ui/units/datasets/components/RelationsMap/RelationsMap.scss create mode 100644 src/ui/units/datasets/components/SelectSourcePrototypes/SelectSourcePrototypes.scss create mode 100644 src/ui/units/datasets/components/SelectSourcePrototypes/SelectSourcePrototypes.tsx create mode 100644 src/ui/units/datasets/components/SelectSourcePrototypes/SourcesTable.tsx create mode 100644 src/ui/units/datasets/components/SelectSourcePrototypes/__tests__/utils.test.ts create mode 100644 src/ui/units/datasets/components/SelectSourcePrototypes/constants.ts create mode 100644 src/ui/units/datasets/components/SelectSourcePrototypes/utils.ts create mode 100644 src/ui/units/datasets/components/Source/Source.js create mode 100644 src/ui/units/datasets/components/Source/Source.scss create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/SourceEditorDialog.scss create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/SourceEditorDialog.tsx create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/components/EditorFormItem.tsx create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/components/Form.tsx create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/components/InputFormItem.tsx create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/components/ParamSelector.tsx create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/components/SelectFormItem.tsx create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/components/SourceError.tsx create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/components/SourceHelpTooltip.tsx create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/components/SourceSwitcher.tsx create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/components/index.ts create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/types.ts create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/utils/helpers.ts create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/utils/index.ts create mode 100644 src/ui/units/datasets/components/SourceEditorDialog/utils/validation.ts create mode 100644 src/ui/units/datasets/components/UserName/UserName.scss create mode 100644 src/ui/units/datasets/components/UserName/UserName.tsx create mode 100644 src/ui/units/datasets/components/Veil/Veil.scss create mode 100644 src/ui/units/datasets/components/Veil/Veil.tsx create mode 100644 src/ui/units/datasets/components/dialogs/DatasetFieldInspector/DatasetFieldInspector.scss create mode 100644 src/ui/units/datasets/components/dialogs/DatasetFieldInspector/DatasetFieldInspector.tsx create mode 100644 src/ui/units/datasets/components/dialogs/index.ts create mode 100644 src/ui/units/datasets/components/hoc/AvatarDnD.js create mode 100644 src/ui/units/datasets/constants/datasets.ts create mode 100644 src/ui/units/datasets/constants/index.ts create mode 100644 src/ui/units/datasets/containers/Dataset/ActionPanelRightItems.tsx create mode 100644 src/ui/units/datasets/containers/Dataset/Dataset.scss create mode 100644 src/ui/units/datasets/containers/Dataset/Dataset.tsx create mode 100644 src/ui/units/datasets/containers/Dataset/constants.ts create mode 100644 src/ui/units/datasets/containers/Dataset/getAdditionalContextMenuItems.tsx create mode 100644 src/ui/units/datasets/containers/Dataset/useHistoryActions.tsx create mode 100644 src/ui/units/datasets/containers/DatasetCache/DatasetCache.scss create mode 100644 src/ui/units/datasets/containers/DatasetCache/DatasetCache.tsx create mode 100644 src/ui/units/datasets/containers/DatasetCache/components/CacheWarning.tsx create mode 100644 src/ui/units/datasets/containers/DatasetCache/components/Description.tsx create mode 100644 src/ui/units/datasets/containers/DatasetCache/constants.ts create mode 100644 src/ui/units/datasets/containers/DatasetEditor/DatasetEditor.js create mode 100644 src/ui/units/datasets/containers/DatasetEditor/DatasetEditor.scss create mode 100644 src/ui/units/datasets/containers/DatasetEditor/utils.ts create mode 100644 src/ui/units/datasets/containers/DatasetError/DatasetError.ts create mode 100644 src/ui/units/datasets/containers/DatasetFilters/DatasetFilters.tsx create mode 100644 src/ui/units/datasets/containers/DatasetPage/DatasetPage.scss create mode 100644 src/ui/units/datasets/containers/DatasetPage/DatasetPage.tsx create mode 100644 src/ui/units/datasets/containers/DatasetPage/createDatasetPageContext.ts create mode 100644 src/ui/units/datasets/containers/DatasetParameters/DatasetParameters.tsx create mode 100644 src/ui/units/datasets/containers/DatasetPreview/DatasetPreview.js create mode 100644 src/ui/units/datasets/containers/DatasetPreview/DatasetPreview.scss create mode 100644 src/ui/units/datasets/containers/DatasetSources/DatasetSources.scss create mode 100644 src/ui/units/datasets/containers/DatasetSources/DatasetSources.tsx create mode 100644 src/ui/units/datasets/containers/DatasetTabViewer/DatasetTabViewer.tsx create mode 100644 src/ui/units/datasets/helpers/dataset-error-helpers.ts create mode 100644 src/ui/units/datasets/helpers/datasets.ts create mode 100644 src/ui/units/datasets/helpers/mockTexts.ts create mode 100644 src/ui/units/datasets/helpers/utils.test.ts create mode 100644 src/ui/units/datasets/helpers/utils.ts create mode 100644 src/ui/units/datasets/helpers/validation.ts create mode 100644 src/ui/units/datasets/icons/join-full-outer.svg create mode 100644 src/ui/units/datasets/icons/join-inner.svg create mode 100644 src/ui/units/datasets/icons/join-left.svg create mode 100644 src/ui/units/datasets/icons/join-none.svg create mode 100644 src/ui/units/datasets/icons/join-right.svg create mode 100644 src/ui/units/datasets/store/actions/creators/datasetTyped.ts create mode 100644 src/ui/units/datasets/store/actions/creators/dialogs.ts create mode 100644 src/ui/units/datasets/store/actions/creators/index.ts create mode 100644 src/ui/units/datasets/store/actions/creators/utils.ts create mode 100644 src/ui/units/datasets/store/actions/types/dataset.ts create mode 100644 src/ui/units/datasets/store/constants.ts create mode 100644 src/ui/units/datasets/store/edit-history-middleware.ts create mode 100644 src/ui/units/datasets/store/reducers/dataset.ts create mode 100644 src/ui/units/datasets/store/reducers/index.ts create mode 100644 src/ui/units/datasets/store/reducers/utils/index.ts create mode 100644 src/ui/units/datasets/store/reducers/utils/mocks.ts create mode 100644 src/ui/units/datasets/store/reducers/utils/utils.test.ts create mode 100644 src/ui/units/datasets/store/selectors/dataset.ts create mode 100644 src/ui/units/datasets/store/selectors/datasetTyped.ts create mode 100644 src/ui/units/datasets/store/selectors/index.ts create mode 100644 src/ui/units/datasets/store/types/dataset.ts create mode 100644 src/ui/units/datasets/store/types/index.ts create mode 100644 src/ui/units/datasets/styles/mixins.scss create mode 100644 src/ui/units/datasets/styles/variables.scss create mode 100644 src/ui/units/datasets/typings/dataset.ts create mode 100644 src/ui/units/datasets/typings/redux.ts create mode 100644 src/ui/units/preview/components/App/App.scss create mode 100644 src/ui/units/preview/components/App/App.tsx create mode 100644 src/ui/units/preview/components/IndexPage/IndexPage.scss create mode 100644 src/ui/units/preview/components/IndexPage/IndexPage.tsx create mode 100644 src/ui/units/preview/components/Preview/Preview.scss create mode 100644 src/ui/units/preview/components/Preview/Preview.tsx create mode 100644 src/ui/units/preview/hooks/useChartAutoupdate.ts create mode 100644 src/ui/units/preview/modules/constants/constants.ts create mode 100644 src/ui/units/preview/modules/helpers.ts create mode 100644 src/ui/units/ql/components/DND/DNDContainer.tsx create mode 100644 src/ui/units/ql/components/DND/DNDItem.tsx create mode 100644 src/ui/units/ql/components/DND/DNDLayer.tsx create mode 100644 src/ui/units/ql/components/Dialogs/Settings/Settings.scss create mode 100644 src/ui/units/ql/components/Dialogs/Settings/Settings.tsx create mode 100644 src/ui/units/ql/components/Grid/Grid.scss create mode 100644 src/ui/units/ql/components/Grid/Grid.tsx create mode 100644 src/ui/units/ql/components/Grid/GridPane/GridPane.scss create mode 100644 src/ui/units/ql/components/Grid/GridPane/GridPane.tsx create mode 100644 src/ui/units/ql/components/Grid/GridPane/GridPaneView/GridPaneView.scss create mode 100644 src/ui/units/ql/components/Grid/GridPane/GridPaneView/GridPaneView.tsx create mode 100644 src/ui/units/ql/components/Grid/GridPane/GridPaneView/GridPaneViewContent.tsx create mode 100644 src/ui/units/ql/configs/chart-types.ts create mode 100644 src/ui/units/ql/constants/index.ts create mode 100644 src/ui/units/ql/containers/App/App.scss create mode 100644 src/ui/units/ql/containers/App/App.tsx create mode 100644 src/ui/units/ql/containers/PaneMain/PaneMain.scss create mode 100644 src/ui/units/ql/containers/PaneMain/PaneMain.tsx create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/ScreenEditor.scss create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/ScreenEditor.tsx create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabParams/DefaultValue.tsx create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabParams/OverridenValue.tsx create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabParams/TabParams.scss create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabParams/TabParams.tsx create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabParams/utils.ts create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabQuery/ScreenPromQL/ScreenPromQL.scss create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabQuery/ScreenPromQL/ScreenPromQL.tsx create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabQuery/ScreenSQL/ScreenSQL.scss create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabQuery/ScreenSQL/ScreenSQL.tsx create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabQuery/TabQuery.scss create mode 100644 src/ui/units/ql/containers/PaneMain/ScreenEditor/TabQuery/TabQuery.tsx create mode 100644 src/ui/units/ql/containers/PanePreview/PanePreview.scss create mode 100644 src/ui/units/ql/containers/PanePreview/PanePreview.tsx create mode 100644 src/ui/units/ql/containers/PaneTablePreview/PaneTablePreview.scss create mode 100644 src/ui/units/ql/containers/PaneTablePreview/PaneTablePreview.tsx create mode 100644 src/ui/units/ql/containers/PaneVisualization/PaneVisualization.scss create mode 100644 src/ui/units/ql/containers/PaneVisualization/PaneVisualization.tsx create mode 100644 src/ui/units/ql/containers/QL/QL.scss create mode 100644 src/ui/units/ql/containers/QL/QL.tsx create mode 100644 src/ui/units/ql/containers/QL/QLActionPanel/QLActionPanel.scss create mode 100644 src/ui/units/ql/containers/QL/QLActionPanel/QLActionPanel.tsx create mode 100644 src/ui/units/ql/containers/QL/QLActionPanel/useQLActionPanel.ts create mode 100644 src/ui/units/ql/containers/QL/ViewSetup/ViewSetup.scss create mode 100644 src/ui/units/ql/containers/QL/ViewSetup/ViewSetup.tsx create mode 100644 src/ui/units/ql/modules/helpers.ts create mode 100644 src/ui/units/ql/store/actions/dialog.ts create mode 100644 src/ui/units/ql/store/actions/ql.ts create mode 100644 src/ui/units/ql/store/reducers/index.ts create mode 100644 src/ui/units/ql/store/reducers/ql.ts create mode 100644 src/ui/units/ql/store/typings/index.ts create mode 100644 src/ui/units/ql/store/typings/ql.ts create mode 100644 src/ui/units/ql/store/utils/grid.ts create mode 100644 src/ui/units/ql/store/utils/monitoring.ts create mode 100644 src/ui/units/ql/styles/ql.scss create mode 100644 src/ui/units/ql/utils/__tests__/getQlVisualization.test.ts create mode 100644 src/ui/units/ql/utils/__tests__/mocks/getQlVisualization.ts create mode 100644 src/ui/units/ql/utils/chart-settings.ts create mode 100644 src/ui/units/ql/utils/connectionts.ts create mode 100644 src/ui/units/ql/utils/query.ts create mode 100644 src/ui/units/ql/utils/visualization/getAvailableQlVisualizations.ts create mode 100644 src/ui/units/ql/utils/visualization/getDefaultQlVisualization.ts create mode 100644 src/ui/units/ql/utils/visualization/getQlVisualization.ts create mode 100644 src/ui/units/ql/utils/visualization/index.ts create mode 100644 src/ui/units/service-settings/components/AppearanceSettings/AppearanceSettings.scss create mode 100644 src/ui/units/service-settings/components/AppearanceSettings/AppearanceSettings.tsx create mode 100644 src/ui/units/service-settings/components/CreateUserForm/CreateUserForm.tsx create mode 100644 src/ui/units/service-settings/components/SectionGroup/SectionGroup.scss create mode 100644 src/ui/units/service-settings/components/SectionGroup/SectionGroup.tsx create mode 100644 src/ui/units/service-settings/components/UsersList/LabelsList/LabelsList.scss create mode 100644 src/ui/units/service-settings/components/UsersList/LabelsList/LabelsList.tsx create mode 100644 src/ui/units/service-settings/components/UsersList/UsersFilters/UsersFilters.scss create mode 100644 src/ui/units/service-settings/components/UsersList/UsersFilters/UsersFilters.tsx create mode 100644 src/ui/units/service-settings/components/UsersList/UsersList.scss create mode 100644 src/ui/units/service-settings/components/UsersList/UsersList.tsx create mode 100644 src/ui/units/service-settings/components/UsersList/constants.ts create mode 100644 src/ui/units/service-settings/components/UsersList/utils.tsx create mode 100644 src/ui/units/service-settings/containers/AccessErrorPage/AccessErrorPage.tsx create mode 100644 src/ui/units/service-settings/containers/App/App.scss create mode 100644 src/ui/units/service-settings/containers/App/App.tsx create mode 100644 src/ui/units/service-settings/containers/CreateProfilePage/CreateProfilePage.scss create mode 100644 src/ui/units/service-settings/containers/CreateProfilePage/CreateProfilePage.tsx create mode 100644 src/ui/units/service-settings/containers/MainPage/MainPage.scss create mode 100644 src/ui/units/service-settings/containers/MainPage/MainPage.tsx create mode 100644 src/ui/units/service-settings/containers/UserProfilePage/UserProfilePage.scss create mode 100644 src/ui/units/service-settings/containers/UserProfilePage/UserProfilePage.tsx create mode 100644 src/ui/units/service-settings/store/actions/serviceSettings.ts create mode 100644 src/ui/units/service-settings/store/constants/serviceSettings.ts create mode 100644 src/ui/units/service-settings/store/reducers/serviceSettings.ts create mode 100644 src/ui/units/service-settings/store/selectors/serviceSettings.ts create mode 100644 src/ui/units/service-settings/store/typings/serviceSettings.ts create mode 100644 src/ui/units/service-settings/styles/variables.scss create mode 100644 src/ui/units/wizard/actions/__tests__/dndItems.test.ts create mode 100644 src/ui/units/wizard/actions/dataset.ts create mode 100644 src/ui/units/wizard/actions/dialog.ts create mode 100644 src/ui/units/wizard/actions/dialogColor.ts create mode 100644 src/ui/units/wizard/actions/dndItems.ts create mode 100644 src/ui/units/wizard/actions/hierarchyEditor.ts create mode 100644 src/ui/units/wizard/actions/index.ts create mode 100644 src/ui/units/wizard/actions/placeholder.ts create mode 100644 src/ui/units/wizard/actions/preview.ts create mode 100644 src/ui/units/wizard/actions/settings.ts create mode 100644 src/ui/units/wizard/actions/tooltip.ts create mode 100644 src/ui/units/wizard/actions/utils/__tests__/getDatasetUpdates.test.ts create mode 100644 src/ui/units/wizard/actions/utils/getDatasetUpdates.ts create mode 100644 src/ui/units/wizard/actions/utils/index.ts create mode 100644 src/ui/units/wizard/actions/utils/mutateAndValidateItem.ts create mode 100644 src/ui/units/wizard/actions/utils/placeholders.ts create mode 100644 src/ui/units/wizard/actions/visualization.ts create mode 100644 src/ui/units/wizard/actions/widget.ts create mode 100644 src/ui/units/wizard/components/AddField/AddField.scss create mode 100644 src/ui/units/wizard/components/AddField/AddField.tsx create mode 100644 src/ui/units/wizard/components/DND/DNDContainer.tsx create mode 100644 src/ui/units/wizard/components/DND/DNDItem.tsx create mode 100644 src/ui/units/wizard/components/DND/DragAndDrop.tsx create mode 100644 src/ui/units/wizard/components/DatasetSelect/DatasetSelect.scss create mode 100644 src/ui/units/wizard/components/DatasetSelect/DatasetSelect.tsx create mode 100644 src/ui/units/wizard/components/DatasetSelect/DatasetSelectItem.scss create mode 100644 src/ui/units/wizard/components/DatasetSelect/DatasetSelectItem.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColor/ColorSettingsContainer/ColorSettingsContainer.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColor/ColorSettingsContainer/ColorSettingsContainer.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorPalette/DialogColorPalette.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorPalette/DialogColorPalette.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColor/PaletteWithCustomColor/PaletteWithCustomColor.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColor/PaletteWithCustomColor/PaletteWithCustomColor.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/DialogColumnSettings.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/DialogColumnSettings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/components/ColumnWidthSetting/ColumnWidthSetting.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/components/ColumnWidthSetting/ColumnWidthSetting.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/components/ColumnWidthSettingsSection/ColumnWidthSettingsSection.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/components/FieldInfo/FieldInfo.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/components/FieldInfo/FieldInfo.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/components/Subheader/Subheader.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/components/Subheader/Subheader.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/hooks/useDialogColumnSettingsState.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogColumnSettings/utils/index.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/DialogField.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/DialogField.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BackgroundSettings/BackgroundSettings.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BackgroundSettings/BackgroundSettings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BackgroundSettings/hooks/helpers.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BackgroundSettings/hooks/useBackgrounColorFieldSelect.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BackgroundSettings/hooks/useBackgroundColorModeRadioButtons.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BackgroundSettings/hooks/useBackgroundNullModeSettings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BackgroundSettings/hooks/useBackgroundSettings.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BackgroundSettings/hooks/useBackgroundSettingsButtonColorDialog.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BackgroundSettings/hooks/useBackgroundSettingsSwitch.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BarsSettings/BarsSettings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BarsSettings/components/ColorControls/ColorsControl.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BarsSettings/components/GradientColorControl/GradientColorControl.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BarsSettings/components/GradientColorControl/GradientColorControl.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BarsSettings/components/MinMaxInputs/MinMaxInputs.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BarsSettings/components/MinMaxInputs/MinMaxInputs.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BarsSettings/components/PaletteColorControl/PaletteColorControl.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BarsSettings/components/PaletteColorControl/PaletteColorControl.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BarsSettings/useBarsSettings/helpers.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BarsSettings/useBarsSettings/useBarsSettings.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/ButtonColorDialog/ButtonColorDialog.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/ButtonColorDialog/ButtonColorDialog.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/DialogFieldInput/DialogFieldInput.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/DialogFieldMainSection/DialogFieldMainSection.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/DialogFieldRow/DialogFieldRow.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/DialogFieldSelect/DialogFieldSelect.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/DialogFieldSelect/DialogFieldSelect.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/DialogFieldSettingTitle/DialogFieldSettingTitle.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/DialogFieldSettingTitle/DialogFieldSettingTitle.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/SubTotalsSettings/SubTotalsSettings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/utils/backgroundSettings.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/utils/barsSettings.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/utils/misc.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/utils/subTotals.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogFieldInspector/DialogFieldInspector.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogGeolayer/DialogGeolayer.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogGeolayer/DialogGeolayer.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLabelSettings/DialogLabelSettings.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLabelSettings/DialogLabelSettings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/DialogLineShapes.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/DialogLineShapes.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/components/CommonSettings/CommonSettings.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/components/CommonSettings/CommonSettings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/components/LineSettings/LineSettings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/components/LineWidth/LineWidth.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/components/LineWidth/LineWidth.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/components/ShapesPalette/ShapesPalette.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/components/ShapesPalette/ShapesPalette.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/constants.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/i18n.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLineShapes/types.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLink.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogLink.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogMetricColors/DialogMetricColors.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogMetricColors/DialogMetricColors.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogMultidataset.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogMultidataset.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogNoRights.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogPlaceholder/DialogPlaceholder.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogPlaceholder/DialogPlaceholder.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogPlaceholder/components/DialogPlaceholderRow/DialogPlaceholderRow.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogPlaceholder/constants/radio-buttons.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogPlaceholder/utils.ts create mode 100644 src/ui/units/wizard/components/Dialogs/DialogPointsSize.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogPointsSize.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapes.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapes.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapesPalette/DialogShapesPalette.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapesPalette/DialogShapesPalette.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/DialogTooltipSettings/DialogTooltipSettings.scss create mode 100644 src/ui/units/wizard/components/Dialogs/DialogTooltipSettings/DialogTooltipSettings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Dialogs.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/CenterSetting/CenterSetting.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/CenterSetting/CenterSetting.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/IndicatorTitleSetting/IndicatorTitleSetting.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/IndicatorTitleSetting/IndicatorTitleSetting.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/LimitInput/LimitInput.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/LimitInput/LimitInput.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingFeed/SettingFeed.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingFeed/SettingFeed.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingNavigator/NavigatorSubSettings/NavigatorSubSettings.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingNavigator/NavigatorSubSettings/NavigatorSubSettings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingNavigator/SettingNavigator.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingNavigator/SettingNavigator.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingPagination/SettingPagination.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingSwitcher/SettingSwitcher.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingSwitcher/SettingSwitcher.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingTitleMode/SettingTitleMode.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/SettingTitleMode/SettingTitleMode.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/Settings.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/Settings.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/ZoomSetting/ZoomSetting.scss create mode 100644 src/ui/units/wizard/components/Dialogs/Settings/ZoomSetting/ZoomSetting.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/components/DialogRadioButtons/DialogRadioButtons.scss create mode 100644 src/ui/units/wizard/components/Dialogs/components/DialogRadioButtons/DialogRadioButtons.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/components/DialogRow/DialogRow.scss create mode 100644 src/ui/units/wizard/components/Dialogs/components/DialogRow/DialogRow.tsx create mode 100644 src/ui/units/wizard/components/Dialogs/components/TitleWithTooltip/TitleWithTooltip.scss create mode 100644 src/ui/units/wizard/components/Dialogs/components/TitleWithTooltip/TitleWithTooltip.tsx create mode 100644 src/ui/units/wizard/components/GeolayersSelect/GeolayersSelect.scss create mode 100644 src/ui/units/wizard/components/GeolayersSelect/GeolayersSelect.tsx create mode 100644 src/ui/units/wizard/components/GradientPalettePreview/GradientPalettePreview.scss create mode 100644 src/ui/units/wizard/components/GradientPalettePreview/GradientPalettePreview.tsx create mode 100644 src/ui/units/wizard/components/HierarchyEditor/HierarchyEditor.scss create mode 100644 src/ui/units/wizard/components/HierarchyEditor/HierarchyEditor.tsx create mode 100644 src/ui/units/wizard/components/MinifiedPalette/MinifiedPalette.scss create mode 100644 src/ui/units/wizard/components/MinifiedPalette/MinifiedPalette.tsx create mode 100644 src/ui/units/wizard/components/Palette/Palette.scss create mode 100644 src/ui/units/wizard/components/Palette/Palette.tsx create mode 100644 src/ui/units/wizard/components/PlaceholderActionIcon/PlaceholderActionIcon.scss create mode 100644 src/ui/units/wizard/components/PlaceholderActionIcon/PlaceholderActionIcon.tsx create mode 100644 src/ui/units/wizard/components/SearchInput/SearchInput.scss create mode 100644 src/ui/units/wizard/components/SearchInput/SearchInput.tsx create mode 100644 src/ui/units/wizard/components/ValuesList/ValuesList.scss create mode 100644 src/ui/units/wizard/components/ValuesList/ValuesList.tsx create mode 100644 src/ui/units/wizard/components/VisualizationItemTitleWithAggregation/VisualizationItemTitleWithAggregation.scss create mode 100644 src/ui/units/wizard/components/VisualizationItemTitleWithAggregation/VisualizationItemTitleWithAggregation.tsx create mode 100644 src/ui/units/wizard/constants/aggregations.ts create mode 100644 src/ui/units/wizard/constants/dialogColor.ts create mode 100644 src/ui/units/wizard/constants/formats.ts create mode 100644 src/ui/units/wizard/constants/grouping.ts create mode 100644 src/ui/units/wizard/constants/index.ts create mode 100644 src/ui/units/wizard/constants/paletteTypes.ts create mode 100644 src/ui/units/wizard/constants/placeholders.ts create mode 100644 src/ui/units/wizard/containers/App.scss create mode 100644 src/ui/units/wizard/containers/App.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionDataset/DatasetItem/DatasetItem.scss create mode 100644 src/ui/units/wizard/containers/Wizard/SectionDataset/DatasetItem/DatasetItem.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionDataset/FieldsContainer/FieldsContainer.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionDataset/FieldsContainer/ParametersContainer/ParametersContainer.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionDataset/FieldsContainer/components/SectionContainer/SectionContainer.scss create mode 100644 src/ui/units/wizard/containers/Wizard/SectionDataset/FieldsContainer/components/SectionContainer/SectionContainer.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionDataset/HierarchyEditor/HierarchyEditor.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionDataset/SectionDataset.scss create mode 100644 src/ui/units/wizard/containers/Wizard/SectionDataset/SectionDataset.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionPreview/SectionPreview.scss create mode 100644 src/ui/units/wizard/containers/Wizard/SectionPreview/SectionPreview.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/AddField/AddField.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/AvailablePlaceholder/AvailablePlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/ColorsPlaceholder/ColorsPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/DashboardFiltersPlaceholder/DashboardFiltersPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/DashboardParametersPlaceholder/DashboardParametersPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/FiltersPlaceholder/FiltersPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/LabelsPlaceholder/LabelsPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/LayerFiltersPlaceholder/LayerFiltersPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/MetricColorsPlaceholder/MetricColorsPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/Placeholder/Placeholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/PlaceholdersContainer.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/SegmentsPlaceholder/SegmentsPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/ShapesPlaceholder/ShapesPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/SortPlaceholder/SortPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/TooltipsPlaceholder/TooltipsPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/VisualizationPlaceholder/VisualizationPlaceholder.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/VisualizationPlaceholder/constants.ts create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/PlaceholdersContainer/utils.ts create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/SectionVisualization.scss create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/SectionVisualization.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/VisualizationItem/VisualizationItem.scss create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/VisualizationItem/VisualizationItem.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/VisualizationLayersControl/CombinedChartLayerTypeSwitcher.scss create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/VisualizationLayersControl/CombinedChartLayerTypeSwitcher.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/VisualizationLayersControl/VisualizationLayersControl.scss create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/VisualizationLayersControl/VisualizationLayersControl.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/VisualizationSelector/VisualizationSelector.scss create mode 100644 src/ui/units/wizard/containers/Wizard/SectionVisualization/VisualizationSelector/VisualizationSelector.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/Wizard.scss create mode 100644 src/ui/units/wizard/containers/Wizard/Wizard.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/WizardActionPanel/WizardActionPanel.scss create mode 100644 src/ui/units/wizard/containers/Wizard/WizardActionPanel/WizardActionPanel.tsx create mode 100644 src/ui/units/wizard/containers/Wizard/WizardActionPanel/useWizardActionPanel.ts create mode 100644 src/ui/units/wizard/reducers/__tests__/mocks/common.mock.ts create mode 100644 src/ui/units/wizard/reducers/__tests__/mocks/preview.mock.ts create mode 100644 src/ui/units/wizard/reducers/__tests__/preview.test.ts create mode 100644 src/ui/units/wizard/reducers/__tests__/utils/previewTestUtils.ts create mode 100644 src/ui/units/wizard/reducers/dataset.ts create mode 100644 src/ui/units/wizard/reducers/dialogColor.ts create mode 100644 src/ui/units/wizard/reducers/hierarchyEditor.ts create mode 100644 src/ui/units/wizard/reducers/index.ts create mode 100644 src/ui/units/wizard/reducers/preview.ts create mode 100644 src/ui/units/wizard/reducers/revisions/reducers.ts create mode 100644 src/ui/units/wizard/reducers/settings.ts create mode 100644 src/ui/units/wizard/reducers/utils/__tests__/clearUnusedVisualizationItems.test.ts create mode 100644 src/ui/units/wizard/reducers/utils/__tests__/getPlaceholdersWithMergedSettings.test.ts create mode 100644 src/ui/units/wizard/reducers/utils/__tests__/updateColorHierarchies.test.ts create mode 100644 src/ui/units/wizard/reducers/utils/checkTableColor.ts create mode 100644 src/ui/units/wizard/reducers/utils/clearUnusedVisualizationItems.ts create mode 100644 src/ui/units/wizard/reducers/utils/getConfigData.ts create mode 100644 src/ui/units/wizard/reducers/utils/getPlaceholdersWithMergedSettings.ts create mode 100644 src/ui/units/wizard/reducers/utils/index.ts create mode 100644 src/ui/units/wizard/reducers/utils/updateColorHierarchies.ts create mode 100644 src/ui/units/wizard/reducers/visualization.ts create mode 100644 src/ui/units/wizard/reducers/widget.ts create mode 100644 src/ui/units/wizard/selectors/dataset.ts create mode 100644 src/ui/units/wizard/selectors/dialogColor.ts create mode 100644 src/ui/units/wizard/selectors/gradient.ts create mode 100644 src/ui/units/wizard/selectors/index.ts create mode 100644 src/ui/units/wizard/selectors/preview.ts create mode 100644 src/ui/units/wizard/selectors/settings.ts create mode 100644 src/ui/units/wizard/selectors/visualization.ts create mode 100644 src/ui/units/wizard/selectors/widget.ts create mode 100644 src/ui/units/wizard/styles/mixins.scss create mode 100644 src/ui/units/wizard/styles/variables.scss create mode 100644 src/ui/units/wizard/typings/index.ts create mode 100644 src/ui/units/wizard/typings/wizard.ts create mode 100644 src/ui/units/wizard/utils/__tests__/helpers.test.ts create mode 100644 src/ui/units/wizard/utils/__tests__/mappers/mapServerToClientConfig.ts create mode 100644 src/ui/units/wizard/utils/__tests__/wizard.test.ts create mode 100644 src/ui/units/wizard/utils/colors.ts create mode 100644 src/ui/units/wizard/utils/filters.ts create mode 100644 src/ui/units/wizard/utils/helpers.ts create mode 100644 src/ui/units/wizard/utils/mappers/mapChartsToClientConfig.ts create mode 100644 src/ui/units/wizard/utils/mappers/mapClientToChartsConfig.ts create mode 100644 src/ui/units/wizard/utils/palette.tsx create mode 100644 src/ui/units/wizard/utils/placeholder.ts create mode 100644 src/ui/units/wizard/utils/shapes.ts create mode 100644 src/ui/units/wizard/utils/table.ts create mode 100644 src/ui/units/wizard/utils/visualization.ts create mode 100644 src/ui/units/wizard/utils/wizard.ts create mode 100644 src/ui/units/workbooks/components/CreateEntry/CreateEntry.scss create mode 100644 src/ui/units/workbooks/components/CreateEntry/CreateEntry.tsx create mode 100644 src/ui/units/workbooks/components/CreateEntry/utils.tsx create mode 100644 src/ui/units/workbooks/components/CreateEntryDialog/CreateEntryDialog.tsx create mode 100644 src/ui/units/workbooks/components/CreateEntryDialog/utils.ts create mode 100644 src/ui/units/workbooks/components/DeleteEntryDialog/DeleteEntryDialog.tsx create mode 100644 src/ui/units/workbooks/components/DuplicateEntryDialog/DuplicateEntryDialog.tsx create mode 100644 src/ui/units/workbooks/components/EmptyWorkbook/EmptyWorkbook.scss create mode 100644 src/ui/units/workbooks/components/EmptyWorkbook/EmptyWorkbook.tsx create mode 100644 src/ui/units/workbooks/components/EmptyWorkbook/EmptyWorkbookContainer.tsx create mode 100644 src/ui/units/workbooks/components/EmptyWorkbook/utils.ts create mode 100644 src/ui/units/workbooks/components/EntryActions/EntryActions.tsx create mode 100644 src/ui/units/workbooks/components/EntryActions/utils.ts create mode 100644 src/ui/units/workbooks/components/RenameEntryDialog/RenameEntryDialog.scss create mode 100644 src/ui/units/workbooks/components/RenameEntryDialog/RenameEntryDialog.tsx create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/ChunkGroup/ChunkGroup.scss create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/ChunkGroup/ChunkGroup.tsx create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/MainTabContent/MainTabContent.scss create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/MainTabContent/MainTabContent.tsx create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/Row/Row.scss create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/Row/Row.tsx create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/WorkbookEntriesTable.scss create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/WorkbookEntriesTable.tsx create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/WorkbookEntriesTableTabs.tsx create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/constants.ts create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/types.ts create mode 100644 src/ui/units/workbooks/components/Table/WorkbookEntriesTable/utils.ts create mode 100644 src/ui/units/workbooks/components/WorkbookActions/WorkbookActions.scss create mode 100644 src/ui/units/workbooks/components/WorkbookActions/WorkbookActions.tsx create mode 100644 src/ui/units/workbooks/components/WorkbookActions/utils.tsx create mode 100644 src/ui/units/workbooks/components/WorkbookFilters/WorkbookFilters.scss create mode 100644 src/ui/units/workbooks/components/WorkbookFilters/WorkbookFilters.tsx create mode 100644 src/ui/units/workbooks/components/WorkbookMainTabContent/WorkbookMainTabContent.tsx create mode 100644 src/ui/units/workbooks/components/WorkbookMainTabContent/useChunkedEntries.ts create mode 100644 src/ui/units/workbooks/components/WorkbookPage/WorkbookPage.scss create mode 100644 src/ui/units/workbooks/components/WorkbookPage/WorkbookPage.tsx create mode 100644 src/ui/units/workbooks/components/WorkbookPage/hooks/useLayout.tsx create mode 100644 src/ui/units/workbooks/components/WorkbookTabContent/WorkbookTabContent.scss create mode 100644 src/ui/units/workbooks/components/WorkbookTabContent/WorkbookTabContent.tsx create mode 100644 src/ui/units/workbooks/components/WorkbookTabContent/useChunkedEntries.ts create mode 100644 src/ui/units/workbooks/components/WorkbookTabs/WorkbookTabs.tsx create mode 100644 src/ui/units/workbooks/components/WorkbookTabs/constants.ts create mode 100644 src/ui/units/workbooks/components/WorkbookTabs/types.ts create mode 100644 src/ui/units/workbooks/components/WorkbookTabs/utils.ts create mode 100644 src/ui/units/workbooks/constants/index.ts create mode 100644 src/ui/units/workbooks/store/actions/index.ts create mode 100644 src/ui/units/workbooks/store/constants/index.ts create mode 100644 src/ui/units/workbooks/store/reducers/index.ts create mode 100644 src/ui/units/workbooks/store/reducers/workbook-page.ts create mode 100644 src/ui/units/workbooks/store/selectors/index.ts create mode 100644 src/ui/units/workbooks/types/index.ts create mode 100644 src/ui/units/workbooks/utils/entry.ts create mode 100644 src/ui/utils/__tests__/docs.test.ts create mode 100644 src/ui/utils/__tests__/helpers.test.ts create mode 100644 src/ui/utils/__tests__/validation.test.ts create mode 100644 src/ui/utils/absurd.ts create mode 100644 src/ui/utils/chart-modeling/add-chart-modeling-series.ts create mode 100644 src/ui/utils/chart-modeling/index.ts create mode 100644 src/ui/utils/collectionFiltersStorage.ts create mode 100644 src/ui/utils/colors.ts create mode 100644 src/ui/utils/connections/icons.ts create mode 100644 src/ui/utils/connections/index.ts create mode 100644 src/ui/utils/copyItems.ts create mode 100644 src/ui/utils/copyText.ts create mode 100644 src/ui/utils/custom-export-menu-item.ts create mode 100644 src/ui/utils/date.ts create mode 100644 src/ui/utils/docs/helpers.ts create mode 100644 src/ui/utils/docs/index.ts create mode 100644 src/ui/utils/embedded.ts create mode 100644 src/ui/utils/errorContentTypes.ts create mode 100644 src/ui/utils/errors/errorByCode.ts create mode 100644 src/ui/utils/errors/manual.ts create mode 100644 src/ui/utils/errors/parse.ts create mode 100644 src/ui/utils/getActionPanelItems.tsx create mode 100644 src/ui/utils/getAllEntryScopes.ts create mode 100644 src/ui/utils/getBasicActionPanelItems.tsx create mode 100644 src/ui/utils/getCurrentUserRights.ts create mode 100644 src/ui/utils/getOverridedTheme.ts create mode 100644 src/ui/utils/getRestrictedParamNames.ts create mode 100644 src/ui/utils/getRevisionsPanelEntryScopesTexts.ts create mode 100644 src/ui/utils/getScopeTypeIcon.ts create mode 100644 src/ui/utils/getTopLevelEntryScopes.ts create mode 100644 src/ui/utils/getTypeSelectOptions.tsx create mode 100644 src/ui/utils/gridStorage.ts create mode 100644 src/ui/utils/helpers.ts create mode 100644 src/ui/utils/history.tsx create mode 100644 src/ui/utils/icons.ts create mode 100644 src/ui/utils/index.ts create mode 100644 src/ui/utils/isEnabledFeature.ts create mode 100644 src/ui/utils/line-shapes.tsx create mode 100644 src/ui/utils/markdown/get-render-markdown-fn.ts create mode 100644 src/ui/utils/markdown/get-render-yfm-fn.tsx create mode 100644 src/ui/utils/markdown/index.ts create mode 100644 src/ui/utils/markup.tsx create mode 100644 src/ui/utils/mermaid-sanitize.ts create mode 100644 src/ui/utils/migrateItemDataOnPaste.ts create mode 100644 src/ui/utils/mobile.ts create mode 100644 src/ui/utils/openModalSubscriber.ts create mode 100644 src/ui/utils/os.ts create mode 100644 src/ui/utils/palettes.tsx create mode 100644 src/ui/utils/readOnly.ts create mode 100644 src/ui/utils/revisions.ts create mode 100644 src/ui/utils/scrollableContainerContext.tsx create mode 100644 src/ui/utils/sdkRequests.ts create mode 100644 src/ui/utils/setEntryKey.ts create mode 100644 src/ui/utils/shared-entries/get-is-restricted-shared-entry.ts create mode 100644 src/ui/utils/shared-entries/get-is-shared-entry.ts create mode 100644 src/ui/utils/shared-entries/index.ts create mode 100644 src/ui/utils/stringUtils.ts create mode 100644 src/ui/utils/urlSearch.ts create mode 100644 src/ui/utils/urlUtils.ts create mode 100644 src/ui/utils/utils.ts create mode 100644 src/ui/utils/validation.ts create mode 100644 src/ui/utils/visualizations/line.ts create mode 100644 src/ui/utils/visualizations/placeholders/colors.ts create mode 100644 src/ui/utils/visualizations/placeholders/common-measures.ts create mode 100644 src/ui/utils/waitOperation.ts create mode 100644 src/ui/utils/widgetColors.ts create mode 100644 src/ui/utils/widgets/colors.ts create mode 100644 src/ui/utils/widgets/internalMargins.ts create mode 100644 src/ui/utils/workbooks.ts create mode 100644 statoscope.config.js create mode 100644 tests/.eslintrc create mode 100644 tests/Dockerfile create mode 100644 tests/README.md create mode 100644 tests/constants/config/charts.ts create mode 100644 tests/constants/config/connections.ts create mode 100644 tests/constants/config/dash.ts create mode 100644 tests/constants/config/datasets.ts create mode 100644 tests/constants/config/index.ts create mode 100644 tests/constants/config/ql.ts create mode 100644 tests/constants/config/wizard.ts create mode 100644 tests/constants/constants.ts create mode 100644 tests/constants/mock-data.ts create mode 100644 tests/constants/test-entities/charts.ts create mode 100644 tests/constants/test-entities/connections.ts create mode 100644 tests/constants/test-entities/dash.ts create mode 100644 tests/constants/test-entities/datasets.ts create mode 100644 tests/constants/test-entities/ql.ts create mode 100644 tests/constants/test-entities/wizard.ts create mode 100644 tests/constants/test-entities/workbook.ts create mode 100644 tests/data/post-init.sh create mode 100644 tests/data/sales-db.sql create mode 100644 tests/data/superstore-sales.csv create mode 100644 tests/data/us-e2e-data.sql create mode 100644 tests/docker-compose.base.yml create mode 100644 tests/docker-compose.e2e.dev.yml create mode 100644 tests/docker-compose.e2e.nginx.yml create mode 100644 tests/docker-compose.e2e.yml create mode 100644 tests/documentation/env_configuration.md create mode 100644 tests/opensource-suites/__screenshots__/charts/preview/widget-width.test.ts/Charts-Preview-Table-widget-must-be-the-same-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/dash/screenshots/baseWidgets.test.ts/Dashboards---Base-widgets-screenshots-Widgets-without-autoheight-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/dash/screenshots/headerWidgets.test.ts/Dashboards---Header-widgets-screenshots-Headers-with-hints-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/dash/screenshots/markdownCases.test.ts/Dashboards---Markdown-cases-screenshots-Tab-with-different-markdown-widgets-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/dash/screenshots/mobile.test.ts/Dashboards---Mobile-screenshots-Custom-order-of-widgets-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/dash/screenshots/mobile.test.ts/Dashboards---Mobile-screenshots-Default-order-of-widgets-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/dash/screenshots/widgetsBackground.test.ts/Dashboards---widgets-backgrounds-screenshots-Chart-bg-color-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/dash/screenshots/widgetsBackground.test.ts/Dashboards---widgets-backgrounds-screenshots-Title-bg-color-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/dash/screenshots/widgetsBackground.test.ts/Dashboards---widgets-backgrounds-screenshots-Title-text-color-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/dash/widgets/katex.test.ts/Dashboards-Text-widget-KaTeX-formula-inside-the-table-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/ql/visualizations/column/column.test.ts/Check-that-fields-order-does-not-affect-axis-settings-screenshot.png create mode 100644 tests/opensource-suites/__screenshots__/ql/visualizations/pie/labels.test.ts/QL-Pie-chart-Column-names-in-Labels-section-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/data-labels.test.ts/Wizard-Area-chart-Datetime-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/data-labels.test.ts/Wizard-Area-chart-Markup-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/data-labels.test.ts/Wizard-Area-chart-String-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/data-labels.test.ts/Wizard-Area-normalised-chart-Percentage-as-labels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/html.test.ts/Wizard-Area-chart-Html-labels-on-X-axis-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/html.test.ts/Wizard-Area-chart-Html-legend-color-field-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/split.test.ts/Wizard-Area-chart-Split-with-few-values-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/split.test.ts/Wizard-Area-chart-Split-with-single-value-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/stacking.test.ts/Wizard-Area-chart-Stacking-disabled-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/tooltip.test.ts/Wizard-Area-chart-Different-format-of-the-two-measures-in-tooltip-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/tooltip.test.ts/Wizard-Area-normalised-chart-Percentage-formatting-in-tooltip-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/area/tooltip.test.ts/Wizard-Area-normalised-chart-Tooltip-rows-sorting-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/coloring.test.ts/Wizard-Bar-x-chart-Coloring-by-measure-field-gradient-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/coloring.test.ts/Wizard-Bar-x-chart-Coloring-with-hex-colors-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/data-labels.test.ts/Wizard-Bar-x-chart-Datetime-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/data-labels.test.ts/Wizard-Bar-x-chart-Markup-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/data-labels.test.ts/Wizard-Bar-x-chart-MeasureValues-as-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/data-labels.test.ts/Wizard-Bar-x-chart-String-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/data-labels.test.ts/Wizard-Bar-x-normalised-chart-Percentage-as-labels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/html.test.ts/Wizard-Bar-x-chart-Html-labels-on-X-axis-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/html.test.ts/Wizard-Bar-x-chart-Html-legend-color-field-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/negative-values.test.ts/Wizard-Bar-x-chart-Negative-Y-values-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/split.test.ts/Wizard-Bar-x-chart-Split-with-few-values-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/split.test.ts/Wizard-Bar-x-chart-Split-with-single-value-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/tooltip.test.ts/Wizard-Bar-x-chart-Different-format-of-the-two-measures-in-tooltip-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-x/tooltip.test.ts/Wizard-Bar-x-normalised-chart-Percentage-formatting-in-tooltip-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/axis-settings.test.ts/Wizard-Bar-y-chart-Logarithmic-x-axis-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/axis-settings.test.ts/Wizard-Bar-y-chart-Logarithmic-x-axis-with-zero-values-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/axis-settings.test.ts/Wizard-Bar-y-chart-Rotated-axis-labels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/coloring.test.ts/Wizard-Bar-y-chart-Coloring-by-Measure-Values-gradient-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/coloring.test.ts/Wizard-Bar-y-chart-Coloring-by-Measure-Values-with-Measure-Names-in-Y-section-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/coloring.test.ts/Wizard-Bar-y-chart-Coloring-by-measure-field-gradient-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/data-labels.test.ts/Wizard-Bar-y-chart-Datetime-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/data-labels.test.ts/Wizard-Bar-y-chart-Markup-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/data-labels.test.ts/Wizard-Bar-y-chart-MeasureValues-as-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/data-labels.test.ts/Wizard-Bar-y-chart-String-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/data-labels.test.ts/Wizard-Normalise-bar-y-chart-Percentage-as-labels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/grouping.test.ts/Wizard-Bar-y-chart-Grouping-by-Measure-Names-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/legend.test.ts/Wizard-Bar-y-chart-The-legend-should-not-be-displayed-when-the-setting-is-turned-off-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/sorting.test.ts/Wizard-Bar-y-chart-Sorting-by-X-field-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/tooltip.test.ts/Wizard-Bar-y-chart-Different-format-of-the-two-measures-in-tooltip-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/bar-y/tooltip.test.ts/Wizard-Bar-y-normalised-chart-Percentage-formatting-in-tooltip-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/axis.test.ts/Wizard-Combined-chart-The-negative-right-Y-axi-9512c-h-the-positive-left-on-the-second-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/coloring.test.ts/Wizard-Combined-chart-When-adding-the-second-Y-29d82-hould-be-colored-by-Measure-Names-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/data-labels.test.ts/Wizard-Combined-chart-DataLabels-with-prefix-and-postfix-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/data-labels.test.ts/Wizard-Combined-chart-DataLabels-with-prefix-and-postfix-screenshot-2.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/legend.test.ts/Wizard-Combined-chart-The-legend-should-be-dis-31745-ith-single-series-column-line-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/legend.test.ts/Wizard-Combined-chart-The-legend-should-be-displayed-for-two-layers-with-single-lines-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/legend.test.ts/Wizard-Combined-chart-The-second-empty-layer-s-461db-fect-the-visibility-of-the-legend-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/legend.test.ts/Wizard-Combined-chart-With-different-fields-on-223c3-r-field-title-color-field-value-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/legend.test.ts/Wizard-Combined-chart-With-different-fields-on-2dedf-s-field-title-color-field-value-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/legend.test.ts/Wizard-Combined-chart-With-same-fields-on-Y-ax-5e5aa--displayed-as-color-field-value-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/legend.test.ts/Wizard-Combined-chart-With-same-fields-on-Y-ax-96a2b-r-field-title-color-field-value-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/navigator.test.ts/Wizard-Combined-chart-Navigator-must-be-disabled-for-the-combined-chart-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/stacking.test.ts/Wizard-Combined-chart-StackId-for-different-layer-series-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/combined/tooltip.test.ts/Wizard-Combined-chart-Tooltip-for-layers-with-and-without-colors-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/coloring.test.ts/Wizard-Flat-table-Empty-values---ignore-nulls-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/coloring.test.ts/Wizard-Flat-table-Gradient-coloring-for-null-values-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/coloring.test.ts/Wizard-Flat-table-Gradient-coloring-for-zero-values-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/coloring.test.ts/Wizard-Flat-table-Gradient-coloring-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/linear-indicator.test.ts/Wizard-Flat-table-Linear-indicator-with-column-background-color-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/linear-indicator.test.ts/Wizard-Flat-table-Linear-indicator-with-manual-aee01-ax-value-is-less-than-the-actual-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/pinned-columns.test.ts/Wizard-Flat-table-Pinned-columns-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/pinned-columns.test.ts/Wizard-Flat-table-Pinned-columns-screenshot-2.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/pinned-columns.test.ts/Wizard-Flat-table-Pinned-columns-screenshot-3.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/virtualization.test.ts/Wizard-Flat-table-Correct-order-of-rows-when-scrolling-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/virtualization.test.ts/Wizard-Flat-table-Correct-order-of-rows-when-scrolling-screenshot-2.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/virtualization.test.ts/Wizard-Flat-table-Dynamic-column-width-during-row-virtualization-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/virtualization.test.ts/Wizard-Flat-table-Dynamic-column-width-during-row-virtualization-screenshot-2.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/flat-table/virtualization.test.ts/Wizard-Flat-table-The-correct-placement-of-the-totals-row-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/axis.test.ts/Wizard-Line-chart-Auto-scale-from-0-to-max-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/axis.test.ts/Wizard-Line-chart-Axis-titles-with-line-breaks-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/axis.test.ts/Wizard-Line-chart-Chart-starts-and-ends-on-data-points-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/axis.test.ts/Wizard-Line-chart-Date-and-time-on-the-Y-axis-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/axis.test.ts/Wizard-Line-chart-The-right-Y-axis-only-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/axis.test.ts/Wizard-Line-chart-Two-Y-axes-left-and-right-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/html.test.ts/Wizard-Line-chart-Html-labels-on-X-axis-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/html.test.ts/Wizard-Line-chart-Html-legend-color-field-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/shapes.test.ts/Wizard-Line-chart-Multiple-fields-on-the-Y-axi-e685a-es-colors-are-assigned-correctly-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/split.test.ts/Wizard-Line-chart-Html-in-split-value---tooltip-should-be-rendered-with-split-value-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/split.test.ts/Wizard-Line-chart-Split-with-few-values-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/split.test.ts/Wizard-Line-chart-Split-with-single-value-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/line/split.test.ts/Wizard-Line-chart-Split-with-two-Y-axes-left-and-right-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pie/basic.test.ts/Wizard-Pie-chart-Auto-coloring-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pie/basic.test.ts/Wizard-Pie-chart-Auto-coloring-screenshot-2.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pie/basic.test.ts/Wizard-Pie-chart-Auto-coloring-screenshot-3.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pie/basic.test.ts/Wizard-Pie-chart-Auto-coloring-screenshot-4.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pie/basic.test.ts/Wizard-Pie-chart-Auto-coloring-screenshot-5.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pie/basic.test.ts/Wizard-Pie-chart-Auto-coloring-screenshot-6.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pie/dataset-field-settings.test.ts/Wizard-Pie-chart-Use-color-palette-from-dataset-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pie/hierarchy.test.ts/Wizard-Pie-chart-Go-to-the-next-level-when-clicking-on-a-chart-with-a-hierarchy-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pie/markup.test.ts/Wizard-Pie-chart-Markup-with-special-characters-in-dataLabels-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/coloring.test.ts/Wizard-Pivot-table-Coloring-zero-values-with-a-gradient-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/coloring.test.ts/Wizard-Pivot-table-Custom-palette-bar-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/coloring.test.ts/Wizard-Pivot-table-Two-colors-bar-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/coloring.test.ts/Wizard-Pivot-table-Two-colors-bar-screenshot-2.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/column-settings.test.ts/Wizard-Pivot-table-Columns-width-for-complex-three-level-header-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/formatting.test.ts/Wizard-Pivot-table-Null-values-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/header.test.ts/Wizard-Pivot-table-Complex-three-level-header-in-the-table-head-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/header.test.ts/Wizard-Pivot-table-Complex-three-level-header-in-the-table-rows-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/header.test.ts/Wizard-Pivot-table-Grouping-rows-with-markup-values-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/header.test.ts/Wizard-Pivot-table-Grouping-rows-with-the-same-value-in-the-previous-column-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/no-data.test.ts/Wizard-Pivot-table-No-data-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/old-pivot-table.test.ts/Wizard-Pivot-table-Table-with-Rows-only-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/pinned-columns.test.ts/Wizard-Pivot-table-Pinned-columns-with-subtotals-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/pinned-columns.test.ts/Wizard-Pivot-table-Pinned-columns-with-subtotals-screenshot-2.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/totals.test.ts/Wizard-Pivot-table-Subtotals-for-Columns-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/totals.test.ts/Wizard-Pivot-table-Subtotals-for-Rows-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/virtualization.test.ts/Wizard-Pivot-table-Correct-order-of-rows-when-scrolling-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/virtualization.test.ts/Wizard-Pivot-table-Correct-order-of-rows-when-scrolling-screenshot-2.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/virtualization.test.ts/Wizard-Pivot-table-Scroll-for-tables-with-row-grouping-and-fixed-column-width-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/pivot-table/virtualization.test.ts/Wizard-Pivot-table-The-correct-placement-of-the-totals-row-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/scatter/axis-settings.test.ts/Wizard-Scatter-chart-Min-and-max-values-for-Y-axis-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/scatter/coloring.test.ts/Wizard-Scatter-chart-Coloring-by-measure-field-gradient-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/scatter/coloring.test.ts/Wizard-Scatter-chart-Point-color-set-by-the-fake-title-of-the-field-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/treemap/hierarchy.test.ts/Wizard-Treemap-chart-Go-to-the-next-level-when-clicking-on-a-chart-with-a-hierarchy-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/treemap/html-in-chart.test.ts/Wizard-Treemap-chart-Markdown-dimension-screenshot-1.png create mode 100644 tests/opensource-suites/__screenshots__/wizard/visualizations/treemap/tooltip.test.ts/Wizard-Treemap-chart-Tooltip-when-hovering-chart-area-screenshot-1.png create mode 100644 tests/opensource-suites/charts/export/forbiddenExport.test.ts create mode 100644 tests/opensource-suites/charts/preview/widget-width.test.ts create mode 100644 tests/opensource-suites/collections/contentTableItemsLinks.test.ts create mode 100644 tests/opensource-suites/connections/clickhouse.test.ts create mode 100644 tests/opensource-suites/connections/postgres.test.ts create mode 100644 tests/opensource-suites/connections/ytsaurus.test.ts create mode 100644 tests/opensource-suites/dash/base/actionPanel.test.ts create mode 100644 tests/opensource-suites/dash/base/fixedHeader.test.ts create mode 100644 tests/opensource-suites/dash/base/loading.test.ts create mode 100644 tests/opensource-suites/dash/base/relations.test.ts create mode 100644 tests/opensource-suites/dash/constants.ts create mode 100644 tests/opensource-suites/dash/params/params.test.ts create mode 100644 tests/opensource-suites/dash/relations/ignore.test.ts create mode 100644 tests/opensource-suites/dash/relations/relationType.test.ts create mode 100644 tests/opensource-suites/dash/relations/relations.test.ts create mode 100644 tests/opensource-suites/dash/relations/relationsEmpty.test.ts create mode 100644 tests/opensource-suites/dash/relations/relationsWithErrorChart.test.ts create mode 100644 tests/opensource-suites/dash/relations/validation.test.ts create mode 100644 tests/opensource-suites/dash/revisions/revisionsList.test.ts create mode 100644 tests/opensource-suites/dash/revisions/revisionsListCopyDashboards.test.ts create mode 100644 tests/opensource-suites/dash/revisions/revisionsListWithCreationDashboards.test.ts create mode 100644 tests/opensource-suites/dash/revisions/revisionsListWithCreationSaveAsCopy.test.ts create mode 100644 tests/opensource-suites/dash/revisions/revisionsSwitching.test.ts create mode 100644 tests/opensource-suites/dash/screenshots/baseWidgets.test.ts create mode 100644 tests/opensource-suites/dash/screenshots/headerWidgets.test.ts create mode 100644 tests/opensource-suites/dash/screenshots/markdownCases.test.ts create mode 100644 tests/opensource-suites/dash/screenshots/mobile.test.ts create mode 100644 tests/opensource-suites/dash/screenshots/widgetsBackground.test.ts create mode 100644 tests/opensource-suites/dash/selectors/acceptableValues.test.ts create mode 100644 tests/opensource-suites/dash/selectors/addSelectors.test.ts create mode 100644 tests/opensource-suites/dash/selectors/autoupdateRelatedSelectors.test.ts create mode 100644 tests/opensource-suites/dash/selectors/configManipulations.test.ts create mode 100644 tests/opensource-suites/dash/selectors/datetimeSelectors.test.ts create mode 100644 tests/opensource-suites/dash/selectors/externalInfluencesOnGroup.test.ts create mode 100644 tests/opensource-suites/dash/selectors/global/baseActions.test.ts create mode 100644 tests/opensource-suites/dash/selectors/groupBaseActions.test.ts create mode 100644 tests/opensource-suites/dash/selectors/groupButtonActions.test.ts create mode 100644 tests/opensource-suites/dash/selectors/selectorWithInvalidField.test.ts create mode 100644 tests/opensource-suites/dash/selectors/selectorsLabels.test.ts create mode 100644 tests/opensource-suites/dash/state/stateChange.test.ts create mode 100644 tests/opensource-suites/dash/tabs/tabs.test.ts create mode 100644 tests/opensource-suites/dash/tabs/tabsChange.test.ts create mode 100644 tests/opensource-suites/dash/tabs/tabsWidgetOrder.test.ts create mode 100644 tests/opensource-suites/dash/toc/toc.test.ts create mode 100644 tests/opensource-suites/dash/toc/tocSettings.test.ts create mode 100644 tests/opensource-suites/dash/widgets/autoheight.test.ts create mode 100644 tests/opensource-suites/dash/widgets/autoheightDynamicCases.test.ts create mode 100644 tests/opensource-suites/dash/widgets/katex.test.ts create mode 100644 tests/opensource-suites/dash/widgets/markdown.test.ts create mode 100644 tests/opensource-suites/dataset/base/dataset-editor.test.ts create mode 100644 tests/opensource-suites/dataset/base/preview.test.ts create mode 100644 tests/opensource-suites/dataset/base/save-button.test.ts create mode 100644 tests/opensource-suites/dataset/base/sources.test.ts create mode 100644 tests/opensource-suites/dataset/base/tabs.test.ts create mode 100644 tests/opensource-suites/dataset/base/unload-alert.test.ts create mode 100644 tests/opensource-suites/dataset/field-settings/colors.test.ts create mode 100644 tests/opensource-suites/dataset/filters/dataset-filters.test.ts create mode 100644 tests/opensource-suites/ql/common/run.test.ts create mode 100644 tests/opensource-suites/ql/connections/selectSQLConnection.test.ts create mode 100644 tests/opensource-suites/ql/export/dataExport.test.ts create mode 100644 tests/opensource-suites/ql/params/dates.test.ts create mode 100644 tests/opensource-suites/ql/params/intervals.test.ts create mode 100644 tests/opensource-suites/ql/visualizations/column/column.test.ts create mode 100644 tests/opensource-suites/ql/visualizations/flat-table/column-settings.test.ts create mode 100644 tests/opensource-suites/ql/visualizations/pie/labels.test.ts create mode 100644 tests/opensource-suites/ql/visualizations/selectVisualization.test.ts create mode 100644 tests/opensource-suites/ql/visualizations/treemap/basic.test.ts create mode 100644 tests/opensource-suites/utils.ts create mode 100644 tests/opensource-suites/wizard/datasets/add-dataset.test.ts create mode 100644 tests/opensource-suites/wizard/datasets/replace-dataset.test.ts create mode 100644 tests/opensource-suites/wizard/edit-history/basic.test.ts create mode 100644 tests/opensource-suites/wizard/fields/add-field.test.ts create mode 100644 tests/opensource-suites/wizard/fields/field-actions.test.ts create mode 100644 tests/opensource-suites/wizard/fields/parameters.test.ts create mode 100644 tests/opensource-suites/wizard/filters/filters-with-dialog-color.test.ts create mode 100644 tests/opensource-suites/wizard/tooltips/tooltip-header-formatting.test.ts create mode 100644 tests/opensource-suites/wizard/utils.ts create mode 100644 tests/opensource-suites/wizard/visualizations/area/data-labels.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/area/export.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/area/hierarchy.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/area/html.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/area/split.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/area/stacking.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/area/tooltip.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-x/coloring.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-x/data-labels.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-x/export.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-x/hierarchy.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-x/html.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-x/negative-values.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-x/split.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-x/tooltip.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-y/axis-settings.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-y/coloring.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-y/data-labels.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-y/grouping.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-y/hierarchy.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-y/legend.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-y/sorting.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/bar-y/tooltip.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/combined/axis.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/combined/coloring.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/combined/config.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/combined/data-labels.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/combined/hierarchies.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/combined/legend.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/combined/measure-names-values.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/combined/navigator.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/combined/stacking.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/combined/tooltip.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/constants.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/coloring.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/column-settings.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/drag-n-drop.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/export.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/formatting.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/linear-indicator.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/pinned-columns.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/preview.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/sorting.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/totals.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/flat-table/virtualization.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/line/axis.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/line/export.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/line/hierarchy.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/line/html.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/line/preview.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/line/shapes.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/line/split.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/line/tooltip.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/metric/basic.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/metric/color.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/metric/preview.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/metric/title.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pie/basic.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pie/dataset-field-settings.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pie/drag-n-drop.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pie/export.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pie/hierarchy.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pie/markup.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/coloring.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/column-settings.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/drag-n-drop.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/export.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/formatting.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/header.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/no-data.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/old-pivot-table.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/pagination.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/pinned-columns.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/sorting.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/totals.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/pivot-table/virtualization.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/scatter/axis-settings.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/scatter/coloring.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/treemap/export.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/treemap/hierarchy.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/treemap/html-in-chart.test.ts create mode 100644 tests/opensource-suites/wizard/visualizations/treemap/tooltip.test.ts create mode 100644 tests/page-objects/BasePage.ts create mode 100644 tests/page-objects/ChartPage.ts create mode 100644 tests/page-objects/collections/CollectionsPage.ts create mode 100644 tests/page-objects/collections/ContentTable.ts create mode 100644 tests/page-objects/collections/ContentTableRow.ts create mode 100644 tests/page-objects/collections/EmptyState.ts create mode 100644 tests/page-objects/collections/index.ts create mode 100644 tests/page-objects/common/ChartContainer.ts create mode 100644 tests/page-objects/common/DialogControl.ts create mode 100644 tests/page-objects/common/DialogControlPO/AppearanceInnerTitle.ts create mode 100644 tests/page-objects/common/DialogControlPO/AppearanceTitle.ts create mode 100644 tests/page-objects/common/DialogControlPO/DatasetFieldSelector.ts create mode 100644 tests/page-objects/common/DialogControlPO/ElementType.ts create mode 100644 tests/page-objects/common/DialogControlPO/FieldName.ts create mode 100644 tests/page-objects/common/DialogControlPO/ImpactTabsIdsSelector.ts create mode 100644 tests/page-objects/common/DialogControlPO/ImpactTypeSelector.ts create mode 100644 tests/page-objects/common/DialogControlPO/RequiredCheckbox.ts create mode 100644 tests/page-objects/common/DialogControlPO/SelectDatasetButton.ts create mode 100644 tests/page-objects/common/DialogControlPO/SourceType.ts create mode 100644 tests/page-objects/common/DialogControlPO/SourceTypeSelect.ts create mode 100644 tests/page-objects/common/DialogParameter.ts create mode 100644 tests/page-objects/common/Revisions.ts create mode 100644 tests/page-objects/common/Visualization.ts create mode 100644 tests/page-objects/common/abstract/CheckboxElementPO.ts create mode 100644 tests/page-objects/common/abstract/ElementPO.ts create mode 100644 tests/page-objects/common/abstract/RadioGroupElementPO.ts create mode 100644 tests/page-objects/common/abstract/SelectElementPO.ts create mode 100644 tests/page-objects/common/abstract/TextInputElementPO.ts create mode 100644 tests/page-objects/common/abstract/types.ts create mode 100644 tests/page-objects/common/utils.ts create mode 100644 tests/page-objects/connections/ConnectionsPage.ts create mode 100644 tests/page-objects/constants/base.ts create mode 100644 tests/page-objects/constants/chartkit.ts create mode 100644 tests/page-objects/constants/common-selectors.ts create mode 100644 tests/page-objects/constants/common-urls.ts create mode 100644 tests/page-objects/dashboard/ChartkitControl.ts create mode 100644 tests/page-objects/dashboard/DashTabs.ts create mode 100644 tests/page-objects/dashboard/DashboardPage.ts create mode 100644 tests/page-objects/dashboard/DashboardSettings.ts create mode 100644 tests/page-objects/dashboard/Description.ts create mode 100644 tests/page-objects/dashboard/FixedHeader.ts create mode 100644 tests/page-objects/dashboard/TableOfContent.ts create mode 100644 tests/page-objects/dashboard/controlActions/ControlActions.ts create mode 100644 tests/page-objects/dashboard/controlActions/constants.ts create mode 100644 tests/page-objects/dataset/DatasetConnectionSection.ts create mode 100644 tests/page-objects/dataset/DatasetFieldsTable.ts create mode 100644 tests/page-objects/dataset/DatasetPage.ts create mode 100644 tests/page-objects/dataset/DatasetTabSection.ts create mode 100644 tests/page-objects/dataset/constants.ts create mode 100644 tests/page-objects/dataset/utils.ts create mode 100644 tests/page-objects/ql/PreviewTable.ts create mode 100644 tests/page-objects/ql/QLPage.ts create mode 100644 tests/page-objects/types.ts create mode 100644 tests/page-objects/wizard/BarsSettings.ts create mode 100644 tests/page-objects/wizard/ChartKit.ts create mode 100644 tests/page-objects/wizard/ChartSettings.ts create mode 100644 tests/page-objects/wizard/ColorDialog.ts create mode 100644 tests/page-objects/wizard/ColumnSettings.ts create mode 100644 tests/page-objects/wizard/DatasetSelector.ts create mode 100644 tests/page-objects/wizard/DialogMultidataset.ts create mode 100644 tests/page-objects/wizard/Field.ts create mode 100644 tests/page-objects/wizard/FieldEditor.ts create mode 100644 tests/page-objects/wizard/FilterEditor.ts create mode 100644 tests/page-objects/wizard/HierarchyEditor.ts create mode 100644 tests/page-objects/wizard/LabelsSettingsDialog.ts create mode 100644 tests/page-objects/wizard/LinkDialog.ts create mode 100644 tests/page-objects/wizard/MetricColorsDialog.ts create mode 100644 tests/page-objects/wizard/NavigationMinimal.ts create mode 100644 tests/page-objects/wizard/ParameterEditor.ts create mode 100644 tests/page-objects/wizard/PlaceholderDialog.ts create mode 100644 tests/page-objects/wizard/SectionVisualization.ts create mode 100644 tests/page-objects/wizard/ShapeDialog.ts create mode 100644 tests/page-objects/wizard/VisualizationItemDialog.ts create mode 100644 tests/page-objects/wizard/WizardPage.ts create mode 100644 tests/page-objects/workbook/ActionsMoreDropdown.ts create mode 100644 tests/page-objects/workbook/CreateEntryButton.ts create mode 100644 tests/page-objects/workbook/DialogCollectionStructure.ts create mode 100644 tests/page-objects/workbook/DialogCreateEntry.ts create mode 100644 tests/page-objects/workbook/DialogDeleteWorkbook.ts create mode 100644 tests/page-objects/workbook/DialogWorkbook.ts create mode 100644 tests/page-objects/workbook/EditEntityButton.ts create mode 100644 tests/page-objects/workbook/Filters.ts create mode 100644 tests/page-objects/workbook/NavigationMinimalPopup.ts create mode 100644 tests/page-objects/workbook/Workbook.ts create mode 100644 tests/playwright.config.ts create mode 100644 tests/suites/charts/comments/comments.test.ts create mode 100644 tests/suites/charts/export/export.test.ts create mode 100644 tests/suites/charts/inspector/inspector.test.ts create mode 100644 tests/suites/charts/navigator/navigator.test.ts create mode 100644 tests/suites/charts/render/tooManyRowsChart.test.ts create mode 100644 tests/suites/charts/shareLink/shareLink.test.ts create mode 100644 tests/suites/charts/tooltip-in-wizard-charts.test.ts create mode 100644 tests/suites/connections/check-s3-button.test.ts create mode 100644 tests/suites/dash/base/actionPanel.test.ts create mode 100644 tests/suites/dash/base/autoRefresh.test.ts create mode 100644 tests/suites/dash/base/globalAndChartsDefaultParams.test.ts create mode 100644 tests/suites/dash/base/loading.test.ts create mode 100644 tests/suites/dash/base/relations.test.ts create mode 100644 tests/suites/dash/connections/dashWithConnections.test.ts create mode 100644 tests/suites/dash/connections/dashWithoutConnections.test.ts create mode 100644 tests/suites/dash/connections/ignoreConnection.test.ts create mode 100644 tests/suites/dash/connections/loadingConnectionsModal.test.ts create mode 100644 tests/suites/dash/constants.ts create mode 100644 tests/suites/dash/dash-meta/description.test.ts create mode 100644 tests/suites/dash/helpers.ts create mode 100644 tests/suites/dash/mobile/mobileDash.test.ts create mode 100644 tests/suites/dash/params/editorParams.test.ts create mode 100644 tests/suites/dash/params/params.test.ts create mode 100644 tests/suites/dash/revisions/revisionsListCopyDashboards.test.ts create mode 100644 tests/suites/dash/revisions/revisionsListReadyDashboards.test.ts create mode 100644 tests/suites/dash/revisions/revisionsListWithCreationDashboards.test.ts create mode 100644 tests/suites/dash/revisions/revisionsListWithCreationSaveAsCopy.test.ts create mode 100644 tests/suites/dash/selectors/acceptableValues.test.ts create mode 100644 tests/suites/dash/selectors/addSelectors.test.ts create mode 100644 tests/suites/dash/selectors/chartInnerControls.test.ts create mode 100644 tests/suites/dash/selectors/datetimeSelectors.test.ts create mode 100644 tests/suites/dash/selectors/editorSelectorChange.test.ts create mode 100644 tests/suites/dash/selectors/editorSelectorsResetButton/withUpdateOnChange/withGlobalParams.test.ts create mode 100644 tests/suites/dash/selectors/editorSelectorsResetButton/withUpdateOnChange/withoutGlobalParams.test.ts create mode 100644 tests/suites/dash/selectors/editorSelectorsResetButton/withoutUpdateOnChange/withGlobalParams.test.ts create mode 100644 tests/suites/dash/selectors/editorSelectorsResetButton/withoutUpdateOnChange/withoutGlobalParams.test.ts create mode 100644 tests/suites/dash/selectors/selectorsLabels.test.ts create mode 100644 tests/suites/dash/state/stateChange.test.ts create mode 100644 tests/suites/dash/state/stateExternalSelector.test.ts create mode 100644 tests/suites/dash/statistics/collectChartStats.test.ts create mode 100644 tests/suites/dash/statistics/collectDashStats.test.ts create mode 100644 tests/suites/dash/tabs/tabs.test.ts create mode 100644 tests/suites/dash/tabs/tabsChange.test.ts create mode 100644 tests/suites/dash/tabs/tabsWidgetOrder.test.ts create mode 100644 tests/suites/dash/toc/toc.test.ts create mode 100644 tests/suites/dash/toc/tocSettings.test.ts create mode 100644 tests/suites/dash/widgets/autoheight.test.ts create mode 100644 tests/suites/dash/widgets/customTooltip.test.ts create mode 100644 tests/suites/dash/widgets/markdown.test.ts create mode 100644 tests/suites/dash/widgets/menu.test.ts create mode 100644 tests/suites/dash/widgets/renderCharts.test.ts create mode 100644 tests/suites/dataset/autoCreateYT.test.ts create mode 100644 tests/suites/dataset/avatars/avatars.test.ts create mode 100644 tests/suites/dataset/avatars/avatarsHandle.test.ts create mode 100644 tests/suites/dataset/avatars/avatarsRelations.test.ts create mode 100644 tests/suites/dataset/checkDeletedConn.test.ts create mode 100644 tests/suites/dataset/checkTransitionFromExistToNew.test.ts create mode 100644 tests/suites/dataset/history.test.ts create mode 100644 tests/suites/dataset/navigate-from-creation.test.ts create mode 100644 tests/suites/dataset/parameters/addParameter.test.ts create mode 100644 tests/suites/dataset/parameters/deleteParameter.test.ts create mode 100644 tests/suites/dataset/parameters/duplicateParameter.test.ts create mode 100644 tests/suites/dataset/parameters/editParameter.test.ts create mode 100644 tests/suites/dataset/parameters/parameterDialogValidation.test.ts create mode 100644 tests/suites/dataset/parameters/parametersTab.test.ts create mode 100644 tests/suites/dataset/relationsWithValidationError.test.ts create mode 100644 tests/suites/dataset/revisions.test.ts create mode 100644 tests/suites/dataset/sourceEditorDialog.test.ts create mode 100644 tests/suites/dataset/sources-list/dataset-sources-listing.test.ts create mode 100644 tests/suites/dataset/sources-list/helpers.ts create mode 100644 tests/suites/dataset/sources-list/mock-data.ts create mode 100644 tests/suites/dataset/tabsHandling.test.ts create mode 100644 tests/suites/editor/common/initialLoad.test.ts create mode 100644 tests/suites/preview/comments/comments.test.ts create mode 100644 tests/suites/preview/editor-actions/resetButton.test.ts create mode 100644 tests/suites/preview/tooltip/customTooltip.test.ts create mode 100644 tests/suites/sql/action-panel/qlRevisions.test.ts create mode 100644 tests/suites/sql/chart-actions/render.test.ts create mode 100644 tests/suites/sql/chart-actions/saving.test.ts create mode 100644 tests/suites/sql/common/loadChart.test.ts create mode 100644 tests/suites/sql/connections/selectSQLConnection.test.ts create mode 100644 tests/suites/sql/export/dataExport.test.ts create mode 100644 tests/suites/sql/params/base.test.ts create mode 100644 tests/suites/sql/params/intervals.test.ts create mode 100644 tests/suites/sql/visualizations/flatTable.test.ts create mode 100644 tests/suites/sql/visualizations/selectVisualization.test.ts create mode 100644 tests/suites/wizard/action-panel/revisionsControls.test.ts create mode 100644 tests/suites/wizard/action-panel/wizardRevisions.test.ts create mode 100644 tests/suites/wizard/chart-actions/checkMenu.test.ts create mode 100644 tests/suites/wizard/chart-actions/saveChart.test.ts create mode 100644 tests/suites/wizard/chart-settings/grouping.test.ts create mode 100644 tests/suites/wizard/chart-settings/navigator.test.ts create mode 100644 tests/suites/wizard/chart-settings/pagination.test.ts create mode 100644 tests/suites/wizard/colors/changeColorScheme.test.ts create mode 100644 tests/suites/wizard/colors/coloredByMeasureNames.test.ts create mode 100644 tests/suites/wizard/colors/colorizingByMeasureValues.test.ts create mode 100644 tests/suites/wizard/colors/combineColorsWithShapes.test.ts create mode 100644 tests/suites/wizard/colors/gradientColorsDialog.test.ts create mode 100644 tests/suites/wizard/colum-settings/columnWidth.test.ts create mode 100644 tests/suites/wizard/combined-chart/colorize-combined-chart.test.ts create mode 100644 tests/suites/wizard/combined-chart/combined-chart-tooltip.test.ts create mode 100644 tests/suites/wizard/combined-chart/hierarchies-combined-chart.test.ts create mode 100644 tests/suites/wizard/combined-chart/labels.test.ts create mode 100644 tests/suites/wizard/combined-chart/layers-and-multidatasets.test.ts create mode 100644 tests/suites/wizard/combined-chart/measure-names-values.test.ts create mode 100644 tests/suites/wizard/common/initialLoad.test.ts create mode 100644 tests/suites/wizard/constants.ts create mode 100644 tests/suites/wizard/data-types/dateGrouping.test.ts create mode 100644 tests/suites/wizard/datasets/addDataset.test.ts create mode 100644 tests/suites/wizard/datasets/datasetActions.test.ts create mode 100644 tests/suites/wizard/datasets/datasetPopup.test.ts create mode 100644 tests/suites/wizard/datasets/replaceDataset.test.ts create mode 100644 tests/suites/wizard/datasets/requestDataset.test.ts create mode 100644 tests/suites/wizard/dialog-field/barsSettings.test.ts create mode 100644 tests/suites/wizard/export/flatTable.test.ts create mode 100644 tests/suites/wizard/export/line-chart.test.ts create mode 100644 tests/suites/wizard/export/pivotTable.test.ts create mode 100644 tests/suites/wizard/export/scatterChart.test.ts create mode 100644 tests/suites/wizard/fields/dragAndDropField.test.ts create mode 100644 tests/suites/wizard/fields/duplicateField.test.ts create mode 100644 tests/suites/wizard/fields/editField.test.ts create mode 100644 tests/suites/wizard/fields/editVisualizationField.test.ts create mode 100644 tests/suites/wizard/fields/noneExistingDsField.test.ts create mode 100644 tests/suites/wizard/fields/removeField.test.ts create mode 100644 tests/suites/wizard/fields/searchField.test.ts create mode 100644 tests/suites/wizard/filters/dashboardFilters.test.ts create mode 100644 tests/suites/wizard/filters/dashboardFiltersWithOperator.test.ts create mode 100644 tests/suites/wizard/filters/filtersWithDate.test.ts create mode 100644 tests/suites/wizard/filters/filtersWithMultiDataset.test.ts create mode 100644 tests/suites/wizard/filters/layersFilters.test.ts create mode 100644 tests/suites/wizard/filters/obligatoryFilters.test.ts create mode 100644 tests/suites/wizard/filters/replaceFilter.test.ts create mode 100644 tests/suites/wizard/flatTable/pagination.test.ts create mode 100644 tests/suites/wizard/flatTable/sort.test.ts create mode 100644 tests/suites/wizard/geolayer/addDatasetToGeolayerChart.test.ts create mode 100644 tests/suites/wizard/geolayer/createGeolayers.test.ts create mode 100644 tests/suites/wizard/geolayer/geopointAddColor.test.ts create mode 100644 tests/suites/wizard/geolayer/geopoints/addLabel.test.ts create mode 100644 tests/suites/wizard/geolayer/geopoints/addMeasure.test.ts create mode 100644 tests/suites/wizard/geolayer/geopoints/addTooltip.test.ts create mode 100644 tests/suites/wizard/geolayer/removeGeolayers.test.ts create mode 100644 tests/suites/wizard/geolayer/renameGeolayers.test.ts create mode 100644 tests/suites/wizard/hierarchy/createHierarchy.test.ts create mode 100644 tests/suites/wizard/hierarchy/deleteHierarchy.test.ts create mode 100644 tests/suites/wizard/hierarchy/drillDown.test.ts create mode 100644 tests/suites/wizard/hierarchy/drillDownInBigDataset.test.ts create mode 100644 tests/suites/wizard/hierarchy/drillDownReset.test.ts create mode 100644 tests/suites/wizard/hierarchy/editHierarchy.test.ts create mode 100644 tests/suites/wizard/labels/axisLabels.test.ts create mode 100644 tests/suites/wizard/labels/dataLabelsPosition.test.ts create mode 100644 tests/suites/wizard/labels/editLabel.test.ts create mode 100644 tests/suites/wizard/labels/formatLabels.test.ts create mode 100644 tests/suites/wizard/links/dialogLinks.test.ts create mode 100644 tests/suites/wizard/metric/metric.test.ts create mode 100644 tests/suites/wizard/metric/metricCustomization.test.ts create mode 100644 tests/suites/wizard/navigation/navigation.test.ts create mode 100644 tests/suites/wizard/parameters/deleteParameter.test.ts create mode 100644 tests/suites/wizard/parameters/editParameter.test.ts create mode 100644 tests/suites/wizard/parameters/urlParameters.test.ts create mode 100644 tests/suites/wizard/pivot-table/formatting.test.ts create mode 100644 tests/suites/wizard/pivot-table/header.test.ts create mode 100644 tests/suites/wizard/pivot-table/markdown.test.ts create mode 100644 tests/suites/wizard/pivot-table/mocks/headerMock.ts create mode 100644 tests/suites/wizard/pivot-table/pagination.test.ts create mode 100644 tests/suites/wizard/placeholder-settings/autoScale.test.ts create mode 100644 tests/suites/wizard/placeholder-settings/axisMode.test.ts create mode 100644 tests/suites/wizard/placeholder-settings/axisType.test.ts create mode 100644 tests/suites/wizard/placeholder-settings/connectNulls.test.ts create mode 100644 tests/suites/wizard/placeholders/general.placeholders.test.ts create mode 100644 tests/suites/wizard/placeholders/labelsPlaceholder.test.ts create mode 100644 tests/suites/wizard/placeholders/shapesPlaceholder.test.ts create mode 100644 tests/suites/wizard/placeholders/sortPlaceholder.test.ts create mode 100644 tests/suites/wizard/placeholders/tooltipsPlaceholder.test.ts create mode 100644 tests/suites/wizard/preview/apiRun.test.ts create mode 100644 tests/suites/wizard/preview/dataExport.test.ts create mode 100644 tests/suites/wizard/shapes/combineShapesWithColors.test.ts create mode 100644 tests/suites/wizard/shapes/measureNamesShapesActions.test.ts create mode 100644 tests/suites/wizard/shapes/shapesWithoutColors.test.ts create mode 100644 tests/suites/wizard/sort/sortPlaceholderActions.test.ts create mode 100644 tests/suites/wizard/sort/treeSort.test.ts create mode 100644 tests/suites/wizard/visualization-selector/visualizationsList.test.ts create mode 100644 tests/tsconfig.json create mode 100644 tests/types/config/charts.ts create mode 100644 tests/types/config/connections.ts create mode 100644 tests/types/config/dash.ts create mode 100644 tests/types/config/datasets.ts create mode 100644 tests/types/config/index.ts create mode 100644 tests/types/config/ql.ts create mode 100644 tests/types/config/wizard.ts create mode 100644 tests/utils/constants.ts create mode 100644 tests/utils/helpers.ts create mode 100644 tests/utils/index.ts create mode 100644 tests/utils/playwright/constants.ts create mode 100644 tests/utils/playwright/datalens/datalens-test-setup.ts create mode 100644 tests/utils/playwright/datalens/setup-e2e.ts create mode 100644 tests/utils/playwright/datalens/utils.ts create mode 100644 tests/utils/playwright/globalTestDefinition.ts create mode 100644 tests/utils/playwright/types.ts create mode 100644 tests/utils/playwright/utils.ts create mode 100644 tsconfig.jest.json create mode 100644 tsconfig.json create mode 100644 workflow.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cb6ef5b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,32 @@ +.git + +.eslintrc +.stylelintrc +jest.config.ts +README.md +tsconfig.jest.json + +packages +services +technotes + +.idea +.DS_Store +.vscode + +node_modules + +.env + +dist/server +dist/i18n +dist/shared +dist/public/build + +dist/run/* +!dist/run/.keep + +secrets/* +!secrets/.keep + +tests/artifacts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ab17439 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[{*.json,*.yaml,*.md}] +indent_size = 2 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..916e638 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +dist +node_modules +src/i18n/keysets +tests/dist +build/ \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..8d91f99 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,26 @@ +{ + "extends": [ + "@gravity-ui/eslint-config", + "@gravity-ui/eslint-config/prettier", + "@gravity-ui/eslint-config/import-order" + ], + "root": true, + "env": { + "node": true, + "jest": true + }, + "rules": { + "@typescript-eslint/consistent-type-imports": "error", + "security/detect-child-process": 0, + "security/detect-non-literal-fs-filename": 0, + "camelcase": 0, + "no-param-reassign": [ + "warn", + { + "props": true, + "ignorePropertyModificationsFor": ["acc"] + } + ], + "no-console": ["error", {"allow": ["warn", "error"]}] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e953754 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +.idea +.DS_Store +.vscode +.history + +node_modules + +.env +.build + +dist/server/* +dist/ui/* +dist/i18n +dist/shared +dist/public/build +dist/run/* + +build/ + +secrets/* +!secrets/.keep + +tests/dist/* +src/i18n/keysets + +tests/metadata +tests/certs +tests/test-results +tests/artifacts +tests/logs + +reference.json +input.json +result.json +.last-run.json diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000..a2efbc2 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,14 @@ +{ + "!(src/ui/**/*)*.{js,jsx,ts,tsx}": [ + "eslint --fix --quiet", + "prettier --write" + ], + "src/ui/**/*.{js,jsx,ts,tsx}": [ + "eslint -c src/ui/.ci-eslintrc --fix --quiet", + "prettier --write" + ], + "*.{css,scss}": [ + "stylelint --fix --quiet", + "prettier --write" + ] +} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..2dbe828 --- /dev/null +++ b/.npmrc @@ -0,0 +1,4 @@ +unsafe-perm=true +registry=https://registry.npmjs.org + +frozen-lockfile=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..8fdd954 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..75eaa51 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +src/i18n/keysets +src/i18n-keysets +tests/dist +tests/artifacts +*.yaml +build/ \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..5da9c2e --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1 @@ +module.exports = require('@gravity-ui/prettier-config'); diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 0000000..45bcb5d --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,6 @@ +{ + "extends": ["@gravity-ui/stylelint-config", "@gravity-ui/stylelint-config/prettier"], + "rules": { + "declaration-block-no-redundant-longhand-properties": null + } +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..c7bf444 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +We welcome everyone to contribute to our product, see [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..498f2b5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Notice to external contributors + +## General info + +Hello! In order for us (YANDEX LLC) to accept patches and other contributions from you, you will have to adopt our Yandex Contributor License Agreement (the "**CLA**"). The current version of the CLA can be found here https://yandex.ru/legal/cla/?lang=en + +By adopting the CLA, you state the following: + +- You obviously wish and are willingly licensing your contributions to us for our open source projects under the terms of the CLA, +- You have read the terms and conditions of the CLA and agree with them in full, +- You are legally able to provide and license your contributions as stated, +- We may use your contributions for our open source projects and for any other our project too, +- We rely on your assurances concerning the rights of third parties in relation to your contributions. + +If you agree with these principles, please read and adopt our CLA. By providing us your contributions, you hereby declare that you have already read and adopt our CLA, and we may freely merge your contributions with our corresponding open source project and use it in further in accordance with terms and conditions of the CLA. + +## Provide contributions + +If you have already adopted terms and conditions of the CLA, you are able to provide your contributions. When you submit your pull request, please add the following information into it: + +``` +I hereby agree to the terms of the CLA available at: [link]. +``` + +Replace the bracketed text as follows: + +- [link] is the link to the current version of the CLA: https://yandex.ru/legal/cla/?lang=en. + +It is enough to provide us such notification once. + +## Other questions + +If you have any questions, please mail us at datalens-opensource@yandex-team.ru. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a8d0f80 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,133 @@ +ARG UBUNTU_VERSION=24.04 + +# use native build platform for build js files only once +FROM --platform=${BUILDPLATFORM} ubuntu:${UBUNTU_VERSION} AS native-build-stage + +ARG BUILDARCH +ARG NODE_MAJOR=22 +ARG PNPM_VERSION=10.17.1 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get -y upgrade && apt-get -y install ca-certificates curl gnupg + +# node +RUN mkdir -p /etc/apt/keyrings && \ + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list + +RUN apt-get update && apt-get -y install nodejs g++ make + +RUN npm install -g pnpm@${PNPM_VERSION} + +RUN useradd -m -u 1001 app && mkdir /opt/app && chown app:app /opt/app + +WORKDIR /opt/app + +COPY package.json pnpm-lock.yaml .npmrc /opt/app/ +RUN --mount=type=cache,id=pnpm-store-${BUILDARCH},target=/pnpm_store \ + pnpm config set store-dir=/pnpm_store --location=project && \ + pnpm config set cache-dir=/pnpm_cache --location=project && \ + pnpm config delete virtual-store-dir --location=project && \ + pnpm install --frozen-lockfile --prefer-offline + +COPY ./dist /opt/app/dist +COPY ./src /opt/app/src +COPY app-builder.config.ts tsconfig.json /opt/app/ + +RUN pnpm run build && chown app /opt/app/dist/run + +# runtime base image for both platform +FROM ubuntu:${UBUNTU_VERSION} AS base-stage + +ARG NODE_MAJOR=22 +ARG PNPM_VERSION=10.17.1 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && apt-get -y upgrade && apt-get -y install ca-certificates curl gnupg + +# node +RUN mkdir -p /etc/apt/keyrings && \ + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_MAJOR}.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list + +RUN apt-get update && apt-get -y install nginx supervisor nodejs + +RUN npm install -g pnpm@${PNPM_VERSION} + +# remove unnecessary packages +RUN apt-get -y purge curl gnupg gnupg2 && \ + apt-get -y autoremove && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* && \ + rm -rf /etc/apt/sources.list.d/nodesource.list && \ + rm -rf /etc/apt/keyrings/nodesource.gpg + +# timezone setting +ENV TZ="Etc/UTC" +RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# user app +RUN useradd -m -u 1001 app && mkdir /opt/app && chown app:app /opt/app + +# install package dependencies for production +FROM base-stage AS install-stage + +ARG TARGETARCH + +# install system dependencies +RUN apt-get update && apt-get -y install g++ make + +WORKDIR /opt/app + +COPY package.json pnpm-lock.yaml .npmrc /opt/app/ + +RUN --mount=type=cache,id=pnpm-store-${TARGETARCH},target=/pnpm_store \ + pnpm config set store-dir=/pnpm_store --location=project && \ + pnpm config set cache-dir=/pnpm_cache --location=project && \ + pnpm config delete virtual-store-dir --location=project && \ + pnpm install --frozen-lockfile --prefer-offline --prod + +# production running stage +FROM base-stage AS runtime-stage + +COPY deploy/nginx /etc/nginx +COPY deploy/supervisor/supervisord.conf /etc/supervisor/supervisord.conf + +# prepare rootless permissions for supervisor and nginx +ARG USER=app +RUN chown -R ${USER} /etc/nginx && \ + chown -R ${USER} /etc/supervisor && \ + rm -rf /etc/supervisor/conf.d && \ + rm -rf /etc/nginx/sites-available && \ + rm -rf /etc/nginx/sites-enabled && \ + rm -rf /etc/nginx/nginx-default.conf + +ARG app_version +ENV APP_VERSION=$app_version +ENV TMPDIR=/tmp + +WORKDIR /opt/app + +COPY --from=install-stage /opt/app/package.json /opt/app/pnpm-lock.yaml /opt/app/ +COPY --from=install-stage /opt/app/node_modules /opt/app/node_modules +COPY --from=native-build-stage /opt/app/dist /opt/app/dist + +RUN chown -R ${USER} /opt/app/dist/run + +USER app + +ENV NODE_ENV=production + +ENV APP_BUILDER_CDN=false +ENV UI_CORE_CDN=false + +ENV APP_MODE=full +ENV APP_ENV=production +ENV APP_INSTALLATION=opensource +ENV APP_PORT=3030 + +EXPOSE 8080 + +ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..106c6bf --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ +Copyright 2023 YANDEX LLC + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2023 YANDEX LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d094c32 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# DataLens + +### Prerequisites + +[Install docker](https://docs.docker.com/engine/install/) + +[Install docker compose plugin](https://docs.docker.com/compose/install/linux/) if it not already installed + +### Start project in dev mode + +#### In Docker mode (easy-mode): + +On Linux systems: + +```bash +git clone git@github.com:datalens-tech/datalens.git +cd datalens +./init.sh --ipv6 --docker-ipv6 --dev-light --dev-root --dev-ui +``` + +On macOS systems: + +```bash +git clone git@github.com:datalens-tech/datalens.git +cd datalens +./init.sh --dev-light --dev-ui +``` + +#### Local mode: + +Install Node.js >= v18.17.0 manually or via [node version manager](https://github.com/nvm-sh/nvm). + +Start project in dev mode: + +```bash +# Start backend for datalens: +git clone git@github.com:datalens-tech/datalens.git +cd datalens +docker compose -f docker-compose.dev.yaml up + +# Start datalens ui in dev mode: +git clone git@github.com:datalens-tech/datalens-ui.git +cd ui +pnpm install --frozen-lockfile +pnpm run dev +``` + +Now you can open datalens in dev mode at [http://localhost:8080](http://localhost:8080) + +### Credentials for postgres + +Hostname: + +``` +pg-demo-connection +``` + +Port: + +``` +5432 +``` + +Path to database: + +``` +demo +``` + +Username: + +``` +demo +``` + +Password: + +``` +demo +``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..4fe7ad1 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Reporting a Vulnerability + +We're extremely grateful for security researchers and users who report vulnerabilities they discovered in DataLens. All reports are thoroughly investigated. + +To report a potential vulnerability in DataLens please email details to [datalens-security@yandex-team.ru](mailto:datalens-security@yandex-team.ru). + +### When Should I Report a Vulnerability? + +- You think you discovered a potential security vulnerability in DataLens +- You are unsure how a vulnerability affects DataLens + +## Security Vulnerability Response + +Each report is acknowledged and analyzed by DataLens maintainers within 5 working days. +We will keep the reporter informed about the issue progress. + +## Public Disclosure Timing + +A public disclosure date is negotiated by DataLens maintainers and the bug submitter. We prefer to fully disclose the bug as soon as possible once a mitigation is available for DataLens users. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for vendor coordination. The timeframe for disclosure is from immediate (especially if it's already publicly known) to 90 days. For a vulnerability with a straightforward mitigation, we expect report date to disclosure date to be on the order of 7 days. diff --git a/api/server/app-env.ts b/api/server/app-env.ts new file mode 100644 index 0000000..93826c6 --- /dev/null +++ b/api/server/app-env.ts @@ -0,0 +1,8 @@ +export { + appEnv, + isChartsMode, + isDatalensMode, + isFullMode, + isApiMode, + isPublicApiMode, +} from '../../src/server/app-env'; diff --git a/api/server/callbacks.ts b/api/server/callbacks.ts new file mode 100644 index 0000000..443a1c7 --- /dev/null +++ b/api/server/callbacks.ts @@ -0,0 +1 @@ +export {onFail, onMissingEntry, defaultOnFail} from '../../src/server/callbacks'; diff --git a/api/server/charts-engine.ts b/api/server/charts-engine.ts new file mode 100644 index 0000000..db68aa8 --- /dev/null +++ b/api/server/charts-engine.ts @@ -0,0 +1,9 @@ +export { + initChartsEngine, + applyPluginRoutes, +} from '../../src/server/modes/charts/init-charts-engine'; + +export {resolveParams} from '../../src/server/components/charts-engine/components/utils'; +export {getTelemetryCallbacks} from '../../src/server/modes/charts/telemetry'; + +export {RUNNER_NAME} from '../../src/server/components/charts-engine/runners/constants'; diff --git a/api/server/components.ts b/api/server/components.ts new file mode 100644 index 0000000..b9e9fda --- /dev/null +++ b/api/server/components.ts @@ -0,0 +1,45 @@ +export {CacheClient} from '../../src/server/components/cache-client'; +export {RedisConfig, getRedisConfig} from '../../src/server/utils/redis'; +export { + getLandingLayout, + Utils, + getChartkitLayoutSettings, + getPlatform, +} from '../../src/server/components'; +export {getAppLayoutSettings} from '../../src/server/components/app-layout/app-layout-settings'; +export {default as resolveEntryByLink} from '../../src/server/components/resolve-entry-by-link'; +export {default as metrikaDataFormatter} from '../../src/server/components/metrika-data-formatter'; + +export { + ChartsEngine, + CommentsFetcher, + Console, + DataFetcher, +} from '../../src/server/components/charts-engine'; + +export {Request} from '../../src/server/components/charts-engine/components/request'; + +export {renderHTML} from '../../src/server/components/charts-engine/components/markdown'; + +export {initPublicApiSwagger} from '../../src/server/components/public-api'; + +export { + PUBLIC_API_ROUTE, + PUBLIC_API_VERSION, + PUBLIC_API_VERSION_HEADER, + PUBLIC_API_ACTION_REQ_PARAM, + PUBLIC_API_ACTION_NAME, +} from '../../src/server/components/public-api'; +export {getPublicApiActionsV1} from '../../src/server/components/public-api/config'; +export type { + PublicApiBaseConfig, + PublicApiConfig, + PublicApiSecuritySchemes, +} from '../../src/server/components/public-api/types'; +export {ApiTag as PublicApiTag} from '../../src/server/components/public-api/constants'; +export { + preparePublicApiBaseConfig, + parsePublicApiVersionHeader, +} from '../../src/server/components/public-api/utils'; + +export {createAuthArgsMiddleware} from '../../src/server/components'; diff --git a/api/server/configs.ts b/api/server/configs.ts new file mode 100644 index 0000000..a71027c --- /dev/null +++ b/api/server/configs.ts @@ -0,0 +1,4 @@ +export {default as common} from '../../src/server/configs/common'; +export {default as datalensChartTemplate} from '../../src/server/configs/shared/datalens-chart-template'; +export {default as qlChartTemplate} from '../../src/server/configs/shared/ql-chart-template'; +export {default as controlDashChartTemplate} from '../../src/server/configs/shared/control-dash-chart-template'; diff --git a/api/server/constants.ts b/api/server/constants.ts new file mode 100644 index 0000000..bf082fe --- /dev/null +++ b/api/server/constants.ts @@ -0,0 +1,3 @@ +export {SERVICE_NAME_DATALENS} from '../../src/server/constants'; +export {IPV6_AXIOS_OPTIONS} from '../../src/server/constants/axios'; +export {PUBLIC_API_ORG_ID_HEADER} from '../../src/server/constants/public-api'; diff --git a/api/server/controllers.ts b/api/server/controllers.ts new file mode 100644 index 0000000..ad2e10e --- /dev/null +++ b/api/server/controllers.ts @@ -0,0 +1,3 @@ +export {ping} from '../../src/server/controllers/ping'; +export {chartsController} from '../../src/server/components/charts-engine/controllers/charts'; +export {createPublicApiController} from '../../src/server/controllers'; diff --git a/api/server/dev-env.ts b/api/server/dev-env.ts new file mode 100644 index 0000000..aae169d --- /dev/null +++ b/api/server/dev-env.ts @@ -0,0 +1 @@ +import('../../src/server/local-dev'); diff --git a/api/server/expresskit.ts b/api/server/expresskit.ts new file mode 100644 index 0000000..2671bf9 --- /dev/null +++ b/api/server/expresskit.ts @@ -0,0 +1 @@ +export {getExpressKit} from '../../src/server/expresskit'; diff --git a/api/server/local-dev.ts b/api/server/local-dev.ts new file mode 100644 index 0000000..6e21a6f --- /dev/null +++ b/api/server/local-dev.ts @@ -0,0 +1 @@ +import '../../src/server/local-dev'; diff --git a/api/server/middlewares.ts b/api/server/middlewares.ts new file mode 100644 index 0000000..c3c3846 --- /dev/null +++ b/api/server/middlewares.ts @@ -0,0 +1,10 @@ +export { + xDlContext, + scrRequests, + getCtxMiddleware, + getConnectorIconsMiddleware, + beforeAuthDefaults, + serverFeatureWithBoundedContext, + patchLogger, + createAppLayoutMiddleware, +} from '../../src/server/middlewares'; diff --git a/api/server/plugins.ts b/api/server/plugins.ts new file mode 100644 index 0000000..47cf174 --- /dev/null +++ b/api/server/plugins.ts @@ -0,0 +1,7 @@ +export {configuredDashApiPlugin} from '../../src/server/modes/charts/plugins/dash-api'; +export {dashApiValidation} from '../../src/server/modes/charts/plugins/data-api-json-schema'; + +export {plugin as ql} from '../../src/server/modes/charts/plugins/ql'; +export {configurableRequestWithDatasetPlugin} from '../../src/server/modes/charts/plugins/request-with-dataset'; +export {plugin as loginsBlacklist} from '../../src/server/modes/charts/plugins/logins-blacklist'; +export {plugin as runnerKeyAdapter} from '../../src/server/modes/charts/plugins/runner-key-adapter'; diff --git a/api/server/registry.ts b/api/server/registry.ts new file mode 100644 index 0000000..8071e6d --- /dev/null +++ b/api/server/registry.ts @@ -0,0 +1,5 @@ +export {registry} from '../../src/server/registry'; +export {registerAppPlugins} from '../../src/server/registry/utils/register-app-plugins'; + +export {OnEmbedsControllerBeforeResponse} from '../../src/server/registry/units/common/functions-map/on-embeds-controller-before-response'; +export {OnEmbedsControllerStart} from '../../src/server/registry/units/common/functions-map/on-embeds-controller-start'; diff --git a/api/server/types.ts b/api/server/types.ts new file mode 100644 index 0000000..78d6338 --- /dev/null +++ b/api/server/types.ts @@ -0,0 +1,16 @@ +export type { + GetLayoutConfig, + AppLayoutSettings, + AppLayoutSettingsName, +} from '../../src/server/types/app-layout'; +export type { + BasicControllers, + ExtendedAppRouteDescription, +} from '../../src/server/types/controllers'; + +export type {Plugin, SourceConfig} from '../../src/server/components/charts-engine/types'; +export type {Graph} from '../../src/server/components/charts-engine/components/processor/comments-fetcher'; +export type {ResolvedConfig} from '../../src/server/components/charts-engine/components/storage/types'; +export type {PublicApiVersionConfig} from '../../src/server/components/public-api/types'; + +export type {AnyApiServiceActionConfig} from '../../src/server/types/gateway'; diff --git a/api/server/utils.ts b/api/server/utils.ts new file mode 100644 index 0000000..8743876 --- /dev/null +++ b/api/server/utils.ts @@ -0,0 +1,14 @@ +export { + getGatewayConfig, + isGatewayError, + GatewayApiErrorResponse, +} from '../../src/server/utils/gateway'; +export {getUtilsAxios} from '../../src/server/utils/axios'; +export {getConfiguredRoute, getDashboardsRedirectPath} from '../../src/server/utils/routes'; +export {addTranslationsScript} from '../../src/server/utils/language'; +export {getEnvCert} from '../../src/server/utils/env-utils'; + +import {default as utils} from '../../src/server/utils'; +export const getFormattedLogin = utils.getFormattedLogin; +export const getEnvVariable = utils.getEnvVariable; +export const getName = utils.getName; diff --git a/api/ui/components/Collapse.ts b/api/ui/components/Collapse.ts new file mode 100644 index 0000000..727d7e4 --- /dev/null +++ b/api/ui/components/Collapse.ts @@ -0,0 +1 @@ +export {Collapse} from '../../../src/ui/components/Collapse/Collapse'; diff --git a/api/ui/components/ViewError.ts b/api/ui/components/ViewError.ts new file mode 100644 index 0000000..25fbed1 --- /dev/null +++ b/api/ui/components/ViewError.ts @@ -0,0 +1 @@ +export {ViewError} from '../../../src/ui/components/ViewError/ViewError'; diff --git a/api/ui/constants/common.ts b/api/ui/constants/common.ts new file mode 100644 index 0000000..d25965f --- /dev/null +++ b/api/ui/constants/common.ts @@ -0,0 +1,2 @@ +export {DL, PRODUCT_NAME, DLS_SUBJECT, URL_OPTIONS} from '../../../src/ui/constants/common'; +export {SYSTEM_THEME} from '../../../src/shared'; diff --git a/api/ui/constants/yfm.ts b/api/ui/constants/yfm.ts new file mode 100644 index 0000000..04dc845 --- /dev/null +++ b/api/ui/constants/yfm.ts @@ -0,0 +1 @@ +export {YFM_MARKDOWN_CLASSNAME} from '../../../src/ui/constants/yfm'; diff --git a/api/ui/hocs.ts b/api/ui/hocs.ts new file mode 100644 index 0000000..6bc0911 --- /dev/null +++ b/api/ui/hocs.ts @@ -0,0 +1,2 @@ +export {withHiddenUnmount} from '../../src/ui/hoc/withHiddenUnmount'; +export {withEnabledFeature} from '../../src/ui/hoc/withEnabledFeature'; diff --git a/api/ui/hooks.ts b/api/ui/hooks.ts new file mode 100644 index 0000000..dcce453 --- /dev/null +++ b/api/ui/hooks.ts @@ -0,0 +1 @@ +export {usePrevious, useEffectOnce} from '../../src/ui/hooks'; diff --git a/api/ui/libs/logger.ts b/api/ui/libs/logger.ts new file mode 100644 index 0000000..b14efc7 --- /dev/null +++ b/api/ui/libs/logger.ts @@ -0,0 +1 @@ +export {default as logger} from '../../../src/ui/libs/logger'; diff --git a/api/ui/libs/metrica.ts b/api/ui/libs/metrica.ts new file mode 100644 index 0000000..4e61ff1 --- /dev/null +++ b/api/ui/libs/metrica.ts @@ -0,0 +1 @@ +export {CounterName, GoalId, reachMetricaGoal} from '../../../src/ui/libs/metrica'; diff --git a/api/ui/libs/oldSdk.ts b/api/ui/libs/oldSdk.ts new file mode 100644 index 0000000..8c38927 --- /dev/null +++ b/api/ui/libs/oldSdk.ts @@ -0,0 +1,2 @@ +export type {ConfigSdk} from '../../../src/ui/libs/sdk/types'; +export {default as SDK, sdk} from '../../../src/ui/libs/sdk'; diff --git a/api/ui/libs/sdk.ts b/api/ui/libs/sdk.ts new file mode 100644 index 0000000..80a6791 --- /dev/null +++ b/api/ui/libs/sdk.ts @@ -0,0 +1,5 @@ +export { + handleRequestError, + registerSDKDispatch, +} from '../../../src/ui/libs/schematic-sdk/parse-error'; +export type {OperationError, DatalensSdk} from '../../../src/ui/libs/schematic-sdk'; diff --git a/api/ui/tsconfig.json b/api/ui/tsconfig.json new file mode 100644 index 0000000..f06a201 --- /dev/null +++ b/api/ui/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "../../dist", + "resolveJsonModule": true, + "jsx": "react", + "module": "ESNext", + }, + "include": [ + "**/*" + ] +} diff --git a/api/ui/typings.ts b/api/ui/typings.ts new file mode 100644 index 0000000..1f9ecc2 --- /dev/null +++ b/api/ui/typings.ts @@ -0,0 +1 @@ +export type {DataLensApiError} from '../../src/ui/typings'; diff --git a/api/ui/utils/absurd.ts b/api/ui/utils/absurd.ts new file mode 100644 index 0000000..1d316a4 --- /dev/null +++ b/api/ui/utils/absurd.ts @@ -0,0 +1 @@ +export {absurd} from '../../../src/ui/utils/absurd'; diff --git a/api/ui/utils/common.ts b/api/ui/utils/common.ts new file mode 100644 index 0000000..f138897 --- /dev/null +++ b/api/ui/utils/common.ts @@ -0,0 +1,4 @@ +import {default as utils} from '../../../src/ui/utils/utils'; + +export const getEntryNameFromKey = utils.getEntryNameFromKey; +export const parseErrorResponse = utils.parseErrorResponse; diff --git a/app-builder.config.ts b/app-builder.config.ts new file mode 100644 index 0000000..e66618d --- /dev/null +++ b/app-builder.config.ts @@ -0,0 +1,101 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +import type {ServiceConfig} from '@gravity-ui/app-builder'; +// eslint-disable-next-line import/no-extraneous-dependencies +import type {FileCacheOptions, MemoryCacheOptions} from 'webpack'; + +const appDirectory = fs.realpathSync(process.cwd()); +const resolveApp = (relativePath: string) => path.resolve(appDirectory, relativePath); + +const getFileCacheConfig = () => { + if (process.env.APP_ENV === 'development') { + return { + type: 'filesystem', + } as FileCacheOptions; + } else { + return { + type: 'memory', + } as MemoryCacheOptions; + } +}; + +const vendors = (vendorsList: string[]) => { + return vendorsList.concat([ + 'react-split-pane', + 'react-dnd', + 'react-grid-layout', + 'react-beautiful-dnd', + '@floating-ui/react', + ]); +}; + +const devClientPort = process.env?.['DEV_CLIENT_PORT']; +const devServerPort = process.env?.['DEV_SERVER_PORT']; + +const config: ServiceConfig = { + client: { + bundler: 'rspack', + alias: { + i18n: 'src/i18n', + shared: 'src/shared', + ui: 'src/ui', + }, + modules: [ + 'node_modules', + resolveApp('node_modules'), + resolveApp('src/ui'), + resolveApp('src/ui/units'), + ], + includes: ['src/shared', 'src/i18n', 'node_modules/monaco-editor/esm/vs'], + excludeFromClean: ['!i18n', '!i18n/**/*'], + vendors, + icons: ['src/ui/assets/icons', 'node_modules/@gravity-ui/icons'], + monaco: { + languages: ['typescript', 'javascript', 'json', 'sql', 'mysql'], + }, + polyfill: { + process: true, + }, + disableReactRefresh: true, + contextReplacement: { + locale: ['ru', 'en'], + }, + watchOptions: { + ignored: '**/server', + aggregateTimeout: 1000, + }, + cache: getFileCacheConfig(), + externals: { + highcharts: 'Highcharts', + }, + fallback: { + url: require.resolve('url'), + 'react/jsx-runtime': require.resolve('react/jsx-runtime'), + path: false, + fs: false, + 'cose-base': false, + 'layout-base': false, + 'highlight.js': false, + buffer: false, + }, + javaScriptLoader: 'swc', + ...(devClientPort + ? { + devServer: { + port: parseInt(devClientPort, 10), + }, + } + : {}), + }, + server: { + watch: ['dist/i18n', 'dist/shared'], + ...(devServerPort + ? { + port: parseInt(devServerPort, 10), + } + : {}), + }, +}; + +export default config; diff --git a/deploy/nginx/entrypoint.sh b/deploy/nginx/entrypoint.sh new file mode 100644 index 0000000..6b125ba --- /dev/null +++ b/deploy/nginx/entrypoint.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +set -e + +DEFAULT_CONF_FILE="etc/nginx/nginx.conf" + +# check if we have ipv6 available +if [ -f "/proc/net/if_inet6" ]; then + # check config file exists + if [ -f "/$DEFAULT_CONF_FILE" ]; then + # check write permission + if touch /$DEFAULT_CONF_FILE 2>/dev/null; then + # check if the file is already modified, e.g. on a container restart + if grep -q -s "listen \[::]\:8080;" /$DEFAULT_CONF_FILE; then + echo '{"level":"INFO","msg":"ipv6 listen already enabled"}' + else + # enable ipv6 on nginx.conf listen sockets + sed -i -E 's|listen 8080;|listen 8080;\n listen [::]:8080;|' /$DEFAULT_CONF_FILE + fi + else + echo '{"level":"INFO","msg":"can not modify /'"${DEFAULT_CONF_FILE}"' (read-only file system?)"}' + fi + else + echo '{"level":"INFO","msg":"/'"${DEFAULT_CONF_FILE}"' is not a file or does not exist"}' + fi +else + echo '{"level":"INFO","msg":"ipv6 not available"}' +fi + +exec '/usr/sbin/nginx' diff --git a/deploy/nginx/nginx.conf b/deploy/nginx/nginx.conf new file mode 100644 index 0000000..d3dfb21 --- /dev/null +++ b/deploy/nginx/nginx.conf @@ -0,0 +1,121 @@ +worker_processes auto; + +error_log /proc/self/fd/1 crit; +pid /tmp/nginx.pid; +daemon off; + +events { + worker_connections 1024; +} + +http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + map $msec $msec_no_decimal { ~(.*)\.(.*) $1$2; } + + map "$http_x_forwarded_for:$remote_addr" $fallback_forwarded_for { + default "$http_x_forwarded_for, $realip_remote_addr"; + "~^:" $realip_remote_addr; + } + + log_format custom escape=json + '{' + '"time":$msec_no_decimal,' + '"pid":"$pid",' + '"name":"datalens-ui",' + '"hostname":"$hostname",' + '"req":{' + '"id":"$request_id",' + '"method":"$request_method",' + '"url":"$request_uri",' + '"referer":"$http_referer",' + '"user_agent":"$http_user_agent",' + '"headers":{' + '"host":"$host",' + '"x-forwarded-for":"$fallback_forwarded_for"' + '}' + '},' + '"res":{' + '"statusCode": $status' + '},' + '"responseTime":"$request_time"' + '"level":"ACCESS",' + '"msg":"[$status] [$request_time] $http_host $remote_addr `$request` [$request_id]"' + '}'; + + access_log /proc/self/fd/1 custom; + error_log /proc/self/fd/1 crit; + + keepalive_timeout 100; + proxy_connect_timeout 365s; + proxy_send_timeout 365s; + proxy_read_timeout 365s; + + ssl_prefer_server_ciphers on; + ssl_protocols TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; + + ssl_session_cache shared:SSL:128m; + ssl_session_timeout 28h; + + types_hash_max_size 2048; + + server_names_hash_bucket_size 64; + + proxy_buffer_size 32k; + proxy_buffers 8 32k; + + fastcgi_buffers 8 32k; + fastcgi_buffer_size 32k; + + client_header_buffer_size 5120k; + large_client_header_buffers 16 5120k; + + client_max_body_size 200m; + client_body_buffer_size 128k; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + gzip on; + gzip_types text/plain text/css application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json; + + server { + listen 8080; + + add_header Access-Control-Allow-Headers "Content-Type,X-Request-ID,X-Charts-Cache-Token,X-Charts-Request-ID,X-CSRF-Token,X-DL-Allow-Superuser,X-DL-Sudo,X-Chart-Id,X-Timezone-Offset,X-Dash-Info,x-dl-tenantid"; + add_header Access-Control-Expose-Headers "X-Request-Id, X-Trace-Id" always; + add_header Access-Control-Allow-Credentials "true" always; + add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; + add_header Access-Control-Max-Age 86400; + + if ($request_method = OPTIONS) { + return 200; + } + + proxy_hide_header Access-Control-Allow-Origin; + + root /opt/app/dist/public; + + location / { + try_files $uri @node; + } + + location @node { + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://localhost:3030; + proxy_redirect off; + } + } + + fastcgi_intercept_errors on; +} diff --git a/deploy/supervisor/supervisord.conf b/deploy/supervisor/supervisord.conf new file mode 100644 index 0000000..643f220 --- /dev/null +++ b/deploy/supervisor/supervisord.conf @@ -0,0 +1,28 @@ +[supervisord] +nodaemon=true +pidfile=/tmp/supervisord.pid +logfile=/dev/null +logfile_maxbytes=0 +childlogdir=/tmp + +[unix_http_server] +file=/tmp/supervisor.sock + +[supervisorctl] +serverurl=unix:///tmp/supervisor.sock + +[program:node] +command=node dist/server +autostart=true +autorestart=unexpected +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +redirect_stderr=true + +[program:nginx] +command=/etc/nginx/entrypoint.sh +autostart=true +autorestart=unexpected +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +redirect_stderr=true diff --git a/dev-nginx/nginx.example.conf b/dev-nginx/nginx.example.conf new file mode 100644 index 0000000..ec20b01 --- /dev/null +++ b/dev-nginx/nginx.example.conf @@ -0,0 +1,37 @@ +server { + listen 80; + server_name localhost; + + root /dist/public/; + + access_log logs/opensource-datalens.access.log; + error_log logs/log/nginx/opensource-datalens.error.log; + + location / { + try_files $uri @node; + } + + location /build/ { + try_files $uri @build; + } + + location @node { + proxy_pass http://unix:/dist/run/server.sock; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Request-ID $request_id; + proxy_redirect off; + } + + location @build { + proxy_pass http://unix:/dist/run/client.sock; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Request-ID $request_id; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_redirect off; + } + +} diff --git a/dev/env/opensource/development.env b/dev/env/opensource/development.env new file mode 100644 index 0000000..c4075de --- /dev/null +++ b/dev/env/opensource/development.env @@ -0,0 +1,6 @@ +APP_MODE=full +APP_ENV=development +APP_INSTALLATION=opensource +APP_DEV_MODE=1 + +US_MASTER_TOKEN=us-master-token diff --git a/dist/deploy-public/robots/external/production/robots.txt b/dist/deploy-public/robots/external/production/robots.txt new file mode 100644 index 0000000..ade1f4d --- /dev/null +++ b/dist/deploy-public/robots/external/production/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: / +Disallow: /*? diff --git a/dist/public/favicon.ico b/dist/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c4e599771a8112da3ef762fb03c1c73d144eadfb GIT binary patch literal 1047 zcmV+y1nB#TP)Px&(Md!>R9HvFmrra|RTRd5_q|G|kTwH!gVbb*7^VwjiLr*TP?VK1#3D^Sb)Eb6zD< z>2kJKr55SEaWT}6GFs;imikIC(M^so)S01F*y=z4dLkKRa=Mf82W^-pJRXJgBN)2_ zPyd9Gp)ybaT6Yo2b_Oxx3qX(Eim|M&ABbUAfgwKw40G;hKxP{5-hi8z;A(FvFex-! z{MhgS&?CP$5n0}gQnqm!egFiF`I0?9K4OEodLEvP27yWO&x;oI?eL&`K>+~Eb8|(% zWn4TC@?MOqIY!*O36Kk?!x-@}kq4lY$tWw#fdb=>_f>PAieY*7Gwud*Aky0<)?u?|B&Yr2sf#NwoThMC&ef=61VvfaTAk-l$31N#>$nCdFfU z?>v`d-0}P2Z8){dp9#%6NlF{QAIWZvs@6sG-c9_a9yoj48;Us=lvUy0KRCgYF~~fF zzh@yc<)6v5D1Gw#^DJ(N!_nR_081Q#Priap+rvVzVF1+sn1oAl3}1Z{wtZ&T1ZRA{6&^n< zjF5E<7qbwpe+xFXcq?OQA(B$O`O_rNC!);GK>Dvjl#VEox2s>a=jvavOY!nLSR40N zzT!+g*5WVWsxvF?W_0h@1g6=RS3XY1TZkwrHXhWBg#Ob*d*<8m3zf9K75QZ;Ay;d5 z-WTh_AZ6r$5QTL7;h7F5EpVabMIhZPU);nHK{Z@_dTe@f-k4V^(22ihj&r>jm5g2{|lXpl7a#% RJ+J@(002ovPDHLkV1lz0@M-`6 literal 0 HcmV?d00001 diff --git a/dist/public/os-favicon.ico b/dist/public/os-favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3bf11cfcb1b34e660ec826ea432c4f6bdea65ecd GIT binary patch literal 1681 zcmV;C25$L@P)Px*OG!jQR9Hu?mv4}kRT;*A*Lj{7HiKP47H}$4K`Z}d6MZmCGi%HOnW=Q9W(1uk zoEShFB4Kx%dCOhEhQ`W2jaVOyKGc__Nt&n+Mb_Xj;y4>NnKEsNSb~K>$nN_-=gRZ! zv+Dx8?5doZci#7T-uv9=y6%NYd=(y8udEjzq#avq-0nG!>M`V~j@Z)mWA@N-E zfAsP_9h!)1jcZhQ9vXCjZv)N&CXX{`0C$5}fpMdwB27N^AR@C}p62D+S z=0YZgL-7t!7x1=ft|k4k$oceWAWi|0clzomZ^oQkId}p1H_!^q0;Zk>DgwRWhhr@~ zKwSfDa9cRSE#Otad6oT>xwUv;opz6$e8g>LcpX69Em@Oowy#c$BX5q) zu#ye>@2K0b#5HDdX)vEBdL?2Ih}b9e)uLOEmd|Vaj*!KBK#S79lD3cBj$r_yvsecD zL1_d}6g6E>UH=*Plq7??=VlE*WVoh^^i@e;D7y9QArM*aZ8je+gTpIgPj=rR-ZYkb za%l7On0Da#*spnP)Jqdue8{No*3hH_`x}+tgL%|71MBtI(xmR0q2Zmtdw>J6rgx=! zQQFsVQQQDZ$1UO8@4y~lip*Knv1%C7F**ty5lUeDB=*Y9{mqc~F_V3)P=Dz3{oti# zbY@4<)b)HvN78fX@^b8y<+RX-*psalT|PhH$1yYCP{gkrC*nDdIYjU;^B6WY1U=+D#=Dl$;mf8jwCM7E`OOnx29> z1+?i$>;*0?vmpLlbn9bfKGKBFq6>5vFc2s{rO@SW?9LKxtEO}3V7kT~b?14(l`+Qo zpc^X|%u!WWBOJu2OBkw!yFE1=_RA?CSzlJ@^d|6EfM?T+D-P zvGENjm`F=ww|-c@TjE-1ckdv}dXqwDd>Xh|VtaA9uH$AEZwkPONm-XaWBXxXMpbb4 z#hz?=`THhk*+u-$_*&pE3BW6fW(8Y|nr@`7M6GXeSXn1uGX81lNd8&u&c0X`Vo-xE z!V7>`5`fgV+l00jHO>$K$h-XUX#aj90MdS3Ci7Tvg|0mlTfnNrGeXOh-vS?;z!vyH z@Dkwf@qjLU(?yti!wlXVU?K7f7{M#iOXK?N)FPA~ROl#HA#KDIF{*vc<}+4Rtnu6s z;zFd`tJa$jQ+ze&w8yX{qUbG2+X;4B?kic1a16(O6pUhqLk*Q8W2#KM@r+V4XjHY7 z3b`byl8)EC)`EwKsH6BM(zi<@3i{-kpyISn;*R4;hlo6cd`rcI<5TfqGh!on<%G_j zDN@h3=o9)UG4_T$>~2Q(lCvi?;&(yIhNB=IpzoLK1h0#CWu1fVII+`A@5Rh6 z+i^{wC_7)1HX5{0;~3@`O^$N65^tP3>Mys3V)|;LlO9Y>;HB8)s$q51;aMSgXC?Ix z#B=pdZdYT3AW1FMNhG{+L9)i@WmzdmuB!_0xQI6wJ^8OH@|m7B*k+uhnNx;hj~*y- z4u`Vble1Q~8Yd@4+8lGfHl`}7JE!215LsAB_d&60_DgYD(UYf^oP@~q(2VzBrMgJL z{h_Goh9O;eq9|SBvqF0*N>eMf(5}MF9~PzQ)f(o6EPf7@E-LSaN(&A~vL{NPrCZOG zPaU3>iARk;R0eBy#Yy_;=%TbrMJdbiPb%q@BHE|G3pQ;~*2uJz3Ly3Qr%PFWINV#_ zgzBCa8gUWQs$qF=cID{S)64Q9&x03}$AI^r=47O^{;aHZ#Swl^Kan}Kgu~o{+*zrf za#IA`N2YzG&#|v&W!1(-SgWBFt0#|xhFze608@<9y)oS/src/ui'], + ...getJestBaseConfig({isUIProject: true}), + }, + { + displayName: 'Shared', + testEnvironment: 'node', + roots: ['/src/shared'], + ...getJestBaseConfig(), + }, + { + displayName: 'Server', + testEnvironment: 'node', + roots: ['/src/server'], + ...getJestBaseConfig(), + }, + ], +}; + +export default jestConfig; diff --git a/jest/base-config.ts b/jest/base-config.ts new file mode 100644 index 0000000..1fb4081 --- /dev/null +++ b/jest/base-config.ts @@ -0,0 +1,51 @@ +import type {GlobalConfigTsJest, InitialOptionsTsJest} from 'ts-jest'; + +import {getIgnoredNodeModulesRegexp} from './mappers/ignore-node-modules-mapper'; +import {CSS_MAPPER, TYPESCRIPT_ALIASES_MAPPER} from './mappers/moduleNameMappers'; +import {UI_GLOBAL_MOCK_PATH} from './mocks'; +import { + TESTING_LIBRARY_SETUP_AFTER_ENV_FILE_PATH, + TESTING_LIBRARY_SETUP_FILE_PATH, +} from './setup-files'; +import {IMAGE_TRANSFORMER} from './transformers'; + +const tsconfig = require('../tsconfig.jest.json'); + +export const getJestBaseConfig = (options?: {isUIProject?: boolean}): InitialOptionsTsJest => { + const transform = {}; + const globals: GlobalConfigTsJest = { + 'ts-jest': { + tsconfig: 'tsconfig.jest.json', + isolatedModules: true, + }, + }; + const moduleNameMapper = { + ...TYPESCRIPT_ALIASES_MAPPER, + }; + + const setupFiles: string[] = []; + const setupFilesAfterEnv: string[] = []; + const transformIgnorePatterns: string[] = []; + + if (options?.isUIProject) { + Object.assign(transform, IMAGE_TRANSFORMER); + Object.assign(moduleNameMapper, CSS_MAPPER); + + setupFiles.push(TESTING_LIBRARY_SETUP_FILE_PATH, UI_GLOBAL_MOCK_PATH); + setupFilesAfterEnv.push(TESTING_LIBRARY_SETUP_AFTER_ENV_FILE_PATH); + + transformIgnorePatterns.push(getIgnoredNodeModulesRegexp()); + } + + return { + preset: 'ts-jest/presets/js-with-ts', + modulePaths: [tsconfig.compilerOptions.baseUrl], + testMatch: ['**/*.test.[jt]s?(x)'], + moduleNameMapper, + transform, + setupFiles, + setupFilesAfterEnv, + transformIgnorePatterns, + globals, + }; +}; diff --git a/jest/mappers/ignore-node-modules-mapper.ts b/jest/mappers/ignore-node-modules-mapper.ts new file mode 100644 index 0000000..e251d3f --- /dev/null +++ b/jest/mappers/ignore-node-modules-mapper.ts @@ -0,0 +1,29 @@ +// Usually jest ignores node_modules, since in an ideal world all modules are already correctly assembled +// Here we add modules that need to be run through the ts-jest transformer +const IGNORE_NODE_MODULES_LIST = [ + '@gravity-ui', + '@diplodoc/latex-extension', + '@diplodoc/mermaid-extension', + 'react-dnd', + 'dnd-core', + 'monaco-editor', + 'tinygesture', + 'jsondiffpatch', + 'd3', + 'd3-array', + 'internmap', + 'delaunator', + 'robust-predicates', +]; + +export const getIgnoredNodeModulesRegexp = () => { + // Create pattern that matches module names in both npm/yarn and pnpm structures: + // - npm/yarn: node_modules/@scope/package/... + // - pnpm: node_modules/.pnpm/@scope+package@version/node_modules/@scope/package/... + // We need to handle both / and + as separators (pnpm uses + instead of / in .pnpm folder) + const modulesToTransform = IGNORE_NODE_MODULES_LIST.map((module) => + module.replace(/[@/]/g, '[@/+]'), + ).join('|'); + + return `node_modules/(?!.*(?:${modulesToTransform}))`; +}; diff --git a/jest/mappers/moduleNameMappers.ts b/jest/mappers/moduleNameMappers.ts new file mode 100644 index 0000000..4244511 --- /dev/null +++ b/jest/mappers/moduleNameMappers.ts @@ -0,0 +1,13 @@ +import {pathsToModuleNameMapper} from 'ts-jest'; + +import {UI_STYLE_MOCK_PATH} from '../mocks'; + +const tsconfig = require('../../tsconfig.jest.json'); + +export const TYPESCRIPT_ALIASES_MAPPER = pathsToModuleNameMapper(tsconfig.compilerOptions.paths, { + prefix: '', +}); + +export const CSS_MAPPER = { + '\\.(scss|css)$': UI_STYLE_MOCK_PATH, +}; diff --git a/jest/mocks/globals.mock.js b/jest/mocks/globals.mock.js new file mode 100644 index 0000000..64ac4f6 --- /dev/null +++ b/jest/mocks/globals.mock.js @@ -0,0 +1,71 @@ +// The window implementation in jest does not contain a matchMedia field. +// Official description in the documentation +// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom + +/* eslint-disable */ +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); + +HTMLCanvasElement.prototype.getContext = () => { + // return whatever getContext has to return +}; + +// Adding the DL object to the Window. +Object.defineProperty(window, 'DL', { + writable: true, + value: { + user: {}, + requestId: '', + endpoints: { + charts: '', + }, + userSettings: {}, + }, +}); + +jest.mock(`../../src/ui/utils/utils.ts`, () => { + return { + isIframe: jest.fn(), + isEnabledFeature: jest.fn(), + }; +}); + +jest.mock(`../../src/ui/libs/schematic-sdk/index.ts`, () => { + return { + iam: {}, + bi: {}, + system: {}, + banners: {}, + }; +}); + +jest.mock(`../../src/ui/libs/userSettings/index.ts`, () => { + return { + UserSettings: { + getInstance: () => { + return { + getSettings: () => { + return {}; + }, + }; + }, + }, + }; +}); + +jest.mock('@gravity-ui/chartkit/gravity-charts', () => { + return { + CustomShapeRenderer: { + pieCenterText: () => {}, + }, + }; +}); diff --git a/jest/mocks/index.ts b/jest/mocks/index.ts new file mode 100644 index 0000000..dfca906 --- /dev/null +++ b/jest/mocks/index.ts @@ -0,0 +1,3 @@ +const PATH_TO_MOCKS = `/jest/mocks`; +export const UI_GLOBAL_MOCK_PATH = `${PATH_TO_MOCKS}/globals.mock.js`; +export const UI_STYLE_MOCK_PATH = `${PATH_TO_MOCKS}/style.mock.js`; diff --git a/jest/mocks/style.mock.js b/jest/mocks/style.mock.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/jest/mocks/style.mock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/jest/setup-files/index.ts b/jest/setup-files/index.ts new file mode 100644 index 0000000..bfe75d1 --- /dev/null +++ b/jest/setup-files/index.ts @@ -0,0 +1,3 @@ +const SETUP_FILES_PATH = '/jest/setup-files'; +export const TESTING_LIBRARY_SETUP_FILE_PATH = `${SETUP_FILES_PATH}/testing-library.setup.ts`; +export const TESTING_LIBRARY_SETUP_AFTER_ENV_FILE_PATH = `${SETUP_FILES_PATH}/testing-library.setup-after.ts`; diff --git a/jest/setup-files/testing-library.setup-after.ts b/jest/setup-files/testing-library.setup-after.ts new file mode 100644 index 0000000..6440fb7 --- /dev/null +++ b/jest/setup-files/testing-library.setup-after.ts @@ -0,0 +1,2 @@ +// https://github.com/testing-library/jest-dom#usage +import '@testing-library/jest-dom'; diff --git a/jest/setup-files/testing-library.setup.ts b/jest/setup-files/testing-library.setup.ts new file mode 100644 index 0000000..dbdb0e0 --- /dev/null +++ b/jest/setup-files/testing-library.setup.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import {configure} from '@testing-library/dom'; + +configure({testIdAttribute: 'data-qa'}); diff --git a/jest/transformers/image-transformer.js b/jest/transformers/image-transformer.js new file mode 100644 index 0000000..57c3dc8 --- /dev/null +++ b/jest/transformers/image-transformer.js @@ -0,0 +1,11 @@ +const path = require('path'); + +// https://jestjs.io/docs/code-transformation#examples + +module.exports = { + process(_sourceText, sourcePath) { + return { + code: `module.exports = ${JSON.stringify(path.basename(sourcePath))};`, + }; + }, +}; diff --git a/jest/transformers/index.ts b/jest/transformers/index.ts new file mode 100644 index 0000000..9aa6122 --- /dev/null +++ b/jest/transformers/index.ts @@ -0,0 +1,7 @@ +const JEST_PATH_TO_IMAGE_TRANSFORMER = '/jest/transformers/image-transformer.js'; +const JEST_IMAGE_TRANSFORMER_REGEXP = + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$'; + +export const IMAGE_TRANSFORMER = { + [JEST_IMAGE_TRANSFORMER_REGEXP]: JEST_PATH_TO_IMAGE_TRANSFORMER, +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..242842b --- /dev/null +++ b/package.json @@ -0,0 +1,295 @@ +{ + "name": "@datalens-ui/opensource", + "version": "0.0.0", + "files": [ + "build", + "scripts" + ], + "description": "DataLens Opensource", + "private": true, + "scripts": { + "preinstall": "npx only-allow pnpm", + "deps:global": "pnpm --version | grep -q 10.17.1 && echo 'pnpm@10.17.1 already installed' || npm i -g pnpm@10.17.1", + "i18n:prepare": "tsc src/i18n/prepare-keysets/index.ts --esModuleInterop --outDir dist/i18n && node dist/i18n/prepare-keysets/index.js", + "dev": "pnpm run i18n:prepare && SETUP_DEV_ENV_INSTALLATION=opensource node ./scripts/setup-dev-env.js && APP_BUILDER_ENTRY_FILTER=dl-main LOCAL_DEV_PORT=8080 app-builder dev", + "build": "pnpm run i18n:prepare && NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 APP_BUILDER_CDN=false app-builder build", + "build:analyze": "pnpm run i18n:prepare && APP_BUILDER_ANALYZE_BUNDLE=statoscope UI_CORE_CDN=false APP_BUILDER_CDN=false NODE_ENV=production NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 app-builder build", + "cdn": "pnpm run i18n:prepare && NODE_ENV=production NODE_OPTIONS=--max_old_space_size=4096 app-builder build", + "start": "NODE_ENV=production node dist/server", + "lint:js:main": "eslint --ignore-pattern 'src/ui/*' '**/*.{js,jsx,ts,tsx}' --quiet", + "lint:js:ui": "eslint -c src/ui/.ci-eslintrc 'src/ui/**/*.{js,jsx,ts,tsx}' --quiet", + "lint:styles": "stylelint 'src/ui/**/*.scss'", + "lint:prettier:src": "prettier --check 'src/**/*.{js,jsx,ts,tsx,json,css,scss}'", + "lint:prettier:tests": "prettier --check 'tests/**/*.{js,jsx,ts,tsx,json,css,scss}'", + "lint": "pnpm run lint:prettier:src && pnpm run lint:js:main && pnpm run lint:js:ui && pnpm run lint:styles && pnpm run lint:features", + "lint:fix": "pnpm run lint:js:main --fix && pnpm run lint:js:ui --fix && pnpm run lint:styles --fix", + "lint:features": "bash scripts/ci/lint-typecheck/features.sh", + "typecheck:i18n": "tsc -p src/i18n --noEmit", + "typecheck:server": "tsc -p src/server --noEmit", + "typecheck:ui": "tsc -p src/ui --noEmit", + "typecheck:tests": "cd tests && tsc --noEmit", + "typecheck": "pnpm run i18n:prepare && pnpm run typecheck:server && pnpm run typecheck:ui && pnpm run typecheck:tests", + "test:jest": "pnpm run i18n:prepare && jest", + "test:jestWatch": "pnpm run i18n:prepare && jest --watchAll", + "platform-tools": "platform-tools", + "test:install:chromium": "npx playwright install --with-deps chromium", + "test:e2e": "cd tests && npx playwright test --config=./playwright.config.ts --project=basic", + "test:e2e:opensource": "cd tests && npx playwright test --config=./playwright.config.ts --project=opensource", + "test:e2e:us-dump": "./scripts/e2e/us-dump.sh --clear-deleted --clear-e2e --clear-revisions", + "test:e2e:docker": "cd ./tests && docker compose -f ./docker-compose.e2e.yml down --volumes && docker compose -f ./docker-compose.e2e.yml up --quiet-pull --pull always --build --no-log-prefix --exit-code-from e2e e2e", + "test:e2e:docker:snapshots": "cd ./tests && docker compose -f ./docker-compose.e2e.yml down --volumes && E2E_TEST_NAME_PATTERN=${E2E_TEST_NAME_PATTERN:-@screenshot} E2E_RETRY_TIMES=0 E2E_MAX_WORKERS=1 E2E_UPDATE_SNAPSHOTS=1 docker compose -f ./docker-compose.e2e.yml up --quiet-pull --pull always --build --no-log-prefix e2e; docker compose -f ./docker-compose.e2e.yml cp e2e:/opt/app/tests/opensource-suites/__screenshots__ ./opensource-suites", + "test:e2e:docker:report": "cd ./tests && docker compose -f ./docker-compose.e2e.yml cp e2e:/opt/app/tests/artifacts ./artifacts", + "test:e2e:docker:up": "cd ./tests && docker compose -f ./docker-compose.e2e.yml up --quiet-pull --pull always --build -d ui", + "test:e2e:docker:up-no-build": "cd ./tests && docker compose -f ./docker-compose.e2e.yml up --no-build --quiet-pull --pull always -d ui", + "test:e2e:docker:up-no-ui": "cd ./tests && docker compose -f ./docker-compose.e2e.dev.yml up --pull always -d data-api control-api us auth", + "test:e2e:docker:up-no-ui-no-auth": "cd ./tests && AUTH_TYPE=NONE AUTH_ENABLED=false docker compose -f ./docker-compose.e2e.dev.yml up --pull always -d data-api control-api us", + "test:e2e:docker:up-nginx": "cd ./tests && docker compose -f ./docker-compose.e2e.nginx.yml up -d nginx", + "test:e2e:docker:down": "cd ./tests && docker compose -f ./docker-compose.e2e.yml down --volumes", + "test:e2e:docker:down-nginx": "cd ./tests && docker compose -f ./docker-compose.e2e.nginx.yml down --volumes", + "test:e2e:docker:logs": "cd ./tests && docker --log-level error compose -f ./docker-compose.e2e.yml logs --no-color", + "test:e2e:docker:logs-nginx": "cd ./tests && docker --log-level error compose -f ./docker-compose.e2e.nginx.yml logs --no-color", + "statoscope:validate-diff": "statoscope validate --input input.json --reference reference.json", + "statoscope:validate": "statoscope validate --input ./dist/public/build/stats.json" + }, + "repository": { + "type": "git", + "url": "git@github.com:datalens-tech/datalens-ui.git" + }, + "author": "DataLens Team ", + "license": "Apache-2.0", + "dependencies": { + "@asteasolutions/zod-to-openapi": "^8.1.0", + "@braintree/sanitize-url": "^6.0.0", + "@datalens-tech/ui-sandbox-modules": "^0.36.0", + "@datalens-tech/xlsx": "^0.20.1", + "@diplodoc/cut-extension": "^0.7.4", + "@diplodoc/file-extension": "^0.2.1", + "@diplodoc/latex-extension": "^1.1.0", + "@diplodoc/mermaid-extension": "^1.4.0", + "@diplodoc/tabs-extension": "^3.7.5", + "@diplodoc/transform": "^4.64.1", + "@gravity-ui/app-layout": "^2.1.0", + "@gravity-ui/browserslist-config": "^4.3.0", + "@gravity-ui/chartkit": "^7.44.0", + "@gravity-ui/dashkit": "^10.0.0", + "@gravity-ui/date-utils": "^2.5.6", + "@gravity-ui/expresskit": "^3.0.2", + "@gravity-ui/gateway": "^4.10.4", + "@gravity-ui/i18n": "^1.7.0", + "@gravity-ui/nodekit": "^2.10.1", + "@node-rs/crc32": "^1.7.2", + "ajv": "^8.12.0", + "axios": "^1.13.2", + "axios-retry": "^3.9.1", + "chroma-js": "^3.1.2", + "clipboard-copy": "^3.2.0", + "dotenv": "^8.2.0", + "form-data": "^4.0.0", + "hashids": "^2.2.1", + "iconv-lite": "^0.4.24", + "ioredis": "^4.28.0", + "ismobilejs": "^1.1.1", + "js-sha1": "^0.6.0", + "js-yaml": "^4.1.0", + "json-fn": "^1.1.1", + "jsondiffpatch": "^0.6.0", + "jsonwebtoken": "^9.0.0", + "katex": "0.16.21", + "lodash": "^4.17.21", + "luxon": "^1.28.0", + "markdown-it-color": "^2.1.1", + "markdown-it-emoji": "^2.0.2", + "markdown-it-ins": "^4.0.0", + "markdown-it-link-attributes": "^4.0.1", + "markdown-it-mark": "^4.0.0", + "markdown-it-sub": "^2.0.0", + "mime": "^1.6.0", + "moment": "^2.29.4", + "moment-timezone": "^0.5.34", + "node-cache": "^5.1.2", + "object-sizeof": "^2.6.5", + "p-queue": "^6.6.2", + "qs": "^6.11.2", + "querystring": "^0.2.0", + "quickjs-emscripten": "^0.31.0", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-hotkeys-hook": "^4.5.0", + "react-window": "^1.8.9", + "request": "^2.88.2", + "request-ip": "^3.3.0", + "request-promise-native": "^1.0.9", + "set-cookie-parser": "^2.7.1", + "swagger-ui-express": "^5.0.1", + "uuid": "^9.0.1", + "workerpool": "^9.1.1", + "zod": "^4.1.12" + }, + "devDependencies": { + "@floating-ui/react": "^0.27.13", + "@gravity-ui/app-builder": "^0.38.0", + "@gravity-ui/components": "^4.12.0", + "@gravity-ui/date-components": "^3.2.3", + "@gravity-ui/eslint-config": "^3.2.0", + "@gravity-ui/icons": "^2.16.0", + "@gravity-ui/markdown-editor": "^15.26.1", + "@gravity-ui/navigation": "^3.8.0", + "@gravity-ui/prettier-config": "^1.1.0", + "@gravity-ui/react-data-table": "^2.1.1", + "@gravity-ui/sdk": "^1.5.1", + "@gravity-ui/stylelint-config": "^4.0.1", + "@gravity-ui/tsconfig": "^1.0.0", + "@gravity-ui/ui-logger": "^1.1.0", + "@gravity-ui/uikit": "^7.18.0", + "@jest/types": "^29.6.3", + "@microsoft/fetch-event-source": "^2.0.1", + "@playwright/test": "^1.48.2", + "@reduxjs/toolkit": "^1.8.3", + "@statoscope/cli": "^5.28.2", + "@statoscope/stats-validator-plugin-webpack": "^5.28.2", + "@statoscope/webpack-ui": "^5.28.2", + "@stripe/react-stripe-js": "^1.8.1", + "@stripe/stripe-js": "^1.30.0", + "@tanstack/react-table": "^8.12.0", + "@tanstack/react-virtual": "^3.8.1", + "@testing-library/jest-dom": "^5.16.2", + "@testing-library/react": "^12.1.4", + "@types/chroma-js": "^3.1.2", + "@types/d3-array": "3.2.2", + "@types/d3-color": "^3.1.3", + "@types/d3-interpolate": "3.0.4", + "@types/d3-scale": "4.0.9", + "@types/d3-selection": "3.0.11", + "@types/d3-shape": "3.1.8", + "@types/dompurify": "^3.0.5", + "@types/express": "^4.17.21", + "@types/gtag.js": "0.0.10", + "@types/hashids": "^1.0.32", + "@types/history": "^4.7.11", + "@types/http-proxy": "^1.17.10", + "@types/ioredis": "^4.28.8", + "@types/jest": "^29.2.5", + "@types/js-yaml": "^4.0.5", + "@types/jsonwebtoken": "^9.0.1", + "@types/lodash": "^4.14.168", + "@types/luxon": "^1.26.2", + "@types/markdown-it": "^13.0.7", + "@types/markdown-it-emoji": "^2.0.2", + "@types/markdown-it-link-attributes": "^3.0.4", + "@types/mime": "1.3.5", + "@types/node": "^20.16.10", + "@types/qs": "6.9.11", + "@types/rc-slider": "^8.6.6", + "@types/react": "^17.0.40", + "@types/react-dom": "^17.0.13", + "@types/react-inspector": "^4.0.2", + "@types/react-redux": "^7.1.16", + "@types/react-router": "^5.1.11", + "@types/react-router-dom": "^5.1.7", + "@types/react-window": "^1.8.5", + "@types/recompose": "^0.30.7", + "@types/redux-logger": "^3.0.9", + "@types/request": "2.48.7", + "@types/request-ip": "^0.0.41", + "@types/request-promise-native": "^1.0.21", + "@types/set-cookie-parser": "^2.4.10", + "@types/swagger-ui-express": "^4.1.8", + "@types/testing-library__jest-dom": "^5.14.3", + "@types/uuid": "^9.0.8", + "@types/webpack-env": "^1.16.0", + "bem-cn-lite": "^4.0.0", + "blueimp-md5": "^2.19.0", + "classnames": "^2.5.1", + "cli-color": "2.0.1", + "colormap": "^2.3.2", + "copy-to-clipboard": "^3.3.3", + "core-js": "3.43.0", + "csstype": "^3.1.3", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-selection": "^3.0.0", + "d3-shape": "^3.2.0", + "dompurify": "^3.2.4", + "eslint": "^8.56.0", + "highcharts": "^8.2.2", + "history": "^4.10.1", + "htmlparser2": "^9.1.0", + "http-proxy": "^1.18.1", + "husky": "^8.0.3", + "immutability-helper": "^3.1.1", + "jest": "^29.3.1", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^29.3.1", + "jest-environment-node": "^29.3.1", + "jest-html-reporters": "^2.1.2", + "lint-staged": "^13.2.2", + "lowlight": "^3.1.0", + "ml-regression-polynomial": "^3.0.2", + "mockdate": "^3.0.5", + "monaco-editor": "^0.36.1", + "monaco-editor-webpack-plugin": "^7.1.0", + "playwright-core": "1.48.2", + "postcss": "^8.4.33", + "prettier": "^3.2.5", + "prop-types": "^15.8.0", + "rc-slider": "^11.1.8", + "react-dnd": "^10.0.2", + "react-dnd-html5-backend": "^10.0.2", + "react-inspector": "^5.1.1", + "react-intersection-observer": "^9.4.0", + "react-monaco-editor": "^0.52.0", + "react-redux": "^7.2.4", + "react-router": "^5.3.4", + "react-router-dom": "^5.3.4", + "react-split-pane": "^0.1.92", + "react-virtualized-auto-sizer": "1.0.24", + "react-waypoint": "^10.3.0", + "recompose": "npm:react-recompose@^0.33.0", + "redux": "^4.1.1", + "redux-devtools-extension": "^2.13.9", + "redux-logger": "^3.0.6", + "redux-thunk": "^2.3.0", + "reselect": "^4.1.8", + "resize-observer-polyfill": "^1.5.1", + "robust-point-in-polygon": "^1.0.3", + "stylelint": "^15.11.0", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "tsconfig-paths": "^3.14.0", + "typescript": "^5.4.5", + "url": "^0.11.1", + "utility-types": "^3.10.0", + "yup": "^1.6.1" + }, + "pnpm": { + "overrides": { + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-router": "^5.3.4", + "react-router-dom": "^5.3.4", + "@gravity-ui/uikit>react-virtualized-auto-sizer": "1.0.24", + "@gravity-ui/gateway>@grpc/grpc-js": "^1.13.2" + } + }, + "deploy": { + "project": "stat", + "app": "dx", + "component": "datalens", + "workflow": "workflow.json" + }, + "engines": { + "pnpm": "10.17.1", + "node": ">= 20", + "yarn": "Please use pnpm instead of yarn to install dependencies", + "npm": "Please use pnpm instead of npm to install dependencies" + }, + "packageManager": "pnpm@10.17.1", + "browserslist": [ + "extends @gravity-ui/browserslist-config" + ], + "optionalDependencies": { + "fsevents": "^2.3.2" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..0ab5697 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,24773 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +overrides: + react: ^17.0.2 + react-dom: ^17.0.2 + react-router: ^5.3.4 + react-router-dom: ^5.3.4 + '@gravity-ui/gateway>@grpc/grpc-js': ^1.13.2 + '@gravity-ui/uikit>react-virtualized-auto-sizer': 1.0.24 + +importers: + + .: + dependencies: + '@asteasolutions/zod-to-openapi': + specifier: ^8.1.0 + version: 8.1.0(zod@4.3.6) + '@braintree/sanitize-url': + specifier: ^6.0.0 + version: 6.0.4 + '@datalens-tech/ui-sandbox-modules': + specifier: ^0.36.0 + version: 0.36.0 + '@datalens-tech/xlsx': + specifier: ^0.20.1 + version: 0.20.1 + '@diplodoc/cut-extension': + specifier: ^0.7.4 + version: 0.7.4(@types/markdown-it@13.0.7)(markdown-it@13.0.2)(react@17.0.2) + '@diplodoc/file-extension': + specifier: ^0.2.1 + version: 0.2.1(@types/markdown-it@13.0.7)(markdown-it@13.0.2) + '@diplodoc/latex-extension': + specifier: ^1.1.0 + version: 1.1.0(katex@0.16.21)(markdown-it@13.0.2)(react@17.0.2) + '@diplodoc/mermaid-extension': + specifier: ^1.4.0 + version: 1.4.0(markdown-it@13.0.2)(react@17.0.2) + '@diplodoc/tabs-extension': + specifier: ^3.7.5 + version: 3.7.5(react@17.0.2) + '@diplodoc/transform': + specifier: ^4.64.1 + version: 4.64.1(@types/markdown-it@13.0.7)(highlight.js@11.9.0)(react@17.0.2) + '@gravity-ui/app-layout': + specifier: ^2.1.0 + version: 2.1.0 + '@gravity-ui/browserslist-config': + specifier: ^4.3.0 + version: 4.3.0 + '@gravity-ui/chartkit': + specifier: ^7.44.0 + version: 7.44.0(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@types/react@17.0.40)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/dashkit': + specifier: ^10.0.0 + version: 10.0.0(@gravity-ui/icons@2.16.0(react@17.0.2))(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/date-utils': + specifier: ^2.5.6 + version: 2.5.6 + '@gravity-ui/expresskit': + specifier: ^3.0.2 + version: 3.0.2(@gravity-ui/nodekit@2.10.1(axios@1.13.2)) + '@gravity-ui/gateway': + specifier: ^4.10.4 + version: 4.10.4 + '@gravity-ui/i18n': + specifier: ^1.7.0 + version: 1.8.0 + '@gravity-ui/nodekit': + specifier: ^2.10.1 + version: 2.10.1(axios@1.13.2) + '@node-rs/crc32': + specifier: ^1.7.2 + version: 1.7.2 + ajv: + specifier: ^8.12.0 + version: 8.12.0 + axios: + specifier: ^1.13.2 + version: 1.13.2 + axios-retry: + specifier: ^3.9.1 + version: 3.9.1 + chroma-js: + specifier: ^3.1.2 + version: 3.1.2 + clipboard-copy: + specifier: ^3.2.0 + version: 3.2.0 + dotenv: + specifier: ^8.2.0 + version: 8.2.0 + form-data: + specifier: ^4.0.0 + version: 4.0.5 + hashids: + specifier: ^2.2.1 + version: 2.2.8 + iconv-lite: + specifier: ^0.4.24 + version: 0.4.24 + ioredis: + specifier: ^4.28.0 + version: 4.28.0 + ismobilejs: + specifier: ^1.1.1 + version: 1.1.1 + js-sha1: + specifier: ^0.6.0 + version: 0.6.0 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 + json-fn: + specifier: ^1.1.1 + version: 1.1.1 + jsondiffpatch: + specifier: ^0.6.0 + version: 0.6.0 + jsonwebtoken: + specifier: ^9.0.0 + version: 9.0.0 + katex: + specifier: 0.16.21 + version: 0.16.21 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + luxon: + specifier: ^1.28.0 + version: 1.28.1 + markdown-it-color: + specifier: ^2.1.1 + version: 2.1.1 + markdown-it-emoji: + specifier: ^2.0.2 + version: 2.0.2 + markdown-it-ins: + specifier: ^4.0.0 + version: 4.0.0 + markdown-it-link-attributes: + specifier: ^4.0.1 + version: 4.0.1 + markdown-it-mark: + specifier: ^4.0.0 + version: 4.0.0 + markdown-it-sub: + specifier: ^2.0.0 + version: 2.0.0 + mime: + specifier: ^1.6.0 + version: 1.6.0 + moment: + specifier: ^2.29.4 + version: 2.29.4 + moment-timezone: + specifier: ^0.5.34 + version: 0.5.43 + node-cache: + specifier: ^5.1.2 + version: 5.1.2 + object-sizeof: + specifier: ^2.6.5 + version: 2.6.5 + p-queue: + specifier: ^6.6.2 + version: 6.6.2 + qs: + specifier: ^6.11.2 + version: 6.13.0 + querystring: + specifier: ^0.2.0 + version: 0.2.1 + quickjs-emscripten: + specifier: ^0.31.0 + version: 0.31.0 + react: + specifier: ^17.0.2 + version: 17.0.2 + react-dom: + specifier: ^17.0.2 + version: 17.0.2(react@17.0.2) + react-hotkeys-hook: + specifier: ^4.5.0 + version: 4.5.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-window: + specifier: ^1.8.9 + version: 1.8.11(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + request: + specifier: ^2.88.2 + version: 2.88.2 + request-ip: + specifier: ^3.3.0 + version: 3.3.0 + request-promise-native: + specifier: ^1.0.9 + version: 1.0.9(request@2.88.2) + set-cookie-parser: + specifier: ^2.7.1 + version: 2.7.1 + swagger-ui-express: + specifier: ^5.0.1 + version: 5.0.1(express@4.21.2) + uuid: + specifier: ^9.0.1 + version: 9.0.1 + workerpool: + specifier: ^9.1.1 + version: 9.1.1 + zod: + specifier: ^4.1.12 + version: 4.3.6 + devDependencies: + '@floating-ui/react': + specifier: ^0.27.13 + version: 0.27.13(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/app-builder': + specifier: ^0.38.0 + version: 0.38.0(@swc/helpers@0.5.17)(@types/express@4.17.21)(@types/node@20.16.12)(@types/webpack@5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))(chokidar@4.0.3)(moment-timezone@0.5.43)(monaco-editor-webpack-plugin@7.1.0(monaco-editor@0.36.1)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))))(type-fest@3.13.1) + '@gravity-ui/components': + specifier: ^4.12.0 + version: 4.12.0(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/date-components': + specifier: ^3.2.3 + version: 3.2.3(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/eslint-config': + specifier: ^3.2.0 + version: 3.2.0(@types/eslint@8.44.1)(eslint@8.57.0)(prettier@3.2.5)(typescript@5.6.3) + '@gravity-ui/icons': + specifier: ^2.16.0 + version: 2.16.0(react@17.0.2) + '@gravity-ui/markdown-editor': + specifier: ^15.26.1 + version: 15.26.1(@diplodoc/cut-extension@0.7.4(@types/markdown-it@13.0.7)(markdown-it@13.0.2)(react@17.0.2))(@diplodoc/file-extension@0.2.1(@types/markdown-it@13.0.7)(markdown-it@13.0.2))(@diplodoc/latex-extension@1.1.0(katex@0.16.21)(markdown-it@13.0.2)(react@17.0.2))(@diplodoc/mermaid-extension@1.4.0(markdown-it@13.0.2)(react@17.0.2))(@diplodoc/tabs-extension@3.7.5(react@17.0.2))(@diplodoc/transform@4.64.1(@types/markdown-it@13.0.7)(highlight.js@11.9.0)(react@17.0.2))(@gravity-ui/components@4.12.0(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(highlight.js@11.9.0)(katex@0.16.21)(lowlight@3.1.0)(markdown-it@13.0.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/navigation': + specifier: ^3.8.0 + version: 3.8.0(@bem-react/classname@1.7.0)(@gravity-ui/icons@2.16.0(react@17.0.2))(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/prettier-config': + specifier: ^1.1.0 + version: 1.1.0(prettier@3.2.5) + '@gravity-ui/react-data-table': + specifier: ^2.1.1 + version: 2.1.1(react@17.0.2) + '@gravity-ui/sdk': + specifier: ^1.5.1 + version: 1.5.1(@gravity-ui/gateway@4.10.4) + '@gravity-ui/stylelint-config': + specifier: ^4.0.1 + version: 4.0.1(postcss@8.4.47)(prettier@3.2.5)(stylelint@15.11.0(typescript@5.6.3)) + '@gravity-ui/tsconfig': + specifier: ^1.0.0 + version: 1.0.0 + '@gravity-ui/ui-logger': + specifier: ^1.1.0 + version: 1.1.0 + '@gravity-ui/uikit': + specifier: ^7.18.0 + version: 7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@jest/types': + specifier: ^29.6.3 + version: 29.6.3 + '@microsoft/fetch-event-source': + specifier: ^2.0.1 + version: 2.0.1 + '@playwright/test': + specifier: ^1.48.2 + version: 1.48.2 + '@reduxjs/toolkit': + specifier: ^1.8.3 + version: 1.9.7(react-redux@7.2.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react@17.0.2) + '@statoscope/cli': + specifier: ^5.28.2 + version: 5.28.2(@swc/core@1.13.5(@swc/helpers@0.5.17)) + '@statoscope/stats-validator-plugin-webpack': + specifier: ^5.28.2 + version: 5.28.2(@swc/core@1.13.5(@swc/helpers@0.5.17)) + '@statoscope/webpack-ui': + specifier: ^5.28.2 + version: 5.29.0 + '@stripe/react-stripe-js': + specifier: ^1.8.1 + version: 1.8.1(@stripe/stripe-js@1.30.0)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@stripe/stripe-js': + specifier: ^1.30.0 + version: 1.30.0 + '@tanstack/react-table': + specifier: ^8.12.0 + version: 8.12.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@tanstack/react-virtual': + specifier: ^3.8.1 + version: 3.13.9(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@testing-library/jest-dom': + specifier: ^5.16.2 + version: 5.16.2 + '@testing-library/react': + specifier: ^12.1.4 + version: 12.1.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@types/chroma-js': + specifier: ^3.1.2 + version: 3.1.2 + '@types/d3-array': + specifier: 3.2.2 + version: 3.2.2 + '@types/d3-color': + specifier: ^3.1.3 + version: 3.1.3 + '@types/d3-interpolate': + specifier: 3.0.4 + version: 3.0.4 + '@types/d3-scale': + specifier: 4.0.9 + version: 4.0.9 + '@types/d3-selection': + specifier: 3.0.11 + version: 3.0.11 + '@types/d3-shape': + specifier: 3.1.8 + version: 3.1.8 + '@types/dompurify': + specifier: ^3.0.5 + version: 3.0.5 + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 + '@types/gtag.js': + specifier: 0.0.10 + version: 0.0.10 + '@types/hashids': + specifier: ^1.0.32 + version: 1.0.32 + '@types/history': + specifier: ^4.7.11 + version: 4.7.11 + '@types/http-proxy': + specifier: ^1.17.10 + version: 1.17.10 + '@types/ioredis': + specifier: ^4.28.8 + version: 4.28.8 + '@types/jest': + specifier: ^29.2.5 + version: 29.4.0 + '@types/js-yaml': + specifier: ^4.0.5 + version: 4.0.5 + '@types/jsonwebtoken': + specifier: ^9.0.1 + version: 9.0.1 + '@types/lodash': + specifier: ^4.14.168 + version: 4.14.168 + '@types/luxon': + specifier: ^1.26.2 + version: 1.26.2 + '@types/markdown-it': + specifier: ^13.0.7 + version: 13.0.7 + '@types/markdown-it-emoji': + specifier: ^2.0.2 + version: 2.0.2 + '@types/markdown-it-link-attributes': + specifier: ^3.0.4 + version: 3.0.4 + '@types/mime': + specifier: 1.3.5 + version: 1.3.5 + '@types/node': + specifier: ^20.16.10 + version: 20.16.12 + '@types/qs': + specifier: 6.9.11 + version: 6.9.11 + '@types/rc-slider': + specifier: ^8.6.6 + version: 8.6.6 + '@types/react': + specifier: ^17.0.40 + version: 17.0.40 + '@types/react-dom': + specifier: ^17.0.13 + version: 17.0.13 + '@types/react-inspector': + specifier: ^4.0.2 + version: 4.0.2 + '@types/react-redux': + specifier: ^7.1.16 + version: 7.1.16 + '@types/react-router': + specifier: ^5.1.11 + version: 5.1.11 + '@types/react-router-dom': + specifier: ^5.1.7 + version: 5.1.7 + '@types/react-window': + specifier: ^1.8.5 + version: 1.8.5 + '@types/recompose': + specifier: ^0.30.7 + version: 0.30.7 + '@types/redux-logger': + specifier: ^3.0.9 + version: 3.0.9 + '@types/request': + specifier: 2.48.7 + version: 2.48.7 + '@types/request-ip': + specifier: ^0.0.41 + version: 0.0.41 + '@types/request-promise-native': + specifier: ^1.0.21 + version: 1.0.21 + '@types/set-cookie-parser': + specifier: ^2.4.10 + version: 2.4.10 + '@types/swagger-ui-express': + specifier: ^4.1.8 + version: 4.1.8 + '@types/testing-library__jest-dom': + specifier: ^5.14.3 + version: 5.14.3 + '@types/uuid': + specifier: ^9.0.8 + version: 9.0.8 + '@types/webpack-env': + specifier: ^1.16.0 + version: 1.16.0 + bem-cn-lite: + specifier: ^4.0.0 + version: 4.0.0 + blueimp-md5: + specifier: ^2.19.0 + version: 2.19.0 + classnames: + specifier: ^2.5.1 + version: 2.5.1 + cli-color: + specifier: 2.0.1 + version: 2.0.1 + colormap: + specifier: ^2.3.2 + version: 2.3.2 + copy-to-clipboard: + specifier: ^3.3.3 + version: 3.3.3 + core-js: + specifier: 3.43.0 + version: 3.43.0 + csstype: + specifier: ^3.1.3 + version: 3.1.3 + d3-array: + specifier: ^3.2.4 + version: 3.2.4 + d3-color: + specifier: ^3.1.0 + version: 3.1.0 + d3-interpolate: + specifier: ^3.0.1 + version: 3.0.1 + d3-scale: + specifier: ^4.0.2 + version: 4.0.2 + d3-selection: + specifier: ^3.0.0 + version: 3.0.0 + d3-shape: + specifier: ^3.2.0 + version: 3.2.0 + dompurify: + specifier: ^3.2.4 + version: 3.2.4 + eslint: + specifier: ^8.56.0 + version: 8.57.0 + highcharts: + specifier: ^8.2.2 + version: 8.2.2 + history: + specifier: ^4.10.1 + version: 4.10.1 + htmlparser2: + specifier: ^9.1.0 + version: 9.1.0 + http-proxy: + specifier: ^1.18.1 + version: 1.18.1 + husky: + specifier: ^8.0.3 + version: 8.0.3 + immutability-helper: + specifier: ^3.1.1 + version: 3.1.1 + jest: + specifier: ^29.3.1 + version: 29.4.3(@types/node@20.16.12)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)) + jest-circus: + specifier: ^27.5.1 + version: 27.5.1 + jest-environment-jsdom: + specifier: ^29.3.1 + version: 29.7.0 + jest-environment-node: + specifier: ^29.3.1 + version: 29.4.3 + jest-html-reporters: + specifier: ^2.1.2 + version: 2.1.2 + lint-staged: + specifier: ^13.2.2 + version: 13.2.2 + lowlight: + specifier: ^3.1.0 + version: 3.1.0 + ml-regression-polynomial: + specifier: ^3.0.2 + version: 3.0.2 + mockdate: + specifier: ^3.0.5 + version: 3.0.5 + monaco-editor: + specifier: ^0.36.1 + version: 0.36.1 + monaco-editor-webpack-plugin: + specifier: ^7.1.0 + version: 7.1.0(monaco-editor@0.36.1)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + playwright-core: + specifier: 1.48.2 + version: 1.48.2 + postcss: + specifier: ^8.4.33 + version: 8.4.47 + prettier: + specifier: ^3.2.5 + version: 3.2.5 + prop-types: + specifier: ^15.8.0 + version: 15.8.1 + rc-slider: + specifier: ^11.1.8 + version: 11.1.8(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-dnd: + specifier: ^10.0.2 + version: 10.0.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-dnd-html5-backend: + specifier: ^10.0.2 + version: 10.0.2 + react-inspector: + specifier: ^5.1.1 + version: 5.1.1(react@17.0.2) + react-intersection-observer: + specifier: ^9.4.0 + version: 9.4.0(react@17.0.2) + react-monaco-editor: + specifier: ^0.52.0 + version: 0.52.0(@types/react@17.0.40)(monaco-editor@0.36.1)(react@17.0.2) + react-redux: + specifier: ^7.2.4 + version: 7.2.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-router: + specifier: ^5.3.4 + version: 5.3.4(react@17.0.2) + react-router-dom: + specifier: ^5.3.4 + version: 5.3.4(react@17.0.2) + react-split-pane: + specifier: ^0.1.92 + version: 0.1.92(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-virtualized-auto-sizer: + specifier: 1.0.24 + version: 1.0.24(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-waypoint: + specifier: ^10.3.0 + version: 10.3.0(react@17.0.2) + recompose: + specifier: npm:react-recompose@^0.33.0 + version: react-recompose@0.33.0(react@17.0.2) + redux: + specifier: ^4.1.1 + version: 4.2.1 + redux-devtools-extension: + specifier: ^2.13.9 + version: 2.13.9(redux@4.2.1) + redux-logger: + specifier: ^3.0.6 + version: 3.0.6 + redux-thunk: + specifier: ^2.3.0 + version: 2.4.2(redux@4.2.1) + reselect: + specifier: ^4.1.8 + version: 4.1.8 + resize-observer-polyfill: + specifier: ^1.5.1 + version: 1.5.1 + robust-point-in-polygon: + specifier: ^1.0.3 + version: 1.0.3 + stylelint: + specifier: ^15.11.0 + version: 15.11.0(typescript@5.6.3) + ts-jest: + specifier: ^29.0.5 + version: 29.0.5(@babel/core@7.26.10)(@jest/types@29.6.3)(babel-jest@29.4.3(@babel/core@7.26.10))(jest@29.4.3(@types/node@20.16.12)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)))(typescript@5.6.3) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3) + tsconfig-paths: + specifier: ^3.14.0 + version: 3.15.0 + typescript: + specifier: ^5.4.5 + version: 5.6.3 + url: + specifier: ^0.11.1 + version: 0.11.3 + utility-types: + specifier: ^3.10.0 + version: 3.10.0 + yup: + specifier: ^1.6.1 + version: 1.6.1 + optionalDependencies: + fsevents: + specifier: ^2.3.2 + version: 2.3.3 + +packages: + + '@aashutoshrathi/word-wrap@1.2.6': + resolution: {tarball: https://registry.npmjs.org/@aashutoshrathi%2fword-wrap/-/word-wrap-1.2.6.tgz?rbtorrent=b189fc26f6d495f79a7e93754723bda4f49a9b30} + engines: {node: '>=0.10.0'} + + '@ampproject/remapping@2.2.0': + resolution: {tarball: https://registry.npmjs.org/@ampproject%2fremapping/-/remapping-2.2.0.tgz?rbtorrent=908252c60782b7e361d7fd2b959c16d869b033ba} + engines: {node: '>=6.0.0'} + + '@antfu/install-pkg@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@antfu%2finstall-pkg/-/install-pkg-1.1.0.tgz?rbtorrent=} + + '@asteasolutions/zod-to-openapi@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@asteasolutions%2fzod-to-openapi/-/zod-to-openapi-8.1.0.tgz?rbtorrent=} + peerDependencies: + zod: ^4.0.0 + + '@aws-crypto/crc32@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@aws-crypto%2fcrc32/-/crc32-3.0.0.tgz?rbtorrent=dbbcad82ea61ceca14a8d5ef3a7e88a7fd079dd3} + + '@aws-crypto/crc32c@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@aws-crypto%2fcrc32c/-/crc32c-3.0.0.tgz?rbtorrent=c0b9605c46584e8ac14a333d55e8f7f1fd0c35f1} + + '@aws-crypto/ie11-detection@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@aws-crypto%2fie11-detection/-/ie11-detection-3.0.0.tgz?rbtorrent=183c29781a41ffa8cbb271a38a8b7a02b1ce277b} + + '@aws-crypto/sha1-browser@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@aws-crypto%2fsha1-browser/-/sha1-browser-3.0.0.tgz?rbtorrent=0287c0f777903a3eac881cd95cf2628775ae61a1} + + '@aws-crypto/sha256-browser@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@aws-crypto%2fsha256-browser/-/sha256-browser-3.0.0.tgz?rbtorrent=1b11b045755d7cf6b267785edd6dd98b4e082202} + + '@aws-crypto/sha256-js@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@aws-crypto%2fsha256-js/-/sha256-js-3.0.0.tgz?rbtorrent=c3080b5661eb8a279feadeee3e4fbfe9de012b13} + + '@aws-crypto/supports-web-crypto@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@aws-crypto%2fsupports-web-crypto/-/supports-web-crypto-3.0.0.tgz?rbtorrent=6cc4f3801d9e99e0bd635744e9be8a2509fe1b87} + + '@aws-crypto/util@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@aws-crypto%2futil/-/util-3.0.0.tgz?rbtorrent=95418d4c83c478636bd4b5183ca9332f47498e28} + + '@aws-sdk/client-s3@3.383.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fclient-s3/-/client-s3-3.383.0.tgz?rbtorrent=d932f0ae0bc5cdabebd1b03deee0d7a2b21b011f} + engines: {node: '>=14.0.0'} + + '@aws-sdk/client-sso-oidc@3.382.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fclient-sso-oidc/-/client-sso-oidc-3.382.0.tgz?rbtorrent=81dc9f773b586e2b4a6bce3cdb935edba266aec2} + engines: {node: '>=14.0.0'} + + '@aws-sdk/client-sso@3.382.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fclient-sso/-/client-sso-3.382.0.tgz?rbtorrent=a7695c03e81a9e66ad8dd15c730bb99681377be7} + engines: {node: '>=14.0.0'} + + '@aws-sdk/client-sts@3.382.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fclient-sts/-/client-sts-3.382.0.tgz?rbtorrent=bbabf0409376a767570793449b409ffede2a82a1} + engines: {node: '>=14.0.0'} + + '@aws-sdk/credential-provider-env@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fcredential-provider-env/-/credential-provider-env-3.378.0.tgz?rbtorrent=b3036ea0101e47f559d243904fdae896dc1ddd9c} + engines: {node: '>=14.0.0'} + + '@aws-sdk/credential-provider-ini@3.382.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fcredential-provider-ini/-/credential-provider-ini-3.382.0.tgz?rbtorrent=a3a1955905a9f6ca7b8e9b37f5e7e40803342198} + engines: {node: '>=14.0.0'} + + '@aws-sdk/credential-provider-node@3.382.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fcredential-provider-node/-/credential-provider-node-3.382.0.tgz?rbtorrent=925788600f2235212895edbd17fc255bfc90e064} + engines: {node: '>=14.0.0'} + + '@aws-sdk/credential-provider-process@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fcredential-provider-process/-/credential-provider-process-3.378.0.tgz?rbtorrent=54fcadcc1164ab0530115ad24d6c5ad2f1eb830f} + engines: {node: '>=14.0.0'} + + '@aws-sdk/credential-provider-sso@3.382.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fcredential-provider-sso/-/credential-provider-sso-3.382.0.tgz?rbtorrent=52d695d70806056787f7ca0d659535c1bb0630a6} + engines: {node: '>=14.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fcredential-provider-web-identity/-/credential-provider-web-identity-3.378.0.tgz?rbtorrent=f145730df088d5875ceb58f966b63c050ad4e7ae} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-bucket-endpoint@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-bucket-endpoint/-/middleware-bucket-endpoint-3.378.0.tgz?rbtorrent=f32c667e154ad16c056c9ffeecd62cf723f2361c} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-expect-continue@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-expect-continue/-/middleware-expect-continue-3.378.0.tgz?rbtorrent=fb0df04eed4528b5a7047d03862a5ae5494ca90f} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.383.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-flexible-checksums/-/middleware-flexible-checksums-3.383.0.tgz?rbtorrent=eb0cbfd4c0bf078ad54b183cba50e1c555191765} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-host-header@3.379.1': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-host-header/-/middleware-host-header-3.379.1.tgz?rbtorrent=7af9665ad97fba8f4ea50d4336fe87a4c9f8f252} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-location-constraint@3.379.1': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-location-constraint/-/middleware-location-constraint-3.379.1.tgz?rbtorrent=4a86b45f8b2877b627305bbc9e81bf48a5e40dfa} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-logger@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-logger/-/middleware-logger-3.378.0.tgz?rbtorrent=ed4262442c6fafc41e3c848d1d2ed72c82bf514c} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-recursion-detection/-/middleware-recursion-detection-3.378.0.tgz?rbtorrent=4a9d463c05b6bb5c8dfe46931ccb2a3fa145ca0d} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.379.1': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-sdk-s3/-/middleware-sdk-s3-3.379.1.tgz?rbtorrent=50d24f5112bfab56aad80e7497d41e03303bacb0} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-sdk-sts@3.379.1': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-sdk-sts/-/middleware-sdk-sts-3.379.1.tgz?rbtorrent=005b9a5c0d02ce22205c716025d08307b41ddecd} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-signing@3.379.1': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-signing/-/middleware-signing-3.379.1.tgz?rbtorrent=dc205e6d44721af90b09717e6c352b86e187d917} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-ssec@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-ssec/-/middleware-ssec-3.378.0.tgz?rbtorrent=d09ac57d392106680913985989e3e1090c970532} + engines: {node: '>=14.0.0'} + + '@aws-sdk/middleware-user-agent@3.382.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fmiddleware-user-agent/-/middleware-user-agent-3.382.0.tgz?rbtorrent=97ffc7f6ca79821c16c5019b77bb524ad61784e2} + engines: {node: '>=14.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fsignature-v4-multi-region/-/signature-v4-multi-region-3.378.0.tgz?rbtorrent=dff54e1bac2ef2da9e51daf4533f0e10645851f8} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/signature-v4-crt': ^3.118.0 + peerDependenciesMeta: + '@aws-sdk/signature-v4-crt': + optional: true + + '@aws-sdk/token-providers@3.382.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2ftoken-providers/-/token-providers-3.382.0.tgz?rbtorrent=bc67e12eb64781e262ccd37f188ffa6d5928b3d3} + engines: {node: '>=14.0.0'} + + '@aws-sdk/types@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2ftypes/-/types-3.378.0.tgz?rbtorrent=} + engines: {node: '>=14.0.0'} + + '@aws-sdk/util-arn-parser@3.310.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2futil-arn-parser/-/util-arn-parser-3.310.0.tgz?rbtorrent=2adf77fad96cee66f637d49c7689ddb8b3c2e043} + engines: {node: '>=14.0.0'} + + '@aws-sdk/util-endpoints@3.382.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2futil-endpoints/-/util-endpoints-3.382.0.tgz?rbtorrent=36d2964983c1a667e6f62d5ed930cd17a2fc6634} + engines: {node: '>=14.0.0'} + + '@aws-sdk/util-locate-window@3.168.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2futil-locate-window/-/util-locate-window-3.168.0.tgz?rbtorrent=235a5d024a532d39b40f82299aff5327e2f01ca1} + engines: {node: '>= 12.0.0'} + + '@aws-sdk/util-user-agent-browser@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2futil-user-agent-browser/-/util-user-agent-browser-3.378.0.tgz?rbtorrent=8b64422429182cc4edb0daed9b19f214ef18aaea} + + '@aws-sdk/util-user-agent-node@3.378.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2futil-user-agent-node/-/util-user-agent-node-3.378.0.tgz?rbtorrent=095a8f079539216867ac4ff6c3b8ecabb9ee6487} + engines: {node: '>=14.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/util-utf8-browser@3.168.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2futil-utf8-browser/-/util-utf8-browser-3.168.0.tgz?rbtorrent=d0092d007978c3bbbe906af3e649ae7e92e72b62} + + '@aws-sdk/xml-builder@3.310.0': + resolution: {tarball: https://registry.npmjs.org/@aws-sdk%2fxml-builder/-/xml-builder-3.310.0.tgz?rbtorrent=36a564579948de886f9fe039da3d8c7d45e237b9} + engines: {node: '>=14.0.0'} + + '@babel/code-frame@7.26.2': + resolution: {tarball: https://registry.npmjs.org/@babel%2fcode-frame/-/code-frame-7.26.2.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.2': + resolution: {tarball: https://registry.npmjs.org/@babel%2fcompat-data/-/compat-data-7.26.2.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.8': + resolution: {tarball: https://registry.npmjs.org/@babel%2fcompat-data/-/compat-data-7.26.8.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2fcore/-/core-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.10': + resolution: {tarball: https://registry.npmjs.org/@babel%2fcore/-/core-7.26.10.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/eslint-parser@7.24.1': + resolution: {tarball: https://registry.npmjs.org/@babel%2feslint-parser/-/eslint-parser-7.24.1.tgz?rbtorrent=} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 + + '@babel/generator@7.26.10': + resolution: {tarball: https://registry.npmjs.org/@babel%2fgenerator/-/generator-7.26.10.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.2': + resolution: {tarball: https://registry.npmjs.org/@babel%2fgenerator/-/generator-7.26.2.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-builder-binary-assignment-operator-visitor@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.26.5': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.2': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz?rbtorrent=} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-define-polyfill-provider@0.6.3': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz?rbtorrent=} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-member-expression-to-functions@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-module-imports/-/helper-module-imports-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-module-transforms/-/helper-module-transforms-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.26.5': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-replace-supers/-/helper-replace-supers-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-string-parser/-/helper-string-parser-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-validator-option/-/helper-validator-option-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelper-wrap-function/-/helper-wrap-function-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelpers/-/helpers-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.10': + resolution: {tarball: https://registry.npmjs.org/@babel%2fhelpers/-/helpers-7.26.10.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.10': + resolution: {tarball: https://registry.npmjs.org/@babel%2fparser/-/parser-7.26.10.tgz?rbtorrent=} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.26.2': + resolution: {tarball: https://registry.npmjs.org/@babel%2fparser/-/parser-7.26.2.tgz?rbtorrent=} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz?rbtorrent=0c90e9ab85cf9a35302d2924a3853dcae90472f8} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz?rbtorrent=d2cab9f2b67b4227d46920e88485757edbfc8f2b} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz?rbtorrent=b2ba73d607be77b6111c152996364aacd46c1afa} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz?rbtorrent=4f45c56185f77e15f3edeacc85f108347c47a397} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz?rbtorrent=36357d51bb10aaf5c26881a64899626c55cc066f} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz?rbtorrent=3d3c3400d316038dc99f3975bb4d1d1afd6493c4} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz?rbtorrent=163b203af89e84a1251dfeb48d85014cb45d63a9} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz?rbtorrent=b85bafb6e01e38a9c5a93a241d6993a7c2f4009f} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz?rbtorrent=bd778a3c45cb483889c7f5d460cbf54b28a68010} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz?rbtorrent=989122ab3434b101a6c9bdca9f793283e216ece0} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz?rbtorrent=28564a8c53d1db85b844029de4ade3cf18ff67f2} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz?rbtorrent=380b75f637e83ae8df2d7700981ba12c3825e2eb} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz?rbtorrent=695399e9d53768aa9717a2d14b0da57b8476d04d} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz?rbtorrent=aadcdaf321b676989a404ef157efa627133e6320} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.26.8': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.26.5': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.26.3': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.26.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.26.3': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-constant-elements@7.22.5': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz?rbtorrent=8ebe67e1c2afc815e51bc57f85de34b6c7361be1} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-runtime@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.26.8': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.26.7': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fplugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2fpreset-env/-/preset-env-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-env@7.26.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2fpreset-env/-/preset-env-7.26.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {tarball: https://registry.npmjs.org/@babel%2fpreset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz?rbtorrent=dc33e7b3b616629352239a0e5853ee52c680be0c} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/preset-react@7.26.3': + resolution: {tarball: https://registry.npmjs.org/@babel%2fpreset-react/-/preset-react-7.26.3.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2fpreset-typescript/-/preset-typescript-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2fruntime/-/runtime-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.26.10': + resolution: {tarball: https://registry.npmjs.org/@babel%2fruntime/-/runtime-7.26.10.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/template@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2ftemplate/-/template-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/template@7.26.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2ftemplate/-/template-7.26.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.25.9': + resolution: {tarball: https://registry.npmjs.org/@babel%2ftraverse/-/traverse-7.25.9.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.26.10': + resolution: {tarball: https://registry.npmjs.org/@babel%2ftraverse/-/traverse-7.26.10.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.0': + resolution: {tarball: https://registry.npmjs.org/@babel%2ftypes/-/types-7.26.0.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.10': + resolution: {tarball: https://registry.npmjs.org/@babel%2ftypes/-/types-7.26.10.tgz?rbtorrent=} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {tarball: https://registry.npmjs.org/@bcoe%2fv8-coverage/-/v8-coverage-0.2.3.tgz?rbtorrent=ef6e67a3426a5f6ef7d20def6aac6d6bcc280518} + + '@bem-react/classname@1.7.0': + resolution: {tarball: https://registry.npmjs.org/@bem-react%2fclassname/-/classname-1.7.0.tgz?rbtorrent=} + + '@bem-react/classnames@1.3.10': + resolution: {tarball: https://registry.npmjs.org/@bem-react%2fclassnames/-/classnames-1.3.10.tgz?rbtorrent=7d7ca4cf17c071081d3e0a0f5c8ffffdfdbb02dc} + + '@borewit/text-codec@0.1.1': + resolution: {tarball: https://registry.npmjs.org/@borewit%2ftext-codec/-/text-codec-0.1.1.tgz?rbtorrent=} + + '@braintree/sanitize-url@6.0.4': + resolution: {tarball: https://registry.npmjs.org/@braintree%2fsanitize-url/-/sanitize-url-6.0.4.tgz?rbtorrent=bcb5aa04b855b40325858642f6b78ca8c7286b92} + + '@braintree/sanitize-url@7.1.2': + resolution: {tarball: https://registry.npmjs.org/@braintree%2fsanitize-url/-/sanitize-url-7.1.2.tgz?rbtorrent=} + + '@chevrotain/cst-dts-gen@11.0.3': + resolution: {tarball: https://registry.npmjs.org/@chevrotain%2fcst-dts-gen/-/cst-dts-gen-11.0.3.tgz?rbtorrent=1313837c6c3a516c223461c7300d72142a14d509} + + '@chevrotain/gast@11.0.3': + resolution: {tarball: https://registry.npmjs.org/@chevrotain%2fgast/-/gast-11.0.3.tgz?rbtorrent=281f4bcb4025f18a89eb5df09855ee24264f8739} + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: {tarball: https://registry.npmjs.org/@chevrotain%2fregexp-to-ast/-/regexp-to-ast-11.0.3.tgz?rbtorrent=} + + '@chevrotain/types@11.0.3': + resolution: {tarball: https://registry.npmjs.org/@chevrotain%2ftypes/-/types-11.0.3.tgz?rbtorrent=6cfddb39cbc0c8203c246b54d3dc891d3216ca91} + + '@chevrotain/utils@11.0.3': + resolution: {tarball: https://registry.npmjs.org/@chevrotain%2futils/-/utils-11.0.3.tgz?rbtorrent=fc35368920f631941ba83906f332228a4daa2b99} + + '@codemirror/autocomplete@6.20.0': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2fautocomplete/-/autocomplete-6.20.0.tgz?rbtorrent=} + + '@codemirror/commands@6.10.2': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2fcommands/-/commands-6.10.2.tgz?rbtorrent=} + + '@codemirror/lang-css@6.3.1': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2flang-css/-/lang-css-6.3.1.tgz?rbtorrent=} + + '@codemirror/lang-html@6.4.11': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2flang-html/-/lang-html-6.4.11.tgz?rbtorrent=} + + '@codemirror/lang-javascript@6.2.4': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2flang-javascript/-/lang-javascript-6.2.4.tgz?rbtorrent=} + + '@codemirror/lang-markdown@6.5.0': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2flang-markdown/-/lang-markdown-6.5.0.tgz?rbtorrent=} + + '@codemirror/language@6.12.1': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2flanguage/-/language-6.12.1.tgz?rbtorrent=} + + '@codemirror/lint@6.9.4': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2flint/-/lint-6.9.4.tgz?rbtorrent=} + + '@codemirror/search@6.6.0': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2fsearch/-/search-6.6.0.tgz?rbtorrent=} + + '@codemirror/state@6.5.4': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2fstate/-/state-6.5.4.tgz?rbtorrent=} + + '@codemirror/view@6.39.13': + resolution: {tarball: https://registry.npmjs.org/@codemirror%2fview/-/view-6.39.13.tgz?rbtorrent=} + + '@cspotcode/source-map-support@0.8.1': + resolution: {tarball: https://registry.npmjs.org/@cspotcode%2fsource-map-support/-/source-map-support-0.8.1.tgz?rbtorrent=816aed2d73d30be99838a5db9bf5a951d606c697} + engines: {node: '>=12'} + + '@csstools/cascade-layer-name-parser@1.0.5': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fcascade-layer-name-parser/-/cascade-layer-name-parser-1.0.5.tgz?rbtorrent=a83f723dcd67665d4de526b14946b44b53d426db} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-parser-algorithms': ^2.3.2 + '@csstools/css-tokenizer': ^2.2.1 + + '@csstools/color-helpers@3.0.2': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fcolor-helpers/-/color-helpers-3.0.2.tgz?rbtorrent=b45ccb3021ced5ddcec56e078ca62fa7da36ce55} + engines: {node: ^14 || ^16 || >=18} + + '@csstools/css-calc@1.1.4': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fcss-calc/-/css-calc-1.1.4.tgz?rbtorrent=47a7832b6fe7535628bf4359c41e33335c6f22bc} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-parser-algorithms': ^2.3.2 + '@csstools/css-tokenizer': ^2.2.1 + + '@csstools/css-color-parser@1.4.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fcss-color-parser/-/css-color-parser-1.4.0.tgz?rbtorrent=5d29ba21d336fa1713273d9864ccc4aed7ba03cb} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-parser-algorithms': ^2.3.2 + '@csstools/css-tokenizer': ^2.2.1 + + '@csstools/css-parser-algorithms@2.3.2': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fcss-parser-algorithms/-/css-parser-algorithms-2.3.2.tgz?rbtorrent=3afe03d6a06e86fd37b5b224a21429a80ccd49c6} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-tokenizer': ^2.2.1 + + '@csstools/css-tokenizer@2.2.1': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fcss-tokenizer/-/css-tokenizer-2.2.1.tgz?rbtorrent=f32733a78ce2a607b9539506f07befec7f5db1f1} + engines: {node: ^14 || ^16 || >=18} + + '@csstools/media-query-list-parser@2.1.5': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fmedia-query-list-parser/-/media-query-list-parser-2.1.5.tgz?rbtorrent=dc9a80e74e22881c74728dea9b4154b44d769a57} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-parser-algorithms': ^2.3.2 + '@csstools/css-tokenizer': ^2.2.1 + + '@csstools/postcss-cascade-layers@4.0.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-cascade-layers/-/postcss-cascade-layers-4.0.0.tgz?rbtorrent=c4ff2a495cba05039318e1689b3fb4d2b7f712ef} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-color-function@3.0.7': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-color-function/-/postcss-color-function-3.0.7.tgz?rbtorrent=c4a2539d5aa1360fcd0c9903c61e551588443022} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-color-mix-function@2.0.7': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-color-mix-function/-/postcss-color-mix-function-2.0.7.tgz?rbtorrent=3d5709f7a02da52770c5b932b857664520538e42} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-exponential-functions@1.0.1': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-exponential-functions/-/postcss-exponential-functions-1.0.1.tgz?rbtorrent=b953bed1dc3b7c39306089be12cc5cecd1318e92} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-font-format-keywords@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-font-format-keywords/-/postcss-font-format-keywords-3.0.0.tgz?rbtorrent=bffeb59fa9fcaf09f6e0c8fca91d96c97dd80ad8} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-gamut-mapping@1.0.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-gamut-mapping/-/postcss-gamut-mapping-1.0.0.tgz?rbtorrent=ac6b974a3e2cb42033820d0ab8bb1b5d83cdfaa3} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-gradients-interpolation-method@4.0.7': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.7.tgz?rbtorrent=6c3439ef04a912e5d4217dd99ee8afa6e88d1f6b} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-hwb-function@3.0.6': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-hwb-function/-/postcss-hwb-function-3.0.6.tgz?rbtorrent=3f9fd4cd3fbf59213b2314ee34082aa97a4ae305} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-ic-unit@3.0.2': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-ic-unit/-/postcss-ic-unit-3.0.2.tgz?rbtorrent=3b72125167fb69a7b8d19f5cc15129dedae580cf} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-initial@1.0.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-initial/-/postcss-initial-1.0.0.tgz?rbtorrent=07fd8b373564d3176ae95e984d8f5482a1775439} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-is-pseudo-class@4.0.3': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.3.tgz?rbtorrent=a49f3bfbe6a8aa8d747332272be4192be14bd62a} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-float-and-clear@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-logical-float-and-clear/-/postcss-logical-float-and-clear-2.0.0.tgz?rbtorrent=b58ac54ee900d580df5c280caf71fe7ffb3854b7} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-resize@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-logical-resize/-/postcss-logical-resize-2.0.0.tgz?rbtorrent=32d30f55adb5901cefa9a5703beab0167c72615a} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-viewport-units@2.0.3': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.3.tgz?rbtorrent=35691728c67af807772dc98bea2536c70edbd2b6} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-media-minmax@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-media-minmax/-/postcss-media-minmax-1.1.0.tgz?rbtorrent=4cd2c055712bbf44c8941c772899f06e824f2d54} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.3': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.3.tgz?rbtorrent=e283d720e6ea48c2c665dd31755e0edb503f1dc6} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-nested-calc@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-nested-calc/-/postcss-nested-calc-3.0.0.tgz?rbtorrent=7b21fcf50110285b614784878e6106f1942fd1b1} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-normalize-display-values@3.0.1': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-normalize-display-values/-/postcss-normalize-display-values-3.0.1.tgz?rbtorrent=ad19d10a0a503422fac0333db9e7cafe6f7892c2} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-oklab-function@3.0.7': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-oklab-function/-/postcss-oklab-function-3.0.7.tgz?rbtorrent=1ebce64b294359474130ccaf1504f33bf13f1e1e} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-progressive-custom-properties@3.0.2': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-progressive-custom-properties/-/postcss-progressive-custom-properties-3.0.2.tgz?rbtorrent=127322a5fad21a181f085ede77db2e62a15cd92b} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-relative-color-syntax@2.0.7': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.7.tgz?rbtorrent=b0894e4a6b5b7863f454ed1c53a180a59ba3c152} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-scope-pseudo-class@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-scope-pseudo-class/-/postcss-scope-pseudo-class-3.0.0.tgz?rbtorrent=4aec31f39e156c218fdd6f886bb8cb9606020f6c} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-stepped-value-functions@3.0.2': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.2.tgz?rbtorrent=747e7b436141d73a2627b941b26215fe2d6572df} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-text-decoration-shorthand@3.0.3': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.3.tgz?rbtorrent=763bd1cefa6c1a38f9b0719b9abf79fe2eca9157} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-trigonometric-functions@3.0.2': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.2.tgz?rbtorrent=a09693dbed1e8f9358914e270f7a618dc03244f0} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-unset-value@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fpostcss-unset-value/-/postcss-unset-value-3.0.0.tgz?rbtorrent=37429c6827e6d86e2bc1f68fdb75005e8993a00c} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + '@csstools/selector-specificity@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@csstools%2fselector-specificity/-/selector-specificity-3.0.0.tgz?rbtorrent=46cfafe527246512ec78826daa1760173861827f} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss-selector-parser: ^6.0.13 + + '@datalens-tech/ui-sandbox-modules@0.36.0': + resolution: {tarball: https://registry.npmjs.org/@datalens-tech%2fui-sandbox-modules/-/ui-sandbox-modules-0.36.0.tgz?rbtorrent=} + + '@datalens-tech/xlsx@0.20.1': + resolution: {tarball: https://registry.npmjs.org/@datalens-tech%2fxlsx/-/xlsx-0.20.1.tgz?rbtorrent=} + engines: {node: '>=0.8'} + hasBin: true + + '@diplodoc/color-extension@0.1.1': + resolution: {tarball: https://registry.npmjs.org/@diplodoc%2fcolor-extension/-/color-extension-0.1.1.tgz?rbtorrent=} + + '@diplodoc/cut-extension@0.7.4': + resolution: {tarball: https://registry.npmjs.org/@diplodoc%2fcut-extension/-/cut-extension-0.7.4.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + peerDependenciesMeta: + react: + optional: true + + '@diplodoc/cut-extension@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@diplodoc%2fcut-extension/-/cut-extension-1.1.1.tgz?rbtorrent=} + + '@diplodoc/directive@0.3.2': + resolution: {tarball: https://registry.npmjs.org/@diplodoc%2fdirective/-/directive-0.3.2.tgz?rbtorrent=} + + '@diplodoc/file-extension@0.2.1': + resolution: {tarball: https://registry.npmjs.org/@diplodoc%2ffile-extension/-/file-extension-0.2.1.tgz?rbtorrent=} + peerDependencies: + markdown-it: ^13.0.0 + + '@diplodoc/latex-extension@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@diplodoc%2flatex-extension/-/latex-extension-1.1.0.tgz?rbtorrent=} + peerDependencies: + katex: ^0.16.9 + markdown-it: ^13.0.0 + react: ^17.0.2 + peerDependenciesMeta: + react: + optional: true + + '@diplodoc/mermaid-extension@1.4.0': + resolution: {tarball: https://registry.npmjs.org/@diplodoc%2fmermaid-extension/-/mermaid-extension-1.4.0.tgz?rbtorrent=} + engines: {node: '>=18', npm: '>=9.*'} + peerDependencies: + markdown-it: ^13.0.0 + react: ^17.0.2 + peerDependenciesMeta: + react: + optional: true + + '@diplodoc/tabs-extension@3.7.5': + resolution: {tarball: https://registry.npmjs.org/@diplodoc%2ftabs-extension/-/tabs-extension-3.7.5.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + peerDependenciesMeta: + react: + optional: true + + '@diplodoc/transform@4.64.1': + resolution: {tarball: https://registry.npmjs.org/@diplodoc%2ftransform/-/transform-4.64.1.tgz?rbtorrent=} + peerDependencies: + highlight.js: ^10.0.3 || ^11 + peerDependenciesMeta: + highlight.js: + optional: true + + '@diplodoc/utils@2.1.0': + resolution: {tarball: https://registry.npmjs.org/@diplodoc%2futils/-/utils-2.1.0.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + peerDependenciesMeta: + react: + optional: true + + '@discoveryjs/json-ext@0.5.7': + resolution: {tarball: https://registry.npmjs.org/@discoveryjs%2fjson-ext/-/json-ext-0.5.7.tgz?rbtorrent=556bec265fc8f841dafb2e7a66ac7ed503ff1c14} + engines: {node: '>=10.0.0'} + + '@discoveryjs/natural-compare@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@discoveryjs%2fnatural-compare/-/natural-compare-1.1.0.tgz?rbtorrent=022b52a1374798a3401dd55949ddb27183d27c27} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + '@emnapi/core@1.8.1': + resolution: {tarball: https://registry.npmjs.org/@emnapi%2fcore/-/core-1.8.1.tgz?rbtorrent=} + + '@emnapi/runtime@1.8.1': + resolution: {tarball: https://registry.npmjs.org/@emnapi%2fruntime/-/runtime-1.8.1.tgz?rbtorrent=} + + '@emnapi/wasi-threads@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@emnapi%2fwasi-threads/-/wasi-threads-1.1.0.tgz?rbtorrent=} + + '@esbuild/aix-ppc64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2faix-ppc64/-/aix-ppc64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fandroid-arm64/-/android-arm64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fandroid-arm/-/android-arm-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fandroid-x64/-/android-x64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fdarwin-arm64/-/darwin-arm64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fdarwin-x64/-/darwin-x64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2ffreebsd-arm64/-/freebsd-arm64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2ffreebsd-x64/-/freebsd-x64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2flinux-arm64/-/linux-arm64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2flinux-arm/-/linux-arm-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2flinux-ia32/-/linux-ia32-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2flinux-loong64/-/linux-loong64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2flinux-mips64el/-/linux-mips64el-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2flinux-ppc64/-/linux-ppc64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2flinux-riscv64/-/linux-riscv64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2flinux-s390x/-/linux-s390x-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2flinux-x64/-/linux-x64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fnetbsd-arm64/-/netbsd-arm64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fnetbsd-x64/-/netbsd-x64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fopenbsd-arm64/-/openbsd-arm64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fopenbsd-x64/-/openbsd-x64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fsunos-x64/-/sunos-x64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fwin32-arm64/-/win32-arm64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fwin32-ia32/-/win32-ia32-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.5': + resolution: {tarball: https://registry.npmjs.org/@esbuild%2fwin32-x64/-/win32-x64-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.4.0': + resolution: {tarball: https://registry.npmjs.org/@eslint-community%2feslint-utils/-/eslint-utils-4.4.0.tgz?rbtorrent=e999df4d05b63f8cf1cb262674a1d3e1823a20a8} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.10.0': + resolution: {tarball: https://registry.npmjs.org/@eslint-community%2fregexpp/-/regexpp-4.10.0.tgz?rbtorrent=d22bc2cf43a63ec7db609d5a1577b0cd8aac4e72} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {tarball: https://registry.npmjs.org/@eslint%2feslintrc/-/eslintrc-2.1.4.tgz?rbtorrent=} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.0': + resolution: {tarball: https://registry.npmjs.org/@eslint%2fjs/-/js-8.57.0.tgz?rbtorrent=} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@fastify/deepmerge@1.3.0': + resolution: {tarball: https://registry.npmjs.org/@fastify%2fdeepmerge/-/deepmerge-1.3.0.tgz?rbtorrent=d346692719cb3434f45ddaa5e72a1e4da06a89f8} + + '@floating-ui/core@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@floating-ui%2fcore/-/core-1.7.2.tgz?rbtorrent=} + + '@floating-ui/dom@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@floating-ui%2fdom/-/dom-1.7.2.tgz?rbtorrent=} + + '@floating-ui/react-dom@2.1.4': + resolution: {tarball: https://registry.npmjs.org/@floating-ui%2freact-dom/-/react-dom-2.1.4.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + '@floating-ui/react@0.27.13': + resolution: {tarball: https://registry.npmjs.org/@floating-ui%2freact/-/react-0.27.13.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + '@floating-ui/utils@0.2.10': + resolution: {tarball: https://registry.npmjs.org/@floating-ui%2futils/-/utils-0.2.10.tgz?rbtorrent=} + + '@gravity-ui/app-builder@0.38.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/app-builder/-/app-builder-0.38.0.tgz} + engines: {node: '>= 18'} + hasBin: true + peerDependencies: + '@sentry/webpack-plugin': ^2.7.1 + lightningcss: ^1.21.5 + monaco-editor-webpack-plugin: '*' + peerDependenciesMeta: + '@sentry/webpack-plugin': + optional: true + lightningcss: + optional: true + monaco-editor-webpack-plugin: + optional: true + + '@gravity-ui/app-layout@2.1.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/app-layout/-/app-layout-2.1.0.tgz} + + '@gravity-ui/axios-wrapper@1.5.1': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/axios-wrapper/-/axios-wrapper-1.5.1.tgz} + peerDependencies: + axios: ^1.3.4 + lodash: ^4.17.11 + + '@gravity-ui/browserslist-config@4.3.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/browserslist-config/-/browserslist-config-4.3.0.tgz} + + '@gravity-ui/chartkit@7.44.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/chartkit/-/chartkit-7.44.0.tgz} + peerDependencies: + '@gravity-ui/uikit': ^7.0.0 + react: ^17.0.2 + + '@gravity-ui/charts@1.42.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/charts/-/charts-1.42.0.tgz} + peerDependencies: + '@gravity-ui/uikit': ^7.0.0 + react: ^17.0.2 + react-dom: ^17.0.2 + + '@gravity-ui/components@4.12.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/components/-/components-4.12.0.tgz} + peerDependencies: + '@gravity-ui/uikit': ^7.0.0 + react: ^17.0.2 + react-dom: ^17.0.2 + + '@gravity-ui/dashkit@10.0.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/dashkit/-/dashkit-10.0.0.tgz} + peerDependencies: + '@gravity-ui/icons': ^2.13.0 + '@gravity-ui/uikit': ^7.0.0 + react: ^17.0.2 + + '@gravity-ui/date-components@3.2.3': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/date-components/-/date-components-3.2.3.tgz} + peerDependencies: + '@gravity-ui/uikit': ^7.0.0 + react: ^17.0.2 + react-dom: ^17.0.2 + + '@gravity-ui/date-utils@2.5.6': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/date-utils/-/date-utils-2.5.6.tgz} + + '@gravity-ui/eslint-config@3.2.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/eslint-config/-/eslint-config-3.2.0.tgz} + peerDependencies: + eslint: ^8.0.0 + prettier: ^3.0.0 + peerDependenciesMeta: + prettier: + optional: true + + '@gravity-ui/expresskit@3.0.2': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/expresskit/-/expresskit-3.0.2.tgz} + peerDependencies: + '@gravity-ui/nodekit': ^1.6.0 || ^2.0.0 + + '@gravity-ui/gateway@4.10.4': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/gateway/-/gateway-4.10.4.tgz} + hasBin: true + peerDependencies: + grpc-reflection-js: ^0.3.0 + peerDependenciesMeta: + grpc-reflection-js: + optional: true + + '@gravity-ui/i18n@1.8.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/i18n/-/i18n-1.8.0.tgz} + + '@gravity-ui/icons@2.16.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/icons/-/icons-2.16.0.tgz} + peerDependencies: + react: ^17.0.2 + peerDependenciesMeta: + react: + optional: true + + '@gravity-ui/markdown-editor@15.26.1': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/markdown-editor/-/markdown-editor-15.26.1.tgz} + peerDependencies: + '@diplodoc/cut-extension': ^0.5.0 || ^0.6.1 || ^0.7.1 || ^1.0.0 + '@diplodoc/file-extension': ^0.2.1 + '@diplodoc/folding-headings-extension': ^0.1.0 + '@diplodoc/html-extension': ^2.3.2 + '@diplodoc/latex-extension': ^1.0.3 + '@diplodoc/mermaid-extension': ^1.0.0 + '@diplodoc/quote-link-extension': ^0.1.3 + '@diplodoc/tabs-extension': ^3.5.1 + '@diplodoc/transform': ^4.43.0 + '@gravity-ui/components': ^4.10.0 + '@gravity-ui/uikit': ^7.1.0 + highlight.js: ^11.8.0 + katex: ^0.16.9 + lowlight: ^3.0.0 + markdown-it: ^13.0.0 + react: ^17.0.2 + react-dom: ^17.0.2 + peerDependenciesMeta: + '@diplodoc/folding-headings-extension': + optional: true + '@diplodoc/html-extension': + optional: true + '@diplodoc/latex-extension': + optional: true + '@diplodoc/mermaid-extension': + optional: true + '@diplodoc/quote-link-extension': + optional: true + '@gravity-ui/components': + optional: true + highlight.js: + optional: true + lowlight: + optional: true + + '@gravity-ui/navigation@3.8.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/navigation/-/navigation-3.8.0.tgz} + peerDependencies: + '@bem-react/classname': ^1.6.0 + '@gravity-ui/icons': ^2.2.0 + '@gravity-ui/uikit': ^7.2.0 + react: ^17.0.2 + react-dom: ^17.0.2 + + '@gravity-ui/nodekit@2.10.1': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/nodekit/-/nodekit-2.10.1.tgz} + peerDependencies: + axios: ^1.7.7 + + '@gravity-ui/prettier-config@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/prettier-config/-/prettier-config-1.1.0.tgz} + peerDependencies: + prettier: '*' + + '@gravity-ui/react-data-table@2.1.1': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/react-data-table/-/react-data-table-2.1.1.tgz} + peerDependencies: + react: ^17.0.2 + + '@gravity-ui/sdk@1.5.1': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/sdk/-/sdk-1.5.1.tgz} + peerDependencies: + '@gravity-ui/gateway': '>=1.0.4' + + '@gravity-ui/stylelint-config@4.0.1': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/stylelint-config/-/stylelint-config-4.0.1.tgz} + peerDependencies: + postcss: ^8.0.0 + prettier: ^3.0.0 + stylelint: ^15.8.0 + peerDependenciesMeta: + prettier: + optional: true + + '@gravity-ui/tsconfig@1.0.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/tsconfig/-/tsconfig-1.0.0.tgz} + + '@gravity-ui/ui-logger@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/ui-logger/-/ui-logger-1.1.0.tgz} + + '@gravity-ui/uikit@7.18.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-7.18.0.tgz} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + '@gravity-ui/yagr@4.11.0': + resolution: {tarball: https://registry.npmjs.org/@gravity-ui/yagr/-/yagr-4.11.0.tgz} + engines: {node: '>=16.0.0'} + + '@grpc/grpc-js@1.14.3': + resolution: {tarball: https://registry.npmjs.org/@grpc%2fgrpc-js/-/grpc-js-1.14.3.tgz?rbtorrent=} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.15': + resolution: {tarball: https://registry.npmjs.org/@grpc%2fproto-loader/-/proto-loader-0.7.15.tgz?rbtorrent=} + engines: {node: '>=6'} + hasBin: true + + '@grpc/proto-loader@0.8.0': + resolution: {tarball: https://registry.npmjs.org/@grpc%2fproto-loader/-/proto-loader-0.8.0.tgz?rbtorrent=} + engines: {node: '>=6'} + hasBin: true + + '@humanwhocodes/config-array@0.11.14': + resolution: {tarball: https://registry.npmjs.org/@humanwhocodes%2fconfig-array/-/config-array-0.11.14.tgz?rbtorrent=} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {tarball: https://registry.npmjs.org/@humanwhocodes%2fmodule-importer/-/module-importer-1.0.1.tgz?rbtorrent=766ef96e37dea3309eccf9708a0058dceb33afb8} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {tarball: https://registry.npmjs.org/@humanwhocodes%2fobject-schema/-/object-schema-2.0.3.tgz?rbtorrent=} + deprecated: Use @eslint/object-schema instead + + '@iconify/types@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@iconify%2ftypes/-/types-2.0.0.tgz?rbtorrent=4edb79fdedf7677e3e1d6922c4436da30ac3e176} + + '@iconify/utils@3.1.0': + resolution: {tarball: https://registry.npmjs.org/@iconify%2futils/-/utils-3.1.0.tgz?rbtorrent=} + + '@isaacs/cliui@8.0.2': + resolution: {tarball: https://registry.npmjs.org/@isaacs%2fcliui/-/cliui-8.0.2.tgz?rbtorrent=2cca369f004c6d5cd17180ab17dc0a7d6471901b} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@istanbuljs%2fload-nyc-config/-/load-nyc-config-1.1.0.tgz?rbtorrent=0422e8acddd710280a7b081ec7b95a945c80779a} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {tarball: https://registry.npmjs.org/@istanbuljs%2fschema/-/schema-0.1.3.tgz?rbtorrent=cd625a4f9bad772a6dff021f13e8f62eb86a374b} + engines: {node: '>=8'} + + '@jest/console@27.5.1': + resolution: {tarball: https://registry.npmjs.org/@jest%2fconsole/-/console-27.5.1.tgz?rbtorrent=3f0ce23cdd358a3fba6c8cd24e095bc4bc0d200a} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + '@jest/console@29.4.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2fconsole/-/console-29.4.3.tgz?rbtorrent=b3c9e14ac3e4c80006b0cbe008fb758d7b1e66f8} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.4.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2fcore/-/core-29.4.3.tgz?rbtorrent=23b9438aeb8861fc293e939fa5fe3a3e5e353ac9} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@27.5.1': + resolution: {tarball: https://registry.npmjs.org/@jest%2fenvironment/-/environment-27.5.1.tgz?rbtorrent=3e7f78dbd8c779777907e27fca4482093ccdbe90} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + '@jest/environment@29.7.0': + resolution: {tarball: https://registry.npmjs.org/@jest%2fenvironment/-/environment-29.7.0.tgz?rbtorrent=bf12cc21b5650563ff1df418d4333ff3fbf23aff} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.4.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2fexpect-utils/-/expect-utils-29.4.3.tgz?rbtorrent=00feee03505b962b3e430779d1ce1e1d25654305} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.4.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2fexpect/-/expect-29.4.3.tgz?rbtorrent=9be2e7441be2bbc94352d05c877498aff0b90455} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@27.5.1': + resolution: {tarball: https://registry.npmjs.org/@jest%2ffake-timers/-/fake-timers-27.5.1.tgz?rbtorrent=9da6d9535a95d5124222d76bc9c57ad7434d4fa8} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {tarball: https://registry.npmjs.org/@jest%2ffake-timers/-/fake-timers-29.7.0.tgz?rbtorrent=54521b386759803b8e6cc2adbbb4f895760ad2ad} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@27.5.1': + resolution: {tarball: https://registry.npmjs.org/@jest%2fglobals/-/globals-27.5.1.tgz?rbtorrent=2b72923e6795d1975db2bbea0f75f18a2c658041} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + '@jest/globals@29.4.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2fglobals/-/globals-29.4.3.tgz?rbtorrent=0421b4d9e7edeb7c6ef14e517c9d76aa4b193288} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.4.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2freporters/-/reporters-29.4.3.tgz?rbtorrent=f4a757b8786155293eab9739194542720575f641} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2fschemas/-/schemas-29.6.3.tgz?rbtorrent=a833a7bcdf219d506316a9e088681867c3a98475} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@27.5.1': + resolution: {tarball: https://registry.npmjs.org/@jest%2fsource-map/-/source-map-27.5.1.tgz?rbtorrent=6f5afdf698baac12b9cedc9dd043fbe8a585d9f1} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + '@jest/source-map@29.4.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2fsource-map/-/source-map-29.4.3.tgz?rbtorrent=5d6cd4ef057e132f88a51029d396d9f1120e7eb7} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@27.5.1': + resolution: {tarball: https://registry.npmjs.org/@jest%2ftest-result/-/test-result-27.5.1.tgz?rbtorrent=b876fc422f68c672b5fd2da7966e7d301c8b12e3} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + '@jest/test-result@29.4.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2ftest-result/-/test-result-29.4.3.tgz?rbtorrent=1760ec6e7632a54388d3cbd5a5c78f495522c0e7} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.4.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2ftest-sequencer/-/test-sequencer-29.4.3.tgz?rbtorrent=34c5d9cd587be6b5fdd8c8fd9f3c1cece5a89f0d} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@27.5.1': + resolution: {tarball: https://registry.npmjs.org/@jest%2ftransform/-/transform-27.5.1.tgz?rbtorrent=8210f95385c9c869689a221874e4b76aebb6e482} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + '@jest/transform@29.4.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2ftransform/-/transform-29.4.3.tgz?rbtorrent=7f010e613133f73fff7f06208cb2d0add858f5cd} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@27.5.1': + resolution: {tarball: https://registry.npmjs.org/@jest%2ftypes/-/types-27.5.1.tgz?rbtorrent=06fe9b8baabde44d5fc11b6a2cea71d512a59900} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + '@jest/types@29.6.3': + resolution: {tarball: https://registry.npmjs.org/@jest%2ftypes/-/types-29.6.3.tgz?rbtorrent=6581b90aee1c90b5d0db7a4a8d8da38539a53ad2} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jitl/quickjs-ffi-types@0.31.0': + resolution: {tarball: https://registry.npmjs.org/@jitl%2fquickjs-ffi-types/-/quickjs-ffi-types-0.31.0.tgz?rbtorrent=} + + '@jitl/quickjs-wasmfile-debug-asyncify@0.31.0': + resolution: {tarball: https://registry.npmjs.org/@jitl%2fquickjs-wasmfile-debug-asyncify/-/quickjs-wasmfile-debug-asyncify-0.31.0.tgz?rbtorrent=} + + '@jitl/quickjs-wasmfile-debug-sync@0.31.0': + resolution: {tarball: https://registry.npmjs.org/@jitl%2fquickjs-wasmfile-debug-sync/-/quickjs-wasmfile-debug-sync-0.31.0.tgz?rbtorrent=} + + '@jitl/quickjs-wasmfile-release-asyncify@0.31.0': + resolution: {tarball: https://registry.npmjs.org/@jitl%2fquickjs-wasmfile-release-asyncify/-/quickjs-wasmfile-release-asyncify-0.31.0.tgz?rbtorrent=} + + '@jitl/quickjs-wasmfile-release-sync@0.31.0': + resolution: {tarball: https://registry.npmjs.org/@jitl%2fquickjs-wasmfile-release-sync/-/quickjs-wasmfile-release-sync-0.31.0.tgz?rbtorrent=} + + '@jridgewell/gen-mapping@0.1.1': + resolution: {tarball: https://registry.npmjs.org/@jridgewell%2fgen-mapping/-/gen-mapping-0.1.1.tgz?rbtorrent=1332cb97cd9be234925c51944b972715bb599b6a} + engines: {node: '>=6.0.0'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {tarball: https://registry.npmjs.org/@jridgewell%2fgen-mapping/-/gen-mapping-0.3.5.tgz?rbtorrent=} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.0': + resolution: {tarball: https://registry.npmjs.org/@jridgewell%2fresolve-uri/-/resolve-uri-3.1.0.tgz?rbtorrent=c2b711c5943ca6033a3c50deb531d41709a5a04e} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {tarball: https://registry.npmjs.org/@jridgewell%2fset-array/-/set-array-1.2.1.tgz?rbtorrent=} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.6': + resolution: {tarball: https://registry.npmjs.org/@jridgewell%2fsource-map/-/source-map-0.3.6.tgz?rbtorrent=} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@jridgewell%2fsourcemap-codec/-/sourcemap-codec-1.5.5.tgz?rbtorrent=} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {tarball: https://registry.npmjs.org/@jridgewell%2ftrace-mapping/-/trace-mapping-0.3.25.tgz?rbtorrent=} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {tarball: https://registry.npmjs.org/@jridgewell%2ftrace-mapping/-/trace-mapping-0.3.9.tgz?rbtorrent=8c5f678fe9dd416fcd13904986957bd1aff87e49} + + '@js-sdsl/ordered-map@4.4.2': + resolution: {tarball: https://registry.npmjs.org/@js-sdsl%2fordered-map/-/ordered-map-4.4.2.tgz?rbtorrent=} + + '@jsonjoy.com/base64@1.1.2': + resolution: {tarball: https://registry.npmjs.org/@jsonjoy.com%2fbase64/-/base64-1.1.2.tgz?rbtorrent=} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/json-pack@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@jsonjoy.com%2fjson-pack/-/json-pack-1.1.0.tgz?rbtorrent=} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/util@1.5.0': + resolution: {tarball: https://registry.npmjs.org/@jsonjoy.com%2futil/-/util-1.5.0.tgz?rbtorrent=} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@leichtgewicht/ip-codec@2.0.5': + resolution: {tarball: https://registry.npmjs.org/@leichtgewicht%2fip-codec/-/ip-codec-2.0.5.tgz?rbtorrent=} + + '@lezer/common@1.5.1': + resolution: {tarball: https://registry.npmjs.org/@lezer%2fcommon/-/common-1.5.1.tgz?rbtorrent=} + + '@lezer/css@1.3.0': + resolution: {tarball: https://registry.npmjs.org/@lezer%2fcss/-/css-1.3.0.tgz?rbtorrent=} + + '@lezer/highlight@1.2.3': + resolution: {tarball: https://registry.npmjs.org/@lezer%2fhighlight/-/highlight-1.2.3.tgz?rbtorrent=} + + '@lezer/html@1.3.13': + resolution: {tarball: https://registry.npmjs.org/@lezer%2fhtml/-/html-1.3.13.tgz?rbtorrent=} + + '@lezer/javascript@1.5.4': + resolution: {tarball: https://registry.npmjs.org/@lezer%2fjavascript/-/javascript-1.5.4.tgz?rbtorrent=} + + '@lezer/lr@1.4.8': + resolution: {tarball: https://registry.npmjs.org/@lezer%2flr/-/lr-1.4.8.tgz?rbtorrent=} + + '@lezer/markdown@1.6.3': + resolution: {tarball: https://registry.npmjs.org/@lezer%2fmarkdown/-/markdown-1.6.3.tgz?rbtorrent=} + + '@marijn/find-cluster-break@1.0.2': + resolution: {tarball: https://registry.npmjs.org/@marijn%2ffind-cluster-break/-/find-cluster-break-1.0.2.tgz?rbtorrent=} + + '@mermaid-js/parser@0.6.3': + resolution: {tarball: https://registry.npmjs.org/@mermaid-js%2fparser/-/parser-0.6.3.tgz?rbtorrent=} + + '@microsoft/fetch-event-source@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@microsoft%2ffetch-event-source/-/fetch-event-source-2.0.1.tgz?rbtorrent=ccda2e782e6ac50b67a5e3e390090e7dfb30d8c0} + + '@modern-js/node-bundle-require@2.68.2': + resolution: {tarball: https://registry.npmjs.org/@modern-js%2fnode-bundle-require/-/node-bundle-require-2.68.2.tgz?rbtorrent=} + + '@modern-js/utils@2.68.2': + resolution: {tarball: https://registry.npmjs.org/@modern-js%2futils/-/utils-2.68.2.tgz?rbtorrent=} + + '@module-federation/bridge-react-webpack-plugin@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fbridge-react-webpack-plugin/-/bridge-react-webpack-plugin-0.18.4.tgz?rbtorrent=} + + '@module-federation/cli@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fcli/-/cli-0.18.4.tgz?rbtorrent=} + engines: {node: '>=16.0.0'} + hasBin: true + + '@module-federation/data-prefetch@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fdata-prefetch/-/data-prefetch-0.18.4.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + '@module-federation/dts-plugin@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fdts-plugin/-/dts-plugin-0.18.4.tgz?rbtorrent=} + peerDependencies: + typescript: ^4.9.0 || ^5.0.0 + vue-tsc: '>=1.0.24' + peerDependenciesMeta: + vue-tsc: + optional: true + + '@module-federation/enhanced@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fenhanced/-/enhanced-0.18.4.tgz?rbtorrent=} + hasBin: true + peerDependencies: + typescript: ^4.9.0 || ^5.0.0 + vue-tsc: '>=1.0.24' + webpack: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + vue-tsc: + optional: true + webpack: + optional: true + + '@module-federation/error-codes@0.18.0': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2ferror-codes/-/error-codes-0.18.0.tgz?rbtorrent=} + + '@module-federation/error-codes@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2ferror-codes/-/error-codes-0.18.4.tgz?rbtorrent=} + + '@module-federation/inject-external-runtime-core-plugin@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2finject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-0.18.4.tgz?rbtorrent=} + peerDependencies: + '@module-federation/runtime-tools': 0.18.4 + + '@module-federation/managers@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fmanagers/-/managers-0.18.4.tgz?rbtorrent=} + + '@module-federation/manifest@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fmanifest/-/manifest-0.18.4.tgz?rbtorrent=} + + '@module-federation/rspack@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2frspack/-/rspack-0.18.4.tgz?rbtorrent=} + peerDependencies: + '@rspack/core': '>=0.7' + typescript: ^4.9.0 || ^5.0.0 + vue-tsc: '>=1.0.24' + peerDependenciesMeta: + typescript: + optional: true + vue-tsc: + optional: true + + '@module-federation/runtime-core@0.18.0': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fruntime-core/-/runtime-core-0.18.0.tgz?rbtorrent=} + + '@module-federation/runtime-core@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fruntime-core/-/runtime-core-0.18.4.tgz?rbtorrent=} + + '@module-federation/runtime-tools@0.18.0': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fruntime-tools/-/runtime-tools-0.18.0.tgz?rbtorrent=} + + '@module-federation/runtime-tools@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fruntime-tools/-/runtime-tools-0.18.4.tgz?rbtorrent=} + + '@module-federation/runtime@0.18.0': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fruntime/-/runtime-0.18.0.tgz?rbtorrent=} + + '@module-federation/runtime@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fruntime/-/runtime-0.18.4.tgz?rbtorrent=} + + '@module-federation/sdk@0.18.0': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fsdk/-/sdk-0.18.0.tgz?rbtorrent=} + + '@module-federation/sdk@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fsdk/-/sdk-0.18.4.tgz?rbtorrent=} + + '@module-federation/third-party-dts-extractor@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fthird-party-dts-extractor/-/third-party-dts-extractor-0.18.4.tgz?rbtorrent=} + + '@module-federation/webpack-bundler-runtime@0.18.0': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fwebpack-bundler-runtime/-/webpack-bundler-runtime-0.18.0.tgz?rbtorrent=} + + '@module-federation/webpack-bundler-runtime@0.18.4': + resolution: {tarball: https://registry.npmjs.org/@module-federation%2fwebpack-bundler-runtime/-/webpack-bundler-runtime-0.18.4.tgz?rbtorrent=} + + '@napi-rs/nice-android-arm-eabi@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/nice-android-arm64@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-android-arm64/-/nice-android-arm64-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/nice-darwin-arm64@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/nice-darwin-x64@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/nice-freebsd-x64@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/nice-linux-arm64-gnu@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-arm64-musl@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/nice-linux-ppc64-gnu@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-riscv64-gnu@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-s390x-gnu@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-x64-gnu@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/nice-linux-x64-musl@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/nice-openharmony-arm64@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [openharmony] + + '@napi-rs/nice-win32-arm64-msvc@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/nice-win32-ia32-msvc@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/nice-win32-x64-msvc@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/nice@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fnice/-/nice-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 10'} + + '@napi-rs/wasm-runtime@1.1.1': + resolution: {tarball: https://registry.npmjs.org/@napi-rs%2fwasm-runtime/-/wasm-runtime-1.1.1.tgz?rbtorrent=} + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + resolution: {tarball: https://registry.npmjs.org/@nicolo-ribaudo%2feslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz?rbtorrent=b5d687aab8e3e841c0abf99aa38c9be07b56044b} + + '@node-rs/crc32-android-arm-eabi@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-android-arm-eabi/-/crc32-android-arm-eabi-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@node-rs/crc32-android-arm64@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-android-arm64/-/crc32-android-arm64-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@node-rs/crc32-darwin-arm64@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-darwin-arm64/-/crc32-darwin-arm64-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@node-rs/crc32-darwin-x64@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-darwin-x64/-/crc32-darwin-x64-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@node-rs/crc32-freebsd-x64@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-freebsd-x64/-/crc32-freebsd-x64-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@node-rs/crc32-linux-arm-gnueabihf@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-linux-arm-gnueabihf/-/crc32-linux-arm-gnueabihf-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@node-rs/crc32-linux-arm64-gnu@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-linux-arm64-gnu/-/crc32-linux-arm64-gnu-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@node-rs/crc32-linux-arm64-musl@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-linux-arm64-musl/-/crc32-linux-arm64-musl-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@node-rs/crc32-linux-x64-gnu@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-linux-x64-gnu/-/crc32-linux-x64-gnu-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@node-rs/crc32-linux-x64-musl@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-linux-x64-musl/-/crc32-linux-x64-musl-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@node-rs/crc32-win32-arm64-msvc@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-win32-arm64-msvc/-/crc32-win32-arm64-msvc-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@node-rs/crc32-win32-ia32-msvc@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-win32-ia32-msvc/-/crc32-win32-ia32-msvc-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@node-rs/crc32-win32-x64-msvc@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32-win32-x64-msvc/-/crc32-win32-x64-msvc-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@node-rs/crc32@1.7.2': + resolution: {tarball: https://registry.npmjs.org/@node-rs%2fcrc32/-/crc32-1.7.2.tgz?rbtorrent=} + engines: {node: '>= 10'} + + '@nodelib/fs.scandir@2.1.5': + resolution: {tarball: https://registry.npmjs.org/@nodelib%2ffs.scandir/-/fs.scandir-2.1.5.tgz?rbtorrent=20a3ce459bcd9f4569c95f2cea540cc5a0a6cabd} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {tarball: https://registry.npmjs.org/@nodelib%2ffs.stat/-/fs.stat-2.0.5.tgz?rbtorrent=2a7527fa1e978156ee9355b7937bf361358af755} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {tarball: https://registry.npmjs.org/@nodelib%2ffs.walk/-/fs.walk-1.2.8.tgz?rbtorrent=5ddf43cf39be33dc869be719befc88768abc2eb1} + engines: {node: '>= 8'} + + '@okikio/sharedworker@1.0.7': + resolution: {tarball: https://registry.npmjs.org/@okikio%2fsharedworker/-/sharedworker-1.0.7.tgz?rbtorrent=} + + '@opentelemetry/api-logs@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fapi-logs/-/api-logs-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + + '@opentelemetry/api@1.9.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fapi/-/api-1.9.0.tgz?rbtorrent=} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@1.27.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fcontext-async-hooks/-/context-async-hooks-1.27.0.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.27.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fcore/-/core-1.27.0.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.30.1': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fcore/-/core-1.30.1.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-grpc@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fexporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-http@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fexporter-logs-otlp-http/-/exporter-logs-otlp-http-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-logs-otlp-proto@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fexporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-grpc@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fexporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-http@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fexporter-trace-otlp-http/-/exporter-trace-otlp-http-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fexporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/exporter-zipkin@1.27.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fexporter-zipkin/-/exporter-zipkin-1.27.0.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + + '@opentelemetry/instrumentation@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2finstrumentation/-/instrumentation-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fotlp-exporter-base/-/otlp-exporter-base-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-grpc-exporter-base@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fotlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fotlp-transformer/-/otlp-transformer-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@1.27.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fpropagator-b3/-/propagator-b3-1.27.0.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@1.27.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fpropagator-jaeger/-/propagator-jaeger-1.27.0.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@1.30.1': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fpropagator-jaeger/-/propagator-jaeger-1.30.1.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@1.27.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fresources/-/resources-1.27.0.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fsdk-logs/-/sdk-logs-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.27.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fsdk-metrics/-/sdk-metrics-1.27.0.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-node@0.54.2': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fsdk-node/-/sdk-node-0.54.2.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@1.27.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fsdk-trace-base/-/sdk-trace-base-1.27.0.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@1.27.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fsdk-trace-node/-/sdk-trace-node-1.27.0.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.27.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fsemantic-conventions/-/semantic-conventions-1.27.0.tgz?rbtorrent=} + engines: {node: '>=14'} + + '@opentelemetry/semantic-conventions@1.28.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fsemantic-conventions/-/semantic-conventions-1.28.0.tgz?rbtorrent=} + engines: {node: '>=14'} + + '@opentelemetry/semantic-conventions@1.32.0': + resolution: {tarball: https://registry.npmjs.org/@opentelemetry%2fsemantic-conventions/-/semantic-conventions-1.32.0.tgz?rbtorrent=} + engines: {node: '>=14'} + + '@parcel/watcher-android-arm64@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.0': + resolution: {tarball: https://registry.npmjs.org/@parcel%2fwatcher/-/watcher-2.5.0.tgz?rbtorrent=} + engines: {node: '>= 10.0.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {tarball: https://registry.npmjs.org/@pkgjs%2fparseargs/-/parseargs-0.11.0.tgz?rbtorrent=03f029bc7eace2eb2da5999055257521f38ca896} + engines: {node: '>=14'} + + '@pkgr/core@0.1.1': + resolution: {tarball: https://registry.npmjs.org/@pkgr%2fcore/-/core-0.1.1.tgz?rbtorrent=} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@playwright/test@1.48.2': + resolution: {tarball: https://registry.npmjs.org/@playwright%2ftest/-/test-1.48.2.tgz?rbtorrent=} + engines: {node: '>=18'} + hasBin: true + + '@pmmmwh/react-refresh-webpack-plugin@0.5.15': + resolution: {tarball: https://registry.npmjs.org/@pmmmwh%2freact-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz?rbtorrent=} + engines: {node: '>= 10.13'} + peerDependencies: + '@types/webpack': 4.x || 5.x + react-refresh: '>=0.10.0 <1.0.0' + sockjs-client: ^1.4.0 + type-fest: '>=0.17.0 <5.0.0' + webpack: '>=4.43.0 <6.0.0' + webpack-dev-server: 3.x || 4.x || 5.x + webpack-hot-middleware: 2.x + webpack-plugin-serve: 0.x || 1.x + peerDependenciesMeta: + '@types/webpack': + optional: true + sockjs-client: + optional: true + type-fest: + optional: true + webpack-dev-server: + optional: true + webpack-hot-middleware: + optional: true + webpack-plugin-serve: + optional: true + + '@polka/url@1.0.0-next.29': + resolution: {tarball: https://registry.npmjs.org/@polka%2furl/-/url-1.0.0-next.29.tgz?rbtorrent=} + + '@protobufjs/aspromise@1.1.2': + resolution: {tarball: https://registry.npmjs.org/@protobufjs%2faspromise/-/aspromise-1.1.2.tgz?rbtorrent=0b7cb0c6e97e771b0b2eca1d2d9f2910dd9d8817} + + '@protobufjs/base64@1.1.2': + resolution: {tarball: https://registry.npmjs.org/@protobufjs%2fbase64/-/base64-1.1.2.tgz?rbtorrent=da9d67580737b644d8c733b458503addf120d8be} + + '@protobufjs/codegen@2.0.4': + resolution: {tarball: https://registry.npmjs.org/@protobufjs%2fcodegen/-/codegen-2.0.4.tgz?rbtorrent=3b69160c38c42b9d274718cde5a90435f5d2f957} + + '@protobufjs/eventemitter@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@protobufjs%2feventemitter/-/eventemitter-1.1.0.tgz?rbtorrent=e83d3cb89e7fe726a61212a38fbe0c4cf76c521a} + + '@protobufjs/fetch@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@protobufjs%2ffetch/-/fetch-1.1.0.tgz?rbtorrent=70658be0afc887afaf485d31680cbe7edd2698f7} + + '@protobufjs/float@1.0.2': + resolution: {tarball: https://registry.npmjs.org/@protobufjs%2ffloat/-/float-1.0.2.tgz?rbtorrent=318b84e0df207760a0982047a828e8f40a1755f8} + + '@protobufjs/inquire@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@protobufjs%2finquire/-/inquire-1.1.0.tgz?rbtorrent=65674ee1f39b9156410aab79f8d19144491e0b0b} + + '@protobufjs/path@1.1.2': + resolution: {tarball: https://registry.npmjs.org/@protobufjs%2fpath/-/path-1.1.2.tgz?rbtorrent=07bce51ae4b0385fee21d9cc1f001af292cdd71a} + + '@protobufjs/pool@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@protobufjs%2fpool/-/pool-1.1.0.tgz?rbtorrent=5eb9f740b6ea3add1d9c40df060eba49a7f141be} + + '@protobufjs/utf8@1.1.0': + resolution: {tarball: https://registry.npmjs.org/@protobufjs%2futf8/-/utf8-1.1.0.tgz?rbtorrent=4b100a75703341f9022d0c3db0d47b2be32c5444} + + '@react-dnd/asap@4.0.1': + resolution: {tarball: https://registry.npmjs.org/@react-dnd%2fasap/-/asap-4.0.1.tgz?rbtorrent=f4146bab6bceda39104da08c4dcc1496a719bad2} + + '@react-dnd/invariant@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@react-dnd%2finvariant/-/invariant-2.0.0.tgz?rbtorrent=dbed7e7a9f4439d199984451007bf1bf7f187d95} + + '@react-dnd/shallowequal@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@react-dnd%2fshallowequal/-/shallowequal-2.0.0.tgz?rbtorrent=f4132a60b67f935d0f910181d47c84c7c6567b56} + + '@reduxjs/toolkit@1.9.7': + resolution: {tarball: https://registry.npmjs.org/@reduxjs%2ftoolkit/-/toolkit-1.9.7.tgz?rbtorrent=cd705af064447176e1cdc30ed69c23dad50c55cb} + peerDependencies: + react: ^17.0.2 + react-redux: ^7.2.1 || ^8.0.2 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + + '@rsbuild/plugin-check-syntax@1.3.0': + resolution: {tarball: https://registry.npmjs.org/@rsbuild%2fplugin-check-syntax/-/plugin-check-syntax-1.3.0.tgz?rbtorrent=} + peerDependencies: + '@rsbuild/core': 1.x + peerDependenciesMeta: + '@rsbuild/core': + optional: true + + '@rsdoctor/client@1.1.10': + resolution: {tarball: https://registry.npmjs.org/@rsdoctor%2fclient/-/client-1.1.10.tgz?rbtorrent=} + + '@rsdoctor/core@1.1.10': + resolution: {tarball: https://registry.npmjs.org/@rsdoctor%2fcore/-/core-1.1.10.tgz?rbtorrent=} + + '@rsdoctor/graph@1.1.10': + resolution: {tarball: https://registry.npmjs.org/@rsdoctor%2fgraph/-/graph-1.1.10.tgz?rbtorrent=} + + '@rsdoctor/rspack-plugin@1.1.10': + resolution: {tarball: https://registry.npmjs.org/@rsdoctor%2frspack-plugin/-/rspack-plugin-1.1.10.tgz?rbtorrent=} + peerDependencies: + '@rspack/core': '*' + peerDependenciesMeta: + '@rspack/core': + optional: true + + '@rsdoctor/sdk@1.1.10': + resolution: {tarball: https://registry.npmjs.org/@rsdoctor%2fsdk/-/sdk-1.1.10.tgz?rbtorrent=} + + '@rsdoctor/types@1.1.10': + resolution: {tarball: https://registry.npmjs.org/@rsdoctor%2ftypes/-/types-1.1.10.tgz?rbtorrent=} + peerDependencies: + '@rspack/core': '*' + webpack: 5.x + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + '@rsdoctor/utils@1.1.10': + resolution: {tarball: https://registry.npmjs.org/@rsdoctor%2futils/-/utils-1.1.10.tgz?rbtorrent=} + + '@rsdoctor/webpack-plugin@1.1.10': + resolution: {tarball: https://registry.npmjs.org/@rsdoctor%2fwebpack-plugin/-/webpack-plugin-1.1.10.tgz?rbtorrent=} + peerDependencies: + webpack: 5.x + + '@rspack/binding-darwin-arm64@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding-darwin-arm64/-/binding-darwin-arm64-1.5.5.tgz?rbtorrent=} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding-darwin-x64/-/binding-darwin-x64-1.5.5.tgz?rbtorrent=} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.5.5.tgz?rbtorrent=} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rspack/binding-linux-arm64-musl@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding-linux-arm64-musl/-/binding-linux-arm64-musl-1.5.5.tgz?rbtorrent=} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rspack/binding-linux-x64-gnu@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding-linux-x64-gnu/-/binding-linux-x64-gnu-1.5.5.tgz?rbtorrent=} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rspack/binding-linux-x64-musl@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding-linux-x64-musl/-/binding-linux-x64-musl-1.5.5.tgz?rbtorrent=} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rspack/binding-wasm32-wasi@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding-wasm32-wasi/-/binding-wasm32-wasi-1.5.5.tgz?rbtorrent=} + cpu: [wasm32] + + '@rspack/binding-win32-arm64-msvc@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.5.5.tgz?rbtorrent=} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.5.5.tgz?rbtorrent=} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding-win32-x64-msvc/-/binding-win32-x64-msvc-1.5.5.tgz?rbtorrent=} + cpu: [x64] + os: [win32] + + '@rspack/binding@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fbinding/-/binding-1.5.5.tgz?rbtorrent=} + + '@rspack/core@1.5.5': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fcore/-/core-1.5.5.tgz?rbtorrent=} + engines: {node: '>=18.12.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@rspack/dev-server@1.1.4': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fdev-server/-/dev-server-1.1.4.tgz?rbtorrent=} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@rspack/core': '*' + + '@rspack/lite-tapable@1.0.1': + resolution: {tarball: https://registry.npmjs.org/@rspack%2flite-tapable/-/lite-tapable-1.0.1.tgz?rbtorrent=} + engines: {node: '>=16.0.0'} + + '@rspack/plugin-react-refresh@1.5.3': + resolution: {tarball: https://registry.npmjs.org/@rspack%2fplugin-react-refresh/-/plugin-react-refresh-1.5.3.tgz?rbtorrent=} + peerDependencies: + react-refresh: '>=0.10.0 <1.0.0' + webpack-hot-middleware: 2.x + peerDependenciesMeta: + webpack-hot-middleware: + optional: true + + '@scarf/scarf@1.4.0': + resolution: {tarball: https://registry.npmjs.org/@scarf%2fscarf/-/scarf-1.4.0.tgz?rbtorrent=} + + '@sec-ant/readable-stream@0.4.1': + resolution: {tarball: https://registry.npmjs.org/@sec-ant%2freadable-stream/-/readable-stream-0.4.1.tgz?rbtorrent=} + + '@sinclair/typebox@0.27.8': + resolution: {tarball: https://registry.npmjs.org/@sinclair%2ftypebox/-/typebox-0.27.8.tgz?rbtorrent=b25467f2bdd67f30e10b96618b4406857b793a93} + + '@sindresorhus/is@5.6.0': + resolution: {tarball: https://registry.npmjs.org/@sindresorhus%2fis/-/is-5.6.0.tgz?rbtorrent=9f85121070ee7d81671c9dab6f274581f74792b3} + engines: {node: '>=14.16'} + + '@sinonjs/commons@1.8.6': + resolution: {tarball: https://registry.npmjs.org/@sinonjs%2fcommons/-/commons-1.8.6.tgz?rbtorrent=5d81ce2dbf87c71c37575aea4e78104fc4ffb75e} + + '@sinonjs/commons@3.0.1': + resolution: {tarball: https://registry.npmjs.org/@sinonjs%2fcommons/-/commons-3.0.1.tgz?rbtorrent=} + + '@sinonjs/fake-timers@10.3.0': + resolution: {tarball: https://registry.npmjs.org/@sinonjs%2ffake-timers/-/fake-timers-10.3.0.tgz?rbtorrent=06eb7e54b2c9fd93d5a696184c3af6303111107c} + + '@sinonjs/fake-timers@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@sinonjs%2ffake-timers/-/fake-timers-8.1.0.tgz?rbtorrent=304635607e500d4544a9d2804fd70e6b2ca72e0d} + + '@smithy/abort-controller@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fabort-controller/-/abort-controller-2.0.1.tgz?rbtorrent=cc868615ca3f3d5fb86a5e01d00b56b179a1a18d} + engines: {node: '>=14.0.0'} + + '@smithy/chunked-blob-reader-native@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fchunked-blob-reader-native/-/chunked-blob-reader-native-2.0.0.tgz?rbtorrent=affc273ddc88198bf21ae6a446d47f5ff4cf2247} + + '@smithy/chunked-blob-reader@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fchunked-blob-reader/-/chunked-blob-reader-2.0.0.tgz?rbtorrent=8b7d2e50cd08ed5ca7886eb1ecbc19e1980a4dc8} + + '@smithy/config-resolver@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fconfig-resolver/-/config-resolver-2.0.1.tgz?rbtorrent=c869a9ddf15c6b23ef24d3f26c2c760cc461b07d} + engines: {node: '>=14.0.0'} + + '@smithy/credential-provider-imds@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fcredential-provider-imds/-/credential-provider-imds-2.0.1.tgz?rbtorrent=210b29109024db15c8fd44b66ed512c430823d9f} + engines: {node: '>=14.0.0'} + + '@smithy/eventstream-codec@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2feventstream-codec/-/eventstream-codec-2.0.1.tgz?rbtorrent=6b9d326f758f5b1199020d32ca57b616e760524d} + + '@smithy/eventstream-serde-browser@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2feventstream-serde-browser/-/eventstream-serde-browser-2.0.1.tgz?rbtorrent=9cbc25928961e12fddeb8738866172c9e76304e5} + engines: {node: '>=14.0.0'} + + '@smithy/eventstream-serde-config-resolver@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2feventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.0.1.tgz?rbtorrent=1e70d3332f6309ed28e2603a511770a1bdecf510} + engines: {node: '>=14.0.0'} + + '@smithy/eventstream-serde-node@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2feventstream-serde-node/-/eventstream-serde-node-2.0.1.tgz?rbtorrent=e55a0a167f913459789b0abfcfb24947a814b1f6} + engines: {node: '>=14.0.0'} + + '@smithy/eventstream-serde-universal@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2feventstream-serde-universal/-/eventstream-serde-universal-2.0.1.tgz?rbtorrent=2ff516b5bfcebd72e1e299528dca799480bf30ab} + engines: {node: '>=14.0.0'} + + '@smithy/fetch-http-handler@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2ffetch-http-handler/-/fetch-http-handler-2.0.1.tgz?rbtorrent=6b2f91506f3b275bcb3b794750d1a5ce69fda30c} + + '@smithy/hash-blob-browser@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fhash-blob-browser/-/hash-blob-browser-2.0.1.tgz?rbtorrent=acbeea04158250bf8837786109c3e6853d978447} + + '@smithy/hash-node@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fhash-node/-/hash-node-2.0.1.tgz?rbtorrent=ab83b835ab39ca1583c529e5f318570d0434b1a7} + engines: {node: '>=14.0.0'} + + '@smithy/hash-stream-node@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fhash-stream-node/-/hash-stream-node-2.0.1.tgz?rbtorrent=508d64d6677285343f649d46d6613f4b3b497dd6} + engines: {node: '>=14.0.0'} + + '@smithy/invalid-dependency@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2finvalid-dependency/-/invalid-dependency-2.0.1.tgz?rbtorrent=92ddc18c1edd576fbe9874a64174207f5843d9ec} + + '@smithy/is-array-buffer@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fis-array-buffer/-/is-array-buffer-2.0.0.tgz?rbtorrent=6e96ae6022505284713bd68ec06b2c4f66fc4ca1} + engines: {node: '>=14.0.0'} + + '@smithy/md5-js@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fmd5-js/-/md5-js-2.0.1.tgz?rbtorrent=d8fdb116018c4fc5bbab9d000a10123ac7245eb6} + + '@smithy/middleware-content-length@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fmiddleware-content-length/-/middleware-content-length-2.0.1.tgz?rbtorrent=d0c91be563380f47204f0ea195847c05fc38f73d} + engines: {node: '>=14.0.0'} + + '@smithy/middleware-endpoint@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fmiddleware-endpoint/-/middleware-endpoint-2.0.1.tgz?rbtorrent=8828e584626a01bd19f839a7cea631cd9db4baa1} + engines: {node: '>=14.0.0'} + + '@smithy/middleware-retry@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fmiddleware-retry/-/middleware-retry-2.0.1.tgz?rbtorrent=9843671ef5b7d97351ef38e7e1ac0a0de5e9d88f} + engines: {node: '>=14.0.0'} + + '@smithy/middleware-serde@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fmiddleware-serde/-/middleware-serde-2.0.1.tgz?rbtorrent=dd8d8f2e59cbeb562ac8221af2eba8979b128b10} + engines: {node: '>=14.0.0'} + + '@smithy/middleware-stack@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fmiddleware-stack/-/middleware-stack-2.0.0.tgz?rbtorrent=45260107837e98b6edff4c085d64889e885b1fd9} + engines: {node: '>=14.0.0'} + + '@smithy/node-config-provider@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fnode-config-provider/-/node-config-provider-2.0.1.tgz?rbtorrent=f45ae0219eacf8de0f8bf7d28922bf8305ca8754} + engines: {node: '>=14.0.0'} + + '@smithy/node-http-handler@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fnode-http-handler/-/node-http-handler-2.0.1.tgz?rbtorrent=ecd5e091dcb7a38197ee6c0ae9d808f8e28e5dbe} + engines: {node: '>=14.0.0'} + + '@smithy/property-provider@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fproperty-provider/-/property-provider-2.0.1.tgz?rbtorrent=4906cdcb94ce62e95e15150557680e45e3adb056} + engines: {node: '>=14.0.0'} + + '@smithy/protocol-http@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fprotocol-http/-/protocol-http-2.0.1.tgz?rbtorrent=be060db08ad0c1b00d332cba99c9c1c8e02e82f8} + engines: {node: '>=14.0.0'} + + '@smithy/querystring-builder@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fquerystring-builder/-/querystring-builder-2.0.1.tgz?rbtorrent=4cf9f94896179e591f2557e99213f39aec934b5d} + engines: {node: '>=14.0.0'} + + '@smithy/querystring-parser@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fquerystring-parser/-/querystring-parser-2.0.1.tgz?rbtorrent=1f7cef298f2f71be3ceed3b6e11c79ddb17de166} + engines: {node: '>=14.0.0'} + + '@smithy/service-error-classification@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fservice-error-classification/-/service-error-classification-2.0.0.tgz?rbtorrent=a783476fb161aa9f68c7e7e044331b25538859e0} + engines: {node: '>=14.0.0'} + + '@smithy/shared-ini-file-loader@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fshared-ini-file-loader/-/shared-ini-file-loader-2.0.1.tgz?rbtorrent=d955f8101ece0dfe3f751e83d96640a469392970} + engines: {node: '>=14.0.0'} + + '@smithy/signature-v4@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fsignature-v4/-/signature-v4-2.0.1.tgz?rbtorrent=45b2fb590d17b9a2040adbd6bbc44f7f99b35c44} + engines: {node: '>=14.0.0'} + + '@smithy/smithy-client@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2fsmithy-client/-/smithy-client-2.0.1.tgz?rbtorrent=bf58b4650868d85fc156dcb528c85bf986072253} + engines: {node: '>=14.0.0'} + + '@smithy/types@2.0.2': + resolution: {tarball: https://registry.npmjs.org/@smithy%2ftypes/-/types-2.0.2.tgz?rbtorrent=aa9dbcd015c9758c0c57b7cc5e9bb73004244413} + engines: {node: '>=14.0.0'} + + '@smithy/url-parser@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2furl-parser/-/url-parser-2.0.1.tgz?rbtorrent=eb828fba3e0ac92a1914aa1914668bc1e570dfd2} + + '@smithy/util-base64@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-base64/-/util-base64-2.0.0.tgz?rbtorrent=e92ad4149fc95aecc2ea9122cff0ae99f751dba4} + engines: {node: '>=14.0.0'} + + '@smithy/util-body-length-browser@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-body-length-browser/-/util-body-length-browser-2.0.0.tgz?rbtorrent=f5139dbac661ccc5128194de41aa020b49ed3e24} + + '@smithy/util-body-length-node@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-body-length-node/-/util-body-length-node-2.0.0.tgz?rbtorrent=864d83df715e1470c6d442eccc57cdef3b98a10a} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-buffer-from/-/util-buffer-from-2.0.0.tgz?rbtorrent=816ba5fadf757b445566c71ddd0fbb0003a84adc} + engines: {node: '>=14.0.0'} + + '@smithy/util-config-provider@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-config-provider/-/util-config-provider-2.0.0.tgz?rbtorrent=2c37d0bca6257ff3e4307c7e6ee715e6ed60b768} + engines: {node: '>=14.0.0'} + + '@smithy/util-defaults-mode-browser@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-defaults-mode-browser/-/util-defaults-mode-browser-2.0.1.tgz?rbtorrent=7980cac170549cb7ebaf3442209858166b781a06} + engines: {node: '>= 10.0.0'} + + '@smithy/util-defaults-mode-node@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-defaults-mode-node/-/util-defaults-mode-node-2.0.1.tgz?rbtorrent=9a59160e7a4bd79a0b11c36c7d0ee6d8c8f1c95b} + engines: {node: '>= 10.0.0'} + + '@smithy/util-hex-encoding@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-hex-encoding/-/util-hex-encoding-2.0.0.tgz?rbtorrent=5eed25ac4e8b733479d5e5d738e797803ca17d57} + engines: {node: '>=14.0.0'} + + '@smithy/util-middleware@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-middleware/-/util-middleware-2.0.0.tgz?rbtorrent=1695fbefbdda085505845fafbd3f5cf03cd45333} + engines: {node: '>=14.0.0'} + + '@smithy/util-retry@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-retry/-/util-retry-2.0.0.tgz?rbtorrent=db17c7a3b8bb0fe0d201cd4ce3057c9f18d19dcb} + engines: {node: '>= 14.0.0'} + + '@smithy/util-stream@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-stream/-/util-stream-2.0.1.tgz?rbtorrent=d1d03894cf4032e0f58925840446eb935338dd3f} + engines: {node: '>=14.0.0'} + + '@smithy/util-uri-escape@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-uri-escape/-/util-uri-escape-2.0.0.tgz?rbtorrent=39df1f48f307497d6ac9f3e004b91fc298807a88} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-utf8/-/util-utf8-2.0.0.tgz?rbtorrent=f9d17304686fa98919fa7a63bf9928e6192f7195} + engines: {node: '>=14.0.0'} + + '@smithy/util-waiter@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@smithy%2futil-waiter/-/util-waiter-2.0.1.tgz?rbtorrent=dc5b2b7d95549fd9e9e0ba254f7b61a5988d6fa5} + engines: {node: '>=14.0.0'} + + '@socket.io/component-emitter@3.1.2': + resolution: {tarball: https://registry.npmjs.org/@socket.io%2fcomponent-emitter/-/component-emitter-3.1.2.tgz?rbtorrent=} + + '@statoscope/cli@5.28.2': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fcli/-/cli-5.28.2.tgz?rbtorrent=4860d3356aea5aea0ffa8a26d753ec8d2572e1bb} + engines: {node: '>=12.0.0'} + hasBin: true + + '@statoscope/config@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fconfig/-/config-5.28.1.tgz?rbtorrent=4fe2ccd83fbb6ce3bf4e147b0afed659a058e6fd} + + '@statoscope/extensions@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fextensions/-/extensions-5.28.1.tgz?rbtorrent=6a2931924f734863b09b764de7a431fc08d94797} + + '@statoscope/helpers@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fhelpers/-/helpers-5.28.1.tgz?rbtorrent=fd863f1cb9ad6a018ddc521eb6f0c74a5b2bd586} + + '@statoscope/report-writer@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2freport-writer/-/report-writer-5.28.1.tgz?rbtorrent=318cbf89cc0bb2c57fd656ad58e881a677db1add} + + '@statoscope/stats-extension-compressed@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fstats-extension-compressed/-/stats-extension-compressed-5.28.1.tgz?rbtorrent=4a4c665cbdf123e920dd04afd925651a5e46a625} + + '@statoscope/stats-extension-custom-reports@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fstats-extension-custom-reports/-/stats-extension-custom-reports-5.28.1.tgz?rbtorrent=7377ff1de66245b20d0048d483552f4585525571} + + '@statoscope/stats-extension-package-info@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fstats-extension-package-info/-/stats-extension-package-info-5.28.1.tgz?rbtorrent=2d4845f6b9872c73f4c9f2d4168cdc31e3a350dc} + + '@statoscope/stats-extension-stats-validation-result@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fstats-extension-stats-validation-result/-/stats-extension-stats-validation-result-5.28.1.tgz?rbtorrent=086912a7554ed2f6698bb353056aea1d0db3a28c} + + '@statoscope/stats-validator-plugin-webpack@5.28.2': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fstats-validator-plugin-webpack/-/stats-validator-plugin-webpack-5.28.2.tgz?rbtorrent=806081deafdb107b3ffd71ce6924d1b5a4861899} + + '@statoscope/stats-validator-reporter-console@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fstats-validator-reporter-console/-/stats-validator-reporter-console-5.28.1.tgz?rbtorrent=bea42f74e0699b2616246273a081ceb92193628c} + + '@statoscope/stats-validator-reporter-stats-report@5.28.2': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fstats-validator-reporter-stats-report/-/stats-validator-reporter-stats-report-5.28.2.tgz?rbtorrent=91718a46b9ab390666ef4afad8bc5217cd373321} + + '@statoscope/stats-validator@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fstats-validator/-/stats-validator-5.28.1.tgz?rbtorrent=aec79517ee7261f8629a3fa8ad859e364db2c0b6} + + '@statoscope/stats@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fstats/-/stats-5.28.1.tgz?rbtorrent=a1a3f97e5f9ecc6928ecdd49d2524d12f9e638a9} + + '@statoscope/types@5.28.1': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2ftypes/-/types-5.28.1.tgz?rbtorrent=36ea0ead240bd4ae1a3d8f11dc4ac02971b9b6a8} + + '@statoscope/webpack-model@5.28.2': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fwebpack-model/-/webpack-model-5.28.2.tgz?rbtorrent=c1f9ace51c791958ad9ab4a9e192f867f9d2c2cb} + + '@statoscope/webpack-model@5.29.0': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fwebpack-model/-/webpack-model-5.29.0.tgz?rbtorrent=} + + '@statoscope/webpack-plugin@5.29.0': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fwebpack-plugin/-/webpack-plugin-5.29.0.tgz?rbtorrent=} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + + '@statoscope/webpack-stats-extension-compressed@5.29.0': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fwebpack-stats-extension-compressed/-/webpack-stats-extension-compressed-5.29.0.tgz?rbtorrent=} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + + '@statoscope/webpack-stats-extension-package-info@5.29.0': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fwebpack-stats-extension-package-info/-/webpack-stats-extension-package-info-5.29.0.tgz?rbtorrent=} + peerDependencies: + '@rspack/core': ^1.0.0 + webpack: ^4.0.0 || ^5.0.0 + + '@statoscope/webpack-ui@5.28.2': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fwebpack-ui/-/webpack-ui-5.28.2.tgz?rbtorrent=e957abc69ae0a4f20d5005a0869772b48ef2f417} + + '@statoscope/webpack-ui@5.29.0': + resolution: {tarball: https://registry.npmjs.org/@statoscope%2fwebpack-ui/-/webpack-ui-5.29.0.tgz?rbtorrent=} + + '@stripe/react-stripe-js@1.8.1': + resolution: {tarball: https://registry.npmjs.org/@stripe%2freact-stripe-js/-/react-stripe-js-1.8.1.tgz?rbtorrent=588352f3a5f5020eec0d5db9d24c540b7409c355} + peerDependencies: + '@stripe/stripe-js': ^1.26.0 + react: ^17.0.2 + react-dom: ^17.0.2 + + '@stripe/stripe-js@1.30.0': + resolution: {tarball: https://registry.npmjs.org/@stripe%2fstripe-js/-/stripe-js-1.30.0.tgz?rbtorrent=96cfcbd94d926ec32b60eb62612978f6bc2ee392} + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fbabel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz?rbtorrent=010ef000b326806fe036214577c071f4cfbfef37} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fbabel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz?rbtorrent=dd2c41868f818ef38e98dde47ffcfb014a953be4} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fbabel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz?rbtorrent=d52ef852cfa12d18dbb78a148e3f3856a6b69b10} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fbabel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz?rbtorrent=5752c0ef4bccc674e67328c383519988b24627d0} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fbabel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz?rbtorrent=6c9ed7d195d4a6d73f7319c1e4056ef9fb8a95bf} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fbabel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz?rbtorrent=68922edd48a5fe35fbfd627a05f8966b757859e8} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fbabel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz?rbtorrent=e6cd3591acb94b2cf3b24c60f8010b325bf1695b} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fbabel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz?rbtorrent=3f7a522a816ca4b50df0a6c7f127d53a77745280} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fbabel-preset/-/babel-preset-8.1.0.tgz?rbtorrent=6850764ac5689c5800d24713d1e14c54cd682683} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/core@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fcore/-/core-8.1.0.tgz?rbtorrent=43828422cf6f375f28a3c859cd0e16950f0f03d2} + engines: {node: '>=14'} + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fhast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz?rbtorrent=2c1ad724f3fc162ced3b496524d5a898ec4f25f8} + engines: {node: '>=14'} + + '@svgr/plugin-jsx@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fplugin-jsx/-/plugin-jsx-8.1.0.tgz?rbtorrent=652ab54566ba63393b82f3b35e4b3d30984c7761} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@svgr/plugin-svgo@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fplugin-svgo/-/plugin-svgo-8.1.0.tgz?rbtorrent=bcfc469a525934678955b50f45b152c1aacdbd4d} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + + '@svgr/webpack@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@svgr%2fwebpack/-/webpack-8.1.0.tgz?rbtorrent=e8d0fb1f848493f214d7e61c52e2e9466f900f4d} + engines: {node: '>=14'} + + '@swc/cli@0.7.8': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcli/-/cli-0.7.8.tgz?rbtorrent=} + engines: {node: '>= 16.14.0'} + hasBin: true + peerDependencies: + '@swc/core': ^1.2.66 + chokidar: ^4.0.1 + peerDependenciesMeta: + chokidar: + optional: true + + '@swc/core-darwin-arm64@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore-darwin-x64/-/core-darwin-x64-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-arm64-musl@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc/core-linux-x64-gnu@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-musl@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc/core-win32-arm64-msvc@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.13.5': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcore/-/core-1.13.5.tgz?rbtorrent=} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {tarball: https://registry.npmjs.org/@swc%2fcounter/-/counter-0.1.3.tgz?rbtorrent=} + + '@swc/helpers@0.5.17': + resolution: {tarball: https://registry.npmjs.org/@swc%2fhelpers/-/helpers-0.5.17.tgz?rbtorrent=} + + '@swc/plugin-transform-imports@9.1.0': + resolution: {tarball: https://registry.npmjs.org/@swc%2fplugin-transform-imports/-/plugin-transform-imports-9.1.0.tgz?rbtorrent=} + + '@swc/types@0.1.25': + resolution: {tarball: https://registry.npmjs.org/@swc%2ftypes/-/types-0.1.25.tgz?rbtorrent=} + + '@szmarczak/http-timer@5.0.1': + resolution: {tarball: https://registry.npmjs.org/@szmarczak%2fhttp-timer/-/http-timer-5.0.1.tgz?rbtorrent=99bd8fa633bb5b8e8ed6651d5ebe2d4ea37d6f54} + engines: {node: '>=14.16'} + + '@tanstack/react-table@8.12.0': + resolution: {tarball: https://registry.npmjs.org/@tanstack%2freact-table/-/react-table-8.12.0.tgz?rbtorrent=} + engines: {node: '>=12'} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + '@tanstack/react-virtual@3.13.12': + resolution: {tarball: https://registry.npmjs.org/@tanstack%2freact-virtual/-/react-virtual-3.13.12.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + '@tanstack/react-virtual@3.13.9': + resolution: {tarball: https://registry.npmjs.org/@tanstack%2freact-virtual/-/react-virtual-3.13.9.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + '@tanstack/table-core@8.12.0': + resolution: {tarball: https://registry.npmjs.org/@tanstack%2ftable-core/-/table-core-8.12.0.tgz?rbtorrent=} + engines: {node: '>=12'} + + '@tanstack/virtual-core@3.13.12': + resolution: {tarball: https://registry.npmjs.org/@tanstack%2fvirtual-core/-/virtual-core-3.13.12.tgz?rbtorrent=} + + '@tanstack/virtual-core@3.13.9': + resolution: {tarball: https://registry.npmjs.org/@tanstack%2fvirtual-core/-/virtual-core-3.13.9.tgz?rbtorrent=} + + '@testing-library/dom@8.11.3': + resolution: {tarball: https://registry.npmjs.org/@testing-library%2fdom/-/dom-8.11.3.tgz?rbtorrent=138446092eaf9e0895f8f3363945ad498ba04bd8} + engines: {node: '>=12'} + + '@testing-library/jest-dom@5.16.2': + resolution: {tarball: https://registry.npmjs.org/@testing-library%2fjest-dom/-/jest-dom-5.16.2.tgz?rbtorrent=0957c86d01d221dc226884881c610d0cd66f7343} + engines: {node: '>=8', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@12.1.4': + resolution: {tarball: https://registry.npmjs.org/@testing-library%2freact/-/react-12.1.4.tgz?rbtorrent=7af3bbf31c749dacc67aca007168f268840b65a5} + engines: {node: '>=12'} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + '@tokenizer/inflate@0.2.7': + resolution: {tarball: https://registry.npmjs.org/@tokenizer%2finflate/-/inflate-0.2.7.tgz?rbtorrent=} + engines: {node: '>=18'} + + '@tokenizer/token@0.3.0': + resolution: {tarball: https://registry.npmjs.org/@tokenizer%2ftoken/-/token-0.3.0.tgz?rbtorrent=76f3a79368811349a871a2bf8f5047a117930df0} + + '@tootallnate/once@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@tootallnate%2fonce/-/once-2.0.0.tgz?rbtorrent=23b462a593765450973514e30a9cc4e6b950fcbd} + engines: {node: '>= 10'} + + '@trysound/sax@0.2.0': + resolution: {tarball: https://registry.npmjs.org/@trysound%2fsax/-/sax-0.2.0.tgz?rbtorrent=e622719d72d3aef7f4ec353835d62753cc70c418} + engines: {node: '>=10.13.0'} + + '@tsconfig/node10@1.0.8': + resolution: {tarball: https://registry.npmjs.org/@tsconfig%2fnode10/-/node10-1.0.8.tgz?rbtorrent=ccc2d7e98012da636e0b7b629b57740666b159ae} + + '@tsconfig/node12@1.0.9': + resolution: {tarball: https://registry.npmjs.org/@tsconfig%2fnode12/-/node12-1.0.9.tgz?rbtorrent=34e7ab9126454e091e33b217652aa341bf1cd0ae} + + '@tsconfig/node14@1.0.1': + resolution: {tarball: https://registry.npmjs.org/@tsconfig%2fnode14/-/node14-1.0.1.tgz?rbtorrent=f30564bd5f6d5e30d4b3641b228427f092b9d530} + + '@tsconfig/node16@1.0.2': + resolution: {tarball: https://registry.npmjs.org/@tsconfig%2fnode16/-/node16-1.0.2.tgz?rbtorrent=9513d7470c011779f0317ac2ebfe3464bd3465c2} + + '@tybys/wasm-util@0.10.1': + resolution: {tarball: https://registry.npmjs.org/@tybys%2fwasm-util/-/wasm-util-0.10.1.tgz?rbtorrent=} + + '@types/archy@0.0.32': + resolution: {tarball: https://registry.npmjs.org/@types%2farchy/-/archy-0.0.32.tgz?rbtorrent=c3a7e84b3cdf6d46fbc53585cf01b64a4145da1f} + + '@types/aria-query@4.2.2': + resolution: {tarball: https://registry.npmjs.org/@types%2faria-query/-/aria-query-4.2.2.tgz?rbtorrent=3c39e37b2a7f86d360038b35eb1cecf00e2d1034} + + '@types/babel__core@7.20.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fbabel__core/-/babel__core-7.20.0.tgz?rbtorrent=1f762604a908430851fbebb192afb777b309abdf} + + '@types/babel__generator@7.6.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fbabel__generator/-/babel__generator-7.6.4.tgz?rbtorrent=d74c4ea062c980c6eddab5fa16437c0844aab94c} + + '@types/babel__template@7.4.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fbabel__template/-/babel__template-7.4.1.tgz?rbtorrent=3880464fd37cde6c41a715fe76b070c577b44aca} + + '@types/babel__traverse@7.18.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fbabel__traverse/-/babel__traverse-7.18.3.tgz?rbtorrent=ab9645f0679a49d60c238456385a0cd8f28c6715} + + '@types/body-parser@1.19.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fbody-parser/-/body-parser-1.19.0.tgz?rbtorrent=b01362eab8a7a29a13aa7b790ec7a4531e97f797} + + '@types/bonjour@3.5.13': + resolution: {tarball: https://registry.npmjs.org/@types%2fbonjour/-/bonjour-3.5.13.tgz?rbtorrent=ab4f42b9c482012f9d5ff1937ab0f0bfc0a9dc94} + + '@types/caseless@0.12.5': + resolution: {tarball: https://registry.npmjs.org/@types%2fcaseless/-/caseless-0.12.5.tgz?rbtorrent=b9197f2b8f6b060f16c246b1bb2a218d4f155e13} + + '@types/chroma-js@3.1.2': + resolution: {tarball: https://registry.npmjs.org/@types%2fchroma-js/-/chroma-js-3.1.2.tgz?rbtorrent=} + + '@types/connect-history-api-fallback@1.5.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fconnect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz?rbtorrent=} + + '@types/connect@3.4.38': + resolution: {tarball: https://registry.npmjs.org/@types%2fconnect/-/connect-3.4.38.tgz?rbtorrent=14d09dcf0756f71944bafdf9f5fcbfcfe85324de} + + '@types/cookie@0.6.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fcookie/-/cookie-0.6.0.tgz?rbtorrent=} + + '@types/cors@2.8.19': + resolution: {tarball: https://registry.npmjs.org/@types%2fcors/-/cors-2.8.19.tgz?rbtorrent=} + + '@types/d3-array@3.2.2': + resolution: {} + + '@types/d3-axis@3.0.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-axis/-/d3-axis-3.0.3.tgz?rbtorrent=e72b2213061eff145243df61170dee7ffd8b8be1} + + '@types/d3-brush@3.0.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-brush/-/d3-brush-3.0.3.tgz?rbtorrent=6b31053fcb33d3b50371e334b4d5609557c05bc6} + + '@types/d3-chord@3.0.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-chord/-/d3-chord-3.0.3.tgz?rbtorrent=57e4da6421dc9e887fe9b6bd4d59e74d4bd44fa9} + + '@types/d3-color@3.1.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-color/-/d3-color-3.1.3.tgz?rbtorrent=f36101772b833dc8c5b2994ccc45ff4b7dca02c3} + + '@types/d3-contour@3.0.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-contour/-/d3-contour-3.0.3.tgz?rbtorrent=868b4f8ba3f08c6e457e5e3678330b84bc3103c5} + + '@types/d3-delaunay@6.0.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-delaunay/-/d3-delaunay-6.0.1.tgz?rbtorrent=11d2720fed02752fcc722a16f750a1abe8135711} + + '@types/d3-dispatch@3.0.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-dispatch/-/d3-dispatch-3.0.3.tgz?rbtorrent=fc5a8217507392dff860ade6e375f674f78a479f} + + '@types/d3-drag@3.0.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-drag/-/d3-drag-3.0.3.tgz?rbtorrent=ba423f80a77770fdfab0a802ba72a7fc9a97931d} + + '@types/d3-dsv@3.0.2': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-dsv/-/d3-dsv-3.0.2.tgz?rbtorrent=d705de9e0cc5d088712a2bbb5dddb0b916499321} + + '@types/d3-ease@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-ease/-/d3-ease-3.0.0.tgz?rbtorrent=efb8b91991a086ae5fca48468841a9e53094ab56} + + '@types/d3-fetch@3.0.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-fetch/-/d3-fetch-3.0.3.tgz?rbtorrent=0eb1b9841ee753ec47bf2e83b963a93b502adb95} + + '@types/d3-force@3.0.5': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-force/-/d3-force-3.0.5.tgz?rbtorrent=389cc32cf32fa5a9061e9b73708fe8c270683628} + + '@types/d3-format@3.0.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-format/-/d3-format-3.0.1.tgz?rbtorrent=357892642d02e36f43ef5202605df0d94839e9fd} + + '@types/d3-geo@3.0.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-geo/-/d3-geo-3.0.4.tgz?rbtorrent=00e7ab16ac91bafc553282a453e6e4589a5d0714} + + '@types/d3-hierarchy@3.1.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-hierarchy/-/d3-hierarchy-3.1.3.tgz?rbtorrent=0d1619d5e383e64d6d9197a39d875a437720177e} + + '@types/d3-interpolate@3.0.4': + resolution: {} + + '@types/d3-path@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-path/-/d3-path-3.0.0.tgz?rbtorrent=7a896697f59d7d59593bdab662bb90d028a8fad3} + + '@types/d3-polygon@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-polygon/-/d3-polygon-3.0.0.tgz?rbtorrent=5b6b15914cc8307097e096b15113ea1fc8828874} + + '@types/d3-quadtree@3.0.2': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-quadtree/-/d3-quadtree-3.0.2.tgz?rbtorrent=867316daad6619d9a396b7f9bff9dd291cf03d7d} + + '@types/d3-random@3.0.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-random/-/d3-random-3.0.1.tgz?rbtorrent=c5fdab41a81baf2858e5b77b67f9aa7bc76b61e7} + + '@types/d3-scale-chromatic@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz?rbtorrent=5ec33b3dc2d0d6cc500b79316db374e953549bcb} + + '@types/d3-scale@4.0.9': + resolution: {} + + '@types/d3-selection@3.0.11': + resolution: {} + + '@types/d3-shape@3.1.8': + resolution: {} + + '@types/d3-time-format@4.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-time-format/-/d3-time-format-4.0.0.tgz?rbtorrent=79ebc708e18753f02867ea4b5f0c74662d169bdb} + + '@types/d3-time@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-time/-/d3-time-3.0.0.tgz?rbtorrent=6db4617086d63dd5d16af61ecf3532085b74b071} + + '@types/d3-timer@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-timer/-/d3-timer-3.0.0.tgz?rbtorrent=43c09659d0922b159928366a72aee19aa45fcc39} + + '@types/d3-transition@3.0.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-transition/-/d3-transition-3.0.4.tgz?rbtorrent=c2ddf5c9f258f5b382791bb66bcf6f4236d6a8ad} + + '@types/d3-zoom@3.0.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3-zoom/-/d3-zoom-3.0.4.tgz?rbtorrent=c18f400078cd687073b09b6fa66dc29692d53772} + + '@types/d3@7.4.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fd3/-/d3-7.4.3.tgz?rbtorrent=543bcbbc6e6b5474aa3d4c51fe49b8de173afbf6} + + '@types/diff-match-patch@1.0.36': + resolution: {tarball: https://registry.npmjs.org/@types%2fdiff-match-patch/-/diff-match-patch-1.0.36.tgz?rbtorrent=fa127289c31461c989cdaa5dc18ed4e4cf208be4} + + '@types/dompurify@3.0.5': + resolution: {tarball: https://registry.npmjs.org/@types%2fdompurify/-/dompurify-3.0.5.tgz?rbtorrent=303aaac54a0f7208cf571c4e8c1bf22365215541} + + '@types/eslint-scope@3.7.7': + resolution: {tarball: https://registry.npmjs.org/@types%2feslint-scope/-/eslint-scope-3.7.7.tgz?rbtorrent=134c3c67a9d7c35e8dbb83449bd061a838f66af0} + + '@types/eslint@8.44.1': + resolution: {tarball: https://registry.npmjs.org/@types%2feslint/-/eslint-8.44.1.tgz?rbtorrent=195add5e5d94ac1b7fed8c9cb07275b5b221e657} + + '@types/estree@1.0.5': + resolution: {tarball: https://registry.npmjs.org/@types%2festree/-/estree-1.0.5.tgz?rbtorrent=92ba10df044f75b81ea46bde6d7c3ccc2470cdb1} + + '@types/estree@1.0.6': + resolution: {tarball: https://registry.npmjs.org/@types%2festree/-/estree-1.0.6.tgz?rbtorrent=} + + '@types/express-serve-static-core@4.17.33': + resolution: {tarball: https://registry.npmjs.org/@types%2fexpress-serve-static-core/-/express-serve-static-core-4.17.33.tgz?rbtorrent=d76d16ad0f127a41ece3840bc42f5ad31a107e02} + + '@types/express@4.17.21': + resolution: {tarball: https://registry.npmjs.org/@types%2fexpress/-/express-4.17.21.tgz?rbtorrent=772a9847e0e82d864983385936b976526ac4b16d} + + '@types/fs-extra@11.0.4': + resolution: {tarball: https://registry.npmjs.org/@types%2ffs-extra/-/fs-extra-11.0.4.tgz?rbtorrent=07e084231846fd665514aa4f28b70f0767af13a5} + + '@types/geojson@7946.0.10': + resolution: {tarball: https://registry.npmjs.org/@types%2fgeojson/-/geojson-7946.0.10.tgz?rbtorrent=ae32989e55247616b8ecafbbce9e012f486b7180} + + '@types/glob@7.2.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fglob/-/glob-7.2.0.tgz?rbtorrent=bdcda8329068a37f0bd446cb6bc48e38aeb66cde} + + '@types/graceful-fs@4.1.5': + resolution: {tarball: https://registry.npmjs.org/@types%2fgraceful-fs/-/graceful-fs-4.1.5.tgz?rbtorrent=549b9452600d949fc7ce14ed19f9bf830a6ef5be} + + '@types/gtag.js@0.0.10': + resolution: {tarball: https://registry.npmjs.org/@types%2fgtag.js/-/gtag.js-0.0.10.tgz?rbtorrent=ee77ea7cb9c3b93e1eae6311e2af6d4618363f8b} + + '@types/hashids@1.0.32': + resolution: {tarball: https://registry.npmjs.org/@types%2fhashids/-/hashids-1.0.32.tgz?rbtorrent=ab7a43a750ffaf9517b2401fd67a9894bf84ccc4} + + '@types/hast@3.0.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fhast/-/hast-3.0.4.tgz?rbtorrent=} + + '@types/history@4.7.11': + resolution: {tarball: https://registry.npmjs.org/@types%2fhistory/-/history-4.7.11.tgz?rbtorrent=d8160cbce0bccc4af99c8dbe42be966500a2c7b0} + + '@types/hoist-non-react-statics@3.3.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fhoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz?rbtorrent=a3a28d2abc1d145e51058df7bc99453d10a329fc} + + '@types/http-cache-semantics@4.0.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fhttp-cache-semantics/-/http-cache-semantics-4.0.4.tgz?rbtorrent=f4a3fd1790581e6131256bcd6e41f0a1e42662cf} + + '@types/http-errors@2.0.2': + resolution: {tarball: https://registry.npmjs.org/@types%2fhttp-errors/-/http-errors-2.0.2.tgz?rbtorrent=c3caeb9296e0e530f76bc9ccc848a728005b6af3} + + '@types/http-proxy@1.17.10': + resolution: {tarball: https://registry.npmjs.org/@types%2fhttp-proxy/-/http-proxy-1.17.10.tgz?rbtorrent=0ba933a088c5bff938a55b0e8c1434df20687973} + + '@types/ioredis@4.28.8': + resolution: {tarball: https://registry.npmjs.org/@types%2fioredis/-/ioredis-4.28.8.tgz?rbtorrent=79c6aba2750075dfca7f56bf5c538af94e3fc8cd} + + '@types/is-number@7.0.5': + resolution: {tarball: https://registry.npmjs.org/@types%2fis-number/-/is-number-7.0.5.tgz?rbtorrent=9e5f4a66fe93b1f3d2f9af8efff8e1cede2e7670} + + '@types/istanbul-lib-coverage@2.0.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fistanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz?rbtorrent=d92a0b354b929541d4215c6d5e74543f1d63467e} + + '@types/istanbul-lib-report@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fistanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz?rbtorrent=8940c3903d3524b15863f41740f01fcab2dd99ef} + + '@types/istanbul-reports@3.0.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fistanbul-reports/-/istanbul-reports-3.0.1.tgz?rbtorrent=5f2c62f3b22be130e8539f0689e4a72f3e60f983} + + '@types/jest@29.4.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fjest/-/jest-29.4.0.tgz?rbtorrent=b773bcddde5f6e42ff14d321a4a4d275d7c96bf8} + + '@types/js-cookie@2.2.7': + resolution: {tarball: https://registry.npmjs.org/@types%2fjs-cookie/-/js-cookie-2.2.7.tgz?rbtorrent=c388c0831c989e59a1b951701613c4c82b6ff07c} + + '@types/js-yaml@4.0.5': + resolution: {tarball: https://registry.npmjs.org/@types%2fjs-yaml/-/js-yaml-4.0.5.tgz?rbtorrent=601bf534621ece9418e492ecd532a5126c703425} + + '@types/jsdom@20.0.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fjsdom/-/jsdom-20.0.1.tgz?rbtorrent=48dd0c1e3ea100b567a673c9cab6b699765e45d4} + + '@types/json-schema@7.0.15': + resolution: {tarball: https://registry.npmjs.org/@types%2fjson-schema/-/json-schema-7.0.15.tgz?rbtorrent=b1a1de4eb80bcd1a5bc0e2facd516d39e452686e} + + '@types/json5@0.0.29': + resolution: {tarball: https://registry.npmjs.org/@types%2fjson5/-/json5-0.0.29.tgz?rbtorrent=8310e4fd8701071d37a7c652f86a9f36b6aa625f} + + '@types/jsonfile@6.1.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fjsonfile/-/jsonfile-6.1.4.tgz?rbtorrent=f841af99e600e5fca422fb2d51379dfc9ac2ccf1} + + '@types/jsonwebtoken@9.0.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fjsonwebtoken/-/jsonwebtoken-9.0.1.tgz?rbtorrent=69fdf70c88439884bdd6a1cea903a44bc99b8dcb} + + '@types/linkify-it@3.0.2': + resolution: {tarball: https://registry.npmjs.org/@types%2flinkify-it/-/linkify-it-3.0.2.tgz?rbtorrent=479aeb794c76320e1442b129974669377301ed3c} + + '@types/linkify-it@5.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2flinkify-it/-/linkify-it-5.0.0.tgz?rbtorrent=} + + '@types/lodash@4.14.168': + resolution: {tarball: https://registry.npmjs.org/@types%2flodash/-/lodash-4.14.168.tgz?rbtorrent=74faee6f120662d7211d02953a08dd922b6d56c7} + + '@types/luxon@1.26.2': + resolution: {tarball: https://registry.npmjs.org/@types%2fluxon/-/luxon-1.26.2.tgz?rbtorrent=9b791cdef17a7f140d64ad3b69f7f7f56fcc50fa} + + '@types/markdown-it-emoji@2.0.2': + resolution: {tarball: https://registry.npmjs.org/@types%2fmarkdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz?rbtorrent=3b9748248ab0c093a51aec5bdd73fe41fab63e9f} + + '@types/markdown-it-link-attributes@3.0.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fmarkdown-it-link-attributes/-/markdown-it-link-attributes-3.0.4.tgz?rbtorrent=bd32e93f6685bb87d4a01b4a322a25f6a1cb1df2} + + '@types/markdown-it@12.2.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fmarkdown-it/-/markdown-it-12.2.3.tgz?rbtorrent=556263f9644d91818933767de0c54fbebab728ae} + + '@types/markdown-it@13.0.7': + resolution: {tarball: https://registry.npmjs.org/@types%2fmarkdown-it/-/markdown-it-13.0.7.tgz?rbtorrent=} + + '@types/md5@2.3.5': + resolution: {tarball: https://registry.npmjs.org/@types%2fmd5/-/md5-2.3.5.tgz?rbtorrent=53528483f79905732b1875572db8e455409bb4ba} + + '@types/mdurl@2.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fmdurl/-/mdurl-2.0.0.tgz?rbtorrent=} + + '@types/mime@1.3.5': + resolution: {tarball: https://registry.npmjs.org/@types%2fmime/-/mime-1.3.5.tgz?rbtorrent=cd6bfc1757dcd95470935939ff4acaf11aab9b4e} + + '@types/minimatch@5.1.2': + resolution: {tarball: https://registry.npmjs.org/@types%2fminimatch/-/minimatch-5.1.2.tgz?rbtorrent=cb1c6b72a77af591a5de24f8b605f37445bb3f9b} + + '@types/minimist@1.2.5': + resolution: {tarball: https://registry.npmjs.org/@types%2fminimist/-/minimist-1.2.5.tgz?rbtorrent=05a1b8dd8d576ede3a29c1264274906b1e4c88d2} + + '@types/node-forge@1.3.11': + resolution: {tarball: https://registry.npmjs.org/@types%2fnode-forge/-/node-forge-1.3.11.tgz?rbtorrent=} + + '@types/node@18.19.87': + resolution: {tarball: https://registry.npmjs.org/@types%2fnode/-/node-18.19.87.tgz?rbtorrent=} + + '@types/node@20.16.10': + resolution: {tarball: https://registry.npmjs.org/@types%2fnode/-/node-20.16.10.tgz?rbtorrent=} + + '@types/node@20.16.12': + resolution: {tarball: https://registry.npmjs.org/@types%2fnode/-/node-20.16.12.tgz?rbtorrent=} + + '@types/node@20.19.33': + resolution: {tarball: https://registry.npmjs.org/@types%2fnode/-/node-20.19.33.tgz?rbtorrent=} + + '@types/normalize-package-data@2.4.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fnormalize-package-data/-/normalize-package-data-2.4.4.tgz?rbtorrent=e86a2a1651d628b0b251226ac61a5dc39f05a678} + + '@types/pako@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fpako/-/pako-2.0.1.tgz?rbtorrent=06660678ceb3baf31880a0013449494963a3d148} + + '@types/prettier@2.7.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fprettier/-/prettier-2.7.1.tgz?rbtorrent=44fb7f402155fcd1366f2f12bf8a6b6f314eb4e3} + + '@types/prop-types@15.7.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fprop-types/-/prop-types-15.7.3.tgz?rbtorrent=3ad99bc2b45038550d75dca7c1a9d2b5d748ac30} + + '@types/qs@6.9.11': + resolution: {tarball: https://registry.npmjs.org/@types%2fqs/-/qs-6.9.11.tgz?rbtorrent=} + + '@types/range-parser@1.2.3': + resolution: {tarball: https://registry.npmjs.org/@types%2frange-parser/-/range-parser-1.2.3.tgz?rbtorrent=84d280b890185b6dee05463cab9cd52187c0a9b3} + + '@types/rc-slider@8.6.6': + resolution: {tarball: https://registry.npmjs.org/@types%2frc-slider/-/rc-slider-8.6.6.tgz?rbtorrent=73266698d0e36ed7ad8a55cac70bbe451323aff7} + + '@types/rc-tooltip@3.7.7': + resolution: {tarball: https://registry.npmjs.org/@types%2frc-tooltip/-/rc-tooltip-3.7.7.tgz?rbtorrent=24b678b6baa0739408a808798bdcef53bf2a487d} + + '@types/react-dom@17.0.13': + resolution: {tarball: https://registry.npmjs.org/@types%2freact-dom/-/react-dom-17.0.13.tgz?rbtorrent=a1712d361a9d7a5fe7621349f824439ed04e9f28} + + '@types/react-inspector@4.0.2': + resolution: {tarball: https://registry.npmjs.org/@types%2freact-inspector/-/react-inspector-4.0.2.tgz?rbtorrent=f0d54dff24a17bd6dfc940ed9853e310c4f2708e} + + '@types/react-redux@7.1.16': + resolution: {tarball: https://registry.npmjs.org/@types%2freact-redux/-/react-redux-7.1.16.tgz?rbtorrent=e49f69510bb999e3302b4da01ab8c900df5afb36} + + '@types/react-router-dom@5.1.7': + resolution: {tarball: https://registry.npmjs.org/@types%2freact-router-dom/-/react-router-dom-5.1.7.tgz?rbtorrent=2a069ac6be99ff4206084a9c8614217085588566} + + '@types/react-router@5.1.11': + resolution: {tarball: https://registry.npmjs.org/@types%2freact-router/-/react-router-5.1.11.tgz?rbtorrent=9b0f41def30732e68ff655757ea414ac6c82283d} + + '@types/react-window@1.8.5': + resolution: {tarball: https://registry.npmjs.org/@types%2freact-window/-/react-window-1.8.5.tgz?rbtorrent=9f9bbc0317a3f0c3a9906d92636c7c0bce8b19ba} + + '@types/react@17.0.40': + resolution: {tarball: https://registry.npmjs.org/@types%2freact/-/react-17.0.40.tgz?rbtorrent=860f539ce4102052ca1d2e6c6bbbb886713f666a} + + '@types/recompose@0.30.7': + resolution: {tarball: https://registry.npmjs.org/@types%2frecompose/-/recompose-0.30.7.tgz?rbtorrent=cd82177b5fd0a184e7f6b77b12dd62f7f1c911f3} + + '@types/redux-logger@3.0.9': + resolution: {tarball: https://registry.npmjs.org/@types%2fredux-logger/-/redux-logger-3.0.9.tgz?rbtorrent=0ee3eae2b7f1e4d69f72aacb2d32575c3052738b} + + '@types/request-ip@0.0.41': + resolution: {tarball: https://registry.npmjs.org/@types%2frequest-ip/-/request-ip-0.0.41.tgz?rbtorrent=606d18f660458b9de9440ccd5554a517d3f3cddc} + + '@types/request-promise-native@1.0.21': + resolution: {tarball: https://registry.npmjs.org/@types%2frequest-promise-native/-/request-promise-native-1.0.21.tgz?rbtorrent=1f2e60a4be81416b5f3c9ecec7dd88d2f16e261d} + + '@types/request@2.48.7': + resolution: {tarball: https://registry.npmjs.org/@types%2frequest/-/request-2.48.7.tgz?rbtorrent=88c1f7fbc588b9c6282238c71b09e745d129ad01} + + '@types/retry@0.12.2': + resolution: {tarball: https://registry.npmjs.org/@types%2fretry/-/retry-0.12.2.tgz?rbtorrent=1175f7897119e201bb90b50d28c3b9b9414568be} + + '@types/scheduler@0.16.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fscheduler/-/scheduler-0.16.1.tgz?rbtorrent=1a3f514d1627f1c18fb7450caf3a6bd202d577c9} + + '@types/semver@7.5.8': + resolution: {tarball: https://registry.npmjs.org/@types%2fsemver/-/semver-7.5.8.tgz?rbtorrent=} + + '@types/send@0.17.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fsend/-/send-0.17.4.tgz?rbtorrent=8a1cbcfa3f27394b8ba59fce0cf2c215cc408b11} + + '@types/serve-index@1.9.4': + resolution: {tarball: https://registry.npmjs.org/@types%2fserve-index/-/serve-index-1.9.4.tgz?rbtorrent=297e5fdccc309e7fc3210d20802b91712ba8a6cc} + + '@types/serve-static@1.15.7': + resolution: {tarball: https://registry.npmjs.org/@types%2fserve-static/-/serve-static-1.15.7.tgz?rbtorrent=} + + '@types/set-cookie-parser@2.4.10': + resolution: {tarball: https://registry.npmjs.org/@types%2fset-cookie-parser/-/set-cookie-parser-2.4.10.tgz?rbtorrent=} + + '@types/shimmer@1.2.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fshimmer/-/shimmer-1.2.0.tgz?rbtorrent=} + + '@types/sockjs@0.3.36': + resolution: {tarball: https://registry.npmjs.org/@types%2fsockjs/-/sockjs-0.3.36.tgz?rbtorrent=4645343683cb1eb4505a904f66d113cd73d55b66} + + '@types/stack-utils@2.0.1': + resolution: {tarball: https://registry.npmjs.org/@types%2fstack-utils/-/stack-utils-2.0.1.tgz?rbtorrent=79235d790800e14c7ef59b2b4a4d18f376cc6159} + + '@types/swagger-ui-express@4.1.8': + resolution: {tarball: https://registry.npmjs.org/@types%2fswagger-ui-express/-/swagger-ui-express-4.1.8.tgz?rbtorrent=} + + '@types/tapable@2.2.7': + resolution: {tarball: https://registry.npmjs.org/@types%2ftapable/-/tapable-2.2.7.tgz?rbtorrent=891ffee3a37c04f7c5fb10f25d27006eb2da79b0} + + '@types/testing-library__jest-dom@5.14.3': + resolution: {tarball: https://registry.npmjs.org/@types%2ftesting-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz?rbtorrent=db6afdce913c0c7ed0996dc6b1648ee52d077bd0} + + '@types/tough-cookie@4.0.0': + resolution: {tarball: https://registry.npmjs.org/@types%2ftough-cookie/-/tough-cookie-4.0.0.tgz?rbtorrent=92bb941ef2952224287d6ffeb70d38e66c055977} + + '@types/trusted-types@2.0.7': + resolution: {tarball: https://registry.npmjs.org/@types%2ftrusted-types/-/trusted-types-2.0.7.tgz?rbtorrent=} + + '@types/unist@3.0.3': + resolution: {tarball: https://registry.npmjs.org/@types%2funist/-/unist-3.0.3.tgz?rbtorrent=} + + '@types/uuid@9.0.8': + resolution: {tarball: https://registry.npmjs.org/@types%2fuuid/-/uuid-9.0.8.tgz?rbtorrent=} + + '@types/webpack-env@1.16.0': + resolution: {tarball: https://registry.npmjs.org/@types%2fwebpack-env/-/webpack-env-1.16.0.tgz?rbtorrent=a96f4674be3c256ee68888c97ec90cc73e377d06} + + '@types/webpack@5.28.5': + resolution: {tarball: https://registry.npmjs.org/@types%2fwebpack/-/webpack-5.28.5.tgz?rbtorrent=9c49d346cf291214e530613479df9452a4bdc700} + + '@types/ws@8.5.13': + resolution: {tarball: https://registry.npmjs.org/@types%2fws/-/ws-8.5.13.tgz?rbtorrent=} + + '@types/yargs-parser@21.0.3': + resolution: {tarball: https://registry.npmjs.org/@types%2fyargs-parser/-/yargs-parser-21.0.3.tgz?rbtorrent=2df3442322c0e5eac80bc13fd197709f3c6fe986} + + '@types/yargs@16.0.9': + resolution: {tarball: https://registry.npmjs.org/@types%2fyargs/-/yargs-16.0.9.tgz?rbtorrent=} + + '@types/yargs@17.0.33': + resolution: {tarball: https://registry.npmjs.org/@types%2fyargs/-/yargs-17.0.33.tgz?rbtorrent=} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {tarball: https://registry.npmjs.org/@typescript-eslint%2feslint-plugin/-/eslint-plugin-6.21.0.tgz?rbtorrent=} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@6.21.0': + resolution: {tarball: https://registry.npmjs.org/@typescript-eslint%2fparser/-/parser-6.21.0.tgz?rbtorrent=} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {tarball: https://registry.npmjs.org/@typescript-eslint%2fscope-manager/-/scope-manager-6.21.0.tgz?rbtorrent=} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/type-utils@6.21.0': + resolution: {tarball: https://registry.npmjs.org/@typescript-eslint%2ftype-utils/-/type-utils-6.21.0.tgz?rbtorrent=} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@6.21.0': + resolution: {tarball: https://registry.npmjs.org/@typescript-eslint%2ftypes/-/types-6.21.0.tgz?rbtorrent=} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {tarball: https://registry.npmjs.org/@typescript-eslint%2ftypescript-estree/-/typescript-estree-6.21.0.tgz?rbtorrent=} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@6.21.0': + resolution: {tarball: https://registry.npmjs.org/@typescript-eslint%2futils/-/utils-6.21.0.tgz?rbtorrent=} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {tarball: https://registry.npmjs.org/@typescript-eslint%2fvisitor-keys/-/visitor-keys-6.21.0.tgz?rbtorrent=} + engines: {node: ^16.0.0 || >=18.0.0} + + '@ungap/structured-clone@1.2.0': + resolution: {tarball: https://registry.npmjs.org/@ungap%2fstructured-clone/-/structured-clone-1.2.0.tgz?rbtorrent=f6cfa33ed43b8109d4d439289c996cb251459bba} + + '@webassemblyjs/ast@1.12.1': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fast/-/ast-1.12.1.tgz?rbtorrent=} + + '@webassemblyjs/floating-point-hex-parser@1.11.6': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2ffloating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz?rbtorrent=184f19e72b06b7be45df7c1a54f7a32f92a6f1d4} + + '@webassemblyjs/helper-api-error@1.11.6': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fhelper-api-error/-/helper-api-error-1.11.6.tgz?rbtorrent=47ff234a45bc6cda4327a4ce8e10cd51a8ef40d6} + + '@webassemblyjs/helper-buffer@1.12.1': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fhelper-buffer/-/helper-buffer-1.12.1.tgz?rbtorrent=} + + '@webassemblyjs/helper-numbers@1.11.6': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fhelper-numbers/-/helper-numbers-1.11.6.tgz?rbtorrent=183b85b3f5e61168dc739de652232f0079661ea8} + + '@webassemblyjs/helper-wasm-bytecode@1.11.6': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fhelper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz?rbtorrent=47c04ec1993c2f524605f1e7e337f9e7c1e303e9} + + '@webassemblyjs/helper-wasm-section@1.12.1': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fhelper-wasm-section/-/helper-wasm-section-1.12.1.tgz?rbtorrent=} + + '@webassemblyjs/ieee754@1.11.6': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fieee754/-/ieee754-1.11.6.tgz?rbtorrent=129b37f8fdf28b438fb4ae03a0ffd47b479b9d0c} + + '@webassemblyjs/leb128@1.11.6': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fleb128/-/leb128-1.11.6.tgz?rbtorrent=afd8d10d409989d1166ee8267c7b98522cc178db} + + '@webassemblyjs/utf8@1.11.6': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2futf8/-/utf8-1.11.6.tgz?rbtorrent=1ccd6aa9b0b0c34d1a2c678a0203b99786fd80a7} + + '@webassemblyjs/wasm-edit@1.12.1': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fwasm-edit/-/wasm-edit-1.12.1.tgz?rbtorrent=} + + '@webassemblyjs/wasm-gen@1.12.1': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fwasm-gen/-/wasm-gen-1.12.1.tgz?rbtorrent=} + + '@webassemblyjs/wasm-opt@1.12.1': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fwasm-opt/-/wasm-opt-1.12.1.tgz?rbtorrent=} + + '@webassemblyjs/wasm-parser@1.12.1': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fwasm-parser/-/wasm-parser-1.12.1.tgz?rbtorrent=} + + '@webassemblyjs/wast-printer@1.12.1': + resolution: {tarball: https://registry.npmjs.org/@webassemblyjs%2fwast-printer/-/wast-printer-1.12.1.tgz?rbtorrent=} + + '@xhmikosr/archive-type@7.1.0': + resolution: {tarball: https://registry.npmjs.org/@xhmikosr%2farchive-type/-/archive-type-7.1.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + '@xhmikosr/bin-check@7.1.0': + resolution: {tarball: https://registry.npmjs.org/@xhmikosr%2fbin-check/-/bin-check-7.1.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + '@xhmikosr/bin-wrapper@13.2.0': + resolution: {tarball: https://registry.npmjs.org/@xhmikosr%2fbin-wrapper/-/bin-wrapper-13.2.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + '@xhmikosr/decompress-tar@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@xhmikosr%2fdecompress-tar/-/decompress-tar-8.1.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + '@xhmikosr/decompress-tarbz2@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@xhmikosr%2fdecompress-tarbz2/-/decompress-tarbz2-8.1.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + '@xhmikosr/decompress-targz@8.1.0': + resolution: {tarball: https://registry.npmjs.org/@xhmikosr%2fdecompress-targz/-/decompress-targz-8.1.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + '@xhmikosr/decompress-unzip@7.1.0': + resolution: {tarball: https://registry.npmjs.org/@xhmikosr%2fdecompress-unzip/-/decompress-unzip-7.1.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + '@xhmikosr/decompress@10.2.0': + resolution: {tarball: https://registry.npmjs.org/@xhmikosr%2fdecompress/-/decompress-10.2.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + '@xhmikosr/downloader@15.2.0': + resolution: {tarball: https://registry.npmjs.org/@xhmikosr%2fdownloader/-/downloader-15.2.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + '@xhmikosr/os-filter-obj@3.0.0': + resolution: {tarball: https://registry.npmjs.org/@xhmikosr%2fos-filter-obj/-/os-filter-obj-3.0.0.tgz?rbtorrent=} + engines: {node: ^14.14.0 || >=16.0.0} + + '@xobotyi/scrollbar-width@1.9.5': + resolution: {tarball: https://registry.npmjs.org/@xobotyi%2fscrollbar-width/-/scrollbar-width-1.9.5.tgz?rbtorrent=3ad9d534fcd4b34d68a584f052cc2d83a46b12e5} + + '@xtuc/ieee754@1.2.0': + resolution: {tarball: https://registry.npmjs.org/@xtuc%2fieee754/-/ieee754-1.2.0.tgz?rbtorrent=7b6a13d4979fcc776c78bb6b8ad6cb056bcaf0d2} + + '@xtuc/long@4.2.2': + resolution: {tarball: https://registry.npmjs.org/@xtuc%2flong/-/long-4.2.2.tgz?rbtorrent=dfc162a3e26c7b15626c015fb9e65b02847b2908} + + abab@2.0.6: + resolution: {tarball: https://registry.npmjs.org/abab/-/abab-2.0.6.tgz?rbtorrent=333f07195226d12694aed23a9c74b1a98b9b1e5f} + deprecated: Use your platform's native atob() and btoa() methods instead + + abort-controller@3.0.0: + resolution: {tarball: https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz?rbtorrent=27b0177fe31fb472d84f2938b04103d7d7415452} + engines: {node: '>=6.5'} + + accept-language-parser@1.5.0: + resolution: {tarball: https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz?rbtorrent=91bc6c9271d3a285f2ab808fdd28ebddb578069d} + + accepts@1.3.8: + resolution: {tarball: https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz?rbtorrent=9392f41e7a931c44517cb9c4bce29c0893bf0221} + engines: {node: '>= 0.6'} + + acorn-globals@7.0.1: + resolution: {tarball: https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz?rbtorrent=2269707a1b7fed9fcbb7608d19a6325afba52085} + + acorn-import-attributes@1.9.5: + resolution: {tarball: https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz?rbtorrent=} + peerDependencies: + acorn: ^8 + + acorn-jsx@5.3.2: + resolution: {tarball: https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz?rbtorrent=15ddcff33d62d1a368c5d7f6a8c322a46488a3e3} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {tarball: https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz?rbtorrent=} + engines: {node: '>=0.4.0'} + + acorn@8.14.0: + resolution: {tarball: https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz?rbtorrent=} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.14.1: + resolution: {tarball: https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz?rbtorrent=} + engines: {node: '>=0.4.0'} + hasBin: true + + acorn@8.15.0: + resolution: {tarball: https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz?rbtorrent=} + engines: {node: '>=0.4.0'} + hasBin: true + + adjust-sourcemap-loader@4.0.0: + resolution: {tarball: https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz?rbtorrent=cbf949783e2f88bcbd8955c743cdbaf8b4ab23f8} + engines: {node: '>=8.9'} + + adm-zip@0.5.16: + resolution: {tarball: https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz?rbtorrent=} + engines: {node: '>=12.0'} + + afterframe@1.0.2: + resolution: {tarball: https://registry.npmjs.org/afterframe/-/afterframe-1.0.2.tgz?rbtorrent=ebf1a9b65674c32af4821d89f85e38b72e600871} + + agent-base@6.0.2: + resolution: {tarball: https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz?rbtorrent=70993ec56a8bafd128851eac4ee9e62bbddce1e2} + engines: {node: '>= 6.0.0'} + + aggregate-error@3.1.0: + resolution: {tarball: https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz?rbtorrent=34e7e1179db63faf2831a7c398eb345ba2e72dd9} + engines: {node: '>=8'} + + ajv-formats@2.1.1: + resolution: {tarball: https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz?rbtorrent=e833a2b4bf0ede47bf25c847a9d5a22e95b30672} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {tarball: https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz?rbtorrent=42c671b174a51b1ca9d4206983e0bbc70a9e6199} + peerDependencies: + ajv: ^6.9.1 + + ajv-keywords@5.1.0: + resolution: {tarball: https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz?rbtorrent=057dc6a9f9370215685488e539dcd629b40be888} + peerDependencies: + ajv: ^8.8.2 + + ajv@6.12.6: + resolution: {tarball: https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz?rbtorrent=8700a2f2e42ac5b59b5c6d8142cd2bfd19a56001} + + ajv@8.12.0: + resolution: {tarball: https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz?rbtorrent=e7c08f3d1de28987399ae3f68e23e8d9efe5960c} + + ansi-colors@4.1.3: + resolution: {tarball: https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz?rbtorrent=6e4137bb3a06bf7b7af7a1cf8ddc5e1d003de121} + engines: {node: '>=6'} + + ansi-escapes@4.3.1: + resolution: {tarball: https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz?rbtorrent=7973e1b9cf937d1c1cb2002ec74761e926724438} + engines: {node: '>=8'} + + ansi-escapes@4.3.2: + resolution: {tarball: https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz?rbtorrent=81049c1ddde928e9e2fa54cee36b7a450a820119} + engines: {node: '>=8'} + + ansi-html-community@0.0.8: + resolution: {tarball: https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz?rbtorrent=dc1bd4f30bf8799006ea7e3dc7bc92f5caf54153} + engines: {'0': node >= 0.8.0} + hasBin: true + + ansi-html@0.0.9: + resolution: {tarball: https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz?rbtorrent=a93dabadedbca6951b8627733af0c8353bd8aee6} + engines: {'0': node >= 0.8.0} + hasBin: true + + ansi-regex@5.0.1: + resolution: {tarball: https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz?rbtorrent=1b2c28e53f2df9567d964d2735662fad58107fae} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {tarball: https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz?rbtorrent=} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz?rbtorrent=71a8bd57f3f3a63a5b0ae879a761b238bb3dd3d7} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz?rbtorrent=e3e16432a00ee6588ff4a780fbd7d83c25075f68} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {tarball: https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz?rbtorrent=3eef03a720b0daac7b00c82270a84ee27a5fb496} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {tarball: https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz?rbtorrent=3276fe1fda56c80c46e15fd8a27c918681400d1a} + engines: {node: '>= 8'} + + arch@3.0.0: + resolution: {tarball: https://registry.npmjs.org/arch/-/arch-3.0.0.tgz?rbtorrent=} + + archy@1.0.0: + resolution: {tarball: https://registry.npmjs.org/archy/-/archy-1.0.0.tgz?rbtorrent=45935a119eb63f1e73a2daab57feac406fa6a1f4} + + arg@4.1.3: + resolution: {tarball: https://registry.npmjs.org/arg/-/arg-4.1.3.tgz?rbtorrent=8415d85bdb76a0e259c5161616809aadb965aae0} + + argparse@1.0.10: + resolution: {tarball: https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz?rbtorrent=3a987f1ec92aa7ba68cfa8659011923dc3b8d4f0} + + argparse@2.0.1: + resolution: {tarball: https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz?rbtorrent=196f5093a380b90b5951b3beaf696d4e000de4cc} + + aria-query@5.3.0: + resolution: {tarball: https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz?rbtorrent=04f382017021f323cdcb25a102d827e8b338139b} + + aria-query@5.3.2: + resolution: {tarball: https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.1: + resolution: {tarball: https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {tarball: https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + array-flatten@1.1.1: + resolution: {tarball: https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz?rbtorrent=eefceecfb896133878572a9e86dd3b5e5a8500f2} + + array-includes@3.1.8: + resolution: {tarball: https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + array-union@1.0.2: + resolution: {tarball: https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz?rbtorrent=9ec2effe55137895eb3ac91434db1a6d76f4bdce} + engines: {node: '>=0.10.0'} + + array-union@2.1.0: + resolution: {tarball: https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz?rbtorrent=bf7405990d39ebcf128ec26ca1ee01dcfeb2ea7e} + engines: {node: '>=8'} + + array-uniq@1.0.3: + resolution: {tarball: https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz?rbtorrent=7df2c526b141a6a741dd34a9853c3f1c10ed91e5} + engines: {node: '>=0.10.0'} + + array.prototype.findlast@1.2.5: + resolution: {tarball: https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.5: + resolution: {tarball: https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: + resolution: {tarball: https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz?rbtorrent=a6fd7fc516174febc7649e6a9120214381c4c4eb} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {tarball: https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {tarball: https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz?rbtorrent=e77645d30a5ea773e5e23483879efa5ec52c0178} + engines: {node: '>= 0.4'} + + array.prototype.toreversed@1.1.2: + resolution: {tarball: https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz?rbtorrent=} + + array.prototype.tosorted@1.1.3: + resolution: {tarball: https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz?rbtorrent=} + + arraybuffer.prototype.slice@1.0.3: + resolution: {tarball: https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {tarball: https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + arrify@1.0.1: + resolution: {tarball: https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz?rbtorrent=0bb6fafd8f23f577655e8055f20f3da2993df774} + engines: {node: '>=0.10.0'} + + asn1@0.2.6: + resolution: {tarball: https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz?rbtorrent=555c499985bdfef0271a6015a06d25e31c5bee28} + + assert-plus@1.0.0: + resolution: {tarball: https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz?rbtorrent=7a9eceaa3e925afe2169387af03682d16e748ad4} + engines: {node: '>=0.8'} + + ast-types-flow@0.0.8: + resolution: {tarball: https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz?rbtorrent=07ab45748f2983206276cf3d81c28e96e66bde3d} + + astral-regex@2.0.0: + resolution: {tarball: https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz?rbtorrent=c0a88f625f4933f2e841d4c1e0d8ed23b9442ecc} + engines: {node: '>=8'} + + async-function@1.0.0: + resolution: {tarball: https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + asynckit@0.4.0: + resolution: {tarball: https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz?rbtorrent=1553f85c44099047a7a523650e761f039c77d322} + + at-least-node@1.0.0: + resolution: {tarball: https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz?rbtorrent=8a74e39a7fb797ace6c547f7ff43b7ef7b6df853} + engines: {node: '>= 4.0.0'} + + atob@2.1.2: + resolution: {tarball: https://registry.npmjs.org/atob/-/atob-2.1.2.tgz?rbtorrent=5f593162524914425c3793cf5be01cdaab0cb182} + engines: {node: '>= 4.5.0'} + hasBin: true + + atomic-sleep@1.0.0: + resolution: {tarball: https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz?rbtorrent=d922b2adc840f9f444c87cc9e1f5c934ea606766} + engines: {node: '>=8.0.0'} + + autoprefixer@10.4.16: + resolution: {tarball: https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz?rbtorrent=da1f05514abafc92475657f2a11c36bac448591a} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {tarball: https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + aws-sign2@0.7.0: + resolution: {tarball: https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz?rbtorrent=125c960c3cc8c5d9e4edffe96b3dfdc71de3529b} + + aws4@1.11.0: + resolution: {tarball: https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz?rbtorrent=c6e45a7cfdb04cc8daa31d49ff871bdf71812c30} + + axe-core@4.7.0: + resolution: {tarball: https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz?rbtorrent=579894100d91226ee5b331d8893a34046738c3d8} + engines: {node: '>=4'} + + axios-retry@3.9.1: + resolution: {tarball: https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz?rbtorrent=} + + axios@1.13.2: + resolution: {tarball: https://registry.npmjs.org/axios/-/axios-1.13.2.tgz?rbtorrent=} + + axobject-query@3.2.1: + resolution: {tarball: https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz?rbtorrent=82ab452732f9ab48e3f84f351d0bedfae401f0b3} + + b4a@1.7.3: + resolution: {tarball: https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz?rbtorrent=} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + babel-jest@29.4.3: + resolution: {tarball: https://registry.npmjs.org/babel-jest/-/babel-jest-29.4.3.tgz?rbtorrent=b9623e14f89a152deb9d2a18623a6bc3ffcd9cba} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-loader@9.2.1: + resolution: {tarball: https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz?rbtorrent=} + engines: {node: '>= 14.15.0'} + peerDependencies: + '@babel/core': ^7.12.0 + webpack: '>=5' + + babel-plugin-import@1.13.8: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-import/-/babel-plugin-import-1.13.8.tgz?rbtorrent=f827d05227134ea7b34c45935337a16fbf3da70d} + + babel-plugin-inline-react-svg@2.0.2: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-inline-react-svg/-/babel-plugin-inline-react-svg-2.0.2.tgz?rbtorrent=81ad2b38f9cd176f8b923454199ad852aa92de36} + engines: {node: '>=10.13'} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-plugin-istanbul@6.1.1: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz?rbtorrent=2d81f59a7f6db045be8d31364427e4380a0212d5} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.4.3: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.4.3.tgz?rbtorrent=019066213ccc2fa07cacd668e3b5cca3078ee593} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-plugin-module-resolver@4.1.0: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz?rbtorrent=1db0b3f2c5b3e6985a1778f2451778540dd9f11f} + engines: {node: '>= 8.0.0'} + + babel-plugin-polyfill-corejs2@0.4.11: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz?rbtorrent=} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.10.6: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz?rbtorrent=} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.11.1: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz?rbtorrent=} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.2: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz?rbtorrent=} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-transform-react-remove-prop-types@0.4.24: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz?rbtorrent=e66bde27a887cf43847d9f7c18a8e20f8fcd8431} + + babel-plugin-tsconfig-paths-module-resolver@1.0.4: + resolution: {tarball: https://registry.npmjs.org/babel-plugin-tsconfig-paths-module-resolver/-/babel-plugin-tsconfig-paths-module-resolver-1.0.4.tgz?rbtorrent=218ad4d791bae9b3f63ca7e7a93bcf3453b2573c} + + babel-preset-current-node-syntax@1.0.1: + resolution: {tarball: https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz?rbtorrent=97bc8c63d1c9e9e27a0531c70d6954216345c55b} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.4.3: + resolution: {tarball: https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.4.3.tgz?rbtorrent=d1e522e9c2fea785e8135b50717e55bcd36f51b5} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.0: + resolution: {tarball: https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz?rbtorrent=45519ed9198d84c053b53345df908b7cbb2e65d6} + + balanced-match@2.0.0: + resolution: {tarball: https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz?rbtorrent=7ca284e4fe8c5027738e4d3c057f21585ee7cdc8} + + bare-events@2.8.2: + resolution: {tarball: https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz?rbtorrent=} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + base64-arraybuffer@1.0.2: + resolution: {tarball: https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz?rbtorrent=2f6e4d1d66c0cf158f25fd8188ec38d746199a9c} + engines: {node: '>= 0.6.0'} + + base64-js@1.5.1: + resolution: {tarball: https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz?rbtorrent=c7446c2f65a00b903f15ca23ad4f95c446f448c8} + + base64id@2.0.0: + resolution: {tarball: https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz?rbtorrent=cbdb9edcfdd6c14ba111314075f85cd819d42312} + engines: {node: ^4.5.0 || >= 5.9} + + batch@0.6.1: + resolution: {tarball: https://registry.npmjs.org/batch/-/batch-0.6.1.tgz?rbtorrent=b4fd8ab17e02f9a5ab4fb5d0e327b6f71d83c048} + + bcrypt-pbkdf@1.0.2: + resolution: {tarball: https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz?rbtorrent=e12c31bc38b4e2ff9bff31ba7b490852084ed5ad} + + bem-cn-lite@4.0.0: + resolution: {tarball: https://registry.npmjs.org/bem-cn-lite/-/bem-cn-lite-4.0.0.tgz?rbtorrent=5e89748cc3af62bf04bca70cb6056ed2da84beda} + + bem-cn@3.0.1: + resolution: {tarball: https://registry.npmjs.org/bem-cn/-/bem-cn-3.0.1.tgz?rbtorrent=784f4583528e12672c76a7351686b53b3b25853c} + + big.js@5.2.2: + resolution: {tarball: https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz?rbtorrent=bfdcd37e3d824e33bfd7717d778a7bb5c70cda28} + + bin-version-check@5.1.0: + resolution: {tarball: https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz?rbtorrent=dfb10d0b160fcda572b85bb32c890f430396b1eb} + engines: {node: '>=12'} + + bin-version@6.0.0: + resolution: {tarball: https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz?rbtorrent=e1c98feb5774404b42de19a468b33f1dc68fdb92} + engines: {node: '>=12'} + + binary-extensions@2.2.0: + resolution: {tarball: https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz?rbtorrent=f284585b1980c95d2618a859509fe890daa5b515} + engines: {node: '>=8'} + + blueimp-md5@2.19.0: + resolution: {tarball: https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz?rbtorrent=9951e487b7b2b740b34311d5261461bd1de86438} + + body-parser@1.20.3: + resolution: {tarball: https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz?rbtorrent=} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + body-parser@1.20.4: + resolution: {tarball: https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz?rbtorrent=} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + bonjour-service@1.2.1: + resolution: {tarball: https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz?rbtorrent=} + + boolbase@1.0.0: + resolution: {tarball: https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz?rbtorrent=78bd01bf8083623ac2add4f259ec70a9b5dde5e8} + + bowser@2.11.0: + resolution: {tarball: https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz?rbtorrent=cce3e66076fc933612d48ca5e77cfd6ff94aae9e} + + brace-expansion@1.1.11: + resolution: {tarball: https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz?rbtorrent=a6f8ec4328e2858a0be33ed363e562a14b677c34} + + brace-expansion@2.0.1: + resolution: {tarball: https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz?rbtorrent=320d365acd352e486b7140205554b6b4afcbc99b} + + braces@3.0.3: + resolution: {tarball: https://registry.npmjs.org/braces/-/braces-3.0.3.tgz?rbtorrent=} + engines: {node: '>=8'} + + browserslist-load-config@1.0.0: + resolution: {tarball: https://registry.npmjs.org/browserslist-load-config/-/browserslist-load-config-1.0.0.tgz?rbtorrent=} + + browserslist-to-es-version@1.1.0: + resolution: {tarball: https://registry.npmjs.org/browserslist-to-es-version/-/browserslist-to-es-version-1.1.0.tgz?rbtorrent=} + + browserslist@4.24.4: + resolution: {tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz?rbtorrent=} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + browserslist@4.25.1: + resolution: {tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz?rbtorrent=} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {tarball: https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz?rbtorrent=97e5de1beff815ccae5ef0b564a311ce9d19ddbb} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {tarball: https://registry.npmjs.org/bser/-/bser-2.1.1.tgz?rbtorrent=c8d257b877614f9b844cb7808e63072f571f0833} + + btoa@1.2.1: + resolution: {tarball: https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz?rbtorrent=6a812894aff81e3c1c4e02a1ac81bf93bb318064} + engines: {node: '>= 0.4.0'} + hasBin: true + + buffer-crc32@0.2.13: + resolution: {tarball: https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz?rbtorrent=d242c1cc1bfad3d5ba6ab98f1e3dc29e7a13205b} + + buffer-equal-constant-time@1.0.1: + resolution: {tarball: https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz?rbtorrent=a9e2ec359650df435b19ee226e51d6c3569bba69} + + buffer-from@1.1.1: + resolution: {tarball: https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz?rbtorrent=cc7a3cee251d19df3980fd4c9c984912cae6778a} + + buffer@5.7.1: + resolution: {tarball: https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz?rbtorrent=0575f1d8f9cc38aee8a5d5c6548097df60fdd042} + + buffer@6.0.3: + resolution: {tarball: https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz?rbtorrent=9476dcb46971d13c377c0c1d5f740dfaa80ffd81} + + bundle-name@4.1.0: + resolution: {tarball: https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {tarball: https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz?rbtorrent=ddea92658fe4ad0060298ba85e14cea6f4fa4a1b} + engines: {node: '>= 0.8'} + + cacheable-lookup@7.0.0: + resolution: {tarball: https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz?rbtorrent=424e1ce9d34d84566b14c7218ff859df99f2d205} + engines: {node: '>=14.16'} + + cacheable-request@10.2.14: + resolution: {tarball: https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz?rbtorrent=f64a2d6778367456ca1c7214ce61e04e96f4a3e5} + engines: {node: '>=14.16'} + + call-bind-apply-helpers@1.0.2: + resolution: {tarball: https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + call-bind@1.0.7: + resolution: {tarball: https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {tarball: https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {tarball: https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {tarball: https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz?rbtorrent=b0e6e1803806fb1f7fd9c75832ba9bb6d14c2ea9} + engines: {node: '>=6'} + + camelcase-keys@7.0.2: + resolution: {tarball: https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz?rbtorrent=90f90f9eb61963bc904885dc5defe520ac4dd7e2} + engines: {node: '>=12'} + + camelcase@5.3.1: + resolution: {tarball: https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz?rbtorrent=a4983fe60136b484bd7ad7a59d0fe3845b566d95} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {tarball: https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz?rbtorrent=1b1abe096bf6bc865fcbb137b2b35d71215deefe} + engines: {node: '>=10'} + + caniuse-api@3.0.0: + resolution: {tarball: https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz?rbtorrent=f8132e222f8bf91564048a5ea264c4b2316168b1} + + caniuse-lite@1.0.30001727: + resolution: {tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz?rbtorrent=} + + caniuse-lite@1.0.30001764: + resolution: {tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz?rbtorrent=} + + caseless@0.12.0: + resolution: {tarball: https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz?rbtorrent=4c13015d995bc1213ecd125f3985318570242d96} + + chalk@3.0.0: + resolution: {tarball: https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz?rbtorrent=1aee8ae3e8682b1a3990b143b622a5924419e878} + engines: {node: '>=8'} + + chalk@4.1.2: + resolution: {tarball: https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz?rbtorrent=c439e9071a40e546e84d959fa7090597ededbce4} + engines: {node: '>=10'} + + chalk@5.2.0: + resolution: {tarball: https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz?rbtorrent=192bd9725e7a41e810e07d19ea39961b9da483bd} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chalk@5.4.1: + resolution: {tarball: https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz?rbtorrent=} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + change-emitter@0.1.6: + resolution: {tarball: https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz?rbtorrent=b1a0fa8fd20ee423d4c59fc1479d65e99cf9e67a} + + char-regex@1.0.2: + resolution: {tarball: https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz?rbtorrent=7e49e1f2616a6670159e2f9f9e6d9c4f01aee18c} + engines: {node: '>=10'} + + charenc@0.0.2: + resolution: {tarball: https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz?rbtorrent=33fde712d30d6a210e3932f6b88bd027393d6257} + + cheerio-select@2.1.0: + resolution: {tarball: https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz?rbtorrent=7c8f79a06ef46aae8d92669e62df92822e552c30} + + cheerio@1.0.0: + resolution: {tarball: https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz?rbtorrent=} + engines: {node: '>=18.17'} + + cheminfo-types@1.10.0: + resolution: {tarball: https://registry.npmjs.org/cheminfo-types/-/cheminfo-types-1.10.0.tgz?rbtorrent=} + + chevrotain-allstar@0.3.1: + resolution: {tarball: https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz?rbtorrent=} + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: {tarball: https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz?rbtorrent=de017d9b08e7006e511e47fa759b45b2a666f77d} + + chokidar@3.6.0: + resolution: {tarball: https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz?rbtorrent=} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {tarball: https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz?rbtorrent=} + engines: {node: '>= 14.16.0'} + + chroma-js@3.1.2: + resolution: {tarball: https://registry.npmjs.org/chroma-js/-/chroma-js-3.1.2.tgz?rbtorrent=} + + chrome-trace-event@1.0.3: + resolution: {tarball: https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz?rbtorrent=02464276c25f1008ed7e60c18628567f0037c6af} + engines: {node: '>=6.0'} + + ci-info@3.9.0: + resolution: {tarball: https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz?rbtorrent=63e170502b915411094518f544b1a20acdf29f59} + engines: {node: '>=8'} + + circular-dependency-plugin@5.2.2: + resolution: {tarball: https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz?rbtorrent=8aee0dd2d5a6787e3c6cd78e2117936f9894f4ee} + engines: {node: '>=6.0.0'} + peerDependencies: + webpack: '>=4.0.1' + + cjs-module-lexer@1.2.2: + resolution: {tarball: https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz?rbtorrent=7bce543d222a6614c4926907e3c0f808b736adf5} + + classnames@2.5.1: + resolution: {tarball: https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz?rbtorrent=} + + clean-stack@2.2.0: + resolution: {tarball: https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz?rbtorrent=a50567329ce8cb75d22cc5c7295fa47b810fe346} + engines: {node: '>=6'} + + clean-webpack-plugin@4.0.0: + resolution: {tarball: https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz?rbtorrent=c0e15fa337b32b117c82af209ac6ca1609659406} + engines: {node: '>=10.0.0'} + peerDependencies: + webpack: '>=4.0.0 <6.0.0' + + cli-color@2.0.1: + resolution: {tarball: https://registry.npmjs.org/cli-color/-/cli-color-2.0.1.tgz?rbtorrent=850e9c55305d63c00b23741a77a8a98c009342e6} + engines: {node: '>=0.10'} + + cli-cursor@3.1.0: + resolution: {tarball: https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz?rbtorrent=2c51bc7aef20d6b14e40b4ca4495f95b89a3f2bc} + engines: {node: '>=8'} + + cli-truncate@2.1.0: + resolution: {tarball: https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz?rbtorrent=1441bc5cd31873f4c18f4b3808dbb3b5ecb618eb} + engines: {node: '>=8'} + + cli-truncate@3.1.0: + resolution: {tarball: https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz?rbtorrent=b0d6900a31e870180a872a72d0d2f6e5ff0a9fea} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + clipboard-copy@3.2.0: + resolution: {tarball: https://registry.npmjs.org/clipboard-copy/-/clipboard-copy-3.2.0.tgz?rbtorrent=215de8ae0a9caac0a337a21f32ba9f89cc54e7a9} + + cliui@8.0.1: + resolution: {tarball: https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz?rbtorrent=45d86472a005c309d221fd9207d7001cceaaf5a8} + engines: {node: '>=12'} + + clone@2.1.2: + resolution: {tarball: https://registry.npmjs.org/clone/-/clone-2.1.2.tgz?rbtorrent=d32ac69335b9224b4a3448301ecdb7b82fcde145} + engines: {node: '>=0.8'} + + clsx@1.2.1: + resolution: {tarball: https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz?rbtorrent=3e71e3e6a520b2e4a9dcc69349f8d281908e40f0} + engines: {node: '>=6'} + + clsx@2.1.1: + resolution: {tarball: https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz?rbtorrent=} + engines: {node: '>=6'} + + cluster-key-slot@1.1.0: + resolution: {tarball: https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz?rbtorrent=e8f2b459d67eb1e9c1e564cf3cc1db5ccedd9236} + engines: {node: '>=0.10.0'} + + co@4.6.0: + resolution: {tarball: https://registry.npmjs.org/co/-/co-4.6.0.tgz?rbtorrent=544175185720d4f4b3c9a8d19c056b02d6420e99} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.1: + resolution: {tarball: https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz?rbtorrent=69466a8749c6f9a3809cb4f0e7d4e0acdfbce17b} + + color-convert@2.0.1: + resolution: {tarball: https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz?rbtorrent=769fb795063f259bce31a9f20fc15b0d42646b2e} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {tarball: https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz?rbtorrent=bfe35c629295f687cdeea3875ee112eb55eb6b93} + + colord@2.9.3: + resolution: {tarball: https://registry.npmjs.org/colord/-/colord-2.9.3.tgz?rbtorrent=d681fba4a9247476c0b6db92f600b58769491a03} + + colorette@2.0.19: + resolution: {tarball: https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz?rbtorrent=d9d26583cb0092ad4d64b56b2bb679815ffef578} + + colormap@2.3.2: + resolution: {tarball: https://registry.npmjs.org/colormap/-/colormap-2.3.2.tgz?rbtorrent=1449bc2e988adda92740907677657d82444f7c60} + + combined-stream@1.0.8: + resolution: {tarball: https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz?rbtorrent=a90a2c7a1dc392dce545e609ee7a33b8459e5687} + engines: {node: '>= 0.8'} + + commander@10.0.1: + resolution: {tarball: https://registry.npmjs.org/commander/-/commander-10.0.1.tgz?rbtorrent=e478eaf3ca6a0e054e934be2552afc3e615fd6fe} + engines: {node: '>=14'} + + commander@11.1.0: + resolution: {tarball: https://registry.npmjs.org/commander/-/commander-11.1.0.tgz?rbtorrent=e1b91329041ef76db3ca1f99a743819971d4dcf2} + engines: {node: '>=16'} + + commander@2.20.3: + resolution: {tarball: https://registry.npmjs.org/commander/-/commander-2.20.3.tgz?rbtorrent=75692e44f7c662a0d417a69e5318478865aa2114} + + commander@6.2.1: + resolution: {tarball: https://registry.npmjs.org/commander/-/commander-6.2.1.tgz?rbtorrent=02503f1b17b4c8d238c3f3adc057b150705909e0} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {tarball: https://registry.npmjs.org/commander/-/commander-7.2.0.tgz?rbtorrent=18cfac6af831ecd2cb5f14b3626d147933bd8c37} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {tarball: https://registry.npmjs.org/commander/-/commander-8.3.0.tgz?rbtorrent=97d66ed2b806417dc6b06e2ca4b86023b85a8b10} + engines: {node: '>= 12'} + + common-path-prefix@3.0.0: + resolution: {tarball: https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz?rbtorrent=4f8e3a193723653b153575009ee23ea6c0c17990} + + common-tags@1.8.2: + resolution: {tarball: https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz?rbtorrent=f3e7d73959c727e0df59f235630ad006d3eb7bfd} + engines: {node: '>=4.0.0'} + + commondir@1.0.1: + resolution: {tarball: https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz?rbtorrent=f59e52d32f6ab08be99b331a2281bbfe76e31154} + + compressible@2.0.18: + resolution: {tarball: https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz?rbtorrent=e7ed0b90955ce47b3b52e6796081257db5a8c949} + engines: {node: '>= 0.6'} + + compression@1.7.5: + resolution: {tarball: https://registry.npmjs.org/compression/-/compression-1.7.5.tgz?rbtorrent=} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {tarball: https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz?rbtorrent=d68e847684dabaf80403d1a017a6f3fe9f55e5f6} + + confbox@0.1.8: + resolution: {tarball: https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz?rbtorrent=} + + connect-history-api-fallback@2.0.0: + resolution: {tarball: https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz?rbtorrent=4028232984a75c396859bca32ae154b43137f626} + engines: {node: '>=0.8'} + + connect@3.7.0: + resolution: {tarball: https://registry.npmjs.org/connect/-/connect-3.7.0.tgz?rbtorrent=16b978c0a7218dbad00bc4aca8650965726372c6} + engines: {node: '>= 0.10.0'} + + consolidated-events@2.0.2: + resolution: {tarball: https://registry.npmjs.org/consolidated-events/-/consolidated-events-2.0.2.tgz?rbtorrent=1d6102f93fb5ff1d1e6e9945961fabaf27381293} + + content-disposition@0.5.4: + resolution: {tarball: https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz?rbtorrent=315591899d08a4618297be6d996e7fdb8392e138} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {tarball: https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz?rbtorrent=f556e07c837ef7d53f4835dba6b69a0e1dacae22} + engines: {node: '>= 0.6'} + + convert-source-map@1.7.0: + resolution: {tarball: https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz?rbtorrent=8b9050c477f98d7f5c5f81024a4c0c2fac769c22} + + convert-source-map@2.0.0: + resolution: {tarball: https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz?rbtorrent=8d247ba4886ecef994137a086a4d9817b81854db} + + cookie-parser@1.4.7: + resolution: {tarball: https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz?rbtorrent=} + engines: {node: '>= 0.8.0'} + + cookie-signature@1.0.6: + resolution: {tarball: https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz?rbtorrent=55c823568c26e12f27877518a27e19fec8779a71} + + cookie@0.7.1: + resolution: {tarball: https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz?rbtorrent=} + engines: {node: '>= 0.6'} + + cookie@0.7.2: + resolution: {tarball: https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz?rbtorrent=} + engines: {node: '>= 0.6'} + + cookies@0.9.1: + resolution: {tarball: https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz?rbtorrent=} + engines: {node: '>= 0.8'} + + copy-to-clipboard@3.3.3: + resolution: {tarball: https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz?rbtorrent=e0689f6a9717b04991b84fc1d52e609921655e94} + + core-js-compat@3.39.0: + resolution: {tarball: https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz?rbtorrent=} + + core-js-compat@3.41.0: + resolution: {tarball: https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz?rbtorrent=} + + core-js-pure@3.39.0: + resolution: {tarball: https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.39.0.tgz?rbtorrent=} + + core-js@3.43.0: + resolution: {tarball: https://registry.npmjs.org/core-js/-/core-js-3.43.0.tgz?rbtorrent=} + + core-util-is@1.0.2: + resolution: {tarball: https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz?rbtorrent=58e65fb102586f6a5856302cc2913dd2e90ae84c} + + cors@2.8.5: + resolution: {tarball: https://registry.npmjs.org/cors/-/cors-2.8.5.tgz?rbtorrent=2bacc3b72e438b140fb4a1ab69013c2664e2898f} + engines: {node: '>= 0.10'} + + cose-base@1.0.3: + resolution: {tarball: https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz?rbtorrent=f59e6e6e77ac321a3971f7d3eff6fceccfb0e86f} + + cose-base@2.2.0: + resolution: {tarball: https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz?rbtorrent=b6dff0afcc362c3631864b48cb12c02ac60d29c0} + + cosmiconfig-typescript-loader@5.0.0: + resolution: {tarball: https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz?rbtorrent=2dac8c4c07c1cacd3e770d0ef67180ba2401268d} + engines: {node: '>=v16'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=8.2' + typescript: '>=4' + + cosmiconfig@8.3.6: + resolution: {tarball: https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz?rbtorrent=2a989830d7abc22f0ce76f42d4d8f5df2e4b9342} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cosmiconfig@9.0.0: + resolution: {tarball: https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-require@1.1.1: + resolution: {tarball: https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz?rbtorrent=9e2c910286d79dfd71f5c4e30b4313e92ed0326e} + + crelt@1.0.6: + resolution: {tarball: https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz?rbtorrent=d28d5b12e1dbce9eaf0458d1f9fb1e928b8bbd29} + + cron-parser@4.9.0: + resolution: {tarball: https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz?rbtorrent=77b98cc3a3d01180576af3fd410b0944b01c49fd} + engines: {node: '>=12.0.0'} + + cross-spawn@7.0.6: + resolution: {tarball: https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz?rbtorrent=} + engines: {node: '>= 8'} + + crypt@0.0.2: + resolution: {tarball: https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz?rbtorrent=8b02dfafe59ebbbb6fda2f8c40a88d3a9626ebd3} + + csp-header@5.2.1: + resolution: {tarball: https://registry.npmjs.org/csp-header/-/csp-header-5.2.1.tgz?rbtorrent=c4adc5906a11fcf1c385a9fac1bef5d093a0011f} + engines: {node: '>=10'} + + css-blank-pseudo@6.0.0: + resolution: {tarball: https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-6.0.0.tgz?rbtorrent=6492fbcbba50a088fd493adce076ee4f73d142b6} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + css-box-model@1.2.1: + resolution: {tarball: https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz?rbtorrent=8681bff6f3c11bfde8d20bee4f290b1f3657dc1f} + + css-declaration-sorter@7.2.0: + resolution: {tarball: https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz?rbtorrent=} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.0.9 + + css-functions-list@3.2.2: + resolution: {tarball: https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.2.tgz?rbtorrent=} + engines: {node: '>=12 || >=16'} + + css-has-pseudo@6.0.0: + resolution: {tarball: https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-6.0.0.tgz?rbtorrent=9c15a1d92c7d569b16573a86d3ff6648fe6d3473} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + css-in-js-utils@3.1.0: + resolution: {tarball: https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz?rbtorrent=e1e9c23e96743393906c28012860a0b4659f989b} + + css-loader@7.1.2: + resolution: {tarball: https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz?rbtorrent=} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + webpack: ^5.27.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + css-minimizer-webpack-plugin@7.0.0: + resolution: {tarball: https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-7.0.0.tgz?rbtorrent=} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@parcel/css': '*' + '@swc/css': '*' + clean-css: '*' + csso: '*' + esbuild: '*' + lightningcss: '*' + webpack: ^5.0.0 + peerDependenciesMeta: + '@parcel/css': + optional: true + '@swc/css': + optional: true + clean-css: + optional: true + csso: + optional: true + esbuild: + optional: true + lightningcss: + optional: true + + css-prefers-color-scheme@9.0.0: + resolution: {tarball: https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-9.0.0.tgz?rbtorrent=99ffa195aebcc8a06e80637beab4809f2010b2ac} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + css-select@4.3.0: + resolution: {tarball: https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz?rbtorrent=65f898a7a307109f2095eb5f06494f254d9e09ab} + + css-select@5.1.0: + resolution: {tarball: https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz?rbtorrent=3710a16f74a89da09b38a209b20c71ab054790f6} + + css-tree@1.1.3: + resolution: {tarball: https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz?rbtorrent=89ce8b6814b8b37ce1f3931d3432407cfec9d6ae} + engines: {node: '>=8.0.0'} + + css-tree@2.2.1: + resolution: {tarball: https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz?rbtorrent=02c3c3c4f7e703fa102dbaf068b78a3f0b5cc55d} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + css-tree@2.3.1: + resolution: {tarball: https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz?rbtorrent=a0c3e50877eb09f6ec65da41e8bc08d193bddbce} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.1.0: + resolution: {tarball: https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz?rbtorrent=540277e3bfc3fa98c9b115db893e8ca5911ca2b2} + engines: {node: '>= 6'} + + css.escape@1.5.1: + resolution: {tarball: https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz?rbtorrent=a7dd51a7fc06fd4ea43105947563c7c7641f12c1} + + css@3.0.0: + resolution: {tarball: https://registry.npmjs.org/css/-/css-3.0.0.tgz?rbtorrent=d16f12bb0e668fba27690661e5e23c5f3abcbc6a} + + cssdb@7.8.0: + resolution: {tarball: https://registry.npmjs.org/cssdb/-/cssdb-7.8.0.tgz?rbtorrent=0c280fc61cfaac256b1be7c0f1e7db2d335eeb05} + + cssesc@3.0.0: + resolution: {tarball: https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz?rbtorrent=cf4c290f3068fd57ef65285fec1f049bed3f20a4} + engines: {node: '>=4'} + hasBin: true + + cssfilter@0.0.10: + resolution: {tarball: https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz?rbtorrent=da665acec251ab85f524ec51aef6fdfbb8d2f152} + + cssnano-preset-default@7.0.6: + resolution: {tarball: https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.6.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + cssnano-utils@5.0.0: + resolution: {tarball: https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + cssnano@7.0.6: + resolution: {tarball: https://registry.npmjs.org/cssnano/-/cssnano-7.0.6.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + csso@4.2.0: + resolution: {tarball: https://registry.npmjs.org/csso/-/csso-4.2.0.tgz?rbtorrent=47fa7649685fbfc2e8f3d8a79b5bd7675771b11b} + engines: {node: '>=8.0.0'} + + csso@5.0.5: + resolution: {tarball: https://registry.npmjs.org/csso/-/csso-5.0.5.tgz?rbtorrent=c250589e905768aaa6a40a410d08788dfbb13b72} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + + cssom@0.3.8: + resolution: {tarball: https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz?rbtorrent=571ada0cb157bcd005cc581cc96a343fb895a180} + + cssom@0.5.0: + resolution: {tarball: https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz?rbtorrent=ef0c9685dfc7df206aa33ed13e9655a2842c0b62} + + cssstyle@2.3.0: + resolution: {tarball: https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz?rbtorrent=78a6e58b23da6ffc49a6f16c6645193ebe913510} + engines: {node: '>=8'} + + csstype@3.1.3: + resolution: {tarball: https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz?rbtorrent=} + + cytoscape-cose-bilkent@4.1.0: + resolution: {tarball: https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz?rbtorrent=758b5615df8404b7b14440701bfa146cac6360ee} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {tarball: https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz?rbtorrent=512ef20b98c00fa9552748a556835fffeebe72e6} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {tarball: https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz?rbtorrent=} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {tarball: https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz?rbtorrent=be255a09984f17737c9ec815274c5a599f24844c} + + d3-array@3.2.4: + resolution: {tarball: https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz?rbtorrent=fed05f46925b38734b35200797b2c99696ae331e} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {tarball: https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz?rbtorrent=cb3199ba2066df317e40fcd758d4772dce24e867} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {tarball: https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz?rbtorrent=eda938e92e97b6d3e5e4374f825becad0ecee05e} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz?rbtorrent=393a089f841732533820ab5f9df9a5521c2d1748} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {tarball: https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz?rbtorrent=db2bed42a97ef6a1b4add4598196ec09052396f6} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {tarball: https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz?rbtorrent=783162924eafcc5deb3bd59bb664a66e5b6e40f7} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {tarball: https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz?rbtorrent=cb6b77c0b814dc022ab9a2033812cb2b8bf6d326} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz?rbtorrent=99104c6a775e40435a29fbbed1f7ffe514ab45a1} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {tarball: https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz?rbtorrent=17e6e263a0836e201b1e230791e71dbea1202957} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz?rbtorrent=9b5cd92aa49d2e10b17ff7c94e3915502ecf8f36} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz?rbtorrent=baef73a23e30c3e5dd9b69079d8ac80a6f408bdf} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz?rbtorrent=325cd6c7ed56fad831228f6088de5a93da1d74d0} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {tarball: https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz?rbtorrent=aa39be65fc1625c92a4ea8fd3a50ef32c97e2305} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {tarball: https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz?rbtorrent=bbaea36351243129ea1cf771313555bac95abc0b} + engines: {node: '>=12'} + + d3-geo@3.1.0: + resolution: {tarball: https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz?rbtorrent=b235581d73d2e499f9b1451d7c3034d08f3e66a9} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {tarball: https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz?rbtorrent=0162fe00d748d9a7583f0589957d2ce4aa51c2f9} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz?rbtorrent=a02182d3949949c338fe802595ad647a82a236b3} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {tarball: https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz?rbtorrent=adce259e260e88ae39b01dcd740c4fb9f6fe7161} + + d3-path@3.1.0: + resolution: {tarball: https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz?rbtorrent=caa2648a563276b666625ccd8a6bb07df10c9c55} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz?rbtorrent=3384d98657cff041676d1ff7c3857595678c154e} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz?rbtorrent=3f82a91998bed26ee342c4419896c90107dacac8} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz?rbtorrent=577ed106125250a7cf4be883941211bb616ac058} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {tarball: https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz?rbtorrent=f6c52208658e3fce872ef3817cbf33e88791b2d2} + + d3-scale-chromatic@3.0.0: + resolution: {tarball: https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz?rbtorrent=84820cf93ce769682c2b8ed1f7aeef6864b9f53e} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {tarball: https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz?rbtorrent=d03b23937e7b68a243ea1896ea796d385da83f03} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {tarball: https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz?rbtorrent=52aff486438c800eaafbdb78339171099fe73bb0} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {tarball: https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz?rbtorrent=570c6548c35272b1ebc222c74cca3e1ac782b819} + + d3-shape@3.2.0: + resolution: {tarball: https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz?rbtorrent=6231cc3ecb8acde835f305732a8ddefcadb46a3d} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {tarball: https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz?rbtorrent=c637e9ffbb4fab8e7deb9e1d9c8685206b060b64} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {tarball: https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz?rbtorrent=ac186f12b367bebd04c0c219436559d4ce71d41d} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz?rbtorrent=85b741a322b1376b326418b22517da8af909def4} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {tarball: https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz?rbtorrent=a6dffb8c1d60e250f2faf30507fa03e89844d06e} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {tarball: https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz?rbtorrent=ce58b9240e0ddf4bdad93cffdad20eeabd8732d7} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {tarball: https://registry.npmjs.org/d3/-/d3-7.9.0.tgz?rbtorrent=} + engines: {node: '>=12'} + + d@1.0.1: + resolution: {tarball: https://registry.npmjs.org/d/-/d-1.0.1.tgz?rbtorrent=f5314de5ac017a2d28784dd56128a06e4662e31e} + + dagre-d3-es@7.0.13: + resolution: {tarball: https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz?rbtorrent=} + + damerau-levenshtein@1.0.8: + resolution: {tarball: https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz?rbtorrent=0f4a83856c7ffb64f1103ffbb9b0e6a1e408fa50} + + dashdash@1.14.1: + resolution: {tarball: https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz?rbtorrent=bf98883d6cc64a1786c1f7eba9c54ae173440986} + engines: {node: '>=0.10'} + + data-urls@3.0.2: + resolution: {tarball: https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz?rbtorrent=b9e57775ddf1245ebd97c0f217935e1641b4403f} + engines: {node: '>=12'} + + data-view-buffer@1.0.1: + resolution: {tarball: https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + data-view-buffer@1.0.2: + resolution: {tarball: https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {tarball: https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {tarball: https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {tarball: https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {tarball: https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + date-format@4.0.14: + resolution: {tarball: https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz?rbtorrent=3d7ad2d6d5bf25a8f87eab789df54d920a87707d} + engines: {node: '>=4.0'} + + dateformat@4.6.3: + resolution: {tarball: https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz?rbtorrent=a2b48414c685ac7e3ccbfbc29affcd8c126f41af} + + dayjs@1.11.10: + resolution: {tarball: https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz?rbtorrent=4389b2e995c9abdd1964b063c89cae9e784a01f1} + + dayjs@1.11.13: + resolution: {tarball: https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz?rbtorrent=} + + dayjs@1.11.19: + resolution: {tarball: https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz?rbtorrent=} + + debounce@1.2.1: + resolution: {tarball: https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz?rbtorrent=5b858df7dc3b0155439a57674775c43ab2112c4a} + + debug@2.6.9: + resolution: {tarball: https://registry.npmjs.org/debug/-/debug-2.6.9.tgz?rbtorrent=b02284ec84efd1aee23ff49704863922a0155cb1} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {tarball: https://registry.npmjs.org/debug/-/debug-3.2.7.tgz?rbtorrent=536d1f9e848d8ab7f18c5b581a8d331fd7697139} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.7: + resolution: {tarball: https://registry.npmjs.org/debug/-/debug-4.3.7.tgz?rbtorrent=} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {tarball: https://registry.npmjs.org/debug/-/debug-4.4.3.tgz?rbtorrent=} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize-keys@1.1.1: + resolution: {tarball: https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz?rbtorrent=beab7244474c8302b008452802c9c2b031273adc} + engines: {node: '>=0.10.0'} + + decamelize@1.2.0: + resolution: {tarball: https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz?rbtorrent=4414890a616b790f201e513f668b525afe121d18} + engines: {node: '>=0.10.0'} + + decamelize@5.0.1: + resolution: {tarball: https://registry.npmjs.org/decamelize/-/decamelize-5.0.1.tgz?rbtorrent=35fedb821e0f5e320e64a8166a69afa5c110d185} + engines: {node: '>=10'} + + decimal.js@10.4.3: + resolution: {tarball: https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz?rbtorrent=f551274c49d3d30b19a4fad42ddb8f009266a50c} + + decode-uri-component@0.2.2: + resolution: {tarball: https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz?rbtorrent=b27342a4b39267ec417524035657bdbf700dc2fe} + engines: {node: '>=0.10'} + + decompress-response@6.0.0: + resolution: {tarball: https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz?rbtorrent=a5b67999613882d2dc477a250e27b3b0e13699cb} + engines: {node: '>=10'} + + dedent@0.7.0: + resolution: {tarball: https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz?rbtorrent=4938be03090051506e96b41155a415867f25fcbf} + + deep-diff@0.3.8: + resolution: {tarball: https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz?rbtorrent=fba00a2e9e2a13e39cc097c39c0e3d6839799f67} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + deep-eql@4.1.4: + resolution: {tarball: https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz?rbtorrent=} + engines: {node: '>=6'} + + deep-equal@1.0.1: + resolution: {tarball: https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz?rbtorrent=bb6120a292ff0775d2cdda817095f85b2e32b453} + + deep-is@0.1.3: + resolution: {tarball: https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz?rbtorrent=f770f3e133c15e4fea5f5a0b3223d87d8b3f3d35} + + deepmerge@4.3.1: + resolution: {tarball: https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz?rbtorrent=0a8ef428469a0e660577d7d5281e1a85cc99373e} + engines: {node: '>=0.10.0'} + + default-browser-id@5.0.0: + resolution: {tarball: https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + default-browser@5.2.1: + resolution: {tarball: https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz?rbtorrent=} + engines: {node: '>=18'} + + defaults@2.0.2: + resolution: {tarball: https://registry.npmjs.org/defaults/-/defaults-2.0.2.tgz?rbtorrent=279bfa9d9162f012965dfb73ca3a51039c68ff1d} + engines: {node: '>=16'} + + defer-to-connect@2.0.1: + resolution: {tarball: https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz?rbtorrent=c28cc5e4e46e27d6fc5467164ecb51b6e113814a} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {tarball: https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + define-lazy-prop@2.0.0: + resolution: {tarball: https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz?rbtorrent=67fd6bf0e94c870f927023f815d2e767ce8ae068} + engines: {node: '>=8'} + + define-lazy-prop@3.0.0: + resolution: {tarball: https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz?rbtorrent=0ee793275c39d9ae6fb2b2380673da04e1c6dc72} + engines: {node: '>=12'} + + define-properties@1.2.1: + resolution: {tarball: https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz?rbtorrent=db94dee3c554100b9136ea5006e97206d1b8a1e9} + engines: {node: '>= 0.4'} + + del@4.1.1: + resolution: {tarball: https://registry.npmjs.org/del/-/del-4.1.1.tgz?rbtorrent=9920f7a6103f6eb5bdeab5ffcfabcd2ed5257539} + engines: {node: '>=6'} + + delaunator@5.0.1: + resolution: {tarball: https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz?rbtorrent=} + + delayed-stream@1.0.0: + resolution: {tarball: https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz?rbtorrent=44bab21eb37f612ce8864509107904da4dd90f8d} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {tarball: https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz?rbtorrent=804639b51898a367b8ef98ac57d34edbeb698e0c} + + denque@1.5.1: + resolution: {tarball: https://registry.npmjs.org/denque/-/denque-1.5.1.tgz?rbtorrent=2daaef3c7edd39bcb1c66e1cf2accd3fe65f9391} + engines: {node: '>=0.10'} + + depd@1.1.2: + resolution: {tarball: https://registry.npmjs.org/depd/-/depd-1.1.2.tgz?rbtorrent=0a6aefaeb612e3299120efae6cb484897030a751} + engines: {node: '>= 0.6'} + + depd@2.0.0: + resolution: {tarball: https://registry.npmjs.org/depd/-/depd-2.0.0.tgz?rbtorrent=3d4658af3ff6b387cafddbf392c741a82c7f76e0} + engines: {node: '>= 0.8'} + + dequal@2.0.3: + resolution: {tarball: https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz?rbtorrent=d7e7067dd1da2ccb70740611ee3db410fd5acaa9} + engines: {node: '>=6'} + + destroy@1.2.0: + resolution: {tarball: https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz?rbtorrent=5e2313f4c22911936287ddc65b3b345afc51f3e0} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@1.0.3: + resolution: {tarball: https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz?rbtorrent=b64e4119f2b8684ece543716a33acb4bc6e6eba6} + engines: {node: '>=0.10'} + hasBin: true + + detect-newline@3.1.0: + resolution: {tarball: https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz?rbtorrent=90b4583a612aa97b46436cf59670b7aea9d682d3} + engines: {node: '>=8'} + + detect-node@2.1.0: + resolution: {tarball: https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz?rbtorrent=c8a9f9399e408c076eee1ec6439c26858efdd679} + + devlop@1.1.0: + resolution: {tarball: https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz?rbtorrent=af5cff5aa82c8b9f2b42e95a8e215da850b29d97} + + diff-match-patch@1.0.5: + resolution: {tarball: https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz?rbtorrent=bcf6003a4ca5581ec02cfd7a0af516882b1fbce5} + + diff-sequences@27.5.1: + resolution: {tarball: https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz?rbtorrent=c6598e28edfc6abb56a6971cd83da9515ca5c43a} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + diff-sequences@29.4.3: + resolution: {tarball: https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz?rbtorrent=182c4ad30cc95436ff1e7125f95a251952a626a9} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.2: + resolution: {tarball: https://registry.npmjs.org/diff/-/diff-4.0.2.tgz?rbtorrent=1bac4e8303e581fb5abf61964cffb621700cd3ce} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {tarball: https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz?rbtorrent=50541e66942f02e9149ad91387718b7f45c199a2} + engines: {node: '>=8'} + + dnd-core@10.0.2: + resolution: {tarball: https://registry.npmjs.org/dnd-core/-/dnd-core-10.0.2.tgz?rbtorrent=cc299147fc981ddec12f781342d2fb9b9a547fb4} + + dns-packet@5.6.1: + resolution: {tarball: https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz?rbtorrent=6a8dc91492886c78103ae9374e97649068514e9f} + engines: {node: '>=6'} + + doctrine@2.1.0: + resolution: {tarball: https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz?rbtorrent=1491828e2cd9805b5489cfe362f2952356966d66} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {tarball: https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz?rbtorrent=5b8a14a76d43802548de3e58cbf8d9c9391c6000} + engines: {node: '>=6.0.0'} + + dom-accessibility-api@0.5.13: + resolution: {tarball: https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.13.tgz?rbtorrent=8dba0bbe1d22abfbc207477d52ead13094719013} + + dom-helpers@5.2.1: + resolution: {tarball: https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz?rbtorrent=12957d8d7edeb466542bcf9b5e2c407f1048396f} + + dom-serializer@1.4.1: + resolution: {tarball: https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz?rbtorrent=61843cae8676af539408abc24374b954d683bc71} + + dom-serializer@2.0.0: + resolution: {tarball: https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz?rbtorrent=fae0915acfdc654f54bcb5bc5b1ffac8896cf488} + + domelementtype@2.3.0: + resolution: {tarball: https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz?rbtorrent=ee0905029b165176b7f2de3195de5cace126ed79} + + domexception@4.0.0: + resolution: {tarball: https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz?rbtorrent=4f6053ffcd7e4a6a81b6bd6699a5d08ff5d35de1} + engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead + + domhandler@4.3.1: + resolution: {tarball: https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz?rbtorrent=7cc964a3e980fdb54398efa86d6d8a331cd9c0a3} + engines: {node: '>= 4'} + + domhandler@5.0.3: + resolution: {tarball: https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz?rbtorrent=774f28bea70b9a5efaaf7559e97a304b8927880a} + engines: {node: '>= 4'} + + dompurify@3.2.4: + resolution: {tarball: https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz?rbtorrent=} + + dompurify@3.3.1: + resolution: {tarball: https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz?rbtorrent=} + + domutils@2.8.0: + resolution: {tarball: https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz?rbtorrent=9f6ff36573554830f6e509ded21d9821da14d171} + + domutils@3.2.2: + resolution: {tarball: https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz?rbtorrent=} + + dot-case@3.0.4: + resolution: {tarball: https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz?rbtorrent=ff570e527829f379cea0ff5365910545b5fadcf8} + + dotenv@16.4.5: + resolution: {tarball: https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz?rbtorrent=} + engines: {node: '>=12'} + + dotenv@8.2.0: + resolution: {tarball: https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz?rbtorrent=f52ea77d542d5448e36a0055ef1927c30ad9879e} + engines: {node: '>=8'} + + dunder-proto@1.0.1: + resolution: {tarball: https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + duplexer@0.1.2: + resolution: {tarball: https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz?rbtorrent=d327b34fe886f5bc4ff2ede48deadbc826227169} + + eastasianwidth@0.2.0: + resolution: {tarball: https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz?rbtorrent=48a1942e24c9694bc3fe74193b62c270de25b792} + + ecc-jsbn@0.1.2: + resolution: {tarball: https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz?rbtorrent=bb443a2a76c68b238dc29dfab555a62415c3c168} + + ecdsa-sig-formatter@1.0.11: + resolution: {tarball: https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz?rbtorrent=2a9e498e891d4693121a372c3d69a93f7bcdd7cf} + + ee-first@1.1.1: + resolution: {tarball: https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz?rbtorrent=b33e875b5c9b99376469b3d4fc2448b418091933} + + electron-to-chromium@1.5.118: + resolution: {tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.118.tgz?rbtorrent=} + + electron-to-chromium@1.5.191: + resolution: {tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz?rbtorrent=} + + emittery@0.13.1: + resolution: {tarball: https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz?rbtorrent=a8c49f7c2cd8cffc8c01ccfae1e59a2661c22a88} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {tarball: https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz?rbtorrent=9c63733fe9e4b60ec6164b7b90f008f886358190} + + emoji-regex@9.2.2: + resolution: {tarball: https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz?rbtorrent=b0f455337b66ea63401408ca14fd65d75aaa4983} + + emojis-list@3.0.0: + resolution: {tarball: https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz?rbtorrent=bef139163f62675ce62bc34bb55684ee9e5957a3} + engines: {node: '>= 4'} + + encodeurl@1.0.2: + resolution: {tarball: https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz?rbtorrent=2ee1d518c4b14b1d485befc7a1100de1702f4015} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {tarball: https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz?rbtorrent=} + engines: {node: '>= 0.8'} + + encoding-sniffer@0.2.0: + resolution: {tarball: https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz?rbtorrent=} + + end-of-stream@1.4.4: + resolution: {tarball: https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz?rbtorrent=a90d5140548fbf164b53211d25e22001769bd218} + + engine.io-parser@5.2.3: + resolution: {tarball: https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz?rbtorrent=} + engines: {node: '>=10.0.0'} + + engine.io@6.6.4: + resolution: {tarball: https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz?rbtorrent=} + engines: {node: '>=10.2.0'} + + enhanced-resolve@5.12.0: + resolution: {tarball: https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz?rbtorrent=502e2ced9cd5299b0cc1531f0475c5badf5150a7} + engines: {node: '>=10.13.0'} + + enhanced-resolve@5.17.1: + resolution: {tarball: https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz?rbtorrent=} + engines: {node: '>=10.13.0'} + + entities@2.2.0: + resolution: {tarball: https://registry.npmjs.org/entities/-/entities-2.2.0.tgz?rbtorrent=3964e4ad1b385dac4ec7ab6159e40c4e5fb8c8bd} + + entities@3.0.1: + resolution: {tarball: https://registry.npmjs.org/entities/-/entities-3.0.1.tgz?rbtorrent=120f7e07fa8c540c26a87af2db126e98240f1e8f} + engines: {node: '>=0.12'} + + entities@4.5.0: + resolution: {tarball: https://registry.npmjs.org/entities/-/entities-4.5.0.tgz?rbtorrent=869524ccb80f827e53675f932b6947e6ea7ac70e} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {tarball: https://registry.npmjs.org/entities/-/entities-6.0.1.tgz?rbtorrent=} + engines: {node: '>=0.12'} + + entities@7.0.1: + resolution: {tarball: https://registry.npmjs.org/entities/-/entities-7.0.1.tgz?rbtorrent=} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {tarball: https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz?rbtorrent=71dd915449e6f8dae3ecc07eff6aa2e9930ee890} + engines: {node: '>=6'} + + envinfo@7.14.0: + resolution: {tarball: https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz?rbtorrent=} + engines: {node: '>=4'} + hasBin: true + + error-ex@1.3.2: + resolution: {tarball: https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz?rbtorrent=59060ab6f86726cc6c029e022519ea3aa75a0992} + + error-stack-parser@2.1.4: + resolution: {tarball: https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz?rbtorrent=598ba0b73f119b217898c3f1fe95f35a04e046bd} + + es-abstract@1.23.3: + resolution: {tarball: https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + es-abstract@1.23.9: + resolution: {tarball: https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {tarball: https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {tarball: https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.0.19: + resolution: {tarball: https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + es-module-lexer@1.3.0: + resolution: {tarball: https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz?rbtorrent=44fda98f4240bce7d09f18f7db38f6faaab38cc9} + + es-object-atoms@1.1.1: + resolution: {tarball: https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {tarball: https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {tarball: https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz?rbtorrent=4670467a9d811ebc2051ab7b742839899d6b9134} + + es-shim-unscopables@1.1.0: + resolution: {tarball: https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + es-to-primitive@1.2.1: + resolution: {tarball: https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz?rbtorrent=810b2c3fd69e30768db995fb51f441fb465b89c7} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {tarball: https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + es5-ext@0.10.63: + resolution: {tarball: https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.63.tgz?rbtorrent=} + engines: {node: '>=0.10'} + + es6-iterator@2.0.3: + resolution: {tarball: https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz?rbtorrent=3a5a6bbdec648325d1b8b8ade6ea2e4b0692608b} + + es6-symbol@3.1.3: + resolution: {tarball: https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz?rbtorrent=662b7db1d17cbdb0f4ee3cb4ad3da0ccffd7c205} + + es6-weak-map@2.0.3: + resolution: {tarball: https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz?rbtorrent=9517179807177170f1414471b557212c77e0e720} + + esbuild@0.25.5: + resolution: {tarball: https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz?rbtorrent=} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {tarball: https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz?rbtorrent=} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {tarball: https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz?rbtorrent=3eff6d8634dc1ec132c806f38441e2118d1a2964} + + escape-string-regexp@2.0.0: + resolution: {tarball: https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz?rbtorrent=632055445b6d12ccd304fa8836e60aa10f9cc33f} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {tarball: https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz?rbtorrent=c4f07b8ebef35e122404211f688e5148c3c94d0c} + engines: {node: '>=10'} + + escodegen@2.1.0: + resolution: {tarball: https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz?rbtorrent=734445e1b1aba60bccb4889c7e469e6f15c4aaa7} + engines: {node: '>=6.0'} + hasBin: true + + eslint-config-prettier@9.1.0: + resolution: {tarball: https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz?rbtorrent=} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-import-resolver-node@0.3.9: + resolution: {tarball: https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz?rbtorrent=500fef5a0b8b431822e5120469a77b6e1729501f} + + eslint-import-resolver-typescript@3.6.1: + resolution: {tarball: https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz?rbtorrent=bf030c074e2cb0e58215ae848b06d8e8ed380f68} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + + eslint-module-utils@2.8.1: + resolution: {tarball: https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz?rbtorrent=} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.29.1: + resolution: {tarball: https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz?rbtorrent=} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.8.0: + resolution: {tarball: https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz?rbtorrent=f5cbe9874a90ed90b5d7f1cdab2534535ed889c7} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-plugin-prettier@5.1.3: + resolution: {tarball: https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz?rbtorrent=} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-react-hooks@4.6.0: + resolution: {tarball: https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz?rbtorrent=13d09e829b55dd1f397065f64f62674c0ce0276c} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + + eslint-plugin-react@7.34.1: + resolution: {tarball: https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz?rbtorrent=} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-plugin-security@1.7.1: + resolution: {tarball: https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.7.1.tgz?rbtorrent=97afe115f878ca0856de51041724ccd313b8a7c4} + + eslint-scope@5.1.1: + resolution: {tarball: https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz?rbtorrent=5543028663db29cd442cdd6298fcdc0c11c1f8b2} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {tarball: https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz?rbtorrent=606bf64ab66fdfca148bb12b74d3d0d2436cf967} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@2.1.0: + resolution: {tarball: https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz?rbtorrent=6077c97cf4c2efe6dba02e3233a23b25ea9a0799} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {tarball: https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz?rbtorrent=f46cfe5ec9649990b44460899ccc6647dc17865a} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.0: + resolution: {tarball: https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz?rbtorrent=} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + esniff@2.0.1: + resolution: {tarball: https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz?rbtorrent=} + engines: {node: '>=0.10'} + + espree@9.6.1: + resolution: {tarball: https://registry.npmjs.org/espree/-/espree-9.6.1.tgz?rbtorrent=e134042d196358138036e9ed81c950f68aa96edd} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {tarball: https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz?rbtorrent=441f6e5365a6d63c5e2aaee0fbacef810a562536} + engines: {node: '>=4'} + hasBin: true + + esquery@1.5.0: + resolution: {tarball: https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz?rbtorrent=4c6ba9850b19de198460d731d4b081b7db535bac} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {tarball: https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz?rbtorrent=75ee9b74b9c0a6bec5ed04920f07c443ff85911f} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {tarball: https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz?rbtorrent=3f68ce8b75112184e02b1364df05181a5fb0f440} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {tarball: https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz?rbtorrent=8ebd3be9f72b74badf633d05a4b307c16676736d} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {tarball: https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz?rbtorrent=d18a22c896f2f3220a9e1e10ebc5f4da3863e618} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {tarball: https://registry.npmjs.org/etag/-/etag-1.8.1.tgz?rbtorrent=2f3ff4ae5ba58604fad50c32ca223b29bd8561a6} + engines: {node: '>= 0.6'} + + event-emitter@0.3.5: + resolution: {tarball: https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz?rbtorrent=b5234887c61672f33925b8de1ad9ce9cecb7b77e} + + event-target-shim@5.0.1: + resolution: {tarball: https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz?rbtorrent=873b7944f02b48b1493f4b4f2441dfcc0a8f640e} + engines: {node: '>=6'} + + eventemitter3@4.0.7: + resolution: {tarball: https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz?rbtorrent=0d34e7c3d4a8870f6171c26a1dcab00c36015abf} + + eventemitter3@5.0.1: + resolution: {tarball: https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz?rbtorrent=dd2bb6d825ded93c50507ea50e39beca51b93543} + + events-universal@1.0.1: + resolution: {tarball: https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz?rbtorrent=} + + events@3.3.0: + resolution: {tarball: https://registry.npmjs.org/events/-/events-3.3.0.tgz?rbtorrent=842dc1cb8084985a0b4489adc6c8aed7b0518e09} + engines: {node: '>=0.8.x'} + + execa@5.1.1: + resolution: {tarball: https://registry.npmjs.org/execa/-/execa-5.1.1.tgz?rbtorrent=1014191fee91f170668e1e7d774f56a00a020a13} + engines: {node: '>=10'} + + execa@7.1.1: + resolution: {tarball: https://registry.npmjs.org/execa/-/execa-7.1.1.tgz?rbtorrent=f1f8607c9a7cd042285205075519e538523dd442} + engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} + + execa@8.0.1: + resolution: {tarball: https://registry.npmjs.org/execa/-/execa-8.0.1.tgz?rbtorrent=7e4cb20d39821d07e20e2615abfab42950883a56} + engines: {node: '>=16.17'} + + exit@0.1.2: + resolution: {tarball: https://registry.npmjs.org/exit/-/exit-0.1.2.tgz?rbtorrent=eac5eeb166ca4f4fa947fc127617e270850ce61b} + engines: {node: '>= 0.8.0'} + + expand-tilde@2.0.2: + resolution: {tarball: https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz?rbtorrent=2cd5ed96370f8b1540bff81c940601004c9f22c2} + engines: {node: '>=0.10.0'} + + expect@27.5.1: + resolution: {tarball: https://registry.npmjs.org/expect/-/expect-27.5.1.tgz?rbtorrent=d9c991676565ea9894e315071adb30d12a8eea54} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + expect@29.4.3: + resolution: {tarball: https://registry.npmjs.org/expect/-/expect-29.4.3.tgz?rbtorrent=d319b9514758b4383af36d4ed0f2e29693d91623} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + express-csp-header@5.2.1: + resolution: {tarball: https://registry.npmjs.org/express-csp-header/-/express-csp-header-5.2.1.tgz?rbtorrent=5394c709288a5f0d51d22a065384b17228a1033e} + engines: {node: '>=10'} + + express@4.21.2: + resolution: {tarball: https://registry.npmjs.org/express/-/express-4.21.2.tgz?rbtorrent=} + engines: {node: '>= 0.10.0'} + + ext-list@2.2.2: + resolution: {tarball: https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz?rbtorrent=86533b41b5004a1439021b2e61e39da5ecd4582a} + engines: {node: '>=0.10.0'} + + ext-name@5.0.0: + resolution: {tarball: https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz?rbtorrent=aba6114d617cb1f669786bb9e5203a4f7de37997} + engines: {node: '>=4'} + + ext@1.4.0: + resolution: {tarball: https://registry.npmjs.org/ext/-/ext-1.4.0.tgz?rbtorrent=39f5f847d051f898dcf582103cbbf141279c1d51} + + extend@3.0.2: + resolution: {tarball: https://registry.npmjs.org/extend/-/extend-3.0.2.tgz?rbtorrent=d3db8de3c0c754c04941943e226fe7a87f16248b} + + extsprintf@1.3.0: + resolution: {tarball: https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz?rbtorrent=fbfb5faa21d24da127bac5ad9201c5d6a7959544} + engines: {'0': node >=0.6.0} + + fast-copy@3.0.2: + resolution: {tarball: https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz?rbtorrent=} + + fast-deep-equal@3.1.3: + resolution: {tarball: https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz?rbtorrent=8855b8355a43a0b4004382ab3b8abca525a2cf83} + + fast-diff@1.3.0: + resolution: {tarball: https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz?rbtorrent=3b768b086f5b0aa6ee65231fea056139ac552f62} + + fast-equals@4.0.3: + resolution: {tarball: https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz?rbtorrent=c5dbc7789b02801b18d0446e61a19ab4f23b331a} + + fast-fifo@1.3.2: + resolution: {tarball: https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz?rbtorrent=6b9c55605b33ade92467e956718b7a9c331fd8fa} + + fast-glob@3.3.2: + resolution: {tarball: https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz?rbtorrent=481406b4a1c04a2c853dec97915846ba0c9f5bf6} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {tarball: https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz?rbtorrent=73f9b29e0e2f0f75a0b56fbfa16b1e6c3d1d1c1c} + + fast-levenshtein@2.0.6: + resolution: {tarball: https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz?rbtorrent=c71f14a38b354e3ceb249902e59a2b7ddb34c29d} + + fast-redact@3.5.0: + resolution: {tarball: https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz?rbtorrent=} + engines: {node: '>=6'} + + fast-safe-stringify@2.1.1: + resolution: {tarball: https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz?rbtorrent=9c7a115a48e782e960e48d5d256845f38bc4414c} + + fast-shallow-equal@1.0.0: + resolution: {tarball: https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz?rbtorrent=eb0fbdf537ec34962af92d4aabc6732ef1304c30} + + fast-xml-parser@4.2.5: + resolution: {tarball: https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz?rbtorrent=02c6d3aacb4faef373602622dc1b91fdb4b12333} + hasBin: true + + fastest-levenshtein@1.0.16: + resolution: {tarball: https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz?rbtorrent=8f1fc7b71f78e18724a18317d7f899aa965a0c02} + engines: {node: '>= 4.9.1'} + + fastest-stable-stringify@2.0.2: + resolution: {tarball: https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz?rbtorrent=f85cac89250ae42bed69476dbb4d9a41bbab3b87} + + fastq@1.11.0: + resolution: {tarball: https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz?rbtorrent=4e604520a9f8d3ce58cd9127af529a32e819c739} + + faye-websocket@0.11.4: + resolution: {tarball: https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz?rbtorrent=f6bc60d98d7194bcd15c3aaf3f49b04e03e32243} + engines: {node: '>=0.8.0'} + + fb-watchman@2.0.2: + resolution: {tarball: https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz?rbtorrent=30c0f4dc8d8de32e2805c7e15cd176adb5dfc7f9} + + fdir@6.5.0: + resolution: {tarball: https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz?rbtorrent=} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.2: + resolution: {tarball: https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz?rbtorrent=} + + file-entry-cache@6.0.1: + resolution: {tarball: https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz?rbtorrent=5f231c114b911b55291fff195e0b81afe3dc4605} + engines: {node: ^10.12.0 || >=12.0.0} + + file-entry-cache@7.0.2: + resolution: {tarball: https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-7.0.2.tgz?rbtorrent=} + engines: {node: '>=12.0.0'} + + file-type@19.6.0: + resolution: {tarball: https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + file-type@20.5.0: + resolution: {tarball: https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + filename-reserved-regex@3.0.0: + resolution: {tarball: https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz?rbtorrent=7c63563e1d99cfebf3873dad7ce17b955ac2844c} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + filenamify@6.0.0: + resolution: {tarball: https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz?rbtorrent=28095e97cd4d6e5b8d90a4f9cbc154bd50c08db4} + engines: {node: '>=16'} + + filesize@10.1.6: + resolution: {tarball: https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz?rbtorrent=} + engines: {node: '>= 10.4.0'} + + fill-range@7.1.1: + resolution: {tarball: https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz?rbtorrent=} + engines: {node: '>=8'} + + finalhandler@1.1.2: + resolution: {tarball: https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz?rbtorrent=ce9bf5349ecd0906b66195cd55fb9a150506601e} + engines: {node: '>= 0.8'} + + finalhandler@1.3.1: + resolution: {tarball: https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz?rbtorrent=} + engines: {node: '>= 0.8'} + + find-babel-config@1.2.2: + resolution: {tarball: https://registry.npmjs.org/find-babel-config/-/find-babel-config-1.2.2.tgz?rbtorrent=} + engines: {node: '>=4.0.0'} + + find-cache-dir@3.3.2: + resolution: {tarball: https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz?rbtorrent=4daf427d3b4a16035ab64f801b3970b71f8c09ed} + engines: {node: '>=8'} + + find-cache-dir@4.0.0: + resolution: {tarball: https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz?rbtorrent=976e97cd6d1af39638595842e79b21a887728233} + engines: {node: '>=14.16'} + + find-cache-dir@5.0.0: + resolution: {tarball: https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-5.0.0.tgz?rbtorrent=0b29e76aea0e34c401b1d4943b08efcb6b4fb273} + engines: {node: '>=16'} + + find-file-up@2.0.1: + resolution: {tarball: https://registry.npmjs.org/find-file-up/-/find-file-up-2.0.1.tgz?rbtorrent=384093c82df81333ef1f9de5f7d526a005ba1f83} + engines: {node: '>=8'} + + find-pkg@2.0.0: + resolution: {tarball: https://registry.npmjs.org/find-pkg/-/find-pkg-2.0.0.tgz?rbtorrent=9c72fbc2db2393296a43875d9591d870ebeae46f} + engines: {node: '>=8'} + + find-up@3.0.0: + resolution: {tarball: https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz?rbtorrent=1e9eff2ad647990753b590e819602de41d6375be} + engines: {node: '>=6'} + + find-up@4.1.0: + resolution: {tarball: https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz?rbtorrent=aff561934b413719c6e2d2d91f0c15e9ec721f23} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {tarball: https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz?rbtorrent=9845313c4abccfab4d9007238773b9a95474bb8f} + engines: {node: '>=10'} + + find-up@6.3.0: + resolution: {tarball: https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz?rbtorrent=426d564bda8929e818dbd51aef74d7327d97594a} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + find-versions@5.1.0: + resolution: {tarball: https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz?rbtorrent=921bc4c05a844eec9a2d0be97cd12b20ef77ae62} + engines: {node: '>=12'} + + flat-cache@3.2.0: + resolution: {tarball: https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz?rbtorrent=fc0eab9a3c2cf4c9a1b0027801069e210abc0e01} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.1: + resolution: {tarball: https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz?rbtorrent=} + + follow-redirects@1.15.6: + resolution: {tarball: https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz?rbtorrent=} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.3: + resolution: {tarball: https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz?rbtorrent=02ae04a62c0e6e2d804f06ef92edcca2c8b4ea41} + + for-each@0.3.5: + resolution: {tarball: https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + foreground-child@3.3.0: + resolution: {tarball: https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz?rbtorrent=} + engines: {node: '>=14'} + + forever-agent@0.6.1: + resolution: {tarball: https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz?rbtorrent=94e74b72908d91c14a92fcd81426b28519620efc} + + fork-ts-checker-webpack-plugin@9.0.2: + resolution: {tarball: https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz?rbtorrent=48fc71428aee0324a4fc4840b84d083e3255fc30} + engines: {node: '>=12.13.0', yarn: '>=1.0.0'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 + + form-data-encoder@2.1.4: + resolution: {tarball: https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz?rbtorrent=86dcc58fe211ff3ff1ada61fd27f2b7d0ac349e5} + engines: {node: '>= 14.17'} + + form-data@2.3.3: + resolution: {tarball: https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz?rbtorrent=ed955b02c2c275e41f700770c838f4dd3534a103} + engines: {node: '>= 0.12'} + + form-data@2.5.1: + resolution: {tarball: https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz?rbtorrent=89c5fcbab9fa4ed8e68c09e716748bdf4917205a} + engines: {node: '>= 0.12'} + + form-data@4.0.5: + resolution: {tarball: https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz?rbtorrent=} + engines: {node: '>= 6'} + + forwarded@0.2.0: + resolution: {tarball: https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz?rbtorrent=aa99adefc655551b3a910c3330f84e0ba95ab389} + engines: {node: '>= 0.6'} + + fraction.js@4.3.7: + resolution: {tarball: https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz?rbtorrent=72ec3ceb10904b471b52dfa4a74f3fab555a0685} + + fresh@0.5.2: + resolution: {tarball: https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz?rbtorrent=77477f1b23ed690a472992b3218a06a2c06907f6} + engines: {node: '>= 0.6'} + + fs-extra@10.1.0: + resolution: {tarball: https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz?rbtorrent=5c8dfba10e2d0d46112c93c19bb9afc52205a51c} + engines: {node: '>=12'} + + fs-extra@11.3.0: + resolution: {tarball: https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz?rbtorrent=} + engines: {node: '>=14.14'} + + fs-extra@8.1.0: + resolution: {tarball: https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz?rbtorrent=84cd3f6081c4b51564e5cedff88290941dea1cc7} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@9.1.0: + resolution: {tarball: https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz?rbtorrent=26a0829b58df5dff63940ec95baf1afe4f0177e2} + engines: {node: '>=10'} + + fs-monkey@1.0.6: + resolution: {tarball: https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz?rbtorrent=} + + fs.realpath@1.0.0: + resolution: {tarball: https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz?rbtorrent=ae37248c9abd56036ae23710b5b9d720d60fdc3a} + + fsevents@2.3.2: + resolution: {tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz?rbtorrent=de417e8b4664cffa251a5a9e86d13603edba2eb4} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz?rbtorrent=af48e653c1b69837ab1b663db0e3cccbadea9099} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {tarball: https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz?rbtorrent=423982b67750a5091c8f795109f07009f2be6eac} + + function.prototype.name@1.1.6: + resolution: {tarball: https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz?rbtorrent=1b18df31325568eb5385e633cd5244ae182774f0} + engines: {node: '>= 0.4'} + + function.prototype.name@1.1.8: + resolution: {tarball: https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {tarball: https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz?rbtorrent=801b3c37bf000611107b22bcf2a55c9b8be368dd} + + gensync@1.0.0-beta.2: + resolution: {tarball: https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz?rbtorrent=8dce4b99eb0ee812d609b35c626a12ba3e46d821} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {tarball: https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz?rbtorrent=7388a9547e901af57589cc4a5a879a2c1c79ca6b} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {tarball: https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {tarball: https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz?rbtorrent=8a8f5f00abf0c99d26ef8319e6379a9137492d47} + engines: {node: '>=8.0.0'} + + get-port@5.1.1: + resolution: {tarball: https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz?rbtorrent=c3c19dc9510ff9f167a102abe7d25b014c527de3} + engines: {node: '>=8'} + + get-port@7.1.0: + resolution: {tarball: https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz?rbtorrent=} + engines: {node: '>=16'} + + get-proto@1.0.1: + resolution: {tarball: https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + get-root-node-polyfill@1.0.0: + resolution: {tarball: https://registry.npmjs.org/get-root-node-polyfill/-/get-root-node-polyfill-1.0.0.tgz?rbtorrent=5ec3561ea845f16fb6439c918489ec7ab431ce66} + + get-stream@6.0.1: + resolution: {tarball: https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz?rbtorrent=bdca4f2f993d560de228824b7e49c562d3a4a61b} + engines: {node: '>=10'} + + get-stream@8.0.1: + resolution: {tarball: https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz?rbtorrent=3efa0f9068558115cc3623c75c74ec094cc12b70} + engines: {node: '>=16'} + + get-stream@9.0.1: + resolution: {tarball: https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz?rbtorrent=} + engines: {node: '>=18'} + + get-symbol-description@1.0.2: + resolution: {tarball: https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {tarball: https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + get-tsconfig@4.10.0: + resolution: {tarball: https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz?rbtorrent=} + + get-tsconfig@4.7.3: + resolution: {tarball: https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz?rbtorrent=} + + getpass@0.1.7: + resolution: {tarball: https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz?rbtorrent=56eb58b777605c8a3f90876cfbe48cb5a612b5f9} + + github-slugger@1.5.0: + resolution: {tarball: https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz?rbtorrent=6d610c721f419364417869892be965f88fe51c4e} + + glob-parent@5.1.2: + resolution: {tarball: https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz?rbtorrent=7f75a998dfd20d04f4fba126c6211efce18545ef} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {tarball: https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz?rbtorrent=7590272c760bda1cdac237139f18d8c1b02463a3} + engines: {node: '>=10.13.0'} + + glob-to-regexp@0.4.1: + resolution: {tarball: https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz?rbtorrent=73ef341fadd737fcac9cc5dee8bfc9434db361bc} + + glob@10.4.5: + resolution: {tarball: https://registry.npmjs.org/glob/-/glob-10.4.5.tgz?rbtorrent=} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@7.2.3: + resolution: {tarball: https://registry.npmjs.org/glob/-/glob-7.2.3.tgz?rbtorrent=f1e3c1229b0ce68ff2e1c79bdb0c4be8b1bcd5e6} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + global-modules@1.0.0: + resolution: {tarball: https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz?rbtorrent=5b744632c2a125bdacb5951c1b499beb1d3acdc0} + engines: {node: '>=0.10.0'} + + global-modules@2.0.0: + resolution: {tarball: https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz?rbtorrent=c2d7d966c5a73d03e114d6dd6662f251b8da143c} + engines: {node: '>=6'} + + global-prefix@1.0.2: + resolution: {tarball: https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz?rbtorrent=8a8a0cd2bb082dde6f68a444e295ca2ca57fde52} + engines: {node: '>=0.10.0'} + + global-prefix@3.0.0: + resolution: {tarball: https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz?rbtorrent=000abdac39adc82953b3aba79d11097c1413aa5d} + engines: {node: '>=6'} + + globals@11.12.0: + resolution: {tarball: https://registry.npmjs.org/globals/-/globals-11.12.0.tgz?rbtorrent=558ffec8d36b92a3b7d7ebc2db36b782aefd1694} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {tarball: https://registry.npmjs.org/globals/-/globals-13.24.0.tgz?rbtorrent=} + engines: {node: '>=8'} + + globalthis@1.0.3: + resolution: {tarball: https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz?rbtorrent=03963475d427a5a7e45bdca65e4d8f1491907e27} + engines: {node: '>= 0.4'} + + globalthis@1.0.4: + resolution: {tarball: https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {tarball: https://registry.npmjs.org/globby/-/globby-11.1.0.tgz?rbtorrent=6f24a6e530326734cc50281b8d57f51873232bb9} + engines: {node: '>=10'} + + globby@6.1.0: + resolution: {tarball: https://registry.npmjs.org/globby/-/globby-6.1.0.tgz?rbtorrent=4edab435ff9f3ffb8703ee2acfadaa1189f721d1} + engines: {node: '>=0.10.0'} + + globjoin@0.1.4: + resolution: {tarball: https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz?rbtorrent=7497f0243138c59ccef21b73888255b78e556105} + + gopd@1.2.0: + resolution: {tarball: https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + got@13.0.0: + resolution: {tarball: https://registry.npmjs.org/got/-/got-13.0.0.tgz?rbtorrent=057a75c35baaaf895cb33d3e5c9d737adf4e44ff} + engines: {node: '>=16'} + + graceful-fs@4.2.11: + resolution: {tarball: https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz?rbtorrent=251aa13f9ef4151aff391872f25b06d079421837} + + graphemer@1.4.0: + resolution: {tarball: https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz?rbtorrent=fa59de15d488f5504a7bf8e14d736899234b90bd} + + gzip-size@6.0.0: + resolution: {tarball: https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz?rbtorrent=19bf8b70fc6297bce1dfd3448ee5e71c139d4d7d} + engines: {node: '>=10'} + + hachure-fill@0.5.2: + resolution: {tarball: https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz?rbtorrent=4174066269e2bcbe115e51f6affc74a6a734f82f} + + handle-thing@2.0.1: + resolution: {tarball: https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz?rbtorrent=04bfca229692e6fc2bd4d23a57b3a2bd2d7a992b} + + har-schema@2.0.0: + resolution: {tarball: https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz?rbtorrent=c29bca67a078018281e79896074028e672762a2e} + engines: {node: '>=4'} + + har-validator@5.1.5: + resolution: {tarball: https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz?rbtorrent=3ea559047f07a2e3854a52ed25fd179bb8671ff8} + engines: {node: '>=6'} + deprecated: this library is no longer supported + + hard-rejection@2.1.0: + resolution: {tarball: https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz?rbtorrent=4c5b48a21f185b48e6cb1a5858ff22991ba533e7} + engines: {node: '>=6'} + + has-bigints@1.0.2: + resolution: {tarball: https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz?rbtorrent=70c33a95b4fb085215af9b89ec30e02388f8d510} + + has-bigints@1.1.0: + resolution: {tarball: https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + has-flag@3.0.0: + resolution: {tarball: https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz?rbtorrent=ca7ec6bdf5a36e5aa9d948c64c0f1d8f0b11b442} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {tarball: https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz?rbtorrent=0ed67d1d919c403863c5cdb7bfc454f49a1daff2} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {tarball: https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz?rbtorrent=} + + has-proto@1.0.3: + resolution: {tarball: https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + has-proto@1.2.0: + resolution: {tarball: https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {tarball: https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {tarball: https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + hashids@2.2.8: + resolution: {tarball: https://registry.npmjs.org/hashids/-/hashids-2.2.8.tgz?rbtorrent=c67567d2a32d11084741b17ae3d345c86ee35de2} + + hasown@2.0.2: + resolution: {tarball: https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + help-me@5.0.0: + resolution: {tarball: https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz?rbtorrent=} + + highcharts@8.2.2: + resolution: {tarball: https://registry.npmjs.org/highcharts/-/highcharts-8.2.2.tgz?rbtorrent=06c466e4588ac1bf8c543786b190314dcfed7010} + + highlight.js@11.9.0: + resolution: {tarball: https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz?rbtorrent=a31fbb1a2446a8e494f7a79019a9371abd8b2316} + engines: {node: '>=12.0.0'} + + history@4.10.1: + resolution: {tarball: https://registry.npmjs.org/history/-/history-4.10.1.tgz?rbtorrent=9b40097d0900773128ca95a8e69c6f3d54656df5} + + hoist-non-react-statics@2.5.5: + resolution: {tarball: https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz?rbtorrent=92c6fc518c757f30f7f998dbb965a69caaa83f91} + + hoist-non-react-statics@3.3.2: + resolution: {tarball: https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz?rbtorrent=43ab0056091589d5e8cb9496177e6ea8c613d98b} + + homedir-polyfill@1.0.3: + resolution: {tarball: https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz?rbtorrent=c7f6a647fa8e918c10e6f7d5b4d479a109c5b739} + engines: {node: '>=0.10.0'} + + hosted-git-info@4.1.0: + resolution: {tarball: https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz?rbtorrent=90d8987a5da601029ffbaad1dc84075aeb473bd1} + engines: {node: '>=10'} + + hpack.js@2.1.6: + resolution: {tarball: https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz?rbtorrent=72db725ccc86c60553ba994ee21d46d8c4f76226} + + html-dom-parser@5.1.8: + resolution: {tarball: https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-5.1.8.tgz?rbtorrent=} + + html-encoding-sniffer@3.0.0: + resolution: {tarball: https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz?rbtorrent=aa916da7fb3c23216f988759d91c67206ba15993} + engines: {node: '>=12'} + + html-entities@2.6.0: + resolution: {tarball: https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz?rbtorrent=} + + html-escaper@2.0.2: + resolution: {tarball: https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz?rbtorrent=4ecc9cd44ebe1e7679eae43d27082eca2ec9032c} + + html-react-parser@5.2.17: + resolution: {tarball: https://registry.npmjs.org/html-react-parser/-/html-react-parser-5.2.17.tgz?rbtorrent=} + peerDependencies: + '@types/react': 0.14 || 15 || 16 || 17 || 18 || 19 + react: ^17.0.2 + peerDependenciesMeta: + '@types/react': + optional: true + + html-tags@3.3.1: + resolution: {tarball: https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz?rbtorrent=7b60b8e1c1fe51e3b486436851be07530b8f232e} + engines: {node: '>=8'} + + htmlescape@1.1.1: + resolution: {tarball: https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz?rbtorrent=8c3dcd3d711af3774dd134e775367f64d047bb5b} + engines: {node: '>=0.10'} + + htmlparser2@10.0.0: + resolution: {tarball: https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz?rbtorrent=} + + htmlparser2@10.1.0: + resolution: {tarball: https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz?rbtorrent=} + + htmlparser2@8.0.2: + resolution: {tarball: https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz?rbtorrent=2c2305a38df40a0d3e0158dba6eebddb7ebe9166} + + htmlparser2@9.1.0: + resolution: {tarball: https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz?rbtorrent=} + + http-assert@1.5.0: + resolution: {tarball: https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz?rbtorrent=7d696db8bab647fb95a3b348ed9f669a5efe5492} + engines: {node: '>= 0.8'} + + http-cache-semantics@4.2.0: + resolution: {tarball: https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz?rbtorrent=} + + http-deceiver@1.2.7: + resolution: {tarball: https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz?rbtorrent=595a110135ad5300abaeef2a42c04eb6bd0fd242} + + http-errors@1.6.3: + resolution: {tarball: https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz?rbtorrent=249f9dd97f44a3d6c265efd341c90c24b809dba5} + engines: {node: '>= 0.6'} + + http-errors@1.8.1: + resolution: {tarball: https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz?rbtorrent=2533f70fe27ecc1fd9dbf52aa05a6d6abacd11b4} + engines: {node: '>= 0.6'} + + http-errors@2.0.0: + resolution: {tarball: https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz?rbtorrent=1853a70f7cc99a2e4d1ee38b7b666ca87007c339} + engines: {node: '>= 0.8'} + + http-errors@2.0.1: + resolution: {tarball: https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.8'} + + http-parser-js@0.5.8: + resolution: {tarball: https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz?rbtorrent=2076b2632a6a2ac0110f2ac40e072d600e8fdf27} + + http-proxy-agent@5.0.0: + resolution: {tarball: https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz?rbtorrent=5aca9bda5b60ab0dba23a313e7138bad2c85bb40} + engines: {node: '>= 6'} + + http-proxy-middleware@2.0.9: + resolution: {tarball: https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz?rbtorrent=} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/express': ^4.17.13 + peerDependenciesMeta: + '@types/express': + optional: true + + http-proxy@1.18.1: + resolution: {tarball: https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz?rbtorrent=f3b9a36b11986addf72f89ac49ec5190e00d4b54} + engines: {node: '>=8.0.0'} + + http-signature@1.2.0: + resolution: {tarball: https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz?rbtorrent=0a7d9c421d9ded947c932ef251d3cd24c62ae757} + engines: {node: '>=0.8', npm: '>=1.3.7'} + + http2-wrapper@2.2.1: + resolution: {tarball: https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz?rbtorrent=a748262663999a4c382be21734204eabe37a2be0} + engines: {node: '>=10.19.0'} + + https-proxy-agent@5.0.1: + resolution: {tarball: https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz?rbtorrent=f3ea1bcad234e95362cce4a8dd7244d9d29074e6} + engines: {node: '>= 6'} + + human-signals@2.1.0: + resolution: {tarball: https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz?rbtorrent=fc9fdebe263edc53d5bd07d024726700b62d0ed5} + engines: {node: '>=10.17.0'} + + human-signals@4.3.1: + resolution: {tarball: https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz?rbtorrent=c58624353425c7ccf2a2a9eb7199adde6d923e9a} + engines: {node: '>=14.18.0'} + + human-signals@5.0.0: + resolution: {tarball: https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz?rbtorrent=41285fa415c8dbf67172cc2d1bc50a16a5fe0b9d} + engines: {node: '>=16.17.0'} + + husky@8.0.3: + resolution: {tarball: https://registry.npmjs.org/husky/-/husky-8.0.3.tgz?rbtorrent=0c5d07a13358b99f33df021dfe29fda520d9564e} + engines: {node: '>=14'} + hasBin: true + + hyperdyperid@1.2.0: + resolution: {tarball: https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz?rbtorrent=22ce043df53bc1fd6804c71e02f2837db2b44499} + engines: {node: '>=10.18'} + + hyphenate-style-name@1.1.0: + resolution: {tarball: https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz?rbtorrent=} + + iconv-lite@0.4.24: + resolution: {tarball: https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz?rbtorrent=2ec00ef967690f177de91a2f3365b5c843bf8f3e} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {tarball: https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz?rbtorrent=81e0cc344f80a5a545cf5e673ef2fd10f1ee9233} + engines: {node: '>=0.10.0'} + + icss-utils@5.1.0: + resolution: {tarball: https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz?rbtorrent=ee67b0a820ee316d8601499e0a3e14568c14bbfc} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + ieee754@1.2.1: + resolution: {tarball: https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz?rbtorrent=f8c89388fdebc540fdd36fd4340d98c1f30d2e17} + + ignore-by-default@1.0.1: + resolution: {tarball: https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz?rbtorrent=f1710f138934ecdf9f9294cab7094d32f544f0a9} + + ignore@5.3.1: + resolution: {tarball: https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz?rbtorrent=} + engines: {node: '>= 4'} + + immer@9.0.21: + resolution: {tarball: https://registry.npmjs.org/immer/-/immer-9.0.21.tgz?rbtorrent=93520ce7d7284fed91d60b4f48e4d9c7387c08d0} + + immutability-helper@3.1.1: + resolution: {tarball: https://registry.npmjs.org/immutability-helper/-/immutability-helper-3.1.1.tgz?rbtorrent=502a1d5bb5e4afb6974b5a4e3ba09f15e649f610} + + immutable@4.3.7: + resolution: {tarball: https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz?rbtorrent=} + + import-fresh@3.3.0: + resolution: {tarball: https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz?rbtorrent=ca56bbc60e7b2a6daef0a27d82b329c60a718e02} + engines: {node: '>=6'} + + import-in-the-middle@1.13.1: + resolution: {tarball: https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.13.1.tgz?rbtorrent=} + + import-lazy@4.0.0: + resolution: {tarball: https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz?rbtorrent=7885b57e56847420c5d72e9caf52a6a7c0d25667} + engines: {node: '>=8'} + + import-local@3.1.0: + resolution: {tarball: https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz?rbtorrent=4aaf480e60cc589efc0d387bcc0984619d21a341} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {tarball: https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz?rbtorrent=519514076373d09acd564dc3e94fa52d8a1d4d11} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {tarball: https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz?rbtorrent=31523a14cbd9e391549594aaf18699ccce4c12d2} + engines: {node: '>=8'} + + indent-string@5.0.0: + resolution: {tarball: https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz?rbtorrent=5dc96331edd81d95ce464ceb726721c082d5987b} + engines: {node: '>=12'} + + inflight@1.0.6: + resolution: {tarball: https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz?rbtorrent=5fc387705f71d49a3d36c9e8cf63c62f5efe7396} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.3: + resolution: {tarball: https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz?rbtorrent=e1990ee430bec5f2bce149c7fb6d0b4b0cbb30b5} + + inherits@2.0.4: + resolution: {tarball: https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz?rbtorrent=6d3c5e553faab1c5a0e5657d5f75d80d275fddbe} + + ini@1.3.8: + resolution: {tarball: https://registry.npmjs.org/ini/-/ini-1.3.8.tgz?rbtorrent=40b5cf05cb67c4e21c56bc25d140c6ce199619e4} + + inline-style-parser@0.2.7: + resolution: {tarball: https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz?rbtorrent=} + + inline-style-prefixer@7.0.1: + resolution: {tarball: https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz?rbtorrent=} + + inspect-with-kind@1.0.5: + resolution: {tarball: https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz?rbtorrent=313aed67703719bde1b3625b5fde517b0d4aee31} + + internal-slot@1.0.7: + resolution: {tarball: https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + internal-slot@1.1.0: + resolution: {tarball: https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + internmap@1.0.1: + resolution: {tarball: https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz?rbtorrent=e6edd7c3bcbf9afe8899d4807916d37b2602c2b8} + + internmap@2.0.3: + resolution: {tarball: https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz?rbtorrent=a905f0a269c2c287687fa21becf434961e716a4f} + engines: {node: '>=12'} + + ioredis@4.28.0: + resolution: {tarball: https://registry.npmjs.org/ioredis/-/ioredis-4.28.0.tgz?rbtorrent=c3ba950eea38334ce469795632a24e60879deeee} + engines: {node: '>=6'} + + ipaddr.js@1.9.1: + resolution: {tarball: https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz?rbtorrent=d8bc541cf956f870b225ecf9f62f233f6431d4e4} + engines: {node: '>= 0.10'} + + ipaddr.js@2.2.0: + resolution: {tarball: https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz?rbtorrent=} + engines: {node: '>= 10'} + + is-any-array@2.0.1: + resolution: {tarball: https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.1.tgz?rbtorrent=cec3faa0463785eae7afe2b6768793ce049e28fa} + + is-array-buffer@3.0.4: + resolution: {tarball: https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {tarball: https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {tarball: https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz?rbtorrent=126beea46e1bc82fbfd4de81b86ce1a578ce2e87} + + is-async-function@2.0.0: + resolution: {tarball: https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz?rbtorrent=0f5b3ff415e000d96cc6821e3f64059f3a92198c} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {tarball: https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-bigint@1.0.4: + resolution: {tarball: https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz?rbtorrent=814f93081f3110c8196d4a4119bc96f1c481f612} + + is-bigint@1.1.0: + resolution: {tarball: https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {tarball: https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz?rbtorrent=ae62f3ab9d627d90a4ef4300374bdd25e0c81f14} + engines: {node: '>=8'} + + is-boolean-object@1.1.2: + resolution: {tarball: https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz?rbtorrent=efe061e3a7c583006e19c8aab11931b759de46ad} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {tarball: https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-buffer@1.1.6: + resolution: {tarball: https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz?rbtorrent=65d2089ebcd3cc812e23dd48e431311d046861fa} + + is-callable@1.2.7: + resolution: {tarball: https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz?rbtorrent=d9469785c724b8f9269f543d6be44874dfba0190} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {tarball: https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {tarball: https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {tarball: https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {tarball: https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz?rbtorrent=c085e05d82e423b35e3d370d3494d85d06f68d93} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {tarball: https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {tarball: https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz?rbtorrent=3fb654c3df6056ce9a131fba6df50577e2ea099b} + engines: {node: '>=8'} + hasBin: true + + is-docker@3.0.0: + resolution: {tarball: https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz?rbtorrent=d9e93209fbe96caab7a3c70528c99b74e6bdb46a} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-dom@1.1.0: + resolution: {tarball: https://registry.npmjs.org/is-dom/-/is-dom-1.1.0.tgz?rbtorrent=e6a8dd77be24489bbbc4c75c5b7aab272278833f} + + is-extglob@2.1.1: + resolution: {tarball: https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz?rbtorrent=5ab9e5368b72fd05beaa06c31596612310801866} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.0.2: + resolution: {tarball: https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz?rbtorrent=6762eaa276f3075f70f1f9037688c20bddc5fabc} + + is-finalizationregistry@1.1.1: + resolution: {tarball: https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {tarball: https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz?rbtorrent=9f8a9fc1a318c9b9a907ba74176b8d156ee129cf} + engines: {node: '>=8'} + + is-fullwidth-code-point@4.0.0: + resolution: {tarball: https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz?rbtorrent=a8313323a04e463fcfb8ae71979501fc398b72b3} + engines: {node: '>=12'} + + is-generator-fn@2.1.0: + resolution: {tarball: https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz?rbtorrent=39a25d3613fe5e84662efbc1d9db1bcc6bcdb103} + engines: {node: '>=6'} + + is-generator-function@1.0.10: + resolution: {tarball: https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz?rbtorrent=e68ae7644c6b3258e786428f0a6db68b81fab3e5} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.0: + resolution: {tarball: https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {tarball: https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz?rbtorrent=e4c7a5eee5045dcce66cdffe26446465251888a3} + engines: {node: '>=0.10.0'} + + is-inside-container@1.0.0: + resolution: {tarball: https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz?rbtorrent=5ae635f82d4ce74f2edc96c142d2ff4f44922661} + engines: {node: '>=14.16'} + hasBin: true + + is-map@2.0.3: + resolution: {tarball: https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {tarball: https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-network-error@1.1.0: + resolution: {tarball: https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz?rbtorrent=} + engines: {node: '>=16'} + + is-number-object@1.0.7: + resolution: {tarball: https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz?rbtorrent=a846a78b570dff4970803d5a7c82ed0d5c1e14fb} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {tarball: https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {tarball: https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz?rbtorrent=a36365cac002ce7b5ebe1740bef8809e8e2d89b3} + engines: {node: '>=0.12.0'} + + is-object@1.0.2: + resolution: {tarball: https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz?rbtorrent=c96417bb5aa48cbaacd5099c6d23d25e950867f7} + + is-path-cwd@2.2.0: + resolution: {tarball: https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz?rbtorrent=e16ef9fd142589dba55a56562db045470f5af28f} + engines: {node: '>=6'} + + is-path-in-cwd@2.1.0: + resolution: {tarball: https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz?rbtorrent=20c3d8e118235858bad8add0f04b6f2b9535fb76} + engines: {node: '>=6'} + + is-path-inside@2.1.0: + resolution: {tarball: https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz?rbtorrent=01280f53cf7c31b59ca33e6b000026f3756bd43f} + engines: {node: '>=6'} + + is-path-inside@3.0.3: + resolution: {tarball: https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz?rbtorrent=7459e00b33f2c756a6fbfd8c04c18b80371e21d1} + engines: {node: '>=8'} + + is-plain-obj@1.1.0: + resolution: {tarball: https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz?rbtorrent=dfd1747741b4fb4b3d12472f1410aab1e4795d86} + engines: {node: '>=0.10.0'} + + is-plain-obj@3.0.0: + resolution: {tarball: https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz?rbtorrent=bcab9f9a6b3181cfa8fd8101e205209868873ad1} + engines: {node: '>=10'} + + is-plain-object@5.0.0: + resolution: {tarball: https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz?rbtorrent=f81eab4e3e90d87284e0e845ca06d231b9b83eba} + engines: {node: '>=0.10.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {tarball: https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz?rbtorrent=d2ca374c5b605306f31745a3d3b585049b35e90d} + + is-promise@2.2.2: + resolution: {tarball: https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz?rbtorrent=696b3d668fb826b5a5942cee8cdae16c8b888c5e} + + is-regex@1.1.4: + resolution: {tarball: https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz?rbtorrent=ddb1b55b914d404183508e1a099872c94131a5e8} + engines: {node: '>= 0.4'} + + is-regex@1.2.1: + resolution: {tarball: https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-retry-allowed@2.2.0: + resolution: {tarball: https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz?rbtorrent=4fca2e1742572d709741abf586c76605feec9d6c} + engines: {node: '>=10'} + + is-set@2.0.3: + resolution: {tarball: https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.3: + resolution: {tarball: https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {tarball: https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {tarball: https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz?rbtorrent=4b6d4844b6eee5f9ed67fefd44c545f03e9d215c} + engines: {node: '>=8'} + + is-stream@3.0.0: + resolution: {tarball: https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz?rbtorrent=876a028aaa8686e873e1c92fff6f5951db63b762} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-stream@4.0.1: + resolution: {tarball: https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz?rbtorrent=} + engines: {node: '>=18'} + + is-string@1.0.7: + resolution: {tarball: https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz?rbtorrent=08699ad2f44b5d0f72483d6d036871832f5100ab} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {tarball: https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {tarball: https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz?rbtorrent=ec9e59fd0907ddf19e5f4680dd0a9ed030533844} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {tarball: https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.13: + resolution: {tarball: https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {tarball: https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-typedarray@1.0.0: + resolution: {tarball: https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz?rbtorrent=e9df97288e42a9c268a97c35cd6ed7dc39cddb2d} + + is-weakmap@2.0.2: + resolution: {tarball: https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-weakref@1.0.2: + resolution: {tarball: https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz?rbtorrent=4d282985c271a88a5fccfefedae314b1e476bef8} + + is-weakref@1.1.1: + resolution: {tarball: https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {tarball: https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + is-window@1.0.2: + resolution: {tarball: https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz?rbtorrent=0b1402aea82514778e6b28694dec172b9cbdeebe} + + is-windows@1.0.2: + resolution: {tarball: https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz?rbtorrent=0e14a43b8f23cc3ece5a1d3b187a36aaa168bbf2} + engines: {node: '>=0.10.0'} + + is-wsl@2.2.0: + resolution: {tarball: https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz?rbtorrent=14edc72e1c6ea5585b326a5925c5bfd77fbb6599} + engines: {node: '>=8'} + + is-wsl@3.1.0: + resolution: {tarball: https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz?rbtorrent=dddc55aa9b3fcc2b73d016b862e2ea0105e99776} + engines: {node: '>=16'} + + isarray@0.0.1: + resolution: {tarball: https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz?rbtorrent=03495802deeebf1bf29919bd221d5de87ef336c0} + + isarray@1.0.0: + resolution: {tarball: https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz?rbtorrent=83dabca8b41690244744fa59215842863fc7cdd0} + + isarray@2.0.5: + resolution: {tarball: https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz?rbtorrent=543db49a37a8d09cddab2deba3bcea79bb810141} + + isexe@2.0.0: + resolution: {tarball: https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz?rbtorrent=d70c0ec7af2e1a44ce210026d7e8baafefc4f666} + + ismobilejs@1.1.1: + resolution: {tarball: https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz?rbtorrent=e536b4e6e04b8d6dc103727b33c06644b50ed3ed} + + isomorphic-ws@5.0.0: + resolution: {tarball: https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz?rbtorrent=5ded8bf7648564ceeab3dd35a5cd785c2305a8ca} + peerDependencies: + ws: '*' + + isstream@0.1.2: + resolution: {tarball: https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz?rbtorrent=1684de9d3ce73bc22b741d52f997eec4a30fd5f8} + + istanbul-lib-coverage@3.2.0: + resolution: {tarball: https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz?rbtorrent=8cfde674482726afc31b3aa59882706a472961cf} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {tarball: https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz?rbtorrent=1215a79356eecaf133af009e927203745d783ac0} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.0: + resolution: {tarball: https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz?rbtorrent=3d739310f6a6ad89f564170b1a89ec2cde682658} + engines: {node: '>=8'} + + istanbul-lib-source-maps@4.0.1: + resolution: {tarball: https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz?rbtorrent=c09a9735a94f58b2b2d9857e5f5991bb8a9a7e90} + engines: {node: '>=10'} + + istanbul-reports@3.1.5: + resolution: {tarball: https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz?rbtorrent=0173be9b07e1dde6f160a4969d58e803d298e7f6} + engines: {node: '>=8'} + + iterator.prototype@1.1.2: + resolution: {tarball: https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz?rbtorrent=1a7fa8152dbd55758a2a1431e6c25bc4fc7f327b} + + jackspeak@3.4.3: + resolution: {tarball: https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz?rbtorrent=} + + jest-changed-files@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.4.3.tgz?rbtorrent=ffb93b794907e2964f8aff9e3d5ef7bd81e820be} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz?rbtorrent=6e522e07cf6474fa7ca62c1512acdbb51b11211c} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-circus@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-circus/-/jest-circus-29.4.3.tgz?rbtorrent=c32f653ae4d0a3df46a11d4142bec009db778d84} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-cli/-/jest-cli-29.4.3.tgz?rbtorrent=d519df0845510387066d8806f2a79300dd5da995} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-config/-/jest-config-29.4.3.tgz?rbtorrent=344fe7c3f4406b8bf0ddf1c1334fab60dcb2c5d7} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz?rbtorrent=ba69026b0f6c00fdb3958954f0f316dfe89e6999} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-diff@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-diff/-/jest-diff-29.4.3.tgz?rbtorrent=9cb01dfc284562ff0740fd4bd5c13de179796f8a} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz?rbtorrent=4a792d2b53fc32a10cae2478073cd7e872e1da30} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz?rbtorrent=f6a531d6f7742d6374867a293b6671094eea2f0b} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-each@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-each/-/jest-each-29.4.3.tgz?rbtorrent=8b00cfca168aec896d8569b4968932b9cfbd3956} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-jsdom@29.7.0: + resolution: {tarball: https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz?rbtorrent=e4e762b0e35fe85a4dbb837c75a02ed23f0f6d76} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + + jest-environment-node@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.4.3.tgz?rbtorrent=36444730ad1ed090a1c062121ca7580102979f05} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz?rbtorrent=e6b18dec1af2bdd06a021ee558922c952c013671} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-get-type@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz?rbtorrent=83fee8720c3112b20c65c7a905a940da80f1fc48} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz?rbtorrent=e3dbf8bbfca0f0203766eaf0e5c552e7fe72bf6b} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-haste-map@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.4.3.tgz?rbtorrent=9db9c417f389edc6f58ac6373eb9602fea120b27} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-html-reporters@2.1.2: + resolution: {tarball: https://registry.npmjs.org/jest-html-reporters/-/jest-html-reporters-2.1.2.tgz?rbtorrent=2bed7ec2fd3a824e2ffc26ccc4fe95e795a45d96} + + jest-leak-detector@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.4.3.tgz?rbtorrent=788277320cc0b97b86393670b20ac1251d11752e} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz?rbtorrent=31a8f5549bfd6e894f45e9a6c86e3f2c56f3620a} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-matcher-utils@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.4.3.tgz?rbtorrent=9a696fd4339c7d90b32702e4e99c5bed4ba6c953} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz?rbtorrent=064eea69b46b555bc247d70246968b7deeff5987} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-message-util@29.7.0: + resolution: {tarball: https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz?rbtorrent=fce5bd4f96ba0e4ccc03a6967fe0f5c53f58a675} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz?rbtorrent=0cdff66ddea4a0ae1cb4794529ceff4f7e591ec2} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-mock@29.7.0: + resolution: {tarball: https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz?rbtorrent=449a3d13b5356131085254ec59c1f7c4af64ab15} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {tarball: https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz?rbtorrent=dd907e303a4cbcce6104bd71b8ed70917bc2da80} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz?rbtorrent=d46b49b674037a4433910c55c2f29418d037f7a1} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-regex-util@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz?rbtorrent=67a68f12f9ce2e9b13d0f441b81c1b241162a41f} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.4.3.tgz?rbtorrent=843b017b32c08d820892a88a3184f09a7eaa93e3} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz?rbtorrent=2a74ebc2f7a3e770e0b6623ed1eb91e4e46f001a} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-resolve@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.4.3.tgz?rbtorrent=26c9e7ccc3f1dd36ff6f5a28663570b3c7757b9d} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-runner/-/jest-runner-29.4.3.tgz?rbtorrent=9dd10434dea48e7c80365ed5cfb96554a584f431} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz?rbtorrent=be22867e8248cec235045f091f8cdc365b59c3fa} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-runtime@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.4.3.tgz?rbtorrent=3c6c36bb3f550d6c59b555069a102eeec9d2c8d3} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-serializer@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz?rbtorrent=82e982ca53cf7d4db7fff0bb11f6219b286031c3} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-snapshot@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz?rbtorrent=50033d68ffb7fd200ae815a5262bdefc2e558953} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-snapshot@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.4.3.tgz?rbtorrent=12f6a55d7258538cd9c2cce22ce94a507071214e} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz?rbtorrent=5e119fffab13fc3a10d6f1c0522b641d96bb382e} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-util@29.7.0: + resolution: {tarball: https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz?rbtorrent=5de45600d3e37add061df585e23c8f7da23bff52} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz?rbtorrent=aae1984c6b2622f71c68f017c880ea2db426a1c7} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + jest-validate@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-validate/-/jest-validate-29.4.3.tgz?rbtorrent=e17aa102e6382bff1903dd35188c9792e36b595a} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.4.3.tgz?rbtorrent=7d22d7ef97e64df47bd12ef524a8917f47fb55de} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@27.5.1: + resolution: {tarball: https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz?rbtorrent=3127739fbf46a34b67a33d878946a20896a2c428} + engines: {node: '>= 10.13.0'} + + jest-worker@29.7.0: + resolution: {tarball: https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz?rbtorrent=2fc750412c7dd9adebb590a835625cef942106af} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.4.3: + resolution: {tarball: https://registry.npmjs.org/jest/-/jest-29.4.3.tgz?rbtorrent=e0ff71f4afd392fdadc6e24ded5a3e2c567bc372} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jiti@1.21.6: + resolution: {tarball: https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz?rbtorrent=} + hasBin: true + + jora@1.0.0-beta.8: + resolution: {tarball: https://registry.npmjs.org/jora/-/jora-1.0.0-beta.8.tgz?rbtorrent=6297c931638422b2f5b99af40cdd65230046d1bf} + engines: {node: ^10.12.0 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + joycon@3.1.1: + resolution: {tarball: https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz?rbtorrent=2cd435e3d1274f4a7e9c1a68d91e50b17b7469a0} + engines: {node: '>=10'} + + js-cookie@2.2.1: + resolution: {tarball: https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz?rbtorrent=5797e74c1bee65dc948ee83874f6331b981bf97c} + + js-sha1@0.6.0: + resolution: {tarball: https://registry.npmjs.org/js-sha1/-/js-sha1-0.6.0.tgz?rbtorrent=fef12d8ea2bb4e1dcef4be19772966c4b4f9f9aa} + + js-tokens@4.0.0: + resolution: {tarball: https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz?rbtorrent=d2567250e6aaa096fbb43dc41d38561d75586575} + + js-yaml@3.14.1: + resolution: {tarball: https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz?rbtorrent=39433c2fd42be3e2cecb40aa3b947161445108da} + hasBin: true + + js-yaml@4.1.0: + resolution: {tarball: https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz?rbtorrent=0141469e115b39817bc6a454eda17c088194f73c} + hasBin: true + + jsbn@0.1.1: + resolution: {tarball: https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz?rbtorrent=01c539573dd67344693de8b97edc1fea2046dde3} + + jsdom@20.0.3: + resolution: {tarball: https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz?rbtorrent=c11b82fe70b649237b6436e305f56a9eeeb41d13} + engines: {node: '>=14'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.0.2: + resolution: {tarball: https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz?rbtorrent=cc80a3f0baf7c3c0d886f6e19d7c30dbbdcf8527} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {tarball: https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz?rbtorrent=f7148eb8ca6acc0c31b865b680ebc610f708a384} + + json-cycle@1.5.0: + resolution: {tarball: https://registry.npmjs.org/json-cycle/-/json-cycle-1.5.0.tgz?rbtorrent=b21842d23120b60925e13b0514cfd08c5114693d} + engines: {node: '>= 4'} + + json-fn@1.1.1: + resolution: {tarball: https://registry.npmjs.org/json-fn/-/json-fn-1.1.1.tgz?rbtorrent=a490bf6258dfe898a5d625712d18d5117a66bc0a} + + json-parse-even-better-errors@2.3.1: + resolution: {tarball: https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz?rbtorrent=9028fa81fde62fb4363628ca03346d08c5f551e4} + + json-schema-traverse@0.4.1: + resolution: {tarball: https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz?rbtorrent=df5a39a3a5d620b514175f0bc058853a8bed41d3} + + json-schema-traverse@1.0.0: + resolution: {tarball: https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz?rbtorrent=41590751cab5298a1ed11ae25f72b3edb968db9a} + + json-schema@0.4.0: + resolution: {tarball: https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz?rbtorrent=142ec1b116cee16d1142563249979e35d2eca2b4} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {tarball: https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz?rbtorrent=3c877a5458336cb8c5e38a3e1e6cf11350cfec77} + + json-stream-stringify@3.0.1: + resolution: {tarball: https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.0.1.tgz?rbtorrent=82f097ed6f402f63d1bd476735feea25e924b605} + + json-stringify-safe@5.0.1: + resolution: {tarball: https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz?rbtorrent=5d1d4bbceb527bb96dfdebac699330811b903982} + + json5@1.0.2: + resolution: {tarball: https://registry.npmjs.org/json5/-/json5-1.0.2.tgz?rbtorrent=d85068a8b252f460d7d464cb1d78b5b541ebb99f} + hasBin: true + + json5@2.2.3: + resolution: {tarball: https://registry.npmjs.org/json5/-/json5-2.2.3.tgz?rbtorrent=c320d10c4f75842ba6c193759a72a9e8f130917f} + engines: {node: '>=6'} + hasBin: true + + jsondiffpatch@0.6.0: + resolution: {tarball: https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz?rbtorrent=} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + jsonfile@4.0.0: + resolution: {tarball: https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz?rbtorrent=938a94c976931abc80f1d9031523a39275c2c8c6} + + jsonfile@6.1.0: + resolution: {tarball: https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz?rbtorrent=422cd8804434cf4e1484ab1a91fb04e6f95c7940} + + jsonwebtoken@9.0.0: + resolution: {tarball: https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz?rbtorrent=3d194dad5644dd1625aab4af1faaa8018ffaf03e} + engines: {node: '>=12', npm: '>=6'} + + jsprim@1.4.2: + resolution: {tarball: https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz?rbtorrent=15309a502f2ecfe970f725cd4de3ee05076c9125} + engines: {node: '>=0.6.0'} + + jsx-ast-utils@3.3.5: + resolution: {tarball: https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz?rbtorrent=853c5f884819c453428a57a0f5abf11308377dd2} + engines: {node: '>=4.0'} + + jwa@1.4.1: + resolution: {tarball: https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz?rbtorrent=2229688b1eda67d555bcb78fabed1e2d8697cc58} + + jws@3.2.2: + resolution: {tarball: https://registry.npmjs.org/jws/-/jws-3.2.2.tgz?rbtorrent=cc67fcb8298ff6f26fa398368e2ae0fb91258825} + + katex@0.16.21: + resolution: {tarball: https://registry.npmjs.org/katex/-/katex-0.16.21.tgz?rbtorrent=} + hasBin: true + + katex@0.16.28: + resolution: {tarball: https://registry.npmjs.org/katex/-/katex-0.16.28.tgz?rbtorrent=} + hasBin: true + + keygrip@1.1.0: + resolution: {tarball: https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz?rbtorrent=71fa94f57dbaab7a9ca32a75301540ed2987cf1b} + engines: {node: '>= 0.6'} + + keyv@4.5.4: + resolution: {tarball: https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz?rbtorrent=e30c55a0d1da73915d4dadeff4ffc310b3711faf} + + khroma@2.1.0: + resolution: {tarball: https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz?rbtorrent=9cfbb743b7e0b42de1dc5d927eb7098fa3260e45} + + kind-of@6.0.3: + resolution: {tarball: https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz?rbtorrent=e957778d2ac492c2776ccf51137898b898da3c61} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {tarball: https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz?rbtorrent=573bb26abbe3b8304bef6b93a76c4bdfda3e41de} + engines: {node: '>=6'} + + known-css-properties@0.29.0: + resolution: {tarball: https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.29.0.tgz?rbtorrent=8cbb9a48d5934bdf542aaded895e9fa2387fc097} + + koa-compose@4.1.0: + resolution: {tarball: https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz?rbtorrent=4b875c18837a3302272711ec3384d36014cd7ae2} + + koa@3.0.1: + resolution: {tarball: https://registry.npmjs.org/koa/-/koa-3.0.1.tgz?rbtorrent=} + engines: {node: '>= 18'} + + langium@3.3.1: + resolution: {tarball: https://registry.npmjs.org/langium/-/langium-3.3.1.tgz?rbtorrent=} + engines: {node: '>=16.0.0'} + + language-subtag-registry@0.3.23: + resolution: {tarball: https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz?rbtorrent=} + + language-tags@1.0.9: + resolution: {tarball: https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz?rbtorrent=34bfa068d91696a1c2916f17c48b93456a5fde92} + engines: {node: '>=0.10'} + + launch-editor@2.9.1: + resolution: {tarball: https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz?rbtorrent=} + + layout-base@1.0.2: + resolution: {tarball: https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz?rbtorrent=fee5da6d37212fcbd5380b93b24f781caf8cb517} + + layout-base@2.0.1: + resolution: {tarball: https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz?rbtorrent=8ef7e7411b130f5c2d45ef2d000c4047117f733a} + + lerp@1.0.3: + resolution: {tarball: https://registry.npmjs.org/lerp/-/lerp-1.0.3.tgz?rbtorrent=3721cd6db8737fb3b6d5a4ef016773ba0fc7dccf} + + leven@3.1.0: + resolution: {tarball: https://registry.npmjs.org/leven/-/leven-3.1.0.tgz?rbtorrent=68989fe8f7e4cc8d174ffc963461656bbf67e4b9} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {tarball: https://registry.npmjs.org/levn/-/levn-0.4.1.tgz?rbtorrent=9c261cc23908b31e5b1278bccb79eca238272277} + engines: {node: '>= 0.8.0'} + + lilconfig@2.1.0: + resolution: {tarball: https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz?rbtorrent=dfd0591ffe8fcd33a6dcc4f83ad3cd1d96dc0363} + engines: {node: '>=10'} + + lilconfig@3.1.2: + resolution: {tarball: https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz?rbtorrent=} + engines: {node: '>=14'} + + lines-and-columns@1.1.6: + resolution: {tarball: https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz?rbtorrent=c0cf37fb776eac94da5b51510b26a8a89b2eaf64} + + lines-and-columns@2.0.4: + resolution: {tarball: https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz?rbtorrent=f9146b92a3ac1ea99f7d3bb6cef77f7a4ce34ba5} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + linkify-it@4.0.1: + resolution: {tarball: https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz?rbtorrent=a30c490f03826e98760960760cfcb38269be33d9} + + lint-staged@13.2.2: + resolution: {tarball: https://registry.npmjs.org/lint-staged/-/lint-staged-13.2.2.tgz?rbtorrent=bde8451467af716e0310131a9649877824134bc3} + engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true + + listr2@5.0.8: + resolution: {tarball: https://registry.npmjs.org/listr2/-/listr2-5.0.8.tgz?rbtorrent=d833afe6155da4e9d44d63487f9ed35a3598dd60} + engines: {node: ^14.13.1 || >=16.0.0} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + + loader-runner@4.3.0: + resolution: {tarball: https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz?rbtorrent=e4738a43a5bcf4500fb26e4db63de6fb12e9d48a} + engines: {node: '>=6.11.5'} + + loader-utils@2.0.4: + resolution: {tarball: https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz?rbtorrent=d0610a088649952e2875606fceb970b78c64d2d6} + engines: {node: '>=8.9.0'} + + locate-path@3.0.0: + resolution: {tarball: https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz?rbtorrent=5e141f020e8134962b3a895272d7794b0108dc6a} + engines: {node: '>=6'} + + locate-path@5.0.0: + resolution: {tarball: https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz?rbtorrent=a0f7f1a1c154b0e3a9360fb3ef879cfc618c0a36} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {tarball: https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz?rbtorrent=9fd5a348854666f2910c2388484db02c60554ca2} + engines: {node: '>=10'} + + locate-path@7.2.0: + resolution: {tarball: https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz?rbtorrent=9e4cfb336eabaf8dcc889343ddb9ad127780966e} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lockfile@1.0.4: + resolution: {tarball: https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz?rbtorrent=5ac85e1c32375da9e5531e0811ecbb2a7a1730e0} + + lodash-es@4.17.21: + resolution: {tarball: https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz?rbtorrent=03f1e4a728fdfbb0678d57d72e975064343b8e97} + + lodash.camelcase@4.3.0: + resolution: {tarball: https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz?rbtorrent=adc808ef31018902469ca1a8c73a21b3b5a6dda9} + + lodash.clonedeepwith@4.5.0: + resolution: {tarball: https://registry.npmjs.org/lodash.clonedeepwith/-/lodash.clonedeepwith-4.5.0.tgz?rbtorrent=429025d35f0a71874d922c01a020ecb34bf89025} + + lodash.debounce@4.0.8: + resolution: {tarball: https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz?rbtorrent=168a89c758b82a2e3208139b675b004a909fab32} + + lodash.defaults@4.2.0: + resolution: {tarball: https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz?rbtorrent=92e12d6c9317053ecc845557cddf3e5ea07dcab5} + + lodash.flatten@4.4.0: + resolution: {tarball: https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz?rbtorrent=f55c16665891698c6252f1ad103335929f29d443} + + lodash.get@4.4.2: + resolution: {tarball: https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz?rbtorrent=d730f45b350da198000b82266395ecc49e22c003} + + lodash.has@4.5.2: + resolution: {tarball: https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz?rbtorrent=dc5df2eaf0e58b6cab144313816fdeb552ede838} + + lodash.isarguments@3.1.0: + resolution: {tarball: https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz?rbtorrent=36f2457d967efd892f0e5ea234f257c15b8aa175} + + lodash.isplainobject@4.0.6: + resolution: {tarball: https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz?rbtorrent=a93028dabc5021dd942c3713c3975f0c584de5ca} + + lodash.memoize@4.1.2: + resolution: {tarball: https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz?rbtorrent=1008e1029da05429d90d04d6b41b97eb2a9595fa} + + lodash.merge@4.6.2: + resolution: {tarball: https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz?rbtorrent=82906d7bb307fb4b9769b15821fd55eaca29b9f1} + + lodash.truncate@4.4.2: + resolution: {tarball: https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz?rbtorrent=20eba9e893dfbba7f1123dff052301b9505c9456} + + lodash.unionby@4.8.0: + resolution: {tarball: https://registry.npmjs.org/lodash.unionby/-/lodash.unionby-4.8.0.tgz?rbtorrent=8d6459084e49f19d489d73b59e7c945b65c07a70} + + lodash.uniq@4.5.0: + resolution: {tarball: https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz?rbtorrent=37895515df7115b158aa1d06e822835cb70b1bdf} + + lodash@4.17.21: + resolution: {tarball: https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz?rbtorrent=921df2c64b0e0e10268c2e02b54834c8601e493d} + + log-update@4.0.0: + resolution: {tarball: https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz?rbtorrent=7ef2f254b9348fc98646d5be0aa160d1756e88eb} + engines: {node: '>=10'} + + log4js@6.9.1: + resolution: {tarball: https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz?rbtorrent=68d80c5d6d6a3014c4b7d3c9813b2221971bef40} + engines: {node: '>=8.0'} + + long-timeout@0.1.1: + resolution: {tarball: https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz?rbtorrent=bab809c46b9b8fc8bbc819296d59419e8cf84900} + + long@5.3.1: + resolution: {tarball: https://registry.npmjs.org/long/-/long-5.3.1.tgz?rbtorrent=} + + loose-envify@1.4.0: + resolution: {tarball: https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz?rbtorrent=a3bb39af51195918636d9259c8adab260b176b8c} + hasBin: true + + lower-case@2.0.2: + resolution: {tarball: https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz?rbtorrent=887e32851ad47b310169d288f9f98061b64b717d} + + lowercase-keys@3.0.0: + resolution: {tarball: https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz?rbtorrent=b98a941f32dfe82182d0b804497cda8280401483} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lowlight@3.1.0: + resolution: {tarball: https://registry.npmjs.org/lowlight/-/lowlight-3.1.0.tgz?rbtorrent=626e63003f144d128b60b11b60115584abc6eb7b} + + lru-cache@10.4.3: + resolution: {tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz?rbtorrent=} + + lru-cache@5.1.1: + resolution: {tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz?rbtorrent=270f142ed8ef0554e6b14151af0ec292b31363e6} + + lru-cache@6.0.0: + resolution: {tarball: https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz?rbtorrent=e1af76c311f7f57072554f31e959f850e2aafd00} + engines: {node: '>=10'} + + lru-queue@0.1.0: + resolution: {tarball: https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz?rbtorrent=8d60eadb922f0d1f38c7ab2c9706dd7e32df63ba} + + luxon@1.28.1: + resolution: {tarball: https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz?rbtorrent=3f3a55c2e82dd52a8c1320d0c72851c5bc56c7d3} + + lz-string@1.4.4: + resolution: {tarball: https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz?rbtorrent=eed6318e9d26403bb03c44640313f2575690d082} + hasBin: true + + make-dir@3.1.0: + resolution: {tarball: https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz?rbtorrent=2c1b02825e75cec5bae813f8a889785a84286baa} + engines: {node: '>=8'} + + make-error@1.3.6: + resolution: {tarball: https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz?rbtorrent=83178cd0700b9c5ba3a5aa1410d79f46116ee113} + + makeerror@1.0.12: + resolution: {tarball: https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz?rbtorrent=16bdfeb05fcf13df9a1abf220848024bfb6dc231} + + map-obj@1.0.1: + resolution: {tarball: https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz?rbtorrent=8f86982ce1fccc9f71ce3e27fabc9ee0674c2a5f} + engines: {node: '>=0.10.0'} + + map-obj@4.3.0: + resolution: {tarball: https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz?rbtorrent=b07377d02c0ecc301146452067552a8f4e9a0008} + engines: {node: '>=8'} + + markdown-it-attrs@4.3.1: + resolution: {tarball: https://registry.npmjs.org/markdown-it-attrs/-/markdown-it-attrs-4.3.1.tgz?rbtorrent=} + engines: {node: '>=6'} + peerDependencies: + markdown-it: '>= 9.0.0' + + markdown-it-color@2.1.1: + resolution: {tarball: https://registry.npmjs.org/markdown-it-color/-/markdown-it-color-2.1.1.tgz?rbtorrent=62d54dfe4e45e87123bb0b66c42065c38b65888d} + + markdown-it-deflist@2.1.0: + resolution: {tarball: https://registry.npmjs.org/markdown-it-deflist/-/markdown-it-deflist-2.1.0.tgz?rbtorrent=a0582e8fa73712c8723bb717a9857b916b772cdb} + + markdown-it-directive@2.0.5: + resolution: {tarball: https://registry.npmjs.org/markdown-it-directive/-/markdown-it-directive-2.0.5.tgz?rbtorrent=} + peerDependencies: + '@types/markdown-it': ^12.0.0 || ^13.0.0 + markdown-it: ^12.0.0 || ^13.0.0 + + markdown-it-emoji@2.0.2: + resolution: {tarball: https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.2.tgz?rbtorrent=b9f949e182ae6fa342449fe5fc3d4f741c72ba0f} + + markdown-it-ins@3.0.1: + resolution: {tarball: https://registry.npmjs.org/markdown-it-ins/-/markdown-it-ins-3.0.1.tgz?rbtorrent=6bfe7aa3e9acbf8b2623c901d40c9e8efac76b2c} + + markdown-it-ins@4.0.0: + resolution: {tarball: https://registry.npmjs.org/markdown-it-ins/-/markdown-it-ins-4.0.0.tgz?rbtorrent=} + + markdown-it-link-attributes@4.0.1: + resolution: {tarball: https://registry.npmjs.org/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz?rbtorrent=a52296fda55a53912d88a4f7229e9938f67359b4} + + markdown-it-mark@3.0.1: + resolution: {tarball: https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-3.0.1.tgz?rbtorrent=a3acc6e0b04bee9db0c47ffdc6e97e744b0884ee} + + markdown-it-mark@4.0.0: + resolution: {tarball: https://registry.npmjs.org/markdown-it-mark/-/markdown-it-mark-4.0.0.tgz?rbtorrent=} + + markdown-it-sub@1.0.0: + resolution: {tarball: https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-1.0.0.tgz?rbtorrent=effdcd49bc195fb0b78d61784f15d69d8c642792} + + markdown-it-sub@2.0.0: + resolution: {tarball: https://registry.npmjs.org/markdown-it-sub/-/markdown-it-sub-2.0.0.tgz?rbtorrent=} + + markdown-it-sup@1.0.0: + resolution: {tarball: https://registry.npmjs.org/markdown-it-sup/-/markdown-it-sup-1.0.0.tgz?rbtorrent=42eac0dcd96ef7278403b721b21d9bba09d0a6f1} + + markdown-it@13.0.2: + resolution: {tarball: https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.2.tgz?rbtorrent=0c9a4187aa850098334dbf86f679f42b34ee1a5a} + hasBin: true + + markdownlint-micromark@0.1.7: + resolution: {tarball: https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.7.tgz?rbtorrent=c4ade37c7d813b40ae022e2b47d9f9fda1f7a733} + engines: {node: '>=16'} + + markdownlint-rule-helpers@0.17.2: + resolution: {tarball: https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.17.2.tgz?rbtorrent=d3fa5740fea75402d5e0c3721ae3df7696592956} + engines: {node: '>=12'} + + markdownlint@0.32.1: + resolution: {tarball: https://registry.npmjs.org/markdownlint/-/markdownlint-0.32.1.tgz?rbtorrent=} + engines: {node: '>=18'} + + marked@16.4.2: + resolution: {tarball: https://registry.npmjs.org/marked/-/marked-16.4.2.tgz?rbtorrent=} + engines: {node: '>= 20'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {tarball: https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + mathml-tag-names@2.1.3: + resolution: {tarball: https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz?rbtorrent=a2a04f26801a8a3b891142b852723c5b4307621c} + + md5@2.3.0: + resolution: {tarball: https://registry.npmjs.org/md5/-/md5-2.3.0.tgz?rbtorrent=d54acf44eacea74271ebe1f622d0f22828006b31} + + mdn-data@2.0.14: + resolution: {tarball: https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz?rbtorrent=546d3068674ffe740dda5adb915b7b030f284ad8} + + mdn-data@2.0.28: + resolution: {tarball: https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz?rbtorrent=9d4fd7d4b23dd07d01dbca391ece687693813268} + + mdn-data@2.0.30: + resolution: {tarball: https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz?rbtorrent=106d49159dfafb75159f8becdd59b8126bb97b78} + + mdurl@1.0.1: + resolution: {tarball: https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz?rbtorrent=208366058b43a9610f1f35fac3459d5045923077} + + media-typer@0.3.0: + resolution: {tarball: https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz?rbtorrent=9b7ee58e7e88a03f8f10c81220d0de520b216f82} + engines: {node: '>= 0.6'} + + media-typer@1.1.0: + resolution: {tarball: https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz?rbtorrent=a5e00df29c1ab6a334904dda7e56f24a21428157} + engines: {node: '>= 0.8'} + + memfs@3.5.3: + resolution: {tarball: https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz?rbtorrent=286a8c2aae7ac6efc3cb1b65c06379dc35d23fa7} + engines: {node: '>= 4.0.0'} + + memfs@4.17.0: + resolution: {tarball: https://registry.npmjs.org/memfs/-/memfs-4.17.0.tgz?rbtorrent=} + engines: {node: '>= 4.0.0'} + + memoize-one@5.2.1: + resolution: {tarball: https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz?rbtorrent=f5a5ea737c64466f033ca0be1816edf6aa897085} + + memoizee@0.4.15: + resolution: {tarball: https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz?rbtorrent=07e1c755009e663704b9409fece779a6c757542a} + + meow@10.1.5: + resolution: {tarball: https://registry.npmjs.org/meow/-/meow-10.1.5.tgz?rbtorrent=fe945f7555468bfdeb63472be04e8229dc267944} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + merge-descriptors@1.0.3: + resolution: {tarball: https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz?rbtorrent=} + + merge-stream@2.0.0: + resolution: {tarball: https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz?rbtorrent=d75a9d46cada457d9f9662d5626e938feeafb477} + + merge2@1.4.1: + resolution: {tarball: https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz?rbtorrent=9dc8b87ea416d096f0023fe2267df1db0a526e53} + engines: {node: '>= 8'} + + mermaid@11.12.2: + resolution: {tarball: https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz?rbtorrent=} + + methods@1.1.2: + resolution: {tarball: https://registry.npmjs.org/methods/-/methods-1.1.2.tgz?rbtorrent=d24a15e2ab3b2bb67bae70d2244d434c93695b3d} + engines: {node: '>= 0.6'} + + micromatch@4.0.8: + resolution: {tarball: https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz?rbtorrent=} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {tarball: https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz?rbtorrent=f0ba116ae2c9608848de6e13c43654e0d7f6480e} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {tarball: https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz?rbtorrent=} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {tarball: https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz?rbtorrent=ebb7fcd8ade244558e3f7816c257f2cc22da38ab} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {tarball: https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz?rbtorrent=} + engines: {node: '>=18'} + + mime@1.6.0: + resolution: {tarball: https://registry.npmjs.org/mime/-/mime-1.6.0.tgz?rbtorrent=2e4f9e69f07040a5800dfea8e3d043e637d69b02} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {tarball: https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz?rbtorrent=c19de088a136d3ee458e5c36d8a399411c37a88e} + engines: {node: '>=6'} + + mimic-fn@4.0.0: + resolution: {tarball: https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz?rbtorrent=e670fa6de3eee1beff6f555774ceadbc35b3755b} + engines: {node: '>=12'} + + mimic-response@3.1.0: + resolution: {tarball: https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz?rbtorrent=a204335db4b8aabe3f831ca7609458eed85ccd23} + engines: {node: '>=10'} + + mimic-response@4.0.0: + resolution: {tarball: https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz?rbtorrent=be1973fc1a90556e454f187aa43665422b6fa529} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + min-indent@1.0.1: + resolution: {tarball: https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz?rbtorrent=0ca2d6fefcb15c7fc924657893ec27529a5ebfc7} + engines: {node: '>=4'} + + mini-css-extract-plugin@2.9.2: + resolution: {tarball: https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz?rbtorrent=} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 + + minimalistic-assert@1.0.1: + resolution: {tarball: https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz?rbtorrent=ec37268b43320347a91ad462a9f75c800e480752} + + minimatch@3.1.2: + resolution: {tarball: https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz?rbtorrent=ff75648d716a472d61b2fafcf5250b152d4b84b4} + + minimatch@9.0.3: + resolution: {tarball: https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz?rbtorrent=0f171a699bcf3a37497aa8693e2fcc88a47b8af7} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.5: + resolution: {tarball: https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz?rbtorrent=} + engines: {node: '>=16 || 14 >=14.17'} + + minimist-options@4.1.0: + resolution: {tarball: https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz?rbtorrent=815650692a6e0cd938b683a2dfd2c5b39c4bfb19} + engines: {node: '>= 6'} + + minimist@1.2.8: + resolution: {tarball: https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz?rbtorrent=37c3ed4ebd14901a5c1e7666131bdfedac287a16} + + minipass@7.1.2: + resolution: {tarball: https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz?rbtorrent=} + engines: {node: '>=16 || 14 >=14.17'} + + ml-array-max@1.2.4: + resolution: {tarball: https://registry.npmjs.org/ml-array-max/-/ml-array-max-1.2.4.tgz?rbtorrent=0b5f2e66801149175eedec2c9e2d011c464ecd03} + + ml-array-min@1.2.3: + resolution: {tarball: https://registry.npmjs.org/ml-array-min/-/ml-array-min-1.2.3.tgz?rbtorrent=bbfba1f001aebc94da379045086d536eaabab035} + + ml-array-rescale@1.3.7: + resolution: {tarball: https://registry.npmjs.org/ml-array-rescale/-/ml-array-rescale-1.3.7.tgz?rbtorrent=ea6465a319e315d136b795a1f409c64735053f37} + + ml-matrix@6.12.1: + resolution: {tarball: https://registry.npmjs.org/ml-matrix/-/ml-matrix-6.12.1.tgz?rbtorrent=} + + ml-regression-base@4.0.0: + resolution: {tarball: https://registry.npmjs.org/ml-regression-base/-/ml-regression-base-4.0.0.tgz?rbtorrent=} + + ml-regression-polynomial@3.0.2: + resolution: {tarball: https://registry.npmjs.org/ml-regression-polynomial/-/ml-regression-polynomial-3.0.2.tgz?rbtorrent=} + + mlly@1.8.0: + resolution: {tarball: https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz?rbtorrent=} + + mockdate@3.0.5: + resolution: {tarball: https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz?rbtorrent=94db6ba82685f17804263ed2e636201a8d569035} + + module-details-from-path@1.0.3: + resolution: {tarball: https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz?rbtorrent=ce807d8b78f240754f97ebe242683c2b79d67015} + + moment-timezone-data-webpack-plugin@1.5.1: + resolution: {tarball: https://registry.npmjs.org/moment-timezone-data-webpack-plugin/-/moment-timezone-data-webpack-plugin-1.5.1.tgz?rbtorrent=11b0142253e78f5eb4534c7e4cc743f7b843a56c} + peerDependencies: + moment-timezone: '>= 0.1.0' + webpack: 4.x.x || 5.x.x + + moment-timezone@0.5.43: + resolution: {tarball: https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz?rbtorrent=43de57879d4c4a3daf25b3456e84d5ac1462033e} + + moment@2.29.4: + resolution: {tarball: https://registry.npmjs.org/moment/-/moment-2.29.4.tgz?rbtorrent=f727bdd5e02fc1899128877e6be88b08a8eec617} + + monaco-editor-webpack-plugin@7.1.0: + resolution: {tarball: https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-7.1.0.tgz?rbtorrent=f647f733d7187372395957710ca10be1b5f1a35a} + peerDependencies: + monaco-editor: '>= 0.31.0' + webpack: ^4.5.0 || 5.x + + monaco-editor@0.36.1: + resolution: {tarball: https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.36.1.tgz?rbtorrent=3607c47ca36c7fdece8285f81270b598962adbbe} + + mrmime@2.0.1: + resolution: {tarball: https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz?rbtorrent=} + engines: {node: '>=10'} + + ms@2.0.0: + resolution: {tarball: https://registry.npmjs.org/ms/-/ms-2.0.0.tgz?rbtorrent=b9c1399c483ed1bf4d9fff8735579b86c77001f8} + + ms@2.1.3: + resolution: {tarball: https://registry.npmjs.org/ms/-/ms-2.1.3.tgz?rbtorrent=c0db1b8d3743f39d06054864041516e9b7ac9a4c} + + multicast-dns@7.2.5: + resolution: {tarball: https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz?rbtorrent=3a64325c8ba96d279f506f04894527def075be81} + hasBin: true + + nano-css@5.6.2: + resolution: {tarball: https://registry.npmjs.org/nano-css/-/nano-css-5.6.2.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + nanoid@3.3.8: + resolution: {tarball: https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz?rbtorrent=} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {tarball: https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz?rbtorrent=89fce2f72a9e4c0c53ec26842a78a8d8ecf9f502} + + negotiator@0.6.3: + resolution: {tarball: https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz?rbtorrent=ae67b4fa1586d4a4cac06daf71713d481bc6c45f} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {tarball: https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz?rbtorrent=} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {tarball: https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz?rbtorrent=639a463a6ce562df838872ae1a02a3351329f250} + + next-tick@1.1.0: + resolution: {tarball: https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz?rbtorrent=c7bc6baaf453b9aae34ea69ada69409b920fdcd7} + + no-case@3.0.4: + resolution: {tarball: https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz?rbtorrent=d118eeff2cef92dee1cf9c1ff246f3cc4e41ef62} + + node-abort-controller@3.1.1: + resolution: {tarball: https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz?rbtorrent=451ada428a8396a9a01410af47535acc6cc32cb8} + + node-addon-api@7.1.1: + resolution: {tarball: https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz?rbtorrent=} + + node-cache@5.1.2: + resolution: {tarball: https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz?rbtorrent=7d434e739bc7072699fc3db4dc3b99f508052da8} + engines: {node: '>= 8.0.0'} + + node-forge@1.3.1: + resolution: {tarball: https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz?rbtorrent=a07a9b428349322144d5c9f6136c52778fd329bd} + engines: {node: '>= 6.13.0'} + + node-int64@0.4.0: + resolution: {tarball: https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz?rbtorrent=eca6d0e5aabc70becf25a10fb2f69fb2bf8d2b93} + + node-releases@2.0.19: + resolution: {tarball: https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz?rbtorrent=} + + node-schedule@2.1.1: + resolution: {tarball: https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz?rbtorrent=37075cac0923bd39c901ff53791d07d187c1182b} + engines: {node: '>=6'} + + nodemon@3.1.7: + resolution: {tarball: https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz?rbtorrent=} + engines: {node: '>=10'} + hasBin: true + + normalize-package-data@3.0.3: + resolution: {tarball: https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz?rbtorrent=9c55435761043dca52ca8da73426d336124f3aa0} + engines: {node: '>=10'} + + normalize-path@3.0.0: + resolution: {tarball: https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz?rbtorrent=4134118d28ee8c48a3d2b92bd3f2fa1eb59cc4ac} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {tarball: https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz?rbtorrent=56f1ef711291627449d9de2b2460a887bbeade0f} + engines: {node: '>=0.10.0'} + + normalize-url@8.1.0: + resolution: {tarball: https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz?rbtorrent=} + engines: {node: '>=14.16'} + + npm-run-path@4.0.1: + resolution: {tarball: https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz?rbtorrent=057db3a9a614f658dd27040691c2cab15b8a2284} + engines: {node: '>=8'} + + npm-run-path@5.3.0: + resolution: {tarball: https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz?rbtorrent=} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + nth-check@2.1.1: + resolution: {tarball: https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz?rbtorrent=a5f92968bc233eeeddd1b495e0aace45ebb307aa} + + nwsapi@2.2.7: + resolution: {tarball: https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz?rbtorrent=0dc9236ff97b4066b719a7e654267ae0f92e8f6a} + + oauth-sign@0.9.0: + resolution: {tarball: https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz?rbtorrent=509fe36d08ee35ea8a775ba618460a7f3b638382} + + object-assign@4.1.1: + resolution: {tarball: https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz?rbtorrent=1109a3223e8668f3df1924224b612e45b41d031b} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.1: + resolution: {tarball: https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz?rbtorrent=831a293fd5942bf6b4886c3d337329cb1bb364e1} + + object-inspect@1.13.4: + resolution: {tarball: https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {tarball: https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz?rbtorrent=cc15e5967e273324153e74221960752f7a580f7b} + engines: {node: '>= 0.4'} + + object-sizeof@2.6.5: + resolution: {tarball: https://registry.npmjs.org/object-sizeof/-/object-sizeof-2.6.5.tgz?rbtorrent=} + + object.assign@4.1.5: + resolution: {tarball: https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {tarball: https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + object.entries@1.1.8: + resolution: {tarball: https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {tarball: https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {tarball: https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + object.hasown@1.1.4: + resolution: {tarball: https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + object.values@1.2.0: + resolution: {tarball: https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {tarball: https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + obuf@1.1.2: + resolution: {tarball: https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz?rbtorrent=44ae712f2e3892691f24b42860f90a9147e8155b} + + on-exit-leak-free@2.1.2: + resolution: {tarball: https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz?rbtorrent=617bcb098fd999df22488d07fc4e28702f824e22} + engines: {node: '>=14.0.0'} + + on-finished@2.3.0: + resolution: {tarball: https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz?rbtorrent=57d75759ee2c39e435f5189790dee6e9f6aeb429} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {tarball: https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz?rbtorrent=1be00ab6be77c7d1e37b7ed6b27ad37b13f34a60} + engines: {node: '>= 0.8'} + + on-headers@1.0.2: + resolution: {tarball: https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz?rbtorrent=47bb798539cef8fc41d43270a2acd9225ab68fdb} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {tarball: https://registry.npmjs.org/once/-/once-1.4.0.tgz?rbtorrent=631c10ccf90e53addc6a29d4cca37226f2b67537} + + onetime@5.1.2: + resolution: {tarball: https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz?rbtorrent=c28766c4b4ae3380618f5544e7ce7b62203dba55} + engines: {node: '>=6'} + + onetime@6.0.0: + resolution: {tarball: https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz?rbtorrent=90716c38152f974a9027bdd05505631e23aa27ce} + engines: {node: '>=12'} + + open@10.1.0: + resolution: {tarball: https://registry.npmjs.org/open/-/open-10.1.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + open@8.4.2: + resolution: {tarball: https://registry.npmjs.org/open/-/open-8.4.2.tgz?rbtorrent=2a44b58836035054e3171ff349ffc17baf7a9342} + engines: {node: '>=12'} + + openapi3-ts@4.5.0: + resolution: {tarball: https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-4.5.0.tgz?rbtorrent=} + + opener@1.5.2: + resolution: {tarball: https://registry.npmjs.org/opener/-/opener-1.5.2.tgz?rbtorrent=f20a2e0875d31a25cf61a93352b5f690dd038ec3} + hasBin: true + + optionator@0.9.3: + resolution: {tarball: https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz?rbtorrent=4eb66aeec694957477a7e519e3890ed81df33802} + engines: {node: '>= 0.8.0'} + + orderedmap@2.1.1: + resolution: {tarball: https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz?rbtorrent=97d16289855f19df2c6d2e1d8d3ccc9bf60206af} + + own-keys@1.0.1: + resolution: {tarball: https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + p-cancelable@3.0.0: + resolution: {tarball: https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz?rbtorrent=e5b24d1f5904e82d1050e2b4921bc81041aa83f1} + engines: {node: '>=12.20'} + + p-finally@1.0.0: + resolution: {tarball: https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz?rbtorrent=6fed4fb2551265c8df94d71a4cb0ae664b5ce23f} + engines: {node: '>=4'} + + p-limit@2.3.0: + resolution: {tarball: https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz?rbtorrent=b8608a5f9e7f757a655a3107b2d03c5f4ad1953e} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {tarball: https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz?rbtorrent=87300825ce6a9a987933ecd297b8fa48c4a57313} + engines: {node: '>=10'} + + p-limit@4.0.0: + resolution: {tarball: https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz?rbtorrent=04ebc9376d217bf5eae6948162639f4fef0bed01} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-locate@3.0.0: + resolution: {tarball: https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz?rbtorrent=714328ce24e3b56ab1859d2a9dec2f10545db129} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {tarball: https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz?rbtorrent=3d0b4c1a59e164a804f61680128f696b06b4bfae} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {tarball: https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz?rbtorrent=13abe00d0f43f8b0feaf3b1a103d0b1cbd737f33} + engines: {node: '>=10'} + + p-locate@6.0.0: + resolution: {tarball: https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz?rbtorrent=843ab0db851ee436a6d83342a418ad8542187f54} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-map@2.1.0: + resolution: {tarball: https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz?rbtorrent=a796f50e70f24e93d5a6049912eaafb3e449b66f} + engines: {node: '>=6'} + + p-map@4.0.0: + resolution: {tarball: https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz?rbtorrent=d1843c68b918c5717ccbc4cc1049bddc5e0c047c} + engines: {node: '>=10'} + + p-map@7.0.2: + resolution: {tarball: https://registry.npmjs.org/p-map/-/p-map-7.0.2.tgz?rbtorrent=} + engines: {node: '>=18'} + + p-queue@6.6.2: + resolution: {tarball: https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz?rbtorrent=8e0a4d54e040dddcf2ddd14dbefc5c2da1d6192e} + engines: {node: '>=8'} + + p-queue@8.0.1: + resolution: {tarball: https://registry.npmjs.org/p-queue/-/p-queue-8.0.1.tgz?rbtorrent=} + engines: {node: '>=18'} + + p-retry@6.2.0: + resolution: {tarball: https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz?rbtorrent=} + engines: {node: '>=16.17'} + + p-timeout@3.2.0: + resolution: {tarball: https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz?rbtorrent=cbe20c64e433f1f36f0b9b7027e99de232a18ad8} + engines: {node: '>=8'} + + p-timeout@6.1.3: + resolution: {tarball: https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.3.tgz?rbtorrent=} + engines: {node: '>=14.16'} + + p-try@2.2.0: + resolution: {tarball: https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz?rbtorrent=421dfe28d5b46aeee4fc95238cac425d6f5bd052} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {tarball: https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz?rbtorrent=} + + package-manager-detector@1.6.0: + resolution: {tarball: https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz?rbtorrent=} + + pako@2.1.0: + resolution: {tarball: https://registry.npmjs.org/pako/-/pako-2.1.0.tgz?rbtorrent=f00b2e011ca25b23c2852899c1c28ffcc8466df6} + + parent-module@1.0.1: + resolution: {tarball: https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz?rbtorrent=4d345b2d3a5b298f35a853391cc76fd6feeb7f89} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {tarball: https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz?rbtorrent=30db282ec3a1e44a5cfc43b9942b132426e1d0d0} + engines: {node: '>=8'} + + parse-passwd@1.0.0: + resolution: {tarball: https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz?rbtorrent=85436fe45f0b35bc2cfbd0f13b2da3279a8381ce} + engines: {node: '>=0.10.0'} + + parse-srcset@1.0.2: + resolution: {tarball: https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz?rbtorrent=beb6fb9436c2c205938bb400f6686981156d197a} + + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {tarball: https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz?rbtorrent=} + + parse5-parser-stream@7.1.2: + resolution: {tarball: https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz?rbtorrent=} + + parse5@7.1.2: + resolution: {tarball: https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz?rbtorrent=89495ecad046d0688061e02bd384d3c64da16087} + + parseurl@1.3.3: + resolution: {tarball: https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz?rbtorrent=74e53669c6396757fbc109ac7301a45910bbde3b} + engines: {node: '>= 0.8'} + + path-browserify@1.0.1: + resolution: {tarball: https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz?rbtorrent=3235b7b6645dadbff7266d4790c0ce8dc5c4eaa6} + + path-data-parser@0.1.0: + resolution: {tarball: https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz?rbtorrent=2268c0617510518d334213e5943a7b4ddecc8c9b} + + path-exists@3.0.0: + resolution: {tarball: https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz?rbtorrent=9d98e5514e66895667cb8f58c564b898ac1c2486} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {tarball: https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz?rbtorrent=a6ec4fb4984a31af76ae18f56738e0182af258ca} + engines: {node: '>=8'} + + path-exists@5.0.0: + resolution: {tarball: https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz?rbtorrent=cf3225878ed726e2ef1478824cbe9bd59d332d21} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + path-is-absolute@1.0.1: + resolution: {tarball: https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz?rbtorrent=1a3152a41be6d5d7ee4d03430b58251761675a06} + engines: {node: '>=0.10.0'} + + path-is-inside@1.0.2: + resolution: {tarball: https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz?rbtorrent=7337a88766af6962e48c6f84ccb1b50a14bdecf5} + + path-key@3.1.1: + resolution: {tarball: https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz?rbtorrent=3a9330ed44db721bac3c0b1b9f5fa69a8884bfa5} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {tarball: https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz?rbtorrent=7015ec7a996e73049457f0a526cf7aa82e72e610} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {tarball: https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz?rbtorrent=efa99f9ac82e715695b40043c7265c75a28e6096} + + path-scurry@1.11.1: + resolution: {tarball: https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz?rbtorrent=} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@0.1.12: + resolution: {tarball: https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz?rbtorrent=} + + path-to-regexp@1.9.0: + resolution: {tarball: https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz?rbtorrent=} + + path-type@4.0.0: + resolution: {tarball: https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz?rbtorrent=ef5e3cc7493470bfca3e53ca4702a9e55523dbe1} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {tarball: https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz?rbtorrent=} + + peek-readable@5.4.2: + resolution: {tarball: https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz?rbtorrent=} + engines: {node: '>=14.16'} + + pend@1.2.0: + resolution: {tarball: https://registry.npmjs.org/pend/-/pend-1.2.0.tgz?rbtorrent=ad4161d8968242b4820c9ac435901ebd096748bf} + + performance-now@2.1.0: + resolution: {tarball: https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz?rbtorrent=c2bb90af16057035141ae0e7d6a7aaa7c7895be9} + + picocolors@1.1.1: + resolution: {tarball: https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz?rbtorrent=} + + picomatch@2.3.1: + resolution: {tarball: https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz?rbtorrent=d64d76aa10430f60181256e008cc2a586e86ed0c} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {tarball: https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz?rbtorrent=} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {tarball: https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz?rbtorrent=e4cdf6db3bd6eaa3b32ef702c8465e56af4fad5c} + engines: {node: '>=0.10'} + hasBin: true + + pify@2.3.0: + resolution: {tarball: https://registry.npmjs.org/pify/-/pify-2.3.0.tgz?rbtorrent=097e52bda36a7551758210d65b9283e0ad9b1904} + engines: {node: '>=0.10.0'} + + pify@4.0.1: + resolution: {tarball: https://registry.npmjs.org/pify/-/pify-4.0.1.tgz?rbtorrent=78906541d254e7888d3e8cff527025aa75071fd4} + engines: {node: '>=6'} + + pinkie-promise@2.0.1: + resolution: {tarball: https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz?rbtorrent=516bb7b0cd6973131af466c5a133a35b29b012c9} + engines: {node: '>=0.10.0'} + + pinkie@2.0.4: + resolution: {tarball: https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz?rbtorrent=4fe58bb9c34b9eadc30896cd3436273acea26747} + engines: {node: '>=0.10.0'} + + pino-abstract-transport@1.2.0: + resolution: {tarball: https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz?rbtorrent=} + + pino-abstract-transport@2.0.0: + resolution: {tarball: https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz?rbtorrent=} + + pino-pretty@11.3.0: + resolution: {tarball: https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.3.0.tgz?rbtorrent=} + hasBin: true + + pino-std-serializers@6.2.2: + resolution: {tarball: https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz?rbtorrent=43be68071d98335eceb48e2369c6c1601b4d6439} + + pino@8.21.0: + resolution: {tarball: https://registry.npmjs.org/pino/-/pino-8.21.0.tgz?rbtorrent=} + hasBin: true + + pirates@4.0.5: + resolution: {tarball: https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz?rbtorrent=88ca57f064f62be34a6e817c31a8dacbcfaae0d2} + engines: {node: '>= 6'} + + piscina@4.9.2: + resolution: {tarball: https://registry.npmjs.org/piscina/-/piscina-4.9.2.tgz?rbtorrent=} + + pkg-dir@4.2.0: + resolution: {tarball: https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz?rbtorrent=850a4469dab4c094c5c30dfa5d7976fef134a073} + engines: {node: '>=8'} + + pkg-dir@7.0.0: + resolution: {tarball: https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz?rbtorrent=551d13f4d27cde363058d2b2be9d4968f39db854} + engines: {node: '>=14.16'} + + pkg-types@1.3.1: + resolution: {tarball: https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz?rbtorrent=} + + pkg-up@3.1.0: + resolution: {tarball: https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz?rbtorrent=0d619a9e40490beb518a1939abaaa428c43466a7} + engines: {node: '>=8'} + + playwright-core@1.48.2: + resolution: {tarball: https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz?rbtorrent=} + engines: {node: '>=18'} + hasBin: true + + playwright@1.48.2: + resolution: {tarball: https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz?rbtorrent=} + engines: {node: '>=18'} + hasBin: true + + points-on-curve@0.2.0: + resolution: {tarball: https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz?rbtorrent=1fcb8bfa7e9d636245a7d2a80c9b5b79e939f190} + + points-on-path@0.2.1: + resolution: {tarball: https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz?rbtorrent=7f9eed2ecbdafe7548a5f7ee5590d9156b6abc0a} + + possible-typed-array-names@1.0.0: + resolution: {tarball: https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + possible-typed-array-names@1.1.0: + resolution: {tarball: https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + postcss-attribute-case-insensitive@6.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.2.tgz?rbtorrent=886832d101ff31607ebd9056644e58d50ea77185} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-calc@10.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.2.tgz?rbtorrent=} + engines: {node: ^18.12 || ^20.9 || >=22.0} + peerDependencies: + postcss: ^8.4.38 + + postcss-clamp@4.1.0: + resolution: {tarball: https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz?rbtorrent=16cf82d0900771b020150907e2e73b91a53658d9} + engines: {node: '>=7.6.0'} + peerDependencies: + postcss: ^8.4.6 + + postcss-color-functional-notation@6.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.2.tgz?rbtorrent=7cf34b0fe5b9843456d3e8961ff0e116f41a0924} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-color-hex-alpha@9.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.2.tgz?rbtorrent=25475d9c95ebc10419c8f0ac73beb6d7aa60598c} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-color-rebeccapurple@9.0.1: + resolution: {tarball: https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-9.0.1.tgz?rbtorrent=03558e305a8cf0e0d9a26352b3543cbb1acadb41} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-colormin@7.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.2.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-convert-values@7.0.4: + resolution: {tarball: https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.4.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-custom-media@10.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-10.0.2.tgz?rbtorrent=6a1cfd861d04dec80e8f0dd0ed5fec18b1d6797d} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-custom-properties@13.3.2: + resolution: {tarball: https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.3.2.tgz?rbtorrent=940d3a25928bb2ceabca431e0cfe5bff64001091} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-custom-selectors@7.1.6: + resolution: {tarball: https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.6.tgz?rbtorrent=d32b3fec66406662f7802030eeb4e06eff65ff07} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-dir-pseudo-class@8.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-8.0.0.tgz?rbtorrent=845e9f84067b89d7875bc89b592d5b381070f422} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-discard-comments@7.0.3: + resolution: {tarball: https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.3.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-duplicates@7.0.1: + resolution: {tarball: https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.1.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-empty@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-discard-overridden@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-double-position-gradients@5.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-5.0.2.tgz?rbtorrent=93115e3ad18d9e7a8b20b424b3a8bb6f991ac9f8} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-focus-visible@9.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-9.0.0.tgz?rbtorrent=9c409270757a5a8571bae7a5f9623290bf705594} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-focus-within@8.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-8.0.0.tgz?rbtorrent=15b15c67ee2d7cb938931f75125dabb968b60ccc} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-font-variant@5.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz?rbtorrent=26b235105810e87a71bbb6ac24fbe7477eb2661f} + peerDependencies: + postcss: ^8.1.0 + + postcss-gap-properties@5.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-5.0.0.tgz?rbtorrent=b3d2c931f5a72dab598e713736a366963f70dd7e} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-image-set-function@6.0.1: + resolution: {tarball: https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-6.0.1.tgz?rbtorrent=897ed0973cbf1103d921563f3b711c4bee89909d} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-lab-function@6.0.7: + resolution: {tarball: https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-6.0.7.tgz?rbtorrent=ee29a3a299c1cd1b6f42e31300268b6ff9e204fc} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-loader@8.1.1: + resolution: {tarball: https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz?rbtorrent=} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + postcss: ^7.0.0 || ^8.0.1 + webpack: ^5.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + postcss-logical@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-logical/-/postcss-logical-7.0.0.tgz?rbtorrent=045a2ab75a187a1f77fe20d0af0421cc1ba9871e} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-media-query-parser@0.2.3: + resolution: {tarball: https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz?rbtorrent=4ccab86c948451f64a5ca030f9ea24e5163ca1c7} + + postcss-merge-longhand@7.0.4: + resolution: {tarball: https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.4.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-merge-rules@7.0.4: + resolution: {tarball: https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.4.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-font-values@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-gradients@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-params@7.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.2.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-minify-selectors@7.0.4: + resolution: {tarball: https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.4.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-modules-extract-imports@3.1.0: + resolution: {tarball: https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz?rbtorrent=} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-local-by-default@4.0.5: + resolution: {tarball: https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz?rbtorrent=} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-scope@3.2.0: + resolution: {tarball: https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz?rbtorrent=} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-values@4.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz?rbtorrent=944aca7a245693625c7b93c956365196feb49e88} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-nesting@12.0.1: + resolution: {tarball: https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.1.tgz?rbtorrent=0de035ccfe16f93802729f9aaef2d38601cdc32a} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-normalize-charset@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-display-values@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-positions@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-repeat-style@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-string@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-timing-functions@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-unicode@7.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.2.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-url@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-normalize-whitespace@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-opacity-percentage@2.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-2.0.0.tgz?rbtorrent=d0292df008be329afaa8d5cfb5854217a77bc6c0} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.2 + + postcss-ordered-values@7.0.1: + resolution: {tarball: https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.1.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-overflow-shorthand@5.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-5.0.0.tgz?rbtorrent=b0dab1aa737a4c0e22f07ab6dc181bcd9a310248} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-page-break@3.0.4: + resolution: {tarball: https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz?rbtorrent=0e2cfc188326aef3e99c33776eff16c5e81a8cc8} + peerDependencies: + postcss: ^8 + + postcss-place@9.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-place/-/postcss-place-9.0.0.tgz?rbtorrent=3a40d7a272ccdad8e21a27fc3052b77c9303c1f5} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-prefix-selector@2.1.1: + resolution: {tarball: https://registry.npmjs.org/postcss-prefix-selector/-/postcss-prefix-selector-2.1.1.tgz?rbtorrent=} + peerDependencies: + postcss: ^8.0.0 + + postcss-preset-env@9.2.0: + resolution: {tarball: https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.2.0.tgz?rbtorrent=99dad9b0fe97ef97a142829b527436136e2b231c} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-pseudo-class-any-link@9.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.0.tgz?rbtorrent=1b14031686d10826568fa361b6027a365035cc55} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-reduce-initial@7.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.2.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-reduce-transforms@7.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.0.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-replace-overflow-wrap@4.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz?rbtorrent=da326b49b0e15538b27452323c9d9af5ae0485c9} + peerDependencies: + postcss: ^8.0.3 + + postcss-resolve-nested-selector@0.1.1: + resolution: {tarball: https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz?rbtorrent=1445a0ba62af148867e06e35ea964977919c5aac} + + postcss-safe-parser@6.0.0: + resolution: {tarball: https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz?rbtorrent=ea3dc31abf33358327eae7aec2cfb9e94a4be308} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.3.3 + + postcss-scss@4.0.9: + resolution: {tarball: https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz?rbtorrent=7c43c16109fcd5c74613fd14b3406708250bcf16} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-not@7.0.1: + resolution: {tarball: https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-7.0.1.tgz?rbtorrent=2508fd369531e43b171b9c527c6491f5a93a482c} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + + postcss-selector-parser@6.1.2: + resolution: {tarball: https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz?rbtorrent=} + engines: {node: '>=4'} + + postcss-sorting@8.0.2: + resolution: {tarball: https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz?rbtorrent=b7abe1dedefd8232bfc532a8c005dc7df118d4b1} + peerDependencies: + postcss: ^8.4.20 + + postcss-svgo@7.0.1: + resolution: {tarball: https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.0.1.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >= 18} + peerDependencies: + postcss: ^8.4.31 + + postcss-unique-selectors@7.0.3: + resolution: {tarball: https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.3.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + postcss-value-parser@4.2.0: + resolution: {tarball: https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz?rbtorrent=fdbd35ea94b39730a7690e4c1aacd53ca75a9622} + + postcss@8.4.47: + resolution: {tarball: https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz?rbtorrent=} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {tarball: https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz?rbtorrent=815ffef0632e1a23540119e535e2e0e82f8a184d} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {tarball: https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz?rbtorrent=2db2ddcf3cf54ac1c82955c61c3dff3cad112433} + engines: {node: '>=6.0.0'} + + prettier@3.2.5: + resolution: {tarball: https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz?rbtorrent=} + engines: {node: '>=14'} + hasBin: true + + pretty-format@27.5.1: + resolution: {tarball: https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz?rbtorrent=d5ea5620f2403f99085ad943e48bd966a1fc3670} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-format@29.7.0: + resolution: {tarball: https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz?rbtorrent=007a2181311e91575d605be39511ac6d92b049d2} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + process-nextick-args@2.0.1: + resolution: {tarball: https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz?rbtorrent=7a84ce01bdc2c09524155240448d145a2305acc9} + + process-warning@3.0.0: + resolution: {tarball: https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz?rbtorrent=} + + process@0.11.10: + resolution: {tarball: https://registry.npmjs.org/process/-/process-0.11.10.tgz?rbtorrent=f884ed5a3fde55cd16cbf31bcf3f78c46f8815fc} + engines: {node: '>= 0.6.0'} + + prompts@2.4.2: + resolution: {tarball: https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz?rbtorrent=10aef2b4983f13a97ab5098e5f38a2bf72b7d924} + engines: {node: '>= 6'} + + prop-types@15.8.1: + resolution: {tarball: https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz?rbtorrent=dfd09c705f4836bb67b68ecda9c1bf1b8073d078} + + property-expr@2.0.6: + resolution: {tarball: https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz?rbtorrent=fab3fa244d9ec6180e4244f10c19aaa9897cd29e} + + prosemirror-autocomplete@0.4.3: + resolution: {tarball: https://registry.npmjs.org/prosemirror-autocomplete/-/prosemirror-autocomplete-0.4.3.tgz?rbtorrent=474b6ecfe6cec323dc90f81e03bd5b55ffe8b530} + + prosemirror-codemark@0.4.2: + resolution: {tarball: https://registry.npmjs.org/prosemirror-codemark/-/prosemirror-codemark-0.4.2.tgz?rbtorrent=23fbffdf78c9bc8b71d8d0d85e03451c00a78d69} + peerDependencies: + prosemirror-inputrules: ^1.2.0 + prosemirror-model: ^1.18.1 + prosemirror-state: ^1.4.1 + prosemirror-view: ^1.26.2 + + prosemirror-commands@1.7.1: + resolution: {tarball: https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz?rbtorrent=} + + prosemirror-dropcursor@1.8.2: + resolution: {tarball: https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz?rbtorrent=} + + prosemirror-history@1.5.0: + resolution: {tarball: https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz?rbtorrent=} + + prosemirror-inputrules@1.5.1: + resolution: {tarball: https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz?rbtorrent=} + + prosemirror-keymap@1.2.3: + resolution: {tarball: https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz?rbtorrent=} + + prosemirror-model@1.25.4: + resolution: {tarball: https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz?rbtorrent=} + + prosemirror-schema-basic@1.2.4: + resolution: {tarball: https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz?rbtorrent=} + + prosemirror-schema-list@1.5.1: + resolution: {tarball: https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz?rbtorrent=} + + prosemirror-state@1.4.4: + resolution: {tarball: https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz?rbtorrent=} + + prosemirror-test-builder@1.1.1: + resolution: {tarball: https://registry.npmjs.org/prosemirror-test-builder/-/prosemirror-test-builder-1.1.1.tgz?rbtorrent=c77aeaf71b3a9dfe4b171406dbbf9e30ea051df0} + + prosemirror-transform@1.11.0: + resolution: {tarball: https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz?rbtorrent=} + + prosemirror-utils@1.2.2: + resolution: {tarball: https://registry.npmjs.org/prosemirror-utils/-/prosemirror-utils-1.2.2.tgz?rbtorrent=} + peerDependencies: + prosemirror-model: ^1.19.2 + prosemirror-state: ^1.4.3 + + prosemirror-view@1.41.6: + resolution: {tarball: https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz?rbtorrent=} + + protobufjs@7.4.0: + resolution: {tarball: https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz?rbtorrent=} + engines: {node: '>=12.0.0'} + + protobufjs@7.5.4: + resolution: {tarball: https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz?rbtorrent=} + engines: {node: '>=12.0.0'} + + proxy-addr@2.0.7: + resolution: {tarball: https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz?rbtorrent=6c64bc6ff1aff5df16de0f1fbfdb7018fd308903} + engines: {node: '>= 0.10'} + + proxy-from-env@1.1.0: + resolution: {tarball: https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz?rbtorrent=4d1a9bb3c45bc43fc442ac5c9b5732c44f5a185b} + + psl@1.8.0: + resolution: {tarball: https://registry.npmjs.org/psl/-/psl-1.8.0.tgz?rbtorrent=30f88d87125c60a31b8a8c36d31168694c805383} + + pstree.remy@1.1.8: + resolution: {tarball: https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz?rbtorrent=dd61d0ea4afb615d2f802e16e57a4dd5c37c3fbb} + + pump@3.0.0: + resolution: {tarball: https://registry.npmjs.org/pump/-/pump-3.0.0.tgz?rbtorrent=9ecc0c9b98f93709c4ce54ea1dd4b6ff8536e437} + + punycode@1.4.1: + resolution: {tarball: https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz?rbtorrent=dba7ea53406156a3cf3d957730836dcc75529b43} + + punycode@2.1.1: + resolution: {tarball: https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz?rbtorrent=282ababa2206a1eea92dc3e32923af190bd1e5df} + engines: {node: '>=6'} + + qs@6.13.0: + resolution: {tarball: https://registry.npmjs.org/qs/-/qs-6.13.0.tgz?rbtorrent=} + engines: {node: '>=0.6'} + + qs@6.14.1: + resolution: {tarball: https://registry.npmjs.org/qs/-/qs-6.14.1.tgz?rbtorrent=} + engines: {node: '>=0.6'} + + qs@6.5.3: + resolution: {tarball: https://registry.npmjs.org/qs/-/qs-6.5.3.tgz?rbtorrent=6848026d4ef428188371527025c99536ce16951e} + engines: {node: '>=0.6'} + + querystring@0.2.1: + resolution: {tarball: https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz?rbtorrent=a1ac189d11e53f56fa1bf712e9631bac3ef6a4f4} + engines: {node: '>=0.4.x'} + deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. + + querystringify@2.2.0: + resolution: {tarball: https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz?rbtorrent=d6c9d24c3450290a79a9c9dd9385e9b036c4ccb6} + + queue-microtask@1.2.3: + resolution: {tarball: https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz?rbtorrent=e2336660d43082c0619119f2673f6d38ab6c69db} + + quick-format-unescaped@4.0.4: + resolution: {tarball: https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz?rbtorrent=d5596a974bd100b84f5e0d7f42e2de9b5a462913} + + quick-lru@5.1.1: + resolution: {tarball: https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz?rbtorrent=3e1817d08f7f06276cc9e9421c9542dc86cb59c2} + engines: {node: '>=10'} + + quickjs-emscripten-core@0.31.0: + resolution: {tarball: https://registry.npmjs.org/quickjs-emscripten-core/-/quickjs-emscripten-core-0.31.0.tgz?rbtorrent=} + + quickjs-emscripten@0.31.0: + resolution: {tarball: https://registry.npmjs.org/quickjs-emscripten/-/quickjs-emscripten-0.31.0.tgz?rbtorrent=} + engines: {node: '>=16.0.0'} + + raf-schd@4.0.3: + resolution: {tarball: https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz?rbtorrent=cd6e9a35241cbd110cd993af2338a97c098e6edf} + + rambda@9.4.2: + resolution: {tarball: https://registry.npmjs.org/rambda/-/rambda-9.4.2.tgz?rbtorrent=} + + randombytes@2.1.0: + resolution: {tarball: https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz?rbtorrent=d5c90823cfc32efbe52a231105ba9eb2dbcbcc01} + + range-parser@1.2.1: + resolution: {tarball: https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz?rbtorrent=ff745a9bc988d28205171cc44081bb83f3efdbfc} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {tarball: https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz?rbtorrent=a27f995a386dc1abf4f665d3429c3213a9c1b69f} + engines: {node: '>= 0.8'} + + raw-body@2.5.3: + resolution: {tarball: https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz?rbtorrent=} + engines: {node: '>= 0.8'} + + rc-slider@11.1.8: + resolution: {tarball: https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.8.tgz?rbtorrent=} + engines: {node: '>=8.x'} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + rc-util@5.44.4: + resolution: {tarball: https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react-beautiful-dnd@13.1.1: + resolution: {tarball: https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz?rbtorrent=0b56bc46ddd5be46a68ae0a3af7df78ad8e14ff4} + deprecated: 'react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672' + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react-dnd-html5-backend@10.0.2: + resolution: {tarball: https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-10.0.2.tgz?rbtorrent=9260f8d28e3d0dcea9cc7b176305803cfdf83bdd} + + react-dnd@10.0.2: + resolution: {tarball: https://registry.npmjs.org/react-dnd/-/react-dnd-10.0.2.tgz?rbtorrent=aa3ac176388185eb4531b3a9acb821ab725fd877} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react-dom@17.0.2: + resolution: {tarball: https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz?rbtorrent=a6e4b934fa1b962b2692b981c1f2c97b37dfcbda} + peerDependencies: + react: ^17.0.2 + + react-draggable@4.4.6: + resolution: {tarball: https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz?rbtorrent=8d8eaa0597b816bdd39c2eaacebcced8e12a689d} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react-error-boundary@3.1.4: + resolution: {tarball: https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz?rbtorrent=f4449c4c871bd460d761ca6a0d52293887421715} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: ^17.0.2 + + react-grid-layout@1.5.1: + resolution: {tarball: https://registry.npmjs.org/react-grid-layout/-/react-grid-layout-1.5.1.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react-hotkeys-hook@4.5.0: + resolution: {tarball: https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react-inspector@5.1.1: + resolution: {tarball: https://registry.npmjs.org/react-inspector/-/react-inspector-5.1.1.tgz?rbtorrent=cf1412be238c0c730555d989ea5a2d9b0f3b5792} + peerDependencies: + react: ^17.0.2 + + react-intersection-observer@9.4.0: + resolution: {tarball: https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.4.0.tgz?rbtorrent=4ac987f24f20ee50c4456d41a5af0923a4269381} + peerDependencies: + react: ^17.0.2 + + react-is@16.13.1: + resolution: {tarball: https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz?rbtorrent=7f27902ce96c964630fc52cb9747f3f63152bed6} + + react-is@17.0.2: + resolution: {tarball: https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz?rbtorrent=fa8c94ed4c76c752295c87ac4a870b2f66e7b582} + + react-is@18.3.1: + resolution: {tarball: https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz?rbtorrent=} + + react-lifecycles-compat@3.0.4: + resolution: {tarball: https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz?rbtorrent=a875753276bdf0c46062bfe52f3676f5e3d88d2c} + + react-list@0.8.18: + resolution: {tarball: https://registry.npmjs.org/react-list/-/react-list-0.8.18.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + + react-monaco-editor@0.52.0: + resolution: {tarball: https://registry.npmjs.org/react-monaco-editor/-/react-monaco-editor-0.52.0.tgz?rbtorrent=09dfaa83ca706a284c982afc6521794fac4f51d2} + peerDependencies: + '@types/react': '>=16 <= 18' + monaco-editor: ^0.36.1 + react: ^17.0.2 + + react-property@2.0.2: + resolution: {tarball: https://registry.npmjs.org/react-property/-/react-property-2.0.2.tgz?rbtorrent=742408de1127af72cb7719e227d29d18ebd0e092} + + react-recompose@0.33.0: + resolution: {tarball: https://registry.npmjs.org/react-recompose/-/react-recompose-0.33.0.tgz?rbtorrent=17948aee45866f818a8176e723b2ff9e8ed022c2} + peerDependencies: + react: ^17.0.2 + + react-redux@7.2.4: + resolution: {tarball: https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz?rbtorrent=59f9bbaa208f752a66bab421a0dafa601b17145c} + peerDependencies: + react: ^17.0.2 + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + react-refresh@0.14.2: + resolution: {tarball: https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz?rbtorrent=} + engines: {node: '>=0.10.0'} + + react-resizable@3.0.5: + resolution: {tarball: https://registry.npmjs.org/react-resizable/-/react-resizable-3.0.5.tgz?rbtorrent=9edc25c34ff4878998baa40822d73fdf5de3bb82} + peerDependencies: + react: ^17.0.2 + + react-router-dom@5.3.4: + resolution: {tarball: https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz?rbtorrent=7886826ce8603771b49a729d4f383e1a83039375} + peerDependencies: + react: ^17.0.2 + + react-router@5.3.4: + resolution: {tarball: https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz?rbtorrent=4489fa6fc86c6ffc73a571012878a9870887371d} + peerDependencies: + react: ^17.0.2 + + react-split-pane@0.1.92: + resolution: {tarball: https://registry.npmjs.org/react-split-pane/-/react-split-pane-0.1.92.tgz?rbtorrent=1dc38a4a4a130e97b65ee67270049eaa3b34387c} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react-style-proptype@3.2.2: + resolution: {tarball: https://registry.npmjs.org/react-style-proptype/-/react-style-proptype-3.2.2.tgz?rbtorrent=d6acf590083cd53bd01e0e7d33a5a54dfbe38466} + + react-transition-group@4.4.5: + resolution: {tarball: https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz?rbtorrent=f1a107f33c2e9f1eda59678ac798ab9363046539} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react-universal-interface@0.6.2: + resolution: {tarball: https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz?rbtorrent=3e67079488b82625d8449800185742e94c6a7864} + peerDependencies: + react: ^17.0.2 + tslib: '*' + + react-use@17.6.0: + resolution: {tarball: https://registry.npmjs.org/react-use/-/react-use-17.6.0.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react-virtualized-auto-sizer@1.0.24: + resolution: {tarball: https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz?rbtorrent=} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react-waypoint@10.3.0: + resolution: {tarball: https://registry.npmjs.org/react-waypoint/-/react-waypoint-10.3.0.tgz?rbtorrent=9799fe377bdd05fc328989114971203853cda071} + peerDependencies: + react: ^17.0.2 + + react-window@1.8.11: + resolution: {tarball: https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz?rbtorrent=} + engines: {node: '>8.0.0'} + peerDependencies: + react: ^17.0.2 + react-dom: ^17.0.2 + + react@17.0.2: + resolution: {tarball: https://registry.npmjs.org/react/-/react-17.0.2.tgz?rbtorrent=158b7e468584a48c4b2c726d747dcab04655f07d} + engines: {node: '>=0.10.0'} + + read-pkg-up@8.0.0: + resolution: {tarball: https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-8.0.0.tgz?rbtorrent=3c14e80f9b15189c6689200c50ac6e4788e4938e} + engines: {node: '>=12'} + + read-pkg@6.0.0: + resolution: {tarball: https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz?rbtorrent=f49b5e560cba13ee13433923053e65fd11c1f62d} + engines: {node: '>=12'} + + readable-stream@2.3.8: + resolution: {tarball: https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz?rbtorrent=1b091699aae718c522b6851f8157091cd735489a} + + readable-stream@3.6.2: + resolution: {tarball: https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz?rbtorrent=9181707285298beb5c2ced8e7f05a49d13588ead} + engines: {node: '>= 6'} + + readable-stream@4.7.0: + resolution: {tarball: https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz?rbtorrent=} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdirp@3.6.0: + resolution: {tarball: https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz?rbtorrent=b49e317172353515926b8105bbbfb956ee102cb4} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {tarball: https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz?rbtorrent=} + engines: {node: '>= 14.18.0'} + + real-require@0.2.0: + resolution: {tarball: https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz?rbtorrent=b59f04f29bec0a2fb8306530f253cdc0b6b56bfa} + engines: {node: '>= 12.13.0'} + + redent@3.0.0: + resolution: {tarball: https://registry.npmjs.org/redent/-/redent-3.0.0.tgz?rbtorrent=11fc0d68b640df5b6d0b2812a540b5a0d07b03d1} + engines: {node: '>=8'} + + redent@4.0.0: + resolution: {tarball: https://registry.npmjs.org/redent/-/redent-4.0.0.tgz?rbtorrent=095c483c2deb7aeb86fc6e5e8b30ed608c37aebe} + engines: {node: '>=12'} + + redis-commands@1.7.0: + resolution: {tarball: https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz?rbtorrent=ce4826f0c288d5fa092e7b55e05ce0fb26750b7d} + + redis-errors@1.2.0: + resolution: {tarball: https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz?rbtorrent=c6ff3248b2860d0bfef243a1007c2311cbaec871} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {tarball: https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz?rbtorrent=983f7bfa36fceb94256b81b7ae289022fed42f85} + engines: {node: '>=4'} + + redux-devtools-extension@2.13.9: + resolution: {tarball: https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz?rbtorrent=8de007c25d324fea6ef307f1e5a2091999e37a77} + deprecated: Package moved to @redux-devtools/extension. + peerDependencies: + redux: ^3.1.0 || ^4.0.0 + + redux-logger@3.0.6: + resolution: {tarball: https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz?rbtorrent=ac3dd7687ac4fd4a9224513ccbe30f1e12a8528e} + + redux-thunk@2.4.2: + resolution: {tarball: https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz?rbtorrent=fe7cd307022ccd98270348f73a9e6798287bbdb6} + peerDependencies: + redux: ^4 + + redux@4.2.1: + resolution: {tarball: https://registry.npmjs.org/redux/-/redux-4.2.1.tgz?rbtorrent=fe616bf196ac69fa65543a8675959c346a980762} + + reflect.getprototypeof@1.0.10: + resolution: {tarball: https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + reflect.getprototypeof@1.0.6: + resolution: {tarball: https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + regenerate-unicode-properties@10.2.0: + resolution: {tarball: https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz?rbtorrent=} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {tarball: https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz?rbtorrent=855b44318d2897dcac53ea54ef4d2d63504b5b67} + + regenerator-runtime@0.14.0: + resolution: {tarball: https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz?rbtorrent=ff861eac64281b34aca0a4c09f01f778cddf3e9d} + + regenerator-transform@0.15.2: + resolution: {tarball: https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz?rbtorrent=14d0f065655dda673fc2249bf2d0362bc402751f} + + regex-parser@2.2.11: + resolution: {tarball: https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz?rbtorrent=05b760e8797755b1a3a8ec8852a81b97a1befe18} + + regexp-tree@0.1.27: + resolution: {tarball: https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz?rbtorrent=6ed6242c6919073f97b52c72f122184c1e4acbbc} + hasBin: true + + regexp.prototype.flags@1.5.2: + resolution: {tarball: https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {tarball: https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + regexpu-core@6.1.1: + resolution: {tarball: https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz?rbtorrent=} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {tarball: https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz?rbtorrent=b4f590897d709e8d58d54afc24c167f2a23978d8} + + regjsparser@0.11.2: + resolution: {tarball: https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.2.tgz?rbtorrent=} + hasBin: true + + request-ip@3.3.0: + resolution: {tarball: https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz?rbtorrent=56803868f1b39cf94ad038cc23e9415c85016531} + + request-promise-core@1.1.4: + resolution: {tarball: https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz?rbtorrent=2b84cf9bf30911bd1e69e1960e16f2a61562f945} + engines: {node: '>=0.10.0'} + peerDependencies: + request: ^2.34 + + request-promise-native@1.0.9: + resolution: {tarball: https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz?rbtorrent=9dfd062040c66af7266551e67d873fa92c59af4a} + engines: {node: '>=0.12.0'} + deprecated: request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142 + peerDependencies: + request: ^2.34 + + request@2.88.2: + resolution: {tarball: https://registry.npmjs.org/request/-/request-2.88.2.tgz?rbtorrent=db7c0415806910392cc086d55de0999b9ca3b60b} + engines: {node: '>= 6'} + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + + require-directory@2.1.1: + resolution: {tarball: https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz?rbtorrent=6f3a1c36c5c614529836495759b539128977fc9d} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {tarball: https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz?rbtorrent=bd3be7041da81539f74077cdbcc22bc58128b8d3} + engines: {node: '>=0.10.0'} + + require-in-the-middle@7.5.2: + resolution: {tarball: https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz?rbtorrent=} + engines: {node: '>=8.6.0'} + + requires-port@1.0.0: + resolution: {tarball: https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz?rbtorrent=32b2f205b3d00932e0055f7a3ea7d186b3277264} + + reselect@4.1.8: + resolution: {tarball: https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz?rbtorrent=4bd8d66c7c87d3281380e597352f3d70f5b35257} + + resize-observer-polyfill@1.5.1: + resolution: {tarball: https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz?rbtorrent=31437e8434eaf095f28be85dffb7ac1c2750d901} + + resolve-alpn@1.2.1: + resolution: {tarball: https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz?rbtorrent=baca2cd2ed4b3b29ad5e143219ccfe0a5bb6b2ab} + + resolve-cwd@3.0.0: + resolution: {tarball: https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz?rbtorrent=c94ee5e2e5975ff106dc3d2006594c62b6be6996} + engines: {node: '>=8'} + + resolve-dir@1.0.1: + resolution: {tarball: https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz?rbtorrent=62c8869dcb1fa68cc2d4d911efbf41498aaccb89} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {tarball: https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz?rbtorrent=508d1b2a729a931bbc9b71f267dee6be51ab76db} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {tarball: https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz?rbtorrent=f1ec68b5808ce84bee69c5e261de53c0ae9f982e} + engines: {node: '>=8'} + + resolve-pathname@3.0.0: + resolution: {tarball: https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz?rbtorrent=3d5956294a171511f0dec35550728fc49d04c649} + + resolve-pkg-maps@1.0.0: + resolution: {tarball: https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz?rbtorrent=886c8c992d54b8a805ed8e507473aac2b7ef6487} + + resolve-url-loader@5.0.0: + resolution: {tarball: https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz?rbtorrent=de8df0bc22fb0eaa78d67530f986cb47a20d2546} + engines: {node: '>=12'} + + resolve.exports@1.1.0: + resolution: {tarball: https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz?rbtorrent=94f8bc358816133736c6f5cd56306276a409ef94} + engines: {node: '>=10'} + + resolve.exports@2.0.0: + resolution: {tarball: https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.0.tgz?rbtorrent=5bdaedd503a0dc6f6aacd5b23cdd54fac8917104} + engines: {node: '>=10'} + + resolve@1.22.10: + resolution: {tarball: https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@1.22.8: + resolution: {tarball: https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz?rbtorrent=9f1b757bfc08751220eb63125593165cbe0a8901} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {tarball: https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz?rbtorrent=548e58231d9735f5f1883a792b4fcbf896103528} + hasBin: true + + responselike@3.0.0: + resolution: {tarball: https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz?rbtorrent=35a03f5dcdb553ce246295269addeca90b5fa617} + engines: {node: '>=14.16'} + + restore-cursor@3.1.0: + resolution: {tarball: https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz?rbtorrent=278f744a4b44b390f8b6bbf70f36ed8d15fe5356} + engines: {node: '>=8'} + + retry@0.13.1: + resolution: {tarball: https://registry.npmjs.org/retry/-/retry-0.13.1.tgz?rbtorrent=e6fd0695699bed4f65dc7b700bc311f7ecd6c79f} + engines: {node: '>= 4'} + + reusify@1.0.4: + resolution: {tarball: https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz?rbtorrent=b54638c7c9e1bc2aa81616beeb1ec10051fadbd0} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.3.0: + resolution: {tarball: https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz?rbtorrent=cdcbe825fef2cd4398a92d4adedaff9fc75629d2} + + rfdc@1.4.1: + resolution: {tarball: https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz?rbtorrent=} + + rimraf@2.7.1: + resolution: {tarball: https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz?rbtorrent=d8445c1dcc1f03c5edb6ec1ff20f49a5f4b92c61} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@3.0.2: + resolution: {tarball: https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz?rbtorrent=e56d76da53e035fd23b4819f6bdcbe4abc4e2961} + hasBin: true + + rimraf@5.0.10: + resolution: {tarball: https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz?rbtorrent=} + hasBin: true + + robust-orientation@1.2.1: + resolution: {tarball: https://registry.npmjs.org/robust-orientation/-/robust-orientation-1.2.1.tgz?rbtorrent=d92446898d0e2b1bdeb2ab250580aa4b491dc7d4} + + robust-point-in-polygon@1.0.3: + resolution: {tarball: https://registry.npmjs.org/robust-point-in-polygon/-/robust-point-in-polygon-1.0.3.tgz?rbtorrent=7b9b7069d7bfe3fa443d882ed626fafdcddc32a2} + + robust-predicates@3.0.2: + resolution: {tarball: https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz?rbtorrent=52920e42ef7d77983bb10a2d3476006047841512} + + robust-scale@1.0.2: + resolution: {tarball: https://registry.npmjs.org/robust-scale/-/robust-scale-1.0.2.tgz?rbtorrent=cc0b82f57bf981bab716de39fd3a2940e4a5e3bc} + + robust-subtract@1.0.0: + resolution: {tarball: https://registry.npmjs.org/robust-subtract/-/robust-subtract-1.0.0.tgz?rbtorrent=5fcadb28528a283898da612052516d8f500657c8} + + robust-sum@1.0.0: + resolution: {tarball: https://registry.npmjs.org/robust-sum/-/robust-sum-1.0.0.tgz?rbtorrent=6bf46d1fa87ce5d4dec38543c5aee2458106d4ca} + + rope-sequence@1.3.4: + resolution: {tarball: https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz?rbtorrent=c85972189ea6252dbd6f81ca6e21499171c50071} + + roughjs@4.6.6: + resolution: {tarball: https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz?rbtorrent=} + + rslog@1.2.9: + resolution: {tarball: https://registry.npmjs.org/rslog/-/rslog-1.2.9.tgz?rbtorrent=} + + rspack-manifest-plugin@5.0.3: + resolution: {tarball: https://registry.npmjs.org/rspack-manifest-plugin/-/rspack-manifest-plugin-5.0.3.tgz?rbtorrent=} + engines: {node: '>=14'} + peerDependencies: + '@rspack/core': 0.x || 1.x + peerDependenciesMeta: + '@rspack/core': + optional: true + + rtl-css-js@1.16.1: + resolution: {tarball: https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz?rbtorrent=cac42ace2c14c24f2e21c24a01588de1e205bff4} + + run-applescript@7.0.0: + resolution: {tarball: https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + run-parallel@1.2.0: + resolution: {tarball: https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz?rbtorrent=18038ed05e6416ebf3cd579ce1474ae24770286f} + + rw@1.3.3: + resolution: {tarball: https://registry.npmjs.org/rw/-/rw-1.3.3.tgz?rbtorrent=fbc7628ab00a4fb98db80ba84dd07ded4fe9fc40} + + rxjs@7.8.1: + resolution: {tarball: https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz?rbtorrent=a5d8f45e5594a9c66beda2ac611813f96748dbcd} + + safe-array-concat@1.1.2: + resolution: {tarball: https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz?rbtorrent=} + engines: {node: '>=0.4'} + + safe-array-concat@1.1.3: + resolution: {tarball: https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz?rbtorrent=} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {tarball: https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz?rbtorrent=919e175e4a456facb2f9db2e63065c31db36ac10} + + safe-buffer@5.2.1: + resolution: {tarball: https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz?rbtorrent=e3149b0255bb4dc4e498c9b879904dfcfa607eba} + + safe-push-apply@1.0.0: + resolution: {tarball: https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + safe-regex-test@1.0.3: + resolution: {tarball: https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {tarball: https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + safe-regex@2.1.1: + resolution: {tarball: https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz?rbtorrent=78b6f5b001747a8530cd927cddf24e5f22880cb0} + + safe-stable-stringify@2.5.0: + resolution: {tarball: https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz?rbtorrent=} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {tarball: https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz?rbtorrent=8450e1c808c8ca9ac90ac8d77f2637cca2522d93} + + sanitize-html@2.14.0: + resolution: {tarball: https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.14.0.tgz?rbtorrent=} + + sass-loader@16.0.3: + resolution: {tarball: https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.3.tgz?rbtorrent=} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + sass: ^1.3.0 + sass-embedded: '*' + webpack: ^5.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + node-sass: + optional: true + sass: + optional: true + sass-embedded: + optional: true + webpack: + optional: true + + sass@1.80.6: + resolution: {tarball: https://registry.npmjs.org/sass/-/sass-1.80.6.tgz?rbtorrent=} + engines: {node: '>=14.0.0'} + hasBin: true + + saxes@6.0.0: + resolution: {tarball: https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz?rbtorrent=3c7793da432ac1e3e185e3354fe6bd702f2261fd} + engines: {node: '>=v12.22.7'} + + scheduler@0.20.2: + resolution: {tarball: https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz?rbtorrent=7e10a58b752dfe0f5268c377e3930e4c02a3d540} + + schema-utils@3.3.0: + resolution: {tarball: https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz?rbtorrent=74e1468ef706fdb667f63f73a4efd3492c89c46c} + engines: {node: '>= 10.13.0'} + + schema-utils@4.3.3: + resolution: {tarball: https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz?rbtorrent=} + engines: {node: '>= 10.13.0'} + + screenfull@5.2.0: + resolution: {tarball: https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz?rbtorrent=22d6d53818368db0ccf747cd033320720504a19b} + engines: {node: '>=0.10.0'} + + secure-json-parse@2.7.0: + resolution: {tarball: https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz?rbtorrent=c8172067bd492855d006a697e04b0e0eb82437ac} + + seek-bzip@2.0.0: + resolution: {tarball: https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz?rbtorrent=f1633fca89759c7332d7ebb304490b8effee8642} + hasBin: true + + select-hose@2.0.0: + resolution: {tarball: https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz?rbtorrent=6b98a2ac9e5c0df2eafddee7f279ee73889c6676} + + selfsigned@2.4.1: + resolution: {tarball: https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz?rbtorrent=93085690a0928312c862ff40830074d49f73790f} + engines: {node: '>=10'} + + semver-regex@4.0.5: + resolution: {tarball: https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz?rbtorrent=3026f8260c7fc1afc361f7029b54146adb2e317a} + engines: {node: '>=12'} + + semver-truncate@3.0.0: + resolution: {tarball: https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz?rbtorrent=1cdeb13f0a13d3b6c08778b68ad19cda09a4c5d6} + engines: {node: '>=12'} + + semver@6.3.1: + resolution: {tarball: https://registry.npmjs.org/semver/-/semver-6.3.1.tgz?rbtorrent=e1dd2293882096d15aa6668155640147bb44f66c} + hasBin: true + + semver@7.6.3: + resolution: {tarball: https://registry.npmjs.org/semver/-/semver-7.6.3.tgz?rbtorrent=} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.2: + resolution: {tarball: https://registry.npmjs.org/semver/-/semver-7.7.2.tgz?rbtorrent=} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.3: + resolution: {tarball: https://registry.npmjs.org/semver/-/semver-7.7.3.tgz?rbtorrent=} + engines: {node: '>=10'} + hasBin: true + + send@0.19.0: + resolution: {tarball: https://registry.npmjs.org/send/-/send-0.19.0.tgz?rbtorrent=} + engines: {node: '>= 0.8.0'} + + serialize-javascript@6.0.2: + resolution: {tarball: https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz?rbtorrent=} + + serve-index@1.9.1: + resolution: {tarball: https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz?rbtorrent=1cb2639b67433247b70342f24d2385b7db25ceed} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.2: + resolution: {tarball: https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz?rbtorrent=} + engines: {node: '>= 0.8.0'} + + set-cookie-parser@2.7.1: + resolution: {tarball: https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz?rbtorrent=} + + set-function-length@1.2.2: + resolution: {tarball: https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {tarball: https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + set-harmonic-interval@1.0.1: + resolution: {tarball: https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz?rbtorrent=ea930686fd6ae24aab7d97ed635b232ffc09c676} + engines: {node: '>=6.9'} + + set-proto@1.0.0: + resolution: {tarball: https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + setprototypeof@1.1.0: + resolution: {tarball: https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz?rbtorrent=dbd4ae93af6ced9df840d7f68b6bf5b1f22d2769} + + setprototypeof@1.2.0: + resolution: {tarball: https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz?rbtorrent=639214bef3d5c2047ec9150a2c6982dc65fef215} + + shebang-command@2.0.0: + resolution: {tarball: https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz?rbtorrent=7fd1c3f62049005cb9c78120f598cfb22b2fbac9} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {tarball: https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz?rbtorrent=c66300c869b82aa1debe05fb862438b61e6fd671} + engines: {node: '>=8'} + + shell-quote@1.8.1: + resolution: {tarball: https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz?rbtorrent=e735d0c7a38ef10ce57cf5e4e9c8bbfec19eb646} + + shimmer@1.2.1: + resolution: {tarball: https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz?rbtorrent=7437d15cb2fd1a5887fc6181427cc244f38e393b} + + side-channel-list@1.0.0: + resolution: {tarball: https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {tarball: https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {tarball: https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + side-channel@1.0.6: + resolution: {tarball: https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {tarball: https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {tarball: https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz?rbtorrent=6e32ca0e3ce38fb26c81d20fb2cd6ebbf58f436b} + + signal-exit@4.1.0: + resolution: {tarball: https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz?rbtorrent=87147f490e7277207efc3cf48a3eef5537521e86} + engines: {node: '>=14'} + + simple-update-notifier@2.0.0: + resolution: {tarball: https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz?rbtorrent=ab759eaace217fbc039595cfe5b45e67d0e70e06} + engines: {node: '>=10'} + + sirv@2.0.4: + resolution: {tarball: https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz?rbtorrent=} + engines: {node: '>= 10'} + + sisteransi@1.0.5: + resolution: {tarball: https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz?rbtorrent=e8412209181c68a227938bd45102d2fbfc80cf92} + + slash@3.0.0: + resolution: {tarball: https://registry.npmjs.org/slash/-/slash-3.0.0.tgz?rbtorrent=19a89c4aba1b97e1acaf017bc38adc140804b70e} + engines: {node: '>=8'} + + slice-ansi@3.0.0: + resolution: {tarball: https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz?rbtorrent=f7a4ce82764298470568468eccad4d5dd4c0528e} + engines: {node: '>=8'} + + slice-ansi@4.0.0: + resolution: {tarball: https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz?rbtorrent=ddf6f73e618ff116841f6fcfacf80645009f6e39} + engines: {node: '>=10'} + + slice-ansi@5.0.0: + resolution: {tarball: https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz?rbtorrent=03581525a96e393e0349e9980cd32221a7a4a85c} + engines: {node: '>=12'} + + slugify@1.6.6: + resolution: {tarball: https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz?rbtorrent=0a3c78b16a322191f34d9fe16791ef4757435b37} + engines: {node: '>=8.0.0'} + + snake-case@3.0.4: + resolution: {tarball: https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz?rbtorrent=e0b54f8ae57af7591b72e935c5ba1740a81cdacf} + + socket.io-adapter@2.5.5: + resolution: {tarball: https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz?rbtorrent=} + + socket.io-parser@4.2.4: + resolution: {tarball: https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz?rbtorrent=c94c3fef29d3a840f468d05a8c245934cf0352d3} + engines: {node: '>=10.0.0'} + + socket.io@4.8.1: + resolution: {tarball: https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz?rbtorrent=} + engines: {node: '>=10.2.0'} + + sockjs@0.3.24: + resolution: {tarball: https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz?rbtorrent=75bb49becb148f07698479ca6b544f80ebae26bd} + + sonic-boom@3.8.1: + resolution: {tarball: https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz?rbtorrent=} + + sonic-boom@4.2.0: + resolution: {tarball: https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz?rbtorrent=} + + sort-keys-length@1.0.1: + resolution: {tarball: https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz?rbtorrent=5f198835ab04990a953f31c99f45fb8cb562f276} + engines: {node: '>=0.10.0'} + + sort-keys@1.1.2: + resolution: {tarball: https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz?rbtorrent=d12206454c2d7e621a62aa49637e7899f76a6b47} + engines: {node: '>=0.10.0'} + + sorted-array-functions@1.3.0: + resolution: {tarball: https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz?rbtorrent=c6a8fdb250e1e014812f8b82674b723ab0181981} + + source-list-map@2.0.1: + resolution: {tarball: https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz?rbtorrent=a2e5e8af13a2323cce43ea744f232a27179e9183} + + source-map-js@1.2.1: + resolution: {tarball: https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz?rbtorrent=} + engines: {node: '>=0.10.0'} + + source-map-loader@5.0.0: + resolution: {tarball: https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz?rbtorrent=} + engines: {node: '>= 18.12.0'} + peerDependencies: + webpack: ^5.72.1 + + source-map-resolve@0.6.0: + resolution: {tarball: https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz?rbtorrent=99be52a440f2bc8d536d4e54d8869e90a1d04cbf} + deprecated: See https://github.com/lydell/source-map-resolve#deprecated + + source-map-support@0.5.13: + resolution: {tarball: https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz?rbtorrent=d45b4db330b9bb8bd8932e4dd8be9c83bde891ed} + + source-map-support@0.5.21: + resolution: {tarball: https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz?rbtorrent=95b3dfbd2edf8620842642dca7d91dc4b08ea5d2} + + source-map@0.5.6: + resolution: {tarball: https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz?rbtorrent=565e32eebbc0d1892e5f68f4ac2f4ec1badd0d12} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {tarball: https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz?rbtorrent=f8ee5e6ee3b6e7c3ad66deeedb1d015a07ddfc05} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {tarball: https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz?rbtorrent=40c192ddec2b183d8abcc2d51ecbad269957c22a} + engines: {node: '>= 8'} + + source-map@0.7.6: + resolution: {tarball: https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz?rbtorrent=} + engines: {node: '>= 12'} + + spdx-correct@3.2.0: + resolution: {tarball: https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz?rbtorrent=9b08678a39937f6092074f6b06e030e6687f6a5e} + + spdx-exceptions@2.5.0: + resolution: {tarball: https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz?rbtorrent=} + + spdx-expression-parse@3.0.1: + resolution: {tarball: https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz?rbtorrent=1711792c5b77c0039e24d4acc2fe0d8f3e55dcfa} + + spdx-license-ids@3.0.17: + resolution: {tarball: https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz?rbtorrent=} + + spdy-transport@3.0.0: + resolution: {tarball: https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz?rbtorrent=480bb68ee3554776f57689bdd5ef1cd9a75186fd} + + spdy@4.0.2: + resolution: {tarball: https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz?rbtorrent=2c4be20bafb99e7cbaf7b3e5ca9e7676f1338f10} + engines: {node: '>=6.0.0'} + + split2@4.2.0: + resolution: {tarball: https://registry.npmjs.org/split2/-/split2-4.2.0.tgz?rbtorrent=f782aad8a57f026d521d5335eb8bc87fa563f0cf} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {tarball: https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz?rbtorrent=527efaea75268ac7ced350806fa0f76a6c7893eb} + + sshpk@1.18.0: + resolution: {tarball: https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz?rbtorrent=cae5ff17e422e33d10ebccc3046481dcc4929212} + engines: {node: '>=0.10.0'} + hasBin: true + + stable@0.1.8: + resolution: {tarball: https://registry.npmjs.org/stable/-/stable-0.1.8.tgz?rbtorrent=35ff2cf103933e8a1f7bdf1a889a0c3064964177} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + + stack-generator@2.0.10: + resolution: {tarball: https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz?rbtorrent=6c7743182220a97a4a66fe612c953ac2712dadfc} + + stack-utils@2.0.6: + resolution: {tarball: https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz?rbtorrent=24c4f3c33471863c0c38e83ce6877d5330796b3b} + engines: {node: '>=10'} + + stackframe@1.3.4: + resolution: {tarball: https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz?rbtorrent=4cd1c71e8dfb4255ce289e7593954945e5a06185} + + stacktrace-gps@3.1.2: + resolution: {tarball: https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz?rbtorrent=ac26070164a082ee6d44915114c657329f993d57} + + stacktrace-js@2.0.2: + resolution: {tarball: https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz?rbtorrent=3117e1cff02679b3fd3d27f5ddc053f2e1c3adf9} + + standard-as-callback@2.1.0: + resolution: {tarball: https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz?rbtorrent=81387777c6123b0911977e4e3d3120c878d1fa34} + + statuses@1.5.0: + resolution: {tarball: https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz?rbtorrent=82f0103f4c60044da7aacaa5cae250093516482f} + engines: {node: '>= 0.6'} + + statuses@2.0.1: + resolution: {tarball: https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz?rbtorrent=ece2fe4b2f6c9cb99b2c75dc3491ad47d6ed6fa2} + engines: {node: '>= 0.8'} + + statuses@2.0.2: + resolution: {tarball: https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.8'} + + stealthy-require@1.1.1: + resolution: {tarball: https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz?rbtorrent=92d267533465e58c06a3cbece4bda8875282ec71} + engines: {node: '>=0.10.0'} + + streamroller@3.1.5: + resolution: {tarball: https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz?rbtorrent=20ee547b327fabfcaff7ddcef449057d9089f706} + engines: {node: '>=8.0'} + + streamx@2.23.0: + resolution: {tarball: https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz?rbtorrent=} + + string-argv@0.3.1: + resolution: {tarball: https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz?rbtorrent=8b5b3fd95a8499cb70f6cc77aad1d608039879c0} + engines: {node: '>=0.6.19'} + + string-length@4.0.2: + resolution: {tarball: https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz?rbtorrent=a3c8c266aa0c1c61d368f512ecdd3a98e465e6ab} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {tarball: https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz?rbtorrent=f8412787e89a7bf0fb94ec78f0b1f13d94822a61} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {tarball: https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz?rbtorrent=52857c0825e5c414bc3be3d1b493a5a8e9fab9ad} + engines: {node: '>=12'} + + string.prototype.matchall@4.0.11: + resolution: {tarball: https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.10: + resolution: {tarball: https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + string.prototype.trim@1.2.9: + resolution: {tarball: https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {tarball: https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz?rbtorrent=} + + string.prototype.trimend@1.0.9: + resolution: {tarball: https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {tarball: https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + string_decoder@1.1.1: + resolution: {tarball: https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz?rbtorrent=7ee5be81f884cbabbca9bd38518df624e2e1fe66} + + string_decoder@1.3.0: + resolution: {tarball: https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz?rbtorrent=6fcc818c75109a0c34b6a6b2a760bd239d9462b9} + + strip-ansi@6.0.1: + resolution: {tarball: https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz?rbtorrent=e155382c29545da38fa473395e43b5f24dc0c529} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {tarball: https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz?rbtorrent=9c525a1f4c86e091faa86608c0d2bf4eb46e455d} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {tarball: https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz?rbtorrent=27f22454369e62f06ccc71c24666cb56502ac3f5} + engines: {node: '>=4'} + + strip-bom@4.0.0: + resolution: {tarball: https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz?rbtorrent=3b0f11e80a63742e59a7c132ab46002725ed768d} + engines: {node: '>=8'} + + strip-dirs@3.0.0: + resolution: {tarball: https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz?rbtorrent=898872828606b4dac058b460c75c7f2d0e9d16a1} + + strip-final-newline@2.0.0: + resolution: {tarball: https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz?rbtorrent=08bf552f9f4e13f910ad9a29e236ca5bf30a33a8} + engines: {node: '>=6'} + + strip-final-newline@3.0.0: + resolution: {tarball: https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz?rbtorrent=85c182888c3d8e25167101d9aa41a61648a09cc9} + engines: {node: '>=12'} + + strip-indent@3.0.0: + resolution: {tarball: https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz?rbtorrent=4894735b4d29511e9563e538a6cf8812b75861dd} + engines: {node: '>=8'} + + strip-indent@4.0.0: + resolution: {tarball: https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz?rbtorrent=bd13caa261c9bec254e67038b959b15224ab039a} + engines: {node: '>=12'} + + strip-json-comments@3.1.1: + resolution: {tarball: https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz?rbtorrent=7a5e649515883746bc06f859b6bf0d1d278a408c} + engines: {node: '>=8'} + + strnum@1.0.5: + resolution: {tarball: https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz?rbtorrent=164278276b72ab3cbf191f5b0ac745d584fe6edf} + + strtok3@10.3.4: + resolution: {tarball: https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz?rbtorrent=} + engines: {node: '>=18'} + + strtok3@9.1.1: + resolution: {tarball: https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz?rbtorrent=} + engines: {node: '>=16'} + + style-loader@4.0.0: + resolution: {tarball: https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz?rbtorrent=} + engines: {node: '>= 18.12.0'} + peerDependencies: + webpack: ^5.27.0 + + style-mod@4.1.3: + resolution: {tarball: https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz?rbtorrent=} + + style-search@0.1.0: + resolution: {tarball: https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz?rbtorrent=d4f93b95a3f6756ccda7dc9dc9620419077893db} + + style-to-js@1.1.21: + resolution: {tarball: https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz?rbtorrent=} + + style-to-object@1.0.14: + resolution: {tarball: https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz?rbtorrent=} + + stylehacks@7.0.4: + resolution: {tarball: https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.4.tgz?rbtorrent=} + engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} + peerDependencies: + postcss: ^8.4.31 + + stylelint-order@6.0.4: + resolution: {tarball: https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.4.tgz?rbtorrent=} + peerDependencies: + stylelint: ^14.0.0 || ^15.0.0 || ^16.0.1 + + stylelint-prettier@4.1.0: + resolution: {tarball: https://registry.npmjs.org/stylelint-prettier/-/stylelint-prettier-4.1.0.tgz?rbtorrent=} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + prettier: '>=3.0.0' + stylelint: '>=15.8.0' + + stylelint-scss@5.3.2: + resolution: {tarball: https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-5.3.2.tgz?rbtorrent=} + peerDependencies: + stylelint: ^14.5.1 || ^15.0.0 + + stylelint@15.11.0: + resolution: {tarball: https://registry.npmjs.org/stylelint/-/stylelint-15.11.0.tgz?rbtorrent=983b7a246ba1562fe50828f5dc7406e951bf21ce} + engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true + + stylis@4.3.6: + resolution: {tarball: https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz?rbtorrent=} + + supports-color@5.5.0: + resolution: {tarball: https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz?rbtorrent=c2fcb1f2f38bc39eff52ac9bdbb0db30f56ae250} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {tarball: https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz?rbtorrent=e7f638facdd8140ba916f4748a83127946d9ba10} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {tarball: https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz?rbtorrent=2c1530b152cc8ef76f1f2c4cfa333d9957b8b637} + engines: {node: '>=10'} + + supports-hyperlinks@3.0.0: + resolution: {tarball: https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz?rbtorrent=a2989de63bdf44fd925bf6061b2d442de6ad5ef4} + engines: {node: '>=14.18'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {tarball: https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz?rbtorrent=33b0c2b22bc3c432c80f1a3f2943fb535652fc40} + engines: {node: '>= 0.4'} + + svg-parser@2.0.4: + resolution: {tarball: https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz?rbtorrent=50dc13025a421fc0dedaa18bcf8f3cd9ce2c49bb} + + svg-tags@1.0.0: + resolution: {tarball: https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz?rbtorrent=f9b66ed3abec3ff8f5f2ec69b3fe0ddff7b6d45d} + + svgo@2.8.0: + resolution: {tarball: https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz?rbtorrent=19b45569dcbb4f07e6f55226ccaac27889ee8d47} + engines: {node: '>=10.13.0'} + hasBin: true + + svgo@3.3.2: + resolution: {tarball: https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz?rbtorrent=} + engines: {node: '>=14.0.0'} + hasBin: true + + swagger-ui-dist@5.29.0: + resolution: {tarball: https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.29.0.tgz?rbtorrent=} + + swagger-ui-express@5.0.1: + resolution: {tarball: https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz?rbtorrent=} + engines: {node: '>= v0.10.32'} + peerDependencies: + express: '>=4.0.0 || >=5.0.0-beta' + + swc-loader@0.2.6: + resolution: {tarball: https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.6.tgz?rbtorrent=} + peerDependencies: + '@swc/core': ^1.2.147 + webpack: '>=2' + + symbol-observable@4.0.0: + resolution: {tarball: https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz?rbtorrent=c9eb23fcdaa546af82f0bf59704a14ef04a33eb9} + engines: {node: '>=0.10'} + + symbol-tree@3.2.4: + resolution: {tarball: https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz?rbtorrent=d02118b5ec5d9adf2c13dcd366cfc089df280f73} + + synckit@0.8.8: + resolution: {tarball: https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz?rbtorrent=} + engines: {node: ^14.18.0 || >=16.0.0} + + tabbable@6.2.0: + resolution: {tarball: https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz?rbtorrent=f6d19db05aed3979aa5efb10d7121141674c94e4} + + table@6.8.2: + resolution: {tarball: https://registry.npmjs.org/table/-/table-6.8.2.tgz?rbtorrent=} + engines: {node: '>=10.0.0'} + + tapable@2.2.2: + resolution: {tarball: https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz?rbtorrent=} + engines: {node: '>=6'} + + tar-stream@3.1.7: + resolution: {tarball: https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz?rbtorrent=} + + terser-webpack-plugin@5.3.10: + resolution: {tarball: https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz?rbtorrent=} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.30.3: + resolution: {tarball: https://registry.npmjs.org/terser/-/terser-5.30.3.tgz?rbtorrent=} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {tarball: https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz?rbtorrent=66bc60e9a4516af5733231661ecd9712264a7229} + engines: {node: '>=8'} + + text-decoder@1.2.3: + resolution: {tarball: https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz?rbtorrent=} + + text-table@0.2.0: + resolution: {tarball: https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz?rbtorrent=9494ae77197443588f7082168b3dc30c27236a28} + + thingies@1.21.0: + resolution: {tarball: https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz?rbtorrent=} + engines: {node: '>=10.18'} + peerDependencies: + tslib: ^2 + + thread-stream@2.7.0: + resolution: {tarball: https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz?rbtorrent=} + + throat@6.0.1: + resolution: {tarball: https://registry.npmjs.org/throat/-/throat-6.0.1.tgz?rbtorrent=21c3512a36b6e89bf7a548af64d358736997404e} + + throttle-debounce@3.0.1: + resolution: {tarball: https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz?rbtorrent=c2abb2283bc0ed3c0b87e245d8ffdf56db0e78d7} + engines: {node: '>=10'} + + through@2.3.8: + resolution: {tarball: https://registry.npmjs.org/through/-/through-2.3.8.tgz?rbtorrent=89b09d636c563ec52d7febab3f13f97fcd652a83} + + thunky@1.1.0: + resolution: {tarball: https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz?rbtorrent=5717a5bffa58d9a34dc525fb9551ae1a1b29b646} + + timers-ext@0.1.7: + resolution: {tarball: https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz?rbtorrent=8e1c7cc1384bc6d41357775a9cff6b80a34c19c0} + + tiny-case@1.0.3: + resolution: {tarball: https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz?rbtorrent=b59f19f17aea5458496190d18bce537cf6ff6da3} + + tiny-invariant@1.1.0: + resolution: {tarball: https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz?rbtorrent=1707ac8aea1822eb19f68d7c0ee06a80c4b1b904} + + tiny-warning@1.0.3: + resolution: {tarball: https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz?rbtorrent=e25ed2f9169820e7b306ed696564d5108c9ef194} + + tinyexec@1.0.2: + resolution: {tarball: https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz?rbtorrent=} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {tarball: https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz?rbtorrent=} + engines: {node: '>=12.0.0'} + + tmpl@1.0.5: + resolution: {tarball: https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz?rbtorrent=6ff53e85506b2f27addd1ade49368a551e439fa8} + + to-regex-range@5.0.1: + resolution: {tarball: https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz?rbtorrent=9c494e247bd2815d7991a87d6c0fa999f42b3641} + engines: {node: '>=8.0'} + + toggle-selection@1.0.6: + resolution: {tarball: https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz?rbtorrent=0cf48ed1d03f0363328ee26def334215e270b7f4} + + toidentifier@1.0.1: + resolution: {tarball: https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz?rbtorrent=9f32d166d5476ef5bc672790eadb9a23aad1dbf9} + engines: {node: '>=0.6'} + + token-types@6.1.1: + resolution: {tarball: https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz?rbtorrent=} + engines: {node: '>=14.16'} + + toposort@2.0.2: + resolution: {tarball: https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz?rbtorrent=4db506de73f3ffde4b0ed1e698b16398e7c21b88} + + totalist@3.0.1: + resolution: {tarball: https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz?rbtorrent=5b69f9c1c9accd0f2513aa7d508d80cf1196ed5e} + engines: {node: '>=6'} + + touch@3.1.1: + resolution: {tarball: https://registry.npmjs.org/touch/-/touch-3.1.1.tgz?rbtorrent=} + hasBin: true + + tough-cookie@2.5.0: + resolution: {tarball: https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz?rbtorrent=259bedf0c1feb2960163311e2065fcd80b23ec39} + engines: {node: '>=0.8'} + + tough-cookie@4.1.3: + resolution: {tarball: https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz?rbtorrent=2ded60639da6af06ce52949b4493eef162580516} + engines: {node: '>=6'} + + tr46@3.0.0: + resolution: {tarball: https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz?rbtorrent=730fc64114aa3f8db1edaf41aa426c4de82f4322} + engines: {node: '>=12'} + + tree-dump@1.0.2: + resolution: {tarball: https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz?rbtorrent=} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + trim-newlines@4.1.1: + resolution: {tarball: https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz?rbtorrent=c57feb223bc7f0afbda3150ae8847b171a7ed812} + engines: {node: '>=12'} + + ts-api-utils@1.3.0: + resolution: {tarball: https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz?rbtorrent=} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-checker-rspack-plugin@1.1.1: + resolution: {tarball: https://registry.npmjs.org/ts-checker-rspack-plugin/-/ts-checker-rspack-plugin-1.1.1.tgz?rbtorrent=} + engines: {node: '>=16.0.0'} + peerDependencies: + '@rspack/core': ^1.0.0 + typescript: '>=3.8.0' + peerDependenciesMeta: + '@rspack/core': + optional: true + + ts-dedent@2.2.0: + resolution: {tarball: https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz?rbtorrent=07b9caa01b9a5fb3837cffb741f39288c3383d32} + engines: {node: '>=6.10'} + + ts-easing@0.2.0: + resolution: {tarball: https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz?rbtorrent=97a47ceceaecaa42d865662b83f27a58f7976ab9} + + ts-jest@29.0.5: + resolution: {tarball: https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz?rbtorrent=f96eec4c4f245a12337e115344af6be3a88b2383} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + + ts-node@10.9.2: + resolution: {tarball: https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz?rbtorrent=} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsconfig-paths@3.15.0: + resolution: {tarball: https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz?rbtorrent=} + + tsconfig-to-swcconfig@2.8.1: + resolution: {tarball: https://registry.npmjs.org/tsconfig-to-swcconfig/-/tsconfig-to-swcconfig-2.8.1.tgz?rbtorrent=} + hasBin: true + + tslib@1.14.1: + resolution: {tarball: https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz?rbtorrent=ee282d12ea3f6d275bd7888e15f8c6500d1a3f7e} + + tslib@2.8.1: + resolution: {tarball: https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz?rbtorrent=} + + tsscmp@1.0.6: + resolution: {tarball: https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz?rbtorrent=447e632cefb224bfd5620f012ac1ca0a9caf0498} + engines: {node: '>=0.6.x'} + + tunnel-agent@0.6.0: + resolution: {tarball: https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz?rbtorrent=e258d27a4a7d435c4471596db6f9409582f11344} + + tweetnacl@0.14.5: + resolution: {tarball: https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz?rbtorrent=a8f30bde68a1f21027bd8efedd4f1625138deabd} + + two-product@1.0.2: + resolution: {tarball: https://registry.npmjs.org/two-product/-/two-product-1.0.2.tgz?rbtorrent=4c5546e99f1a8708530d169afdf21924952b0d48} + + two-sum@1.0.0: + resolution: {tarball: https://registry.npmjs.org/two-sum/-/two-sum-1.0.0.tgz?rbtorrent=186c66025535422fe1a44f9edef93e57353beedc} + + type-check@0.4.0: + resolution: {tarball: https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz?rbtorrent=2c9ca61e1431737ce0cdb7ea5296ee7636773d89} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {tarball: https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz?rbtorrent=5c923467b9f02ae07f06abc9e151359d84688f67} + engines: {node: '>=4'} + + type-fest@0.11.0: + resolution: {tarball: https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz?rbtorrent=5481639ac4424b725f1fae292a4000ee2bd31aea} + engines: {node: '>=8'} + + type-fest@0.20.2: + resolution: {tarball: https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz?rbtorrent=9123d1887b948ec86727f205d765941d03eeee8b} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {tarball: https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz?rbtorrent=a1d8bbf027691739cbc062c702cd24f7fcbf38c4} + engines: {node: '>=10'} + + type-fest@1.4.0: + resolution: {tarball: https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz?rbtorrent=5716445f1ca32903832a08a7c3edf534f3bad028} + engines: {node: '>=10'} + + type-fest@2.19.0: + resolution: {tarball: https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz?rbtorrent=22513d0a716622c760b755a8b11e7ebdcfa5c374} + engines: {node: '>=12.20'} + + type-fest@3.13.1: + resolution: {} + engines: {node: '>=14.16'} + + type-is@1.6.18: + resolution: {tarball: https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz?rbtorrent=38f82e5ad1ec85b7be95c74b669214119903c229} + engines: {node: '>= 0.6'} + + type-is@2.0.1: + resolution: {tarball: https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.6'} + + type@1.2.0: + resolution: {tarball: https://registry.npmjs.org/type/-/type-1.2.0.tgz?rbtorrent=4ae44498563e940da43f939602a1e40702915319} + + type@2.7.2: + resolution: {tarball: https://registry.npmjs.org/type/-/type-2.7.2.tgz?rbtorrent=b623838eef6461957911afb7fd3b96643c66de24} + + typed-array-buffer@1.0.2: + resolution: {tarball: https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + typed-array-buffer@1.0.3: + resolution: {tarball: https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {tarball: https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {tarball: https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {tarball: https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {tarball: https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.6: + resolution: {tarball: https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {tarball: https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + typedarray-to-buffer@3.1.5: + resolution: {tarball: https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz?rbtorrent=999ed6b307fc4831eb188c3c4697987461a5ef67} + + typescript@5.6.3: + resolution: {tarball: https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz?rbtorrent=} + engines: {node: '>=14.17'} + hasBin: true + + uc.micro@1.0.6: + resolution: {tarball: https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz?rbtorrent=7ea3f5788daafcfd7da18a22c6b66e4075593edb} + + ufo@1.6.3: + resolution: {tarball: https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz?rbtorrent=} + + uint8array-extras@1.5.0: + resolution: {tarball: https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz?rbtorrent=} + engines: {node: '>=18'} + + unbox-primitive@1.0.2: + resolution: {tarball: https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz?rbtorrent=b1e740f6f2dc6d23a037432e3bc6387a9b168fb0} + + unbox-primitive@1.1.0: + resolution: {tarball: https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + unbzip2-stream@1.4.3: + resolution: {tarball: https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz?rbtorrent=22de84622b5ae1c8edd1b312d780c5ff220f9e31} + + undefsafe@2.0.5: + resolution: {tarball: https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz?rbtorrent=58a3f532a63a8bfdf16377529ba01bcccd9ace0e} + + undici-types@5.26.5: + resolution: {tarball: https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz?rbtorrent=4f727ac907c1a0ac18c3d91872a9a8c61bef61aa} + + undici-types@6.19.8: + resolution: {tarball: https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz?rbtorrent=} + + undici-types@6.21.0: + resolution: {tarball: https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz?rbtorrent=} + + undici@6.21.3: + resolution: {tarball: https://registry.npmjs.org/undici/-/undici-6.21.3.tgz?rbtorrent=} + engines: {node: '>=18.17'} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {tarball: https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz?rbtorrent=} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {tarball: https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz?rbtorrent=ffa5c600ec2c63af00f3d15be3c1b6360060f7d1} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.0: + resolution: {tarball: https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz?rbtorrent=} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.1.0: + resolution: {tarball: https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz?rbtorrent=a607d1ad7360d33a4f2008efdc579964f1e4007a} + engines: {node: '>=4'} + + universal-cookie@7.2.2: + resolution: {tarball: https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz?rbtorrent=} + + universalify@0.1.2: + resolution: {tarball: https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz?rbtorrent=8140e22333f755a70aa0a30041efeda062e02667} + engines: {node: '>= 4.0.0'} + + universalify@0.2.0: + resolution: {tarball: https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz?rbtorrent=703df797b535f9f286b1a0283f02c762b9917344} + engines: {node: '>= 4.0.0'} + + universalify@2.0.0: + resolution: {tarball: https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz?rbtorrent=cd46c97651a135fea7052ab887ca7b23c8119692} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {tarball: https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz?rbtorrent=db35d7e949011b32e4923cd85a989fa998b0c852} + engines: {node: '>= 0.8'} + + upath@2.0.1: + resolution: {tarball: https://registry.npmjs.org/upath/-/upath-2.0.1.tgz?rbtorrent=4bb32b6531ac5774f296e2681d67d94cc4cf380d} + engines: {node: '>=4'} + + update-browserslist-db@1.1.3: + resolution: {tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz?rbtorrent=} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uplot@1.6.32: + resolution: {tarball: https://registry.npmjs.org/uplot/-/uplot-1.6.32.tgz?rbtorrent=} + + uri-js@4.4.1: + resolution: {tarball: https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz?rbtorrent=da8316eab7bedb987c6087fad2c8e1a51ff339fb} + + url-parse@1.5.10: + resolution: {tarball: https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz?rbtorrent=c8596f91b63aeb0c11e979b87fc320e899f4cb27} + + url@0.11.3: + resolution: {tarball: https://registry.npmjs.org/url/-/url-0.11.3.tgz?rbtorrent=0c5ebf005375b730507094094fcf64402446724f} + + use-memo-one@1.1.3: + resolution: {tarball: https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz?rbtorrent=55d9b6dc6cd146dea06878a30e295466f5cc5cfc} + peerDependencies: + react: ^17.0.2 + + util-deprecate@1.0.2: + resolution: {tarball: https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz?rbtorrent=b85aab19594c8c7603748f9711f19ed8a128ae74} + + utility-types@3.10.0: + resolution: {tarball: https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz?rbtorrent=365da761f2b71d4b56cceaa3795e845dc26aa5ff} + engines: {node: '>= 4'} + + utils-merge@1.0.1: + resolution: {tarball: https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz?rbtorrent=faf6dd3c1b1134732f4eefafe0ef8dae1593efcf} + engines: {node: '>= 0.4.0'} + + uuid@11.0.5: + resolution: {tarball: https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz?rbtorrent=} + hasBin: true + + uuid@11.1.0: + resolution: {tarball: https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz?rbtorrent=} + hasBin: true + + uuid@3.4.0: + resolution: {tarball: https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz?rbtorrent=2bc6fbb9e23828cc6207c68a5633c6e14ba743ad} + hasBin: true + + uuid@8.3.2: + resolution: {tarball: https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz?rbtorrent=fd93fb8393e7fc28d23a778ae56120c4f357a737} + hasBin: true + + uuid@9.0.1: + resolution: {tarball: https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz?rbtorrent=cad8b11aaa25b6ca58358005a3187caaa0b5a7ce} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {tarball: https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz?rbtorrent=c110f6332c5463e2f6842022b45e9783b55d8fdb} + + v8-to-istanbul@9.1.0: + resolution: {tarball: https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz?rbtorrent=df9118089a244352ebc2ca165b686bd11623a3b4} + engines: {node: '>=10.12.0'} + + validate-npm-package-license@3.0.4: + resolution: {tarball: https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz?rbtorrent=9af20ae95897d1bdc587569e6a33941230cca7f4} + + value-equal@1.0.1: + resolution: {tarball: https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz?rbtorrent=3a37ce64dd07c43e4084f0442fb10a6d2d7b76c6} + + vary@1.1.2: + resolution: {tarball: https://registry.npmjs.org/vary/-/vary-1.1.2.tgz?rbtorrent=5dbad8130c2f93496058551a924516e73c1155d4} + engines: {node: '>= 0.8'} + + verror@1.10.0: + resolution: {tarball: https://registry.npmjs.org/verror/-/verror-1.10.0.tgz?rbtorrent=d6d38c995bf2cdf29e45cd86de458c33c75105ab} + engines: {'0': node >=0.6.0} + + vscode-jsonrpc@8.2.0: + resolution: {tarball: https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz?rbtorrent=ac43a1e21891e75fd73602ce79b9678fbee28135} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {tarball: https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz?rbtorrent=8ed3f7750363e26368928ab19b14f9e3737e9869} + + vscode-languageserver-textdocument@1.0.12: + resolution: {tarball: https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz?rbtorrent=} + + vscode-languageserver-types@3.17.5: + resolution: {tarball: https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz?rbtorrent=0e7da19e2a46c86ec6ae6e263a6f4afab15adfb1} + + vscode-languageserver@9.0.1: + resolution: {tarball: https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz?rbtorrent=52a3d8ca17b0037d6a4f652b2da82112ba488fb1} + hasBin: true + + vscode-uri@3.0.8: + resolution: {tarball: https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz?rbtorrent=927c6344d283ee340544ad502a3f54a0289b89c4} + + w3c-keyname@2.2.8: + resolution: {tarball: https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz?rbtorrent=70e88da8648a8333e950afe7b0195b6e47150479} + + w3c-xmlserializer@4.0.0: + resolution: {tarball: https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz?rbtorrent=6202e9616c3c96a5f763d9b39268f3485f74a013} + engines: {node: '>=14'} + + walker@1.0.8: + resolution: {tarball: https://registry.npmjs.org/walker/-/walker-1.0.8.tgz?rbtorrent=5c75619aac300f48e31b971f6c857c82b02482d3} + + watchpack@2.4.1: + resolution: {tarball: https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz?rbtorrent=} + engines: {node: '>=10.13.0'} + + wbuf@1.7.3: + resolution: {tarball: https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz?rbtorrent=f194017fc87b339dfca32cf0da079ee65a013660} + + webidl-conversions@7.0.0: + resolution: {tarball: https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz?rbtorrent=87ee0d2ece621f24a9e7243d9a4b214c9dfaacf5} + engines: {node: '>=12'} + + webpack-assets-manifest@5.2.1: + resolution: {tarball: https://registry.npmjs.org/webpack-assets-manifest/-/webpack-assets-manifest-5.2.1.tgz?rbtorrent=} + engines: {node: '>=10.13.0'} + peerDependencies: + webpack: ^5.2.0 + + webpack-bundle-analyzer@4.10.2: + resolution: {tarball: https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz?rbtorrent=} + engines: {node: '>= 10.13.0'} + hasBin: true + + webpack-dev-middleware@7.4.2: + resolution: {tarball: https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz?rbtorrent=} + engines: {node: '>= 18.12.0'} + peerDependencies: + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + + webpack-dev-server@5.2.2: + resolution: {tarball: https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz?rbtorrent=} + engines: {node: '>= 18.12.0'} + hasBin: true + peerDependencies: + webpack: ^5.0.0 + webpack-cli: '*' + peerDependenciesMeta: + webpack: + optional: true + webpack-cli: + optional: true + + webpack-manifest-plugin@5.0.0: + resolution: {tarball: https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-5.0.0.tgz?rbtorrent=08556fa81f70ff28114a3ff49c64ee40497718a7} + engines: {node: '>=12.22.0'} + peerDependencies: + webpack: ^5.47.0 + + webpack-sources@2.3.1: + resolution: {tarball: https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz?rbtorrent=e634dc96586580f8e98fefa0a922ba41d1b57d52} + engines: {node: '>=10.13.0'} + + webpack-sources@3.2.3: + resolution: {tarball: https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz?rbtorrent=92b20a762716d9268a0e0b9507ee6a10b786de6d} + engines: {node: '>=10.13.0'} + + webpack@5.96.1: + resolution: {tarball: https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz?rbtorrent=} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + websocket-driver@0.7.4: + resolution: {tarball: https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz?rbtorrent=01fa0e3f4656cb8aec3576ca83effeee84670701} + engines: {node: '>=0.8.0'} + + websocket-extensions@0.1.4: + resolution: {tarball: https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz?rbtorrent=4f705cfeeaa8f0e622bcd8a7af2ef90f7d92a191} + engines: {node: '>=0.8.0'} + + whatwg-encoding@2.0.0: + resolution: {tarball: https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz?rbtorrent=798f98aecf1a2548c8783499c54cc915924ff408} + engines: {node: '>=12'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-encoding@3.1.1: + resolution: {tarball: https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz?rbtorrent=f69ad1e3dd10e88b5e67530643176b4f84a816b4} + engines: {node: '>=18'} + + whatwg-mimetype@3.0.0: + resolution: {tarball: https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz?rbtorrent=9e2ac43f8789c63b03f46f747b842fc639976309} + engines: {node: '>=12'} + + whatwg-mimetype@4.0.0: + resolution: {tarball: https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz?rbtorrent=494b2ddbde4a8cddb647043ae09a5cbc7bc6e0f3} + engines: {node: '>=18'} + + whatwg-url@11.0.0: + resolution: {tarball: https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz?rbtorrent=a4420942a40216cf91dbc8459492224e62a0422b} + engines: {node: '>=12'} + + which-boxed-primitive@1.0.2: + resolution: {tarball: https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz?rbtorrent=752a8fdd0c6deb3b9c4a087334c55131becaf52b} + + which-boxed-primitive@1.1.1: + resolution: {tarball: https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + which-builtin-type@1.1.3: + resolution: {tarball: https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz?rbtorrent=0b94172fca1fc7fa9a1f8578b728847e71bcda5a} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {tarball: https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {tarball: https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.15: + resolution: {tarball: https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {tarball: https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz?rbtorrent=} + engines: {node: '>= 0.4'} + + which@1.3.1: + resolution: {tarball: https://registry.npmjs.org/which/-/which-1.3.1.tgz?rbtorrent=792972f4cff80b670e7ec9f49cd90738bc5e3b8d} + hasBin: true + + which@2.0.2: + resolution: {tarball: https://registry.npmjs.org/which/-/which-2.0.2.tgz?rbtorrent=555fb1d5b81803e43aa893a20c974171eb647dee} + engines: {node: '>= 8'} + hasBin: true + + worker-loader@3.0.8: + resolution: {tarball: https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz?rbtorrent=221febaaa68ef319619eb25961faa9c915b7ecce} + engines: {node: '>= 10.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + + worker-rspack-loader@3.1.2: + resolution: {tarball: https://registry.npmjs.org/worker-rspack-loader/-/worker-rspack-loader-3.1.2.tgz?rbtorrent=} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + webpack: ^5.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + workerpool@9.1.1: + resolution: {tarball: https://registry.npmjs.org/workerpool/-/workerpool-9.1.1.tgz?rbtorrent=} + + wrap-ansi@6.2.0: + resolution: {tarball: https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz?rbtorrent=ec09dc5c6839cccfdf8bf32922380e9657cd7861} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {tarball: https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz?rbtorrent=312c52ab2db72fb9dc9b57f62d1ee32019be6356} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {tarball: https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz?rbtorrent=35ac52325e795798568a877b07e6bafe0efb1965} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {tarball: https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz?rbtorrent=24f631e7a60519be5a0a6d43ff4add05afa3e58f} + + write-file-atomic@3.0.3: + resolution: {tarball: https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz?rbtorrent=3efd181f80192452af58ef6f006f28332b9d2b60} + + write-file-atomic@4.0.2: + resolution: {tarball: https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz?rbtorrent=2c12d3af09b4f8bc2c95a0c9e6837a6cd7dc0b24} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + write-file-atomic@5.0.1: + resolution: {tarball: https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz?rbtorrent=ea2267d23f1905cafd763856486ccebf6047931a} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + ws@7.5.10: + resolution: {tarball: https://registry.npmjs.org/ws/-/ws-7.5.10.tgz?rbtorrent=} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.17.1: + resolution: {tarball: https://registry.npmjs.org/ws/-/ws-8.17.1.tgz?rbtorrent=} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.18.0: + resolution: {tarball: https://registry.npmjs.org/ws/-/ws-8.18.0.tgz?rbtorrent=} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.18.3: + resolution: {tarball: https://registry.npmjs.org/ws/-/ws-8.18.3.tgz?rbtorrent=} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@4.0.0: + resolution: {tarball: https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz?rbtorrent=0e33efdd5b69c1dc92bdc080ff5c21e4e14e7bdd} + engines: {node: '>=12'} + + xmlchars@2.2.0: + resolution: {tarball: https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz?rbtorrent=cb09d57248092c3be89f5a1eea958838f94a0854} + + y18n@5.0.8: + resolution: {tarball: https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz?rbtorrent=855b16086a3d43ecea19041a0364afa26b8d5b42} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {tarball: https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz?rbtorrent=94518207e37bc5e4dbde13cb7ca4091ab2abe636} + + yallist@4.0.0: + resolution: {tarball: https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz?rbtorrent=ca0ffad79943d0b388e9009deaa7e003cbab93c7} + + yaml@2.8.1: + resolution: {tarball: https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz?rbtorrent=} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@20.2.9: + resolution: {tarball: https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz?rbtorrent=e6ea22fc2e458e3fde8f2ae895500406f1fa5002} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {tarball: https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz?rbtorrent=c2796cf6cfd8c257eaf7415f6413bd3da3b5f3cc} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {tarball: https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz?rbtorrent=d2dd948d7a7001182a317272e37e70f12dd457ca} + engines: {node: '>=12'} + + yauzl@3.2.0: + resolution: {tarball: https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz?rbtorrent=} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {tarball: https://registry.npmjs.org/yn/-/yn-3.1.1.tgz?rbtorrent=6335882d5f8b63bc3b6f0de0b15773b8fe8f2061} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {tarball: https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz?rbtorrent=0258e7cf3d257107b97e0ecb3ab9e4b0d900e7a2} + engines: {node: '>=10'} + + yocto-queue@1.1.1: + resolution: {tarball: https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz?rbtorrent=} + engines: {node: '>=12.20'} + + yup@1.6.1: + resolution: {tarball: https://registry.npmjs.org/yup/-/yup-1.6.1.tgz?rbtorrent=} + + zod@4.3.6: + resolution: {tarball: https://registry.npmjs.org/zod/-/zod-4.3.6.tgz?rbtorrent=} + +snapshots: + + '@aashutoshrathi/word-wrap@1.2.6': {} + + '@ampproject/remapping@2.2.0': + dependencies: + '@jridgewell/gen-mapping': 0.1.1 + '@jridgewell/trace-mapping': 0.3.25 + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + + '@asteasolutions/zod-to-openapi@8.1.0(zod@4.3.6)': + dependencies: + openapi3-ts: 4.5.0 + zod: 4.3.6 + + '@aws-crypto/crc32@3.0.0': + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.378.0 + tslib: 1.14.1 + + '@aws-crypto/crc32c@3.0.0': + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.378.0 + tslib: 1.14.1 + + '@aws-crypto/ie11-detection@3.0.0': + dependencies: + tslib: 1.14.1 + + '@aws-crypto/sha1-browser@3.0.0': + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.378.0 + '@aws-sdk/util-locate-window': 3.168.0 + '@aws-sdk/util-utf8-browser': 3.168.0 + tslib: 1.14.1 + + '@aws-crypto/sha256-browser@3.0.0': + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.378.0 + '@aws-sdk/util-locate-window': 3.168.0 + '@aws-sdk/util-utf8-browser': 3.168.0 + tslib: 1.14.1 + + '@aws-crypto/sha256-js@3.0.0': + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.378.0 + tslib: 1.14.1 + + '@aws-crypto/supports-web-crypto@3.0.0': + dependencies: + tslib: 1.14.1 + + '@aws-crypto/util@3.0.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@aws-sdk/util-utf8-browser': 3.168.0 + tslib: 1.14.1 + + '@aws-sdk/client-s3@3.383.0': + dependencies: + '@aws-crypto/sha1-browser': 3.0.0 + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.382.0 + '@aws-sdk/credential-provider-node': 3.382.0 + '@aws-sdk/middleware-bucket-endpoint': 3.378.0 + '@aws-sdk/middleware-expect-continue': 3.378.0 + '@aws-sdk/middleware-flexible-checksums': 3.383.0 + '@aws-sdk/middleware-host-header': 3.379.1 + '@aws-sdk/middleware-location-constraint': 3.379.1 + '@aws-sdk/middleware-logger': 3.378.0 + '@aws-sdk/middleware-recursion-detection': 3.378.0 + '@aws-sdk/middleware-sdk-s3': 3.379.1 + '@aws-sdk/middleware-signing': 3.379.1 + '@aws-sdk/middleware-ssec': 3.378.0 + '@aws-sdk/middleware-user-agent': 3.382.0 + '@aws-sdk/signature-v4-multi-region': 3.378.0 + '@aws-sdk/types': 3.378.0 + '@aws-sdk/util-endpoints': 3.382.0 + '@aws-sdk/util-user-agent-browser': 3.378.0 + '@aws-sdk/util-user-agent-node': 3.378.0 + '@aws-sdk/xml-builder': 3.310.0 + '@smithy/config-resolver': 2.0.1 + '@smithy/eventstream-serde-browser': 2.0.1 + '@smithy/eventstream-serde-config-resolver': 2.0.1 + '@smithy/eventstream-serde-node': 2.0.1 + '@smithy/fetch-http-handler': 2.0.1 + '@smithy/hash-blob-browser': 2.0.1 + '@smithy/hash-node': 2.0.1 + '@smithy/hash-stream-node': 2.0.1 + '@smithy/invalid-dependency': 2.0.1 + '@smithy/md5-js': 2.0.1 + '@smithy/middleware-content-length': 2.0.1 + '@smithy/middleware-endpoint': 2.0.1 + '@smithy/middleware-retry': 2.0.1 + '@smithy/middleware-serde': 2.0.1 + '@smithy/middleware-stack': 2.0.0 + '@smithy/node-config-provider': 2.0.1 + '@smithy/node-http-handler': 2.0.1 + '@smithy/protocol-http': 2.0.1 + '@smithy/smithy-client': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/url-parser': 2.0.1 + '@smithy/util-base64': 2.0.0 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.0.0 + '@smithy/util-defaults-mode-browser': 2.0.1 + '@smithy/util-defaults-mode-node': 2.0.1 + '@smithy/util-retry': 2.0.0 + '@smithy/util-stream': 2.0.1 + '@smithy/util-utf8': 2.0.0 + '@smithy/util-waiter': 2.0.1 + fast-xml-parser: 4.2.5 + tslib: 2.8.1 + transitivePeerDependencies: + - '@aws-sdk/signature-v4-crt' + - aws-crt + + '@aws-sdk/client-sso-oidc@3.382.0': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/middleware-host-header': 3.379.1 + '@aws-sdk/middleware-logger': 3.378.0 + '@aws-sdk/middleware-recursion-detection': 3.378.0 + '@aws-sdk/middleware-user-agent': 3.382.0 + '@aws-sdk/types': 3.378.0 + '@aws-sdk/util-endpoints': 3.382.0 + '@aws-sdk/util-user-agent-browser': 3.378.0 + '@aws-sdk/util-user-agent-node': 3.378.0 + '@smithy/config-resolver': 2.0.1 + '@smithy/fetch-http-handler': 2.0.1 + '@smithy/hash-node': 2.0.1 + '@smithy/invalid-dependency': 2.0.1 + '@smithy/middleware-content-length': 2.0.1 + '@smithy/middleware-endpoint': 2.0.1 + '@smithy/middleware-retry': 2.0.1 + '@smithy/middleware-serde': 2.0.1 + '@smithy/middleware-stack': 2.0.0 + '@smithy/node-config-provider': 2.0.1 + '@smithy/node-http-handler': 2.0.1 + '@smithy/protocol-http': 2.0.1 + '@smithy/smithy-client': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/url-parser': 2.0.1 + '@smithy/util-base64': 2.0.0 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.0.0 + '@smithy/util-defaults-mode-browser': 2.0.1 + '@smithy/util-defaults-mode-node': 2.0.1 + '@smithy/util-retry': 2.0.0 + '@smithy/util-utf8': 2.0.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.382.0': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/middleware-host-header': 3.379.1 + '@aws-sdk/middleware-logger': 3.378.0 + '@aws-sdk/middleware-recursion-detection': 3.378.0 + '@aws-sdk/middleware-user-agent': 3.382.0 + '@aws-sdk/types': 3.378.0 + '@aws-sdk/util-endpoints': 3.382.0 + '@aws-sdk/util-user-agent-browser': 3.378.0 + '@aws-sdk/util-user-agent-node': 3.378.0 + '@smithy/config-resolver': 2.0.1 + '@smithy/fetch-http-handler': 2.0.1 + '@smithy/hash-node': 2.0.1 + '@smithy/invalid-dependency': 2.0.1 + '@smithy/middleware-content-length': 2.0.1 + '@smithy/middleware-endpoint': 2.0.1 + '@smithy/middleware-retry': 2.0.1 + '@smithy/middleware-serde': 2.0.1 + '@smithy/middleware-stack': 2.0.0 + '@smithy/node-config-provider': 2.0.1 + '@smithy/node-http-handler': 2.0.1 + '@smithy/protocol-http': 2.0.1 + '@smithy/smithy-client': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/url-parser': 2.0.1 + '@smithy/util-base64': 2.0.0 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.0.0 + '@smithy/util-defaults-mode-browser': 2.0.1 + '@smithy/util-defaults-mode-node': 2.0.1 + '@smithy/util-retry': 2.0.0 + '@smithy/util-utf8': 2.0.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sts@3.382.0': + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/credential-provider-node': 3.382.0 + '@aws-sdk/middleware-host-header': 3.379.1 + '@aws-sdk/middleware-logger': 3.378.0 + '@aws-sdk/middleware-recursion-detection': 3.378.0 + '@aws-sdk/middleware-sdk-sts': 3.379.1 + '@aws-sdk/middleware-signing': 3.379.1 + '@aws-sdk/middleware-user-agent': 3.382.0 + '@aws-sdk/types': 3.378.0 + '@aws-sdk/util-endpoints': 3.382.0 + '@aws-sdk/util-user-agent-browser': 3.378.0 + '@aws-sdk/util-user-agent-node': 3.378.0 + '@smithy/config-resolver': 2.0.1 + '@smithy/fetch-http-handler': 2.0.1 + '@smithy/hash-node': 2.0.1 + '@smithy/invalid-dependency': 2.0.1 + '@smithy/middleware-content-length': 2.0.1 + '@smithy/middleware-endpoint': 2.0.1 + '@smithy/middleware-retry': 2.0.1 + '@smithy/middleware-serde': 2.0.1 + '@smithy/middleware-stack': 2.0.0 + '@smithy/node-config-provider': 2.0.1 + '@smithy/node-http-handler': 2.0.1 + '@smithy/protocol-http': 2.0.1 + '@smithy/smithy-client': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/url-parser': 2.0.1 + '@smithy/util-base64': 2.0.0 + '@smithy/util-body-length-browser': 2.0.0 + '@smithy/util-body-length-node': 2.0.0 + '@smithy/util-defaults-mode-browser': 2.0.1 + '@smithy/util-defaults-mode-node': 2.0.1 + '@smithy/util-retry': 2.0.0 + '@smithy/util-utf8': 2.0.0 + fast-xml-parser: 4.2.5 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-env@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/property-provider': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.382.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.378.0 + '@aws-sdk/credential-provider-process': 3.378.0 + '@aws-sdk/credential-provider-sso': 3.382.0 + '@aws-sdk/credential-provider-web-identity': 3.378.0 + '@aws-sdk/types': 3.378.0 + '@smithy/credential-provider-imds': 2.0.1 + '@smithy/property-provider': 2.0.1 + '@smithy/shared-ini-file-loader': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.382.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.378.0 + '@aws-sdk/credential-provider-ini': 3.382.0 + '@aws-sdk/credential-provider-process': 3.378.0 + '@aws-sdk/credential-provider-sso': 3.382.0 + '@aws-sdk/credential-provider-web-identity': 3.378.0 + '@aws-sdk/types': 3.378.0 + '@smithy/credential-provider-imds': 2.0.1 + '@smithy/property-provider': 2.0.1 + '@smithy/shared-ini-file-loader': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/property-provider': 2.0.1 + '@smithy/shared-ini-file-loader': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.382.0': + dependencies: + '@aws-sdk/client-sso': 3.382.0 + '@aws-sdk/token-providers': 3.382.0 + '@aws-sdk/types': 3.378.0 + '@smithy/property-provider': 2.0.1 + '@smithy/shared-ini-file-loader': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/property-provider': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-bucket-endpoint@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@aws-sdk/util-arn-parser': 3.310.0 + '@smithy/protocol-http': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/util-config-provider': 2.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/protocol-http': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.383.0': + dependencies: + '@aws-crypto/crc32': 3.0.0 + '@aws-crypto/crc32c': 3.0.0 + '@aws-sdk/types': 3.378.0 + '@smithy/is-array-buffer': 2.0.0 + '@smithy/protocol-http': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/util-utf8': 2.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.379.1': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/protocol-http': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.379.1': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/protocol-http': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.379.1': + dependencies: + '@aws-sdk/types': 3.378.0 + '@aws-sdk/util-arn-parser': 3.310.0 + '@smithy/protocol-http': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-sts@3.379.1': + dependencies: + '@aws-sdk/middleware-signing': 3.379.1 + '@aws-sdk/types': 3.378.0 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-signing@3.379.1': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/property-provider': 2.0.1 + '@smithy/protocol-http': 2.0.1 + '@smithy/signature-v4': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/util-middleware': 2.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.382.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@aws-sdk/util-endpoints': 3.382.0 + '@smithy/protocol-http': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/protocol-http': 2.0.1 + '@smithy/signature-v4': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.382.0': + dependencies: + '@aws-sdk/client-sso-oidc': 3.382.0 + '@aws-sdk/types': 3.378.0 + '@smithy/property-provider': 2.0.1 + '@smithy/shared-ini-file-loader': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.378.0': + dependencies: + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.310.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.382.0': + dependencies: + '@aws-sdk/types': 3.378.0 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.168.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/types': 2.0.2 + bowser: 2.11.0 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.378.0': + dependencies: + '@aws-sdk/types': 3.378.0 + '@smithy/node-config-provider': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@aws-sdk/util-utf8-browser@3.168.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.310.0': + dependencies: + tslib: 2.8.1 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.2': {} + + '@babel/compat-data@7.26.8': {} + + '@babel/core@7.26.0': + dependencies: + '@ampproject/remapping': 2.2.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.2.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.10 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helpers': 7.26.10 + '@babel/parser': 7.26.10 + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/eslint-parser@7.24.1(@babel/core@7.26.0)(eslint@8.57.0)': + dependencies: + '@babel/core': 7.26.0 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 8.57.0 + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + + '@babel/generator@7.26.10': + dependencies: + '@babel/parser': 7.26.10 + '@babel/types': 7.26.10 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/generator@7.26.2': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/helper-annotate-as-pure@7.25.9': + dependencies: + '@babel/types': 7.26.10 + + '@babel/helper-builder-binary-assignment-operator-visitor@7.25.9': + dependencies: + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-compilation-targets@7.25.9': + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.25.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-compilation-targets@7.26.5': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.25.9 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.10) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.25.9 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + regexpu-core: 6.1.1 + semver: 6.3.1 + + '@babel/helper-create-regexp-features-plugin@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + regexpu-core: 6.1.1 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + debug: 4.4.3(supports-color@5.5.0) + lodash.debounce: 4.0.8 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + debug: 4.4.3(supports-color@5.5.0) + lodash.debounce: 4.0.8 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 + debug: 4.4.3(supports-color@5.5.0) + lodash.debounce: 4.0.8 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-member-expression-to-functions@7.25.9': + dependencies: + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.25.9': + dependencies: + '@babel/types': 7.26.10 + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + dependencies: + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helper-wrap-function@7.25.9': + dependencies: + '@babel/template': 7.26.9 + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.26.0': + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + + '@babel/helpers@7.26.10': + dependencies: + '@babel/template': 7.26.9 + '@babel/types': 7.26.10 + + '@babel/parser@7.26.10': + dependencies: + '@babel/types': 7.26.10 + + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-generator-functions@7.26.8(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.10) + '@babel/traverse': 7.26.10 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-block-scoped-functions@7.26.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/traverse': 7.25.9 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.10) + '@babel/traverse': 7.25.9 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/template': 7.25.9 + + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/template': 7.25.9 + + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-exponentiation-operator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-for-of@7.26.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) + + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.10) + + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-constant-elements@7.22.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-display-name@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-display-name@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-pure-annotations@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + regenerator-transform: 0.15.2 + + '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + regenerator-transform: 0.15.2 + + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-runtime@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.26.0) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.26.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-template-literals@7.26.8(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-typeof-symbol@7.26.7(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-typescript@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.25.9(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/preset-env@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.0) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-exponentiation-operator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.0) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.26.0) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.26.0) + core-js-compat: 3.39.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-env@7.26.9(@babel/core@7.26.10)': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.10) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.10) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.26.10) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoped-functions': 7.26.5(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.26.10) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-for-of': 7.26.9(@babel/core@7.26.10) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.10) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-nullish-coalescing-operator': 7.26.6(@babel/core@7.26.10) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-template-literals': 7.26.8(@babel/core@7.26.10) + '@babel/plugin-transform-typeof-symbol': 7.26.7(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.10) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.10) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.26.10) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.26.10) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.26.10) + core-js-compat: 3.41.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/types': 7.26.0 + esutils: 2.0.3 + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/types': 7.26.0 + esutils: 2.0.3 + + '@babel/preset-react@7.26.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-development': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-pure-annotations': 7.25.9(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-react@7.26.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx-development': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-pure-annotations': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) + '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.10) + '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.26.0': + dependencies: + regenerator-runtime: 0.14.0 + + '@babel/runtime@7.26.10': + dependencies: + regenerator-runtime: 0.14.0 + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@babel/template@7.26.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.10 + '@babel/types': 7.26.10 + + '@babel/traverse@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + debug: 4.4.3(supports-color@5.5.0) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/traverse@7.26.10': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.10 + '@babel/parser': 7.26.10 + '@babel/template': 7.26.9 + '@babel/types': 7.26.10 + debug: 4.4.3(supports-color@5.5.0) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@babel/types@7.26.10': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@bcoe/v8-coverage@0.2.3': {} + + '@bem-react/classname@1.7.0': {} + + '@bem-react/classnames@1.3.10': {} + + '@borewit/text-codec@0.1.1': {} + + '@braintree/sanitize-url@6.0.4': {} + + '@braintree/sanitize-url@7.1.2': {} + + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + + '@codemirror/autocomplete@6.20.0': + dependencies: + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.13 + '@lezer/common': 1.5.1 + + '@codemirror/commands@6.10.2': + dependencies: + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.13 + '@lezer/common': 1.5.1 + + '@codemirror/lang-css@6.3.1': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.4 + '@lezer/common': 1.5.1 + '@lezer/css': 1.3.0 + + '@codemirror/lang-html@6.4.11': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/lang-css': 6.3.1 + '@codemirror/lang-javascript': 6.2.4 + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.13 + '@lezer/common': 1.5.1 + '@lezer/css': 1.3.0 + '@lezer/html': 1.3.13 + + '@codemirror/lang-javascript@6.2.4': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/language': 6.12.1 + '@codemirror/lint': 6.9.4 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.13 + '@lezer/common': 1.5.1 + '@lezer/javascript': 1.5.4 + + '@codemirror/lang-markdown@6.5.0': + dependencies: + '@codemirror/autocomplete': 6.20.0 + '@codemirror/lang-html': 6.4.11 + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.13 + '@lezer/common': 1.5.1 + '@lezer/markdown': 1.6.3 + + '@codemirror/language@6.12.1': + dependencies: + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.13 + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + style-mod: 4.1.3 + + '@codemirror/lint@6.9.4': + dependencies: + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.13 + crelt: 1.0.6 + + '@codemirror/search@6.6.0': + dependencies: + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.13 + crelt: 1.0.6 + + '@codemirror/state@6.5.4': + dependencies: + '@marijn/find-cluster-break': 1.0.2 + + '@codemirror/view@6.39.13': + dependencies: + '@codemirror/state': 6.5.4 + crelt: 1.0.6 + style-mod: 4.1.3 + w3c-keyname: 2.2.8 + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@csstools/cascade-layer-name-parser@1.0.5(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1)': + dependencies: + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + + '@csstools/color-helpers@3.0.2': {} + + '@csstools/css-calc@1.1.4(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1)': + dependencies: + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + + '@csstools/css-color-parser@1.4.0(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1)': + dependencies: + '@csstools/color-helpers': 3.0.2 + '@csstools/css-calc': 1.1.4(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + + '@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1)': + dependencies: + '@csstools/css-tokenizer': 2.2.1 + + '@csstools/css-tokenizer@2.2.1': {} + + '@csstools/media-query-list-parser@2.1.5(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1)': + dependencies: + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + + '@csstools/postcss-cascade-layers@4.0.0(postcss@8.4.47)': + dependencies: + '@csstools/selector-specificity': 3.0.0(postcss-selector-parser@6.1.2) + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + '@csstools/postcss-color-function@3.0.7(postcss@8.4.47)': + dependencies: + '@csstools/css-color-parser': 1.4.0(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + '@csstools/postcss-progressive-custom-properties': 3.0.2(postcss@8.4.47) + postcss: 8.4.47 + + '@csstools/postcss-color-mix-function@2.0.7(postcss@8.4.47)': + dependencies: + '@csstools/css-color-parser': 1.4.0(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + '@csstools/postcss-progressive-custom-properties': 3.0.2(postcss@8.4.47) + postcss: 8.4.47 + + '@csstools/postcss-exponential-functions@1.0.1(postcss@8.4.47)': + dependencies: + '@csstools/css-calc': 1.1.4(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + postcss: 8.4.47 + + '@csstools/postcss-font-format-keywords@3.0.0(postcss@8.4.47)': + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-gamut-mapping@1.0.0(postcss@8.4.47)': + dependencies: + '@csstools/css-color-parser': 1.4.0(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + postcss: 8.4.47 + + '@csstools/postcss-gradients-interpolation-method@4.0.7(postcss@8.4.47)': + dependencies: + '@csstools/css-color-parser': 1.4.0(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + '@csstools/postcss-progressive-custom-properties': 3.0.2(postcss@8.4.47) + postcss: 8.4.47 + + '@csstools/postcss-hwb-function@3.0.6(postcss@8.4.47)': + dependencies: + '@csstools/css-color-parser': 1.4.0(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + postcss: 8.4.47 + + '@csstools/postcss-ic-unit@3.0.2(postcss@8.4.47)': + dependencies: + '@csstools/postcss-progressive-custom-properties': 3.0.2(postcss@8.4.47) + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-initial@1.0.0(postcss@8.4.47)': + dependencies: + postcss: 8.4.47 + + '@csstools/postcss-is-pseudo-class@4.0.3(postcss@8.4.47)': + dependencies: + '@csstools/selector-specificity': 3.0.0(postcss-selector-parser@6.1.2) + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + '@csstools/postcss-logical-float-and-clear@2.0.0(postcss@8.4.47)': + dependencies: + postcss: 8.4.47 + + '@csstools/postcss-logical-resize@2.0.0(postcss@8.4.47)': + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-logical-viewport-units@2.0.3(postcss@8.4.47)': + dependencies: + '@csstools/css-tokenizer': 2.2.1 + postcss: 8.4.47 + + '@csstools/postcss-media-minmax@1.1.0(postcss@8.4.47)': + dependencies: + '@csstools/css-calc': 1.1.4(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + '@csstools/media-query-list-parser': 2.1.5(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + postcss: 8.4.47 + + '@csstools/postcss-media-queries-aspect-ratio-number-values@2.0.3(postcss@8.4.47)': + dependencies: + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + '@csstools/media-query-list-parser': 2.1.5(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + postcss: 8.4.47 + + '@csstools/postcss-nested-calc@3.0.0(postcss@8.4.47)': + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-normalize-display-values@3.0.1(postcss@8.4.47)': + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-oklab-function@3.0.7(postcss@8.4.47)': + dependencies: + '@csstools/css-color-parser': 1.4.0(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + '@csstools/postcss-progressive-custom-properties': 3.0.2(postcss@8.4.47) + postcss: 8.4.47 + + '@csstools/postcss-progressive-custom-properties@3.0.2(postcss@8.4.47)': + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-relative-color-syntax@2.0.7(postcss@8.4.47)': + dependencies: + '@csstools/css-color-parser': 1.4.0(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + '@csstools/postcss-progressive-custom-properties': 3.0.2(postcss@8.4.47) + postcss: 8.4.47 + + '@csstools/postcss-scope-pseudo-class@3.0.0(postcss@8.4.47)': + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + '@csstools/postcss-stepped-value-functions@3.0.2(postcss@8.4.47)': + dependencies: + '@csstools/css-calc': 1.1.4(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + postcss: 8.4.47 + + '@csstools/postcss-text-decoration-shorthand@3.0.3(postcss@8.4.47)': + dependencies: + '@csstools/color-helpers': 3.0.2 + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-trigonometric-functions@3.0.2(postcss@8.4.47)': + dependencies: + '@csstools/css-calc': 1.1.4(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + postcss: 8.4.47 + + '@csstools/postcss-unset-value@3.0.0(postcss@8.4.47)': + dependencies: + postcss: 8.4.47 + + '@csstools/selector-specificity@3.0.0(postcss-selector-parser@6.1.2)': + dependencies: + postcss-selector-parser: 6.1.2 + + '@datalens-tech/ui-sandbox-modules@0.36.0': {} + + '@datalens-tech/xlsx@0.20.1': {} + + '@diplodoc/color-extension@0.1.1': {} + + '@diplodoc/cut-extension@0.7.4(@types/markdown-it@13.0.7)(markdown-it@13.0.2)(react@17.0.2)': + dependencies: + '@diplodoc/directive': 0.3.2(@types/markdown-it@13.0.7)(markdown-it@13.0.2) + '@diplodoc/utils': 2.1.0(react@17.0.2) + optionalDependencies: + react: 17.0.2 + transitivePeerDependencies: + - '@types/markdown-it' + - markdown-it + + '@diplodoc/cut-extension@1.1.1(@types/markdown-it@13.0.7)(markdown-it@13.0.2)(react@17.0.2)': + dependencies: + '@diplodoc/directive': 0.3.2(@types/markdown-it@13.0.7)(markdown-it@13.0.2) + '@diplodoc/utils': 2.1.0(react@17.0.2) + transitivePeerDependencies: + - '@types/markdown-it' + - markdown-it + - react + + '@diplodoc/directive@0.3.2(@types/markdown-it@13.0.7)(markdown-it@13.0.2)': + dependencies: + markdown-it-directive: 2.0.5(@types/markdown-it@13.0.7)(markdown-it@13.0.2) + transitivePeerDependencies: + - '@types/markdown-it' + - markdown-it + + '@diplodoc/file-extension@0.2.1(@types/markdown-it@13.0.7)(markdown-it@13.0.2)': + dependencies: + '@diplodoc/directive': 0.3.2(@types/markdown-it@13.0.7)(markdown-it@13.0.2) + markdown-it: 13.0.2 + transitivePeerDependencies: + - '@types/markdown-it' + + '@diplodoc/latex-extension@1.1.0(katex@0.16.21)(markdown-it@13.0.2)(react@17.0.2)': + dependencies: + katex: 0.16.21 + markdown-it: 13.0.2 + optionalDependencies: + react: 17.0.2 + + '@diplodoc/mermaid-extension@1.4.0(markdown-it@13.0.2)(react@17.0.2)': + dependencies: + '@gravity-ui/icons': 2.16.0(react@17.0.2) + d3: 7.9.0 + markdown-it: 13.0.2 + mermaid: 11.12.2 + ts-dedent: 2.2.0 + optionalDependencies: + react: 17.0.2 + + '@diplodoc/tabs-extension@3.7.5(react@17.0.2)': + optionalDependencies: + react: 17.0.2 + + '@diplodoc/transform@4.64.1(@types/markdown-it@13.0.7)(highlight.js@11.9.0)(react@17.0.2)': + dependencies: + '@diplodoc/cut-extension': 1.1.1(@types/markdown-it@13.0.7)(markdown-it@13.0.2)(react@17.0.2) + '@diplodoc/file-extension': 0.2.1(@types/markdown-it@13.0.7)(markdown-it@13.0.2) + '@diplodoc/tabs-extension': 3.7.5(react@17.0.2) + '@diplodoc/utils': 2.1.0(react@17.0.2) + chalk: 4.1.2 + cheerio: 1.0.0 + css: 3.0.0 + cssfilter: 0.0.10 + get-root-node-polyfill: 1.0.0 + github-slugger: 1.5.0 + js-yaml: 4.1.0 + lodash: 4.17.21 + markdown-it: 13.0.2 + markdown-it-attrs: 4.3.1(markdown-it@13.0.2) + markdown-it-deflist: 2.1.0 + markdown-it-sup: 1.0.0 + markdownlint: 0.32.1 + markdownlint-rule-helpers: 0.17.2 + quick-lru: 5.1.1 + sanitize-html: 2.14.0 + slugify: 1.6.6 + svgo: 3.3.2 + ts-dedent: 2.2.0 + optionalDependencies: + highlight.js: 11.9.0 + transitivePeerDependencies: + - '@types/markdown-it' + - react + + '@diplodoc/utils@2.1.0(react@17.0.2)': + optionalDependencies: + react: 17.0.2 + + '@discoveryjs/json-ext@0.5.7': {} + + '@discoveryjs/natural-compare@1.1.0': {} + + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.5': + optional: true + + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.10.0': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.3(supports-color@5.5.0) + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.0': {} + + '@fastify/deepmerge@1.3.0': {} + + '@floating-ui/core@1.7.2': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.2': + dependencies: + '@floating-ui/core': 1.7.2 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@floating-ui/dom': 1.7.2 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + '@floating-ui/react@0.27.13(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@floating-ui/react-dom': 2.1.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@floating-ui/utils': 0.2.10 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + tabbable: 6.2.0 + + '@floating-ui/utils@0.2.10': {} + + '@gravity-ui/app-builder@0.38.0(@swc/helpers@0.5.17)(@types/express@4.17.21)(@types/node@20.16.12)(@types/webpack@5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))(chokidar@4.0.3)(moment-timezone@0.5.43)(monaco-editor-webpack-plugin@7.1.0(monaco-editor@0.36.1)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))))(type-fest@3.13.1)': + dependencies: + '@aws-sdk/client-s3': 3.383.0 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) + '@babel/plugin-transform-runtime': 7.25.9(@babel/core@7.26.0) + '@babel/preset-env': 7.26.0(@babel/core@7.26.0) + '@babel/preset-react': 7.26.3(@babel/core@7.26.0) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) + '@babel/runtime': 7.26.0 + '@module-federation/enhanced': 0.18.4(@rspack/core@1.5.5(@swc/helpers@0.5.17))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(typescript@5.6.3)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@okikio/sharedworker': 1.0.7 + '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(@types/webpack@5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))(react-refresh@0.14.2)(type-fest@3.13.1)(webpack-dev-server@5.2.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/rspack-plugin': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/webpack-plugin': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + '@rspack/dev-server': 1.1.4(@rspack/core@1.5.5(@swc/helpers@0.5.17))(@types/express@4.17.21)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rspack/plugin-react-refresh': 1.5.3(react-refresh@0.14.2) + '@statoscope/stats': 5.28.1 + '@statoscope/stats-extension-compressed': 5.28.1 + '@statoscope/webpack-model': 5.29.0(@swc/core@1.13.5(@swc/helpers@0.5.17)) + '@statoscope/webpack-plugin': 5.29.0(@rspack/core@1.5.5(@swc/helpers@0.5.17))(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@svgr/core': 8.1.0(typescript@5.6.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.6.3)) + '@svgr/webpack': 8.1.0(typescript@5.6.3) + '@swc/cli': 0.7.8(@swc/core@1.13.5(@swc/helpers@0.5.17))(chokidar@4.0.3) + '@swc/core': 1.13.5(@swc/helpers@0.5.17) + '@swc/plugin-transform-imports': 9.1.0 + babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + babel-plugin-import: 1.13.8 + babel-plugin-inline-react-svg: 2.0.2(@babel/core@7.26.0) + babel-plugin-transform-react-remove-prop-types: 0.4.24 + babel-plugin-tsconfig-paths-module-resolver: 1.0.4 + browserslist: 4.25.1 + chalk: 4.1.2 + circular-dependency-plugin: 5.2.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + clean-webpack-plugin: 4.0.0(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + common-tags: 1.8.2 + core-js: 3.43.0 + cosmiconfig: 8.3.6(typescript@5.6.3) + cosmiconfig-typescript-loader: 5.0.0(@types/node@20.16.12)(cosmiconfig@8.3.6(typescript@5.6.3))(typescript@5.6.3) + css-loader: 7.1.2(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + css-minimizer-webpack-plugin: 7.0.0(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + dotenv: 16.4.5 + execa: 8.0.1 + fast-glob: 3.3.2 + file-type: 19.6.0 + find-cache-dir: 5.0.0 + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.6.3)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + fs-extra: 11.3.0 + get-port: 7.1.0 + mime-types: 2.1.35 + mini-css-extract-plugin: 2.9.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + moment-timezone-data-webpack-plugin: 1.5.1(moment-timezone@0.5.43)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + nodemon: 3.1.7 + p-map: 7.0.2 + p-queue: 8.0.1 + pino-pretty: 11.3.0 + postcss: 8.4.47 + postcss-loader: 8.1.1(@rspack/core@1.5.5(@swc/helpers@0.5.17))(postcss@8.4.47)(typescript@5.6.3)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + postcss-prefix-selector: 2.1.1(postcss@8.4.47) + postcss-preset-env: 9.2.0(postcss@8.4.47) + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + react-refresh: 0.14.2 + resolve-url-loader: 5.0.0 + rimraf: 5.0.10 + rspack-manifest-plugin: 5.0.3(@rspack/core@1.5.5(@swc/helpers@0.5.17)) + sass: 1.80.6 + sass-loader: 16.0.3(@rspack/core@1.5.5(@swc/helpers@0.5.17))(sass@1.80.6)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + semver: 7.7.2 + signal-exit: 4.1.0 + source-map-loader: 5.0.0(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + strip-ansi: 6.0.1 + style-loader: 4.0.0(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + svgo: 3.3.2 + swc-loader: 0.2.6(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + terser-webpack-plugin: 5.3.10(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + ts-checker-rspack-plugin: 1.1.1(@rspack/core@1.5.5(@swc/helpers@0.5.17))(typescript@5.6.3) + ts-node: 10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3) + tsconfig-to-swcconfig: 2.8.1 + tslib: 2.8.1 + typescript: 5.6.3 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + webpack-assets-manifest: 5.2.1(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + webpack-bundle-analyzer: 4.10.2 + webpack-dev-server: 5.2.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + webpack-manifest-plugin: 5.0.0(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + worker-loader: 3.0.8(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + worker-rspack-loader: 3.1.2(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + yargs: 17.7.2 + optionalDependencies: + monaco-editor-webpack-plugin: 7.1.0(monaco-editor@0.36.1)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + transitivePeerDependencies: + - '@aws-sdk/signature-v4-crt' + - '@parcel/css' + - '@rsbuild/core' + - '@swc/css' + - '@swc/helpers' + - '@swc/wasm' + - '@types/express' + - '@types/node' + - '@types/webpack' + - aws-crt + - bare-abort-controller + - bufferutil + - chokidar + - clean-css + - csso + - debug + - esbuild + - moment-timezone + - node-sass + - react-native-b4a + - sass-embedded + - sockjs-client + - supports-color + - type-fest + - uglify-js + - utf-8-validate + - vue-tsc + - webpack-cli + - webpack-hot-middleware + - webpack-plugin-serve + + '@gravity-ui/app-layout@2.1.0': + dependencies: + htmlescape: 1.1.1 + + '@gravity-ui/axios-wrapper@1.5.1(axios@1.13.2)(lodash@4.17.21)': + dependencies: + axios: 1.13.2 + lodash: 4.17.21 + + '@gravity-ui/browserslist-config@4.3.0': {} + + '@gravity-ui/chartkit@7.44.0(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@types/react@17.0.40)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@bem-react/classname': 1.7.0 + '@gravity-ui/charts': 1.42.0(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@types/react@17.0.40)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/date-utils': 2.5.6 + '@gravity-ui/i18n': 1.8.0 + '@gravity-ui/uikit': 7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/yagr': 4.11.0 + afterframe: 1.0.2 + lodash: 4.17.21 + react: 17.0.2 + tslib: 2.8.1 + transitivePeerDependencies: + - '@types/react' + - react-dom + + '@gravity-ui/charts@1.42.0(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@types/react@17.0.40)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@bem-react/classname': 1.7.0 + '@gravity-ui/date-utils': 2.5.6 + '@gravity-ui/i18n': 1.8.0 + '@gravity-ui/icons': 2.16.0(react@17.0.2) + '@gravity-ui/uikit': 7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-color: 3.1.0 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-random: 3.0.1 + d3-sankey: 0.12.3 + d3-scale: 4.0.2 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + html-react-parser: 5.2.17(@types/react@17.0.40)(react@17.0.2) + lodash: 4.17.21 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + tslib: 2.8.1 + transitivePeerDependencies: + - '@types/react' + + '@gravity-ui/components@4.12.0(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@bem-react/classname': 1.7.0 + '@gravity-ui/date-utils': 2.5.6 + '@gravity-ui/i18n': 1.8.0 + '@gravity-ui/icons': 2.16.0(react@17.0.2) + '@gravity-ui/uikit': 7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + lodash: 4.17.21 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + universal-cookie: 7.2.2 + + '@gravity-ui/dashkit@10.0.0(@gravity-ui/icons@2.16.0(react@17.0.2))(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@bem-react/classname': 1.7.0 + '@gravity-ui/icons': 2.16.0(react@17.0.2) + '@gravity-ui/uikit': 7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + hashids: 2.2.8 + immutability-helper: 3.1.1 + prop-types: 15.8.1 + react: 17.0.2 + react-grid-layout: 1.5.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-transition-group: 4.4.5(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + transitivePeerDependencies: + - react-dom + + '@gravity-ui/date-components@3.2.3(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@bem-react/classname': 1.7.0 + '@gravity-ui/date-utils': 2.5.6 + '@gravity-ui/icons': 2.16.0(react@17.0.2) + '@gravity-ui/uikit': 7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + tslib: 2.8.1 + + '@gravity-ui/date-utils@2.5.6': + dependencies: + dayjs: 1.11.10 + lodash: 4.17.21 + + '@gravity-ui/eslint-config@3.2.0(@types/eslint@8.44.1)(eslint@8.57.0)(prettier@3.2.5)(typescript@5.6.3)': + dependencies: + '@babel/core': 7.26.0 + '@babel/eslint-parser': 7.24.1(@babel/core@7.26.0)(eslint@8.57.0) + '@babel/preset-react': 7.26.3(@babel/core@7.26.0) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0)(typescript@5.6.3) + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.6.3) + eslint: 8.57.0 + eslint-config-prettier: 9.1.0(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) + eslint-plugin-prettier: 5.1.3(@types/eslint@8.44.1)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5) + eslint-plugin-react: 7.34.1(eslint@8.57.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) + eslint-plugin-security: 1.7.1 + optionalDependencies: + prettier: 3.2.5 + transitivePeerDependencies: + - '@types/eslint' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + - typescript + + '@gravity-ui/expresskit@3.0.2(@gravity-ui/nodekit@2.10.1(axios@1.13.2))': + dependencies: + '@gravity-ui/nodekit': 2.10.1(axios@1.13.2) + accept-language-parser: 1.5.0 + body-parser: 1.20.4 + cookie-parser: 1.4.7 + csp-header: 5.2.1 + express: 4.21.2 + express-csp-header: 5.2.1 + uuid: 9.0.1 + zod: 4.3.6 + transitivePeerDependencies: + - supports-color + + '@gravity-ui/gateway@4.10.4': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@grpc/proto-loader': 0.7.15 + ajv: 8.12.0 + axios: 1.13.2 + axios-retry: 3.9.1 + lodash: 4.17.21 + object-sizeof: 2.6.5 + protobufjs: 7.4.0 + uuid: 9.0.1 + transitivePeerDependencies: + - debug + + '@gravity-ui/i18n@1.8.0': {} + + '@gravity-ui/icons@2.16.0(react@17.0.2)': + dependencies: + tslib: 2.8.1 + optionalDependencies: + react: 17.0.2 + + '@gravity-ui/markdown-editor@15.26.1(@diplodoc/cut-extension@0.7.4(@types/markdown-it@13.0.7)(markdown-it@13.0.2)(react@17.0.2))(@diplodoc/file-extension@0.2.1(@types/markdown-it@13.0.7)(markdown-it@13.0.2))(@diplodoc/latex-extension@1.1.0(katex@0.16.21)(markdown-it@13.0.2)(react@17.0.2))(@diplodoc/mermaid-extension@1.4.0(markdown-it@13.0.2)(react@17.0.2))(@diplodoc/tabs-extension@3.7.5(react@17.0.2))(@diplodoc/transform@4.64.1(@types/markdown-it@13.0.7)(highlight.js@11.9.0)(react@17.0.2))(@gravity-ui/components@4.12.0(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(highlight.js@11.9.0)(katex@0.16.21)(lowlight@3.1.0)(markdown-it@13.0.2)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@bem-react/classname': 1.7.0 + '@bem-react/classnames': 1.3.10 + '@codemirror/autocomplete': 6.20.0 + '@codemirror/commands': 6.10.2 + '@codemirror/lang-markdown': 6.5.0 + '@codemirror/language': 6.12.1 + '@codemirror/search': 6.6.0 + '@codemirror/state': 6.5.4 + '@codemirror/view': 6.39.13 + '@diplodoc/color-extension': 0.1.1 + '@diplodoc/cut-extension': 0.7.4(@types/markdown-it@13.0.7)(markdown-it@13.0.2)(react@17.0.2) + '@diplodoc/file-extension': 0.2.1(@types/markdown-it@13.0.7)(markdown-it@13.0.2) + '@diplodoc/tabs-extension': 3.7.5(react@17.0.2) + '@diplodoc/transform': 4.64.1(@types/markdown-it@13.0.7)(highlight.js@11.9.0)(react@17.0.2) + '@diplodoc/utils': 2.1.0(react@17.0.2) + '@gravity-ui/i18n': 1.8.0 + '@gravity-ui/icons': 2.16.0(react@17.0.2) + '@gravity-ui/uikit': 7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@lezer/highlight': 1.2.3 + '@lezer/markdown': 1.6.3 + '@types/is-number': 7.0.5 + '@types/markdown-it': 12.2.3 + base64-arraybuffer: 1.0.2 + is-number: 7.0.0 + katex: 0.16.21 + lodash: 4.17.21 + markdown-it: 13.0.2 + markdown-it-attrs: 4.3.1(markdown-it@13.0.2) + markdown-it-emoji: 2.0.2 + markdown-it-ins: 3.0.1 + markdown-it-mark: 3.0.1 + markdown-it-sub: 1.0.0 + prosemirror-autocomplete: 0.4.3 + prosemirror-codemark: 0.4.2(prosemirror-inputrules@1.5.1)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6) + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-test-builder: 1.1.1 + prosemirror-transform: 1.11.0 + prosemirror-utils: 1.2.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4) + prosemirror-view: 1.41.6 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + react-error-boundary: 3.1.4(react@17.0.2) + react-hotkeys-hook: 4.5.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-use: 17.6.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + ts-dedent: 2.2.0 + tslib: 2.8.1 + uuid: 11.0.5 + optionalDependencies: + '@diplodoc/latex-extension': 1.1.0(katex@0.16.21)(markdown-it@13.0.2)(react@17.0.2) + '@diplodoc/mermaid-extension': 1.4.0(markdown-it@13.0.2)(react@17.0.2) + '@gravity-ui/components': 4.12.0(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + highlight.js: 11.9.0 + lowlight: 3.1.0 + + '@gravity-ui/navigation@3.8.0(@bem-react/classname@1.7.0)(@gravity-ui/icons@2.16.0(react@17.0.2))(@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@bem-react/classname': 1.7.0 + '@floating-ui/react': 0.27.13(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/icons': 2.16.0(react@17.0.2) + '@gravity-ui/uikit': 7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + react-transition-group: 4.4.5(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + tslib: 2.8.1 + + '@gravity-ui/nodekit@2.10.1(axios@1.13.2)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/exporter-trace-otlp-grpc': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-node': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.32.0 + axios: 1.13.2 + dotenv: 16.4.5 + pino: 8.21.0 + pino-pretty: 11.3.0 + transitivePeerDependencies: + - supports-color + + '@gravity-ui/prettier-config@1.1.0(prettier@3.2.5)': + dependencies: + prettier: 3.2.5 + + '@gravity-ui/react-data-table@2.1.1(react@17.0.2)': + dependencies: + '@bem-react/classname': 1.7.0 + react: 17.0.2 + react-list: 0.8.18(react@17.0.2) + tslib: 2.8.1 + + '@gravity-ui/sdk@1.5.1(@gravity-ui/gateway@4.10.4)': + dependencies: + '@gravity-ui/axios-wrapper': 1.5.1(axios@1.13.2)(lodash@4.17.21) + '@gravity-ui/gateway': 4.10.4 + axios: 1.13.2 + axios-retry: 3.9.1 + lodash: 4.17.21 + transitivePeerDependencies: + - debug + + '@gravity-ui/stylelint-config@4.0.1(postcss@8.4.47)(prettier@3.2.5)(stylelint@15.11.0(typescript@5.6.3))': + dependencies: + postcss: 8.4.47 + postcss-scss: 4.0.9(postcss@8.4.47) + stylelint: 15.11.0(typescript@5.6.3) + stylelint-order: 6.0.4(stylelint@15.11.0(typescript@5.6.3)) + stylelint-prettier: 4.1.0(prettier@3.2.5)(stylelint@15.11.0(typescript@5.6.3)) + stylelint-scss: 5.3.2(stylelint@15.11.0(typescript@5.6.3)) + optionalDependencies: + prettier: 3.2.5 + + '@gravity-ui/tsconfig@1.0.0': {} + + '@gravity-ui/ui-logger@1.1.0': {} + + '@gravity-ui/uikit@7.18.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@bem-react/classname': 1.7.0 + '@floating-ui/react': 0.27.13(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@gravity-ui/i18n': 1.8.0 + '@gravity-ui/icons': 2.16.0(react@17.0.2) + '@tanstack/react-virtual': 3.13.12(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + blueimp-md5: 2.19.0 + lodash: 4.17.21 + rc-slider: 11.1.8(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react: 17.0.2 + react-beautiful-dnd: 13.1.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-dom: 17.0.2(react@17.0.2) + react-transition-group: 4.4.5(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-virtualized-auto-sizer: 1.0.24(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-window: 1.8.11(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + tabbable: 6.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - react-native + + '@gravity-ui/yagr@4.11.0': + dependencies: + uplot: 1.6.32 + + '@grpc/grpc-js@1.14.3': + dependencies: + '@grpc/proto-loader': 0.8.0 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.1 + protobufjs: 7.5.4 + yargs: 17.7.2 + + '@grpc/proto-loader@0.8.0': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.1 + protobufjs: 7.5.4 + yargs: 17.7.2 + + '@humanwhocodes/config-array@0.11.14': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3(supports-color@5.5.0) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + mlly: 1.8.0 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@27.5.1': + dependencies: + '@jest/types': 27.5.1 + '@types/node': 20.19.33 + chalk: 4.1.2 + jest-message-util: 27.5.1 + jest-util: 27.5.1 + slash: 3.0.0 + + '@jest/console@29.4.3': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.19.33 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.4.3(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3))': + dependencies: + '@jest/console': 29.4.3 + '@jest/reporters': 29.4.3 + '@jest/test-result': 29.4.3 + '@jest/transform': 29.4.3 + '@jest/types': 29.6.3 + '@types/node': 20.16.10 + ansi-escapes: 4.3.1 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.4.3 + jest-config: 29.4.3(@types/node@20.16.10)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)) + jest-haste-map: 29.4.3 + jest-message-util: 29.7.0 + jest-regex-util: 29.4.3 + jest-resolve: 29.4.3 + jest-resolve-dependencies: 29.4.3 + jest-runner: 29.4.3 + jest-runtime: 29.4.3 + jest-snapshot: 29.4.3 + jest-util: 29.7.0 + jest-validate: 29.4.3 + jest-watcher: 29.4.3 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - supports-color + - ts-node + + '@jest/environment@27.5.1': + dependencies: + '@jest/fake-timers': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 20.16.12 + jest-mock: 27.5.1 + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.16.12 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.4.3': + dependencies: + jest-get-type: 29.4.3 + + '@jest/expect@29.4.3': + dependencies: + expect: 29.4.3 + jest-snapshot: 29.4.3 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@27.5.1': + dependencies: + '@jest/types': 27.5.1 + '@sinonjs/fake-timers': 8.1.0 + '@types/node': 20.19.33 + jest-message-util: 27.5.1 + jest-mock: 27.5.1 + jest-util: 27.5.1 + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.16.12 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@27.5.1': + dependencies: + '@jest/environment': 27.5.1 + '@jest/types': 27.5.1 + expect: 27.5.1 + + '@jest/globals@29.4.3': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.4.3 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.4.3': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.4.3 + '@jest/test-result': 29.4.3 + '@jest/transform': 29.4.3 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 20.19.33 + chalk: 4.1.2 + collect-v8-coverage: 1.0.1 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-instrument: 5.2.1 + istanbul-lib-report: 3.0.0 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.5 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.1.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@27.5.1': + dependencies: + callsites: 3.1.0 + graceful-fs: 4.2.11 + source-map: 0.6.1 + + '@jest/source-map@29.4.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@27.5.1': + dependencies: + '@jest/console': 27.5.1 + '@jest/types': 27.5.1 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.1 + + '@jest/test-result@29.4.3': + dependencies: + '@jest/console': 29.4.3 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.1 + + '@jest/test-sequencer@29.4.3': + dependencies: + '@jest/test-result': 29.4.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.4.3 + slash: 3.0.0 + + '@jest/transform@27.5.1': + dependencies: + '@babel/core': 7.26.10 + '@jest/types': 27.5.1 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 1.7.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 27.5.1 + jest-regex-util: 27.5.1 + jest-util: 27.5.1 + micromatch: 4.0.8 + pirates: 4.0.5 + slash: 3.0.0 + source-map: 0.6.1 + write-file-atomic: 3.0.3 + transitivePeerDependencies: + - supports-color + + '@jest/transform@29.4.3': + dependencies: + '@babel/core': 7.26.10 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.4.3 + jest-regex-util: 29.4.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.5 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@27.5.1': + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 20.16.12 + '@types/yargs': 16.0.9 + chalk: 4.1.2 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 20.16.12 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jitl/quickjs-ffi-types@0.31.0': {} + + '@jitl/quickjs-wasmfile-debug-asyncify@0.31.0': + dependencies: + '@jitl/quickjs-ffi-types': 0.31.0 + + '@jitl/quickjs-wasmfile-debug-sync@0.31.0': + dependencies: + '@jitl/quickjs-ffi-types': 0.31.0 + + '@jitl/quickjs-wasmfile-release-asyncify@0.31.0': + dependencies: + '@jitl/quickjs-ffi-types': 0.31.0 + + '@jitl/quickjs-wasmfile-release-sync@0.31.0': + dependencies: + '@jitl/quickjs-ffi-types': 0.31.0 + + '@jridgewell/gen-mapping@0.1.1': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.0': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@js-sdsl/ordered-map@4.4.2': {} + + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@jsonjoy.com/json-pack@1.1.0(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) + '@jsonjoy.com/util': 1.5.0(tslib@2.8.1) + hyperdyperid: 1.2.0 + thingies: 1.21.0(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/util@1.5.0(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@leichtgewicht/ip-codec@2.0.5': {} + + '@lezer/common@1.5.1': {} + + '@lezer/css@1.3.0': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/highlight@1.2.3': + dependencies: + '@lezer/common': 1.5.1 + + '@lezer/html@1.3.13': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/javascript@1.5.4': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + '@lezer/lr': 1.4.8 + + '@lezer/lr@1.4.8': + dependencies: + '@lezer/common': 1.5.1 + + '@lezer/markdown@1.6.3': + dependencies: + '@lezer/common': 1.5.1 + '@lezer/highlight': 1.2.3 + + '@marijn/find-cluster-break@1.0.2': {} + + '@mermaid-js/parser@0.6.3': + dependencies: + langium: 3.3.1 + + '@microsoft/fetch-event-source@2.0.1': {} + + '@modern-js/node-bundle-require@2.68.2': + dependencies: + '@modern-js/utils': 2.68.2 + '@swc/helpers': 0.5.17 + esbuild: 0.25.5 + + '@modern-js/utils@2.68.2': + dependencies: + '@swc/helpers': 0.5.17 + caniuse-lite: 1.0.30001764 + lodash: 4.17.21 + rslog: 1.2.9 + + '@module-federation/bridge-react-webpack-plugin@0.18.4': + dependencies: + '@module-federation/sdk': 0.18.4 + '@types/semver': 7.5.8 + semver: 7.6.3 + + '@module-federation/cli@0.18.4(typescript@5.6.3)': + dependencies: + '@modern-js/node-bundle-require': 2.68.2 + '@module-federation/dts-plugin': 0.18.4(typescript@5.6.3) + '@module-federation/sdk': 0.18.4 + chalk: 3.0.0 + commander: 11.1.0 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - typescript + - utf-8-validate + - vue-tsc + + '@module-federation/data-prefetch@0.18.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@module-federation/runtime': 0.18.4 + '@module-federation/sdk': 0.18.4 + fs-extra: 9.1.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + '@module-federation/dts-plugin@0.18.4(typescript@5.6.3)': + dependencies: + '@module-federation/error-codes': 0.18.4 + '@module-federation/managers': 0.18.4 + '@module-federation/sdk': 0.18.4 + '@module-federation/third-party-dts-extractor': 0.18.4 + adm-zip: 0.5.16 + ansi-colors: 4.1.3 + axios: 1.13.2 + chalk: 3.0.0 + fs-extra: 9.1.0 + isomorphic-ws: 5.0.0(ws@8.18.0) + koa: 3.0.1 + lodash.clonedeepwith: 4.5.0 + log4js: 6.9.1 + node-schedule: 2.1.1 + rambda: 9.4.2 + typescript: 5.6.3 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@module-federation/enhanced@0.18.4(@rspack/core@1.5.5(@swc/helpers@0.5.17))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)(typescript@5.6.3)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@module-federation/bridge-react-webpack-plugin': 0.18.4 + '@module-federation/cli': 0.18.4(typescript@5.6.3) + '@module-federation/data-prefetch': 0.18.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + '@module-federation/dts-plugin': 0.18.4(typescript@5.6.3) + '@module-federation/error-codes': 0.18.4 + '@module-federation/inject-external-runtime-core-plugin': 0.18.4(@module-federation/runtime-tools@0.18.4) + '@module-federation/managers': 0.18.4 + '@module-federation/manifest': 0.18.4(typescript@5.6.3) + '@module-federation/rspack': 0.18.4(@rspack/core@1.5.5(@swc/helpers@0.5.17))(typescript@5.6.3) + '@module-federation/runtime-tools': 0.18.4 + '@module-federation/sdk': 0.18.4 + btoa: 1.2.1 + schema-utils: 4.3.3 + upath: 2.0.1 + optionalDependencies: + typescript: 5.6.3 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - debug + - react + - react-dom + - supports-color + - utf-8-validate + + '@module-federation/error-codes@0.18.0': {} + + '@module-federation/error-codes@0.18.4': {} + + '@module-federation/inject-external-runtime-core-plugin@0.18.4(@module-federation/runtime-tools@0.18.4)': + dependencies: + '@module-federation/runtime-tools': 0.18.4 + + '@module-federation/managers@0.18.4': + dependencies: + '@module-federation/sdk': 0.18.4 + find-pkg: 2.0.0 + fs-extra: 9.1.0 + + '@module-federation/manifest@0.18.4(typescript@5.6.3)': + dependencies: + '@module-federation/dts-plugin': 0.18.4(typescript@5.6.3) + '@module-federation/managers': 0.18.4 + '@module-federation/sdk': 0.18.4 + chalk: 3.0.0 + find-pkg: 2.0.0 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - typescript + - utf-8-validate + - vue-tsc + + '@module-federation/rspack@0.18.4(@rspack/core@1.5.5(@swc/helpers@0.5.17))(typescript@5.6.3)': + dependencies: + '@module-federation/bridge-react-webpack-plugin': 0.18.4 + '@module-federation/dts-plugin': 0.18.4(typescript@5.6.3) + '@module-federation/inject-external-runtime-core-plugin': 0.18.4(@module-federation/runtime-tools@0.18.4) + '@module-federation/managers': 0.18.4 + '@module-federation/manifest': 0.18.4(typescript@5.6.3) + '@module-federation/runtime-tools': 0.18.4 + '@module-federation/sdk': 0.18.4 + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + btoa: 1.2.1 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@module-federation/runtime-core@0.18.0': + dependencies: + '@module-federation/error-codes': 0.18.0 + '@module-federation/sdk': 0.18.0 + + '@module-federation/runtime-core@0.18.4': + dependencies: + '@module-federation/error-codes': 0.18.4 + '@module-federation/sdk': 0.18.4 + + '@module-federation/runtime-tools@0.18.0': + dependencies: + '@module-federation/runtime': 0.18.0 + '@module-federation/webpack-bundler-runtime': 0.18.0 + + '@module-federation/runtime-tools@0.18.4': + dependencies: + '@module-federation/runtime': 0.18.4 + '@module-federation/webpack-bundler-runtime': 0.18.4 + + '@module-federation/runtime@0.18.0': + dependencies: + '@module-federation/error-codes': 0.18.0 + '@module-federation/runtime-core': 0.18.0 + '@module-federation/sdk': 0.18.0 + + '@module-federation/runtime@0.18.4': + dependencies: + '@module-federation/error-codes': 0.18.4 + '@module-federation/runtime-core': 0.18.4 + '@module-federation/sdk': 0.18.4 + + '@module-federation/sdk@0.18.0': {} + + '@module-federation/sdk@0.18.4': {} + + '@module-federation/third-party-dts-extractor@0.18.4': + dependencies: + find-pkg: 2.0.0 + fs-extra: 9.1.0 + resolve: 1.22.8 + + '@module-federation/webpack-bundler-runtime@0.18.0': + dependencies: + '@module-federation/runtime': 0.18.0 + '@module-federation/sdk': 0.18.0 + + '@module-federation/webpack-bundler-runtime@0.18.4': + dependencies: + '@module-federation/runtime': 0.18.4 + '@module-federation/sdk': 0.18.4 + + '@napi-rs/nice-android-arm-eabi@1.1.1': + optional: true + + '@napi-rs/nice-android-arm64@1.1.1': + optional: true + + '@napi-rs/nice-darwin-arm64@1.1.1': + optional: true + + '@napi-rs/nice-darwin-x64@1.1.1': + optional: true + + '@napi-rs/nice-freebsd-x64@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm-gnueabihf@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-arm64-musl@1.1.1': + optional: true + + '@napi-rs/nice-linux-ppc64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-riscv64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-s390x-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-x64-gnu@1.1.1': + optional: true + + '@napi-rs/nice-linux-x64-musl@1.1.1': + optional: true + + '@napi-rs/nice-openharmony-arm64@1.1.1': + optional: true + + '@napi-rs/nice-win32-arm64-msvc@1.1.1': + optional: true + + '@napi-rs/nice-win32-ia32-msvc@1.1.1': + optional: true + + '@napi-rs/nice-win32-x64-msvc@1.1.1': + optional: true + + '@napi-rs/nice@1.1.1': + optionalDependencies: + '@napi-rs/nice-android-arm-eabi': 1.1.1 + '@napi-rs/nice-android-arm64': 1.1.1 + '@napi-rs/nice-darwin-arm64': 1.1.1 + '@napi-rs/nice-darwin-x64': 1.1.1 + '@napi-rs/nice-freebsd-x64': 1.1.1 + '@napi-rs/nice-linux-arm-gnueabihf': 1.1.1 + '@napi-rs/nice-linux-arm64-gnu': 1.1.1 + '@napi-rs/nice-linux-arm64-musl': 1.1.1 + '@napi-rs/nice-linux-ppc64-gnu': 1.1.1 + '@napi-rs/nice-linux-riscv64-gnu': 1.1.1 + '@napi-rs/nice-linux-s390x-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-gnu': 1.1.1 + '@napi-rs/nice-linux-x64-musl': 1.1.1 + '@napi-rs/nice-openharmony-arm64': 1.1.1 + '@napi-rs/nice-win32-arm64-msvc': 1.1.1 + '@napi-rs/nice-win32-ia32-msvc': 1.1.1 + '@napi-rs/nice-win32-x64-msvc': 1.1.1 + optional: true + + '@napi-rs/wasm-runtime@1.1.1': + dependencies: + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + dependencies: + eslint-scope: 5.1.1 + + '@node-rs/crc32-android-arm-eabi@1.7.2': + optional: true + + '@node-rs/crc32-android-arm64@1.7.2': + optional: true + + '@node-rs/crc32-darwin-arm64@1.7.2': + optional: true + + '@node-rs/crc32-darwin-x64@1.7.2': + optional: true + + '@node-rs/crc32-freebsd-x64@1.7.2': + optional: true + + '@node-rs/crc32-linux-arm-gnueabihf@1.7.2': + optional: true + + '@node-rs/crc32-linux-arm64-gnu@1.7.2': + optional: true + + '@node-rs/crc32-linux-arm64-musl@1.7.2': + optional: true + + '@node-rs/crc32-linux-x64-gnu@1.7.2': + optional: true + + '@node-rs/crc32-linux-x64-musl@1.7.2': + optional: true + + '@node-rs/crc32-win32-arm64-msvc@1.7.2': + optional: true + + '@node-rs/crc32-win32-ia32-msvc@1.7.2': + optional: true + + '@node-rs/crc32-win32-x64-msvc@1.7.2': + optional: true + + '@node-rs/crc32@1.7.2': + optionalDependencies: + '@node-rs/crc32-android-arm-eabi': 1.7.2 + '@node-rs/crc32-android-arm64': 1.7.2 + '@node-rs/crc32-darwin-arm64': 1.7.2 + '@node-rs/crc32-darwin-x64': 1.7.2 + '@node-rs/crc32-freebsd-x64': 1.7.2 + '@node-rs/crc32-linux-arm-gnueabihf': 1.7.2 + '@node-rs/crc32-linux-arm64-gnu': 1.7.2 + '@node-rs/crc32-linux-arm64-musl': 1.7.2 + '@node-rs/crc32-linux-x64-gnu': 1.7.2 + '@node-rs/crc32-linux-x64-musl': 1.7.2 + '@node-rs/crc32-win32-arm64-msvc': 1.7.2 + '@node-rs/crc32-win32-ia32-msvc': 1.7.2 + '@node-rs/crc32-win32-x64-msvc': 1.7.2 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.11.0 + + '@okikio/sharedworker@1.0.7': {} + + '@opentelemetry/api-logs@0.54.2': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.27.0 + + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/exporter-logs-otlp-grpc@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.54.2(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-http@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.54.2 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.54.2(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-logs-otlp-proto@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.54.2 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-grpc@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-grpc-exporter-base': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-http@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-trace-otlp-proto@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/exporter-zipkin@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 + + '@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.54.2 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.13.1 + require-in-the-middle: 7.5.2 + semver: 7.7.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.54.2(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-grpc-exporter-base@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@grpc/grpc-js': 1.14.3 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.54.2(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.54.2 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + protobufjs: 7.5.4 + + '@opentelemetry/propagator-b3@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/resources@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 + + '@opentelemetry/sdk-logs@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.54.2 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-node@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.54.2 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-grpc': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-http': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-logs-otlp-proto': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-grpc': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-http': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-trace-otlp-proto': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/exporter-zipkin': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 + + '@opentelemetry/sdk-trace-node@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + semver: 7.7.3 + + '@opentelemetry/semantic-conventions@1.27.0': {} + + '@opentelemetry/semantic-conventions@1.28.0': {} + + '@opentelemetry/semantic-conventions@1.32.0': {} + + '@parcel/watcher-android-arm64@2.5.0': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.0': + optional: true + + '@parcel/watcher-darwin-x64@2.5.0': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.0': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.0': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.0': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.0': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.0': + optional: true + + '@parcel/watcher-win32-arm64@2.5.0': + optional: true + + '@parcel/watcher-win32-ia32@2.5.0': + optional: true + + '@parcel/watcher-win32-x64@2.5.0': + optional: true + + '@parcel/watcher@2.5.0': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.0 + '@parcel/watcher-darwin-arm64': 2.5.0 + '@parcel/watcher-darwin-x64': 2.5.0 + '@parcel/watcher-freebsd-x64': 2.5.0 + '@parcel/watcher-linux-arm-glibc': 2.5.0 + '@parcel/watcher-linux-arm-musl': 2.5.0 + '@parcel/watcher-linux-arm64-glibc': 2.5.0 + '@parcel/watcher-linux-arm64-musl': 2.5.0 + '@parcel/watcher-linux-x64-glibc': 2.5.0 + '@parcel/watcher-linux-x64-musl': 2.5.0 + '@parcel/watcher-win32-arm64': 2.5.0 + '@parcel/watcher-win32-ia32': 2.5.0 + '@parcel/watcher-win32-x64': 2.5.0 + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.1.1': {} + + '@playwright/test@1.48.2': + dependencies: + playwright: 1.48.2 + + '@pmmmwh/react-refresh-webpack-plugin@0.5.15(@types/webpack@5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17)))(react-refresh@0.14.2)(type-fest@3.13.1)(webpack-dev-server@5.2.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + ansi-html: 0.0.9 + core-js-pure: 3.39.0 + error-stack-parser: 2.1.4 + html-entities: 2.6.0 + loader-utils: 2.0.4 + react-refresh: 0.14.2 + schema-utils: 4.3.3 + source-map: 0.7.4 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + optionalDependencies: + '@types/webpack': 5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17)) + type-fest: 3.13.1 + webpack-dev-server: 5.2.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + + '@polka/url@1.0.0-next.29': {} + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@react-dnd/asap@4.0.1': {} + + '@react-dnd/invariant@2.0.0': {} + + '@react-dnd/shallowequal@2.0.0': {} + + '@reduxjs/toolkit@1.9.7(react-redux@7.2.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react@17.0.2)': + dependencies: + immer: 9.0.21 + redux: 4.2.1 + redux-thunk: 2.4.2(redux@4.2.1) + reselect: 4.1.8 + optionalDependencies: + react: 17.0.2 + react-redux: 7.2.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + + '@rsbuild/plugin-check-syntax@1.3.0': + dependencies: + acorn: 8.14.1 + browserslist-to-es-version: 1.1.0 + htmlparser2: 10.0.0 + picocolors: 1.1.1 + source-map: 0.7.6 + + '@rsdoctor/client@1.1.10': {} + + '@rsdoctor/core@1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@rsbuild/plugin-check-syntax': 1.3.0 + '@rsdoctor/graph': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/sdk': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/types': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/utils': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + axios: 1.13.2 + browserslist-load-config: 1.0.0 + enhanced-resolve: 5.12.0 + filesize: 10.1.6 + fs-extra: 11.3.0 + lodash: 4.17.21 + path-browserify: 1.0.1 + semver: 7.7.3 + source-map: 0.7.4 + webpack-bundle-analyzer: 4.10.2 + transitivePeerDependencies: + - '@rsbuild/core' + - '@rspack/core' + - bufferutil + - debug + - supports-color + - utf-8-validate + - webpack + + '@rsdoctor/graph@1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@rsdoctor/types': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/utils': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + lodash.unionby: 4.8.0 + socket.io: 4.8.1 + source-map: 0.7.4 + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - supports-color + - utf-8-validate + - webpack + + '@rsdoctor/rspack-plugin@1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@rsdoctor/core': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/graph': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/sdk': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/types': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/utils': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + lodash: 4.17.21 + optionalDependencies: + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + transitivePeerDependencies: + - '@rsbuild/core' + - bufferutil + - debug + - supports-color + - utf-8-validate + - webpack + + '@rsdoctor/sdk@1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@rsdoctor/client': 1.1.10 + '@rsdoctor/graph': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/types': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/utils': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@types/fs-extra': 11.0.4 + body-parser: 1.20.3 + cors: 2.8.5 + dayjs: 1.11.13 + fs-extra: 11.3.0 + json-cycle: 1.5.0 + lodash: 4.17.21 + open: 8.4.2 + sirv: 2.0.4 + socket.io: 4.8.1 + source-map: 0.7.4 + tapable: 2.2.2 + transitivePeerDependencies: + - '@rspack/core' + - bufferutil + - supports-color + - utf-8-validate + - webpack + + '@rsdoctor/types@1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@types/connect': 3.4.38 + '@types/estree': 1.0.5 + '@types/tapable': 2.2.7 + source-map: 0.7.4 + optionalDependencies: + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + '@rsdoctor/utils@1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@babel/code-frame': 7.26.2 + '@rsdoctor/types': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@types/estree': 1.0.5 + acorn: 8.14.1 + acorn-import-attributes: 1.9.5(acorn@8.14.1) + acorn-walk: 8.3.4 + connect: 3.7.0 + deep-eql: 4.1.4 + envinfo: 7.14.0 + filesize: 10.1.6 + fs-extra: 11.3.0 + get-port: 5.1.1 + json-stream-stringify: 3.0.1 + lines-and-columns: 2.0.4 + picocolors: 1.1.1 + rslog: 1.2.9 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - '@rspack/core' + - supports-color + - webpack + + '@rsdoctor/webpack-plugin@1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@rsdoctor/core': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/graph': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/sdk': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/types': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@rsdoctor/utils': 1.1.10(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + fs-extra: 11.3.0 + lodash: 4.17.21 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + transitivePeerDependencies: + - '@rsbuild/core' + - '@rspack/core' + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@rspack/binding-darwin-arm64@1.5.5': + optional: true + + '@rspack/binding-darwin-x64@1.5.5': + optional: true + + '@rspack/binding-linux-arm64-gnu@1.5.5': + optional: true + + '@rspack/binding-linux-arm64-musl@1.5.5': + optional: true + + '@rspack/binding-linux-x64-gnu@1.5.5': + optional: true + + '@rspack/binding-linux-x64-musl@1.5.5': + optional: true + + '@rspack/binding-wasm32-wasi@1.5.5': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@rspack/binding-win32-arm64-msvc@1.5.5': + optional: true + + '@rspack/binding-win32-ia32-msvc@1.5.5': + optional: true + + '@rspack/binding-win32-x64-msvc@1.5.5': + optional: true + + '@rspack/binding@1.5.5': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.5.5 + '@rspack/binding-darwin-x64': 1.5.5 + '@rspack/binding-linux-arm64-gnu': 1.5.5 + '@rspack/binding-linux-arm64-musl': 1.5.5 + '@rspack/binding-linux-x64-gnu': 1.5.5 + '@rspack/binding-linux-x64-musl': 1.5.5 + '@rspack/binding-wasm32-wasi': 1.5.5 + '@rspack/binding-win32-arm64-msvc': 1.5.5 + '@rspack/binding-win32-ia32-msvc': 1.5.5 + '@rspack/binding-win32-x64-msvc': 1.5.5 + + '@rspack/core@1.5.5(@swc/helpers@0.5.17)': + dependencies: + '@module-federation/runtime-tools': 0.18.0 + '@rspack/binding': 1.5.5 + '@rspack/lite-tapable': 1.0.1 + optionalDependencies: + '@swc/helpers': 0.5.17 + + '@rspack/dev-server@1.1.4(@rspack/core@1.5.5(@swc/helpers@0.5.17))(@types/express@4.17.21)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + chokidar: 3.6.0 + http-proxy-middleware: 2.0.9(@types/express@4.17.21) + p-retry: 6.2.0 + webpack-dev-server: 5.2.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + ws: 8.18.3 + transitivePeerDependencies: + - '@types/express' + - bufferutil + - debug + - supports-color + - utf-8-validate + - webpack + - webpack-cli + + '@rspack/lite-tapable@1.0.1': {} + + '@rspack/plugin-react-refresh@1.5.3(react-refresh@0.14.2)': + dependencies: + error-stack-parser: 2.1.4 + html-entities: 2.6.0 + react-refresh: 0.14.2 + + '@scarf/scarf@1.4.0': {} + + '@sec-ant/readable-stream@0.4.1': {} + + '@sinclair/typebox@0.27.8': {} + + '@sindresorhus/is@5.6.0': {} + + '@sinonjs/commons@1.8.6': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@sinonjs/fake-timers@8.1.0': + dependencies: + '@sinonjs/commons': 1.8.6 + + '@smithy/abort-controller@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader-native@2.0.0': + dependencies: + '@smithy/util-base64': 2.0.0 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@2.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + '@smithy/util-config-provider': 2.0.0 + '@smithy/util-middleware': 2.0.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@2.0.1': + dependencies: + '@smithy/node-config-provider': 2.0.1 + '@smithy/property-provider': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/url-parser': 2.0.1 + tslib: 2.8.1 + + '@smithy/eventstream-codec@2.0.1': + dependencies: + '@aws-crypto/crc32': 3.0.0 + '@smithy/types': 2.0.2 + '@smithy/util-hex-encoding': 2.0.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@2.0.1': + dependencies: + '@smithy/eventstream-serde-universal': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@2.0.1': + dependencies: + '@smithy/eventstream-serde-universal': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/eventstream-serde-universal@2.0.1': + dependencies: + '@smithy/eventstream-codec': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@2.0.1': + dependencies: + '@smithy/protocol-http': 2.0.1 + '@smithy/querystring-builder': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/util-base64': 2.0.0 + tslib: 2.8.1 + + '@smithy/hash-blob-browser@2.0.1': + dependencies: + '@smithy/chunked-blob-reader': 2.0.0 + '@smithy/chunked-blob-reader-native': 2.0.0 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/hash-node@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + '@smithy/util-buffer-from': 2.0.0 + '@smithy/util-utf8': 2.0.0 + tslib: 2.8.1 + + '@smithy/hash-stream-node@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + '@smithy/util-utf8': 2.0.0 + tslib: 2.8.1 + + '@smithy/invalid-dependency@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/md5-js@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + '@smithy/util-utf8': 2.0.0 + tslib: 2.8.1 + + '@smithy/middleware-content-length@2.0.1': + dependencies: + '@smithy/protocol-http': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@2.0.1': + dependencies: + '@smithy/middleware-serde': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/url-parser': 2.0.1 + '@smithy/util-middleware': 2.0.0 + tslib: 2.8.1 + + '@smithy/middleware-retry@2.0.1': + dependencies: + '@smithy/protocol-http': 2.0.1 + '@smithy/service-error-classification': 2.0.0 + '@smithy/types': 2.0.2 + '@smithy/util-middleware': 2.0.0 + '@smithy/util-retry': 2.0.0 + tslib: 2.8.1 + uuid: 8.3.2 + + '@smithy/middleware-serde@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/middleware-stack@2.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/node-config-provider@2.0.1': + dependencies: + '@smithy/property-provider': 2.0.1 + '@smithy/shared-ini-file-loader': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/node-http-handler@2.0.1': + dependencies: + '@smithy/abort-controller': 2.0.1 + '@smithy/protocol-http': 2.0.1 + '@smithy/querystring-builder': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/property-provider@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/protocol-http@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/querystring-builder@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + '@smithy/util-uri-escape': 2.0.0 + tslib: 2.8.1 + + '@smithy/querystring-parser@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/service-error-classification@2.0.0': {} + + '@smithy/shared-ini-file-loader@2.0.1': + dependencies: + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/signature-v4@2.0.1': + dependencies: + '@smithy/eventstream-codec': 2.0.1 + '@smithy/is-array-buffer': 2.0.0 + '@smithy/types': 2.0.2 + '@smithy/util-hex-encoding': 2.0.0 + '@smithy/util-middleware': 2.0.0 + '@smithy/util-uri-escape': 2.0.0 + '@smithy/util-utf8': 2.0.0 + tslib: 2.8.1 + + '@smithy/smithy-client@2.0.1': + dependencies: + '@smithy/middleware-stack': 2.0.0 + '@smithy/types': 2.0.2 + '@smithy/util-stream': 2.0.1 + tslib: 2.8.1 + + '@smithy/types@2.0.2': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@2.0.1': + dependencies: + '@smithy/querystring-parser': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/util-base64@2.0.0': + dependencies: + '@smithy/util-buffer-from': 2.0.0 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@2.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@2.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.0.0': + dependencies: + '@smithy/is-array-buffer': 2.0.0 + tslib: 2.8.1 + + '@smithy/util-config-provider@2.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@2.0.1': + dependencies: + '@smithy/property-provider': 2.0.1 + '@smithy/types': 2.0.2 + bowser: 2.11.0 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@2.0.1': + dependencies: + '@smithy/config-resolver': 2.0.1 + '@smithy/credential-provider-imds': 2.0.1 + '@smithy/node-config-provider': 2.0.1 + '@smithy/property-provider': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@2.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@2.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-retry@2.0.0': + dependencies: + '@smithy/service-error-classification': 2.0.0 + tslib: 2.8.1 + + '@smithy/util-stream@2.0.1': + dependencies: + '@smithy/fetch-http-handler': 2.0.1 + '@smithy/node-http-handler': 2.0.1 + '@smithy/types': 2.0.2 + '@smithy/util-base64': 2.0.0 + '@smithy/util-buffer-from': 2.0.0 + '@smithy/util-hex-encoding': 2.0.0 + '@smithy/util-utf8': 2.0.0 + tslib: 2.8.1 + + '@smithy/util-uri-escape@2.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.0.0': + dependencies: + '@smithy/util-buffer-from': 2.0.0 + tslib: 2.8.1 + + '@smithy/util-waiter@2.0.1': + dependencies: + '@smithy/abort-controller': 2.0.1 + '@smithy/types': 2.0.2 + tslib: 2.8.1 + + '@socket.io/component-emitter@3.1.2': {} + + '@statoscope/cli@5.28.2(@swc/core@1.13.5(@swc/helpers@0.5.17))': + dependencies: + '@discoveryjs/json-ext': 0.5.7 + '@statoscope/config': 5.28.1 + '@statoscope/helpers': 5.28.1 + '@statoscope/report-writer': 5.28.1 + '@statoscope/stats': 5.28.1 + '@statoscope/stats-extension-custom-reports': 5.28.1 + '@statoscope/stats-validator': 5.28.1 + '@statoscope/stats-validator-reporter-console': 5.28.1 + '@statoscope/stats-validator-reporter-stats-report': 5.28.2(@swc/core@1.13.5(@swc/helpers@0.5.17)) + '@statoscope/types': 5.28.1 + '@statoscope/webpack-model': 5.28.2(@swc/core@1.13.5(@swc/helpers@0.5.17)) + '@statoscope/webpack-ui': 5.28.2 + '@types/yargs': 17.0.33 + open: 8.4.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@statoscope/config@5.28.1': + dependencies: + '@statoscope/types': 5.28.1 + chalk: 4.1.2 + + '@statoscope/extensions@5.28.1': {} + + '@statoscope/helpers@5.28.1': + dependencies: + '@types/archy': 0.0.32 + '@types/semver': 7.5.8 + archy: 1.0.0 + jora: 1.0.0-beta.8 + semver: 7.7.3 + + '@statoscope/report-writer@5.28.1': + dependencies: + '@discoveryjs/json-ext': 0.5.7 + '@types/node': 18.19.87 + '@types/pako': 2.0.1 + pako: 2.1.0 + + '@statoscope/stats-extension-compressed@5.28.1': + dependencies: + '@statoscope/extensions': 5.28.1 + '@statoscope/helpers': 5.28.1 + '@statoscope/stats': 5.28.1 + gzip-size: 6.0.0 + + '@statoscope/stats-extension-custom-reports@5.28.1': + dependencies: + '@statoscope/extensions': 5.28.1 + '@statoscope/helpers': 5.28.1 + '@statoscope/stats': 5.28.1 + '@statoscope/types': 5.28.1 + + '@statoscope/stats-extension-package-info@5.28.1': + dependencies: + '@statoscope/extensions': 5.28.1 + '@statoscope/helpers': 5.28.1 + '@statoscope/stats': 5.28.1 + + '@statoscope/stats-extension-stats-validation-result@5.28.1': + dependencies: + '@statoscope/extensions': 5.28.1 + '@statoscope/helpers': 5.28.1 + '@statoscope/stats': 5.28.1 + '@statoscope/types': 5.28.1 + + '@statoscope/stats-validator-plugin-webpack@5.28.2(@swc/core@1.13.5(@swc/helpers@0.5.17))': + dependencies: + '@statoscope/helpers': 5.28.1 + '@statoscope/stats-extension-compressed': 5.28.1 + '@statoscope/stats-validator': 5.28.1 + '@statoscope/types': 5.28.1 + '@statoscope/webpack-model': 5.28.2(@swc/core@1.13.5(@swc/helpers@0.5.17)) + '@types/semver': 7.5.8 + chalk: 4.1.2 + semver: 7.7.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@statoscope/stats-validator-reporter-console@5.28.1': + dependencies: + '@statoscope/types': 5.28.1 + chalk: 4.1.2 + + '@statoscope/stats-validator-reporter-stats-report@5.28.2(@swc/core@1.13.5(@swc/helpers@0.5.17))': + dependencies: + '@discoveryjs/json-ext': 0.5.7 + '@statoscope/report-writer': 5.28.1 + '@statoscope/stats': 5.28.1 + '@statoscope/stats-extension-stats-validation-result': 5.28.1 + '@statoscope/types': 5.28.1 + '@statoscope/webpack-model': 5.28.2(@swc/core@1.13.5(@swc/helpers@0.5.17)) + '@statoscope/webpack-ui': 5.28.2 + open: 8.4.2 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@statoscope/stats-validator@5.28.1': + dependencies: + '@discoveryjs/json-ext': 0.5.7 + '@statoscope/config': 5.28.1 + '@statoscope/stats': 5.28.1 + '@statoscope/types': 5.28.1 + + '@statoscope/stats@5.28.1': {} + + '@statoscope/types@5.28.1': + dependencies: + '@statoscope/stats': 5.28.1 + + '@statoscope/webpack-model@5.28.2(@swc/core@1.13.5(@swc/helpers@0.5.17))': + dependencies: + '@statoscope/extensions': 5.28.1 + '@statoscope/helpers': 5.28.1 + '@statoscope/stats': 5.28.1 + '@statoscope/stats-extension-compressed': 5.28.1 + '@statoscope/stats-extension-custom-reports': 5.28.1 + '@statoscope/stats-extension-package-info': 5.28.1 + '@statoscope/stats-extension-stats-validation-result': 5.28.1 + '@statoscope/types': 5.28.1 + '@types/md5': 2.3.5 + '@types/webpack': 5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17)) + md5: 2.3.0 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@statoscope/webpack-model@5.29.0(@swc/core@1.13.5(@swc/helpers@0.5.17))': + dependencies: + '@statoscope/extensions': 5.28.1 + '@statoscope/helpers': 5.28.1 + '@statoscope/stats': 5.28.1 + '@statoscope/stats-extension-compressed': 5.28.1 + '@statoscope/stats-extension-custom-reports': 5.28.1 + '@statoscope/stats-extension-package-info': 5.28.1 + '@statoscope/stats-extension-stats-validation-result': 5.28.1 + '@statoscope/types': 5.28.1 + '@types/md5': 2.3.5 + '@types/webpack': 5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17)) + md5: 2.3.0 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@statoscope/webpack-plugin@5.29.0(@rspack/core@1.5.5(@swc/helpers@0.5.17))(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@discoveryjs/json-ext': 0.5.7 + '@statoscope/report-writer': 5.28.1 + '@statoscope/stats': 5.28.1 + '@statoscope/stats-extension-compressed': 5.28.1 + '@statoscope/stats-extension-custom-reports': 5.28.1 + '@statoscope/types': 5.28.1 + '@statoscope/webpack-model': 5.29.0(@swc/core@1.13.5(@swc/helpers@0.5.17)) + '@statoscope/webpack-stats-extension-compressed': 5.29.0(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@statoscope/webpack-stats-extension-package-info': 5.29.0(@rspack/core@1.5.5(@swc/helpers@0.5.17))(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + '@statoscope/webpack-ui': 5.29.0 + '@types/node': 18.19.87 + '@types/webpack': 5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17)) + open: 8.4.2 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + transitivePeerDependencies: + - '@rspack/core' + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@statoscope/webpack-stats-extension-compressed@5.29.0(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@statoscope/stats': 5.28.1 + '@statoscope/stats-extension-compressed': 5.28.1 + '@statoscope/webpack-model': 5.29.0(@swc/core@1.13.5(@swc/helpers@0.5.17)) + '@types/webpack': 5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17)) + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@statoscope/webpack-stats-extension-package-info@5.29.0(@rspack/core@1.5.5(@swc/helpers@0.5.17))(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)))': + dependencies: + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + '@statoscope/stats': 5.28.1 + '@statoscope/stats-extension-package-info': 5.28.1 + '@statoscope/webpack-model': 5.29.0(@swc/core@1.13.5(@swc/helpers@0.5.17)) + '@types/webpack': 5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17)) + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@statoscope/webpack-ui@5.28.2': + dependencies: + '@statoscope/types': 5.28.1 + '@types/md5': 2.3.5 + + '@statoscope/webpack-ui@5.29.0': + dependencies: + '@statoscope/types': 5.28.1 + '@types/md5': 2.3.5 + + '@stripe/react-stripe-js@1.8.1(@stripe/stripe-js@1.30.0)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@stripe/stripe-js': 1.30.0 + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + '@stripe/stripe-js@1.30.0': {} + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@svgr/babel-preset@8.1.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.26.10) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.10) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.10) + + '@svgr/core@8.1.0(typescript@5.6.3)': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.10) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.6.3) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@svgr/hast-util-to-babel-ast@8.0.0': + dependencies: + '@babel/types': 7.26.10 + entities: 4.5.0 + + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.6.3))': + dependencies: + '@babel/core': 7.26.10 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.10) + '@svgr/core': 8.1.0(typescript@5.6.3) + '@svgr/hast-util-to-babel-ast': 8.0.0 + svg-parser: 2.0.4 + transitivePeerDependencies: + - supports-color + + '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.6.3))(typescript@5.6.3)': + dependencies: + '@svgr/core': 8.1.0(typescript@5.6.3) + cosmiconfig: 8.3.6(typescript@5.6.3) + deepmerge: 4.3.1 + svgo: 3.3.2 + transitivePeerDependencies: + - typescript + + '@svgr/webpack@8.1.0(typescript@5.6.3)': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-constant-elements': 7.22.5(@babel/core@7.26.10) + '@babel/preset-env': 7.26.9(@babel/core@7.26.10) + '@babel/preset-react': 7.26.3(@babel/core@7.26.10) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.10) + '@svgr/core': 8.1.0(typescript@5.6.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.6.3)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.6.3))(typescript@5.6.3) + transitivePeerDependencies: + - supports-color + - typescript + + '@swc/cli@0.7.8(@swc/core@1.13.5(@swc/helpers@0.5.17))(chokidar@4.0.3)': + dependencies: + '@swc/core': 1.13.5(@swc/helpers@0.5.17) + '@swc/counter': 0.1.3 + '@xhmikosr/bin-wrapper': 13.2.0 + commander: 8.3.0 + minimatch: 9.0.5 + piscina: 4.9.2 + semver: 7.7.3 + slash: 3.0.0 + source-map: 0.7.6 + tinyglobby: 0.2.15 + optionalDependencies: + chokidar: 4.0.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@swc/core-darwin-arm64@1.13.5': + optional: true + + '@swc/core-darwin-x64@1.13.5': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.13.5': + optional: true + + '@swc/core-linux-arm64-gnu@1.13.5': + optional: true + + '@swc/core-linux-arm64-musl@1.13.5': + optional: true + + '@swc/core-linux-x64-gnu@1.13.5': + optional: true + + '@swc/core-linux-x64-musl@1.13.5': + optional: true + + '@swc/core-win32-arm64-msvc@1.13.5': + optional: true + + '@swc/core-win32-ia32-msvc@1.13.5': + optional: true + + '@swc/core-win32-x64-msvc@1.13.5': + optional: true + + '@swc/core@1.13.5(@swc/helpers@0.5.17)': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.25 + optionalDependencies: + '@swc/core-darwin-arm64': 1.13.5 + '@swc/core-darwin-x64': 1.13.5 + '@swc/core-linux-arm-gnueabihf': 1.13.5 + '@swc/core-linux-arm64-gnu': 1.13.5 + '@swc/core-linux-arm64-musl': 1.13.5 + '@swc/core-linux-x64-gnu': 1.13.5 + '@swc/core-linux-x64-musl': 1.13.5 + '@swc/core-win32-arm64-msvc': 1.13.5 + '@swc/core-win32-ia32-msvc': 1.13.5 + '@swc/core-win32-x64-msvc': 1.13.5 + '@swc/helpers': 0.5.17 + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + + '@swc/plugin-transform-imports@9.1.0': + dependencies: + '@swc/counter': 0.1.3 + + '@swc/types@0.1.25': + dependencies: + '@swc/counter': 0.1.3 + + '@szmarczak/http-timer@5.0.1': + dependencies: + defer-to-connect: 2.0.1 + + '@tanstack/react-table@8.12.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@tanstack/table-core': 8.12.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + '@tanstack/react-virtual@3.13.12(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@tanstack/virtual-core': 3.13.12 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + '@tanstack/react-virtual@3.13.9(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@tanstack/virtual-core': 3.13.9 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + '@tanstack/table-core@8.12.0': {} + + '@tanstack/virtual-core@3.13.12': {} + + '@tanstack/virtual-core@3.13.9': {} + + '@testing-library/dom@8.11.3': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/runtime': 7.26.10 + '@types/aria-query': 4.2.2 + aria-query: 5.3.2 + chalk: 4.1.2 + dom-accessibility-api: 0.5.13 + lz-string: 1.4.4 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@5.16.2': + dependencies: + '@babel/runtime': 7.26.10 + '@types/testing-library__jest-dom': 5.14.3 + aria-query: 5.3.2 + chalk: 3.0.0 + css: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.5.13 + lodash: 4.17.21 + redent: 3.0.0 + + '@testing-library/react@12.1.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + dependencies: + '@babel/runtime': 7.26.10 + '@testing-library/dom': 8.11.3 + '@types/react-dom': 17.0.13 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + '@tokenizer/inflate@0.2.7': + dependencies: + debug: 4.4.3(supports-color@5.5.0) + fflate: 0.8.2 + token-types: 6.1.1 + transitivePeerDependencies: + - supports-color + + '@tokenizer/token@0.3.0': {} + + '@tootallnate/once@2.0.0': {} + + '@trysound/sax@0.2.0': {} + + '@tsconfig/node10@1.0.8': {} + + '@tsconfig/node12@1.0.9': {} + + '@tsconfig/node14@1.0.1': {} + + '@tsconfig/node16@1.0.2': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/archy@0.0.32': {} + + '@types/aria-query@4.2.2': {} + + '@types/babel__core@7.20.0': + dependencies: + '@babel/parser': 7.26.10 + '@babel/types': 7.26.10 + '@types/babel__generator': 7.6.4 + '@types/babel__template': 7.4.1 + '@types/babel__traverse': 7.18.3 + + '@types/babel__generator@7.6.4': + dependencies: + '@babel/types': 7.26.10 + + '@types/babel__template@7.4.1': + dependencies: + '@babel/parser': 7.26.10 + '@babel/types': 7.26.10 + + '@types/babel__traverse@7.18.3': + dependencies: + '@babel/types': 7.26.10 + + '@types/body-parser@1.19.0': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.16.12 + + '@types/bonjour@3.5.13': + dependencies: + '@types/node': 20.16.12 + + '@types/caseless@0.12.5': {} + + '@types/chroma-js@3.1.2': {} + + '@types/connect-history-api-fallback@1.5.4': + dependencies: + '@types/express-serve-static-core': 4.17.33 + '@types/node': 20.16.12 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.16.12 + + '@types/cookie@0.6.0': {} + + '@types/cors@2.8.19': + dependencies: + '@types/node': 20.19.33 + + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.3': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.3': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.3': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.10 + + '@types/d3-delaunay@6.0.1': {} + + '@types/d3-dispatch@3.0.3': {} + + '@types/d3-drag@3.0.3': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.2': {} + + '@types/d3-ease@3.0.0': {} + + '@types/d3-fetch@3.0.3': + dependencies: + '@types/d3-dsv': 3.0.2 + + '@types/d3-force@3.0.5': {} + + '@types/d3-format@3.0.1': {} + + '@types/d3-geo@3.0.4': + dependencies: + '@types/geojson': 7946.0.10 + + '@types/d3-hierarchy@3.1.3': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.0.0': {} + + '@types/d3-polygon@3.0.0': {} + + '@types/d3-quadtree@3.0.2': {} + + '@types/d3-random@3.0.1': {} + + '@types/d3-scale-chromatic@3.0.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.0 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.0.0 + + '@types/d3-time-format@4.0.0': {} + + '@types/d3-time@3.0.0': {} + + '@types/d3-timer@3.0.0': {} + + '@types/d3-transition@3.0.4': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.4': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.3 + '@types/d3-brush': 3.0.3 + '@types/d3-chord': 3.0.3 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.3 + '@types/d3-delaunay': 6.0.1 + '@types/d3-dispatch': 3.0.3 + '@types/d3-drag': 3.0.3 + '@types/d3-dsv': 3.0.2 + '@types/d3-ease': 3.0.0 + '@types/d3-fetch': 3.0.3 + '@types/d3-force': 3.0.5 + '@types/d3-format': 3.0.1 + '@types/d3-geo': 3.0.4 + '@types/d3-hierarchy': 3.1.3 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.0.0 + '@types/d3-polygon': 3.0.0 + '@types/d3-quadtree': 3.0.2 + '@types/d3-random': 3.0.1 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.0.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.0 + '@types/d3-time-format': 4.0.0 + '@types/d3-timer': 3.0.0 + '@types/d3-transition': 3.0.4 + '@types/d3-zoom': 3.0.4 + + '@types/diff-match-patch@1.0.36': {} + + '@types/dompurify@3.0.5': + dependencies: + '@types/trusted-types': 2.0.7 + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 8.44.1 + '@types/estree': 1.0.6 + + '@types/eslint@8.44.1': + dependencies: + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.5': {} + + '@types/estree@1.0.6': {} + + '@types/express-serve-static-core@4.17.33': + dependencies: + '@types/node': 20.16.12 + '@types/qs': 6.9.11 + '@types/range-parser': 1.2.3 + + '@types/express@4.17.21': + dependencies: + '@types/body-parser': 1.19.0 + '@types/express-serve-static-core': 4.17.33 + '@types/qs': 6.9.11 + '@types/serve-static': 1.15.7 + + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 20.19.33 + + '@types/geojson@7946.0.10': {} + + '@types/glob@7.2.0': + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 20.19.33 + + '@types/graceful-fs@4.1.5': + dependencies: + '@types/node': 20.19.33 + + '@types/gtag.js@0.0.10': {} + + '@types/hashids@1.0.32': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/history@4.7.11': {} + + '@types/hoist-non-react-statics@3.3.1': + dependencies: + '@types/react': 17.0.40 + hoist-non-react-statics: 3.3.2 + + '@types/http-cache-semantics@4.0.4': {} + + '@types/http-errors@2.0.2': {} + + '@types/http-proxy@1.17.10': + dependencies: + '@types/node': 20.16.12 + + '@types/ioredis@4.28.8': + dependencies: + '@types/node': 20.16.12 + + '@types/is-number@7.0.5': {} + + '@types/istanbul-lib-coverage@2.0.4': {} + + '@types/istanbul-lib-report@3.0.0': + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + + '@types/istanbul-reports@3.0.1': + dependencies: + '@types/istanbul-lib-report': 3.0.0 + + '@types/jest@29.4.0': + dependencies: + expect: 29.4.3 + pretty-format: 29.7.0 + + '@types/js-cookie@2.2.7': {} + + '@types/js-yaml@4.0.5': {} + + '@types/jsdom@20.0.1': + dependencies: + '@types/node': 20.16.12 + '@types/tough-cookie': 4.0.0 + parse5: 7.1.2 + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 20.19.33 + + '@types/jsonwebtoken@9.0.1': + dependencies: + '@types/node': 20.16.12 + + '@types/linkify-it@3.0.2': {} + + '@types/linkify-it@5.0.0': {} + + '@types/lodash@4.14.168': {} + + '@types/luxon@1.26.2': {} + + '@types/markdown-it-emoji@2.0.2': + dependencies: + '@types/markdown-it': 13.0.7 + + '@types/markdown-it-link-attributes@3.0.4': + dependencies: + '@types/markdown-it': 13.0.7 + + '@types/markdown-it@12.2.3': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/markdown-it@13.0.7': + dependencies: + '@types/linkify-it': 3.0.2 + '@types/mdurl': 2.0.0 + + '@types/md5@2.3.5': {} + + '@types/mdurl@2.0.0': {} + + '@types/mime@1.3.5': {} + + '@types/minimatch@5.1.2': {} + + '@types/minimist@1.2.5': {} + + '@types/node-forge@1.3.11': + dependencies: + '@types/node': 20.19.33 + + '@types/node@18.19.87': + dependencies: + undici-types: 5.26.5 + + '@types/node@20.16.10': + dependencies: + undici-types: 6.19.8 + + '@types/node@20.16.12': + dependencies: + undici-types: 6.19.8 + + '@types/node@20.19.33': + dependencies: + undici-types: 6.21.0 + + '@types/normalize-package-data@2.4.4': {} + + '@types/pako@2.0.1': {} + + '@types/prettier@2.7.1': {} + + '@types/prop-types@15.7.3': {} + + '@types/qs@6.9.11': {} + + '@types/range-parser@1.2.3': {} + + '@types/rc-slider@8.6.6': + dependencies: + '@types/rc-tooltip': 3.7.7 + '@types/react': 17.0.40 + + '@types/rc-tooltip@3.7.7': + dependencies: + '@types/react': 17.0.40 + + '@types/react-dom@17.0.13': + dependencies: + '@types/react': 17.0.40 + + '@types/react-inspector@4.0.2': + dependencies: + '@types/react': 17.0.40 + csstype: 3.1.3 + + '@types/react-redux@7.1.16': + dependencies: + '@types/hoist-non-react-statics': 3.3.1 + '@types/react': 17.0.40 + hoist-non-react-statics: 3.3.2 + redux: 4.2.1 + + '@types/react-router-dom@5.1.7': + dependencies: + '@types/history': 4.7.11 + '@types/react': 17.0.40 + '@types/react-router': 5.1.11 + + '@types/react-router@5.1.11': + dependencies: + '@types/history': 4.7.11 + '@types/react': 17.0.40 + + '@types/react-window@1.8.5': + dependencies: + '@types/react': 17.0.40 + + '@types/react@17.0.40': + dependencies: + '@types/prop-types': 15.7.3 + '@types/scheduler': 0.16.1 + csstype: 3.1.3 + + '@types/recompose@0.30.7': + dependencies: + '@types/react': 17.0.40 + + '@types/redux-logger@3.0.9': + dependencies: + redux: 4.2.1 + + '@types/request-ip@0.0.41': + dependencies: + '@types/node': 20.16.12 + + '@types/request-promise-native@1.0.21': + dependencies: + '@types/request': 2.48.7 + + '@types/request@2.48.7': + dependencies: + '@types/caseless': 0.12.5 + '@types/node': 20.16.12 + '@types/tough-cookie': 4.0.0 + form-data: 2.5.1 + + '@types/retry@0.12.2': {} + + '@types/scheduler@0.16.1': {} + + '@types/semver@7.5.8': {} + + '@types/send@0.17.4': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.16.12 + + '@types/serve-index@1.9.4': + dependencies: + '@types/express': 4.17.21 + + '@types/serve-static@1.15.7': + dependencies: + '@types/http-errors': 2.0.2 + '@types/node': 20.16.12 + '@types/send': 0.17.4 + + '@types/set-cookie-parser@2.4.10': + dependencies: + '@types/node': 20.16.12 + + '@types/shimmer@1.2.0': {} + + '@types/sockjs@0.3.36': + dependencies: + '@types/node': 20.16.12 + + '@types/stack-utils@2.0.1': {} + + '@types/swagger-ui-express@4.1.8': + dependencies: + '@types/express': 4.17.21 + '@types/serve-static': 1.15.7 + + '@types/tapable@2.2.7': + dependencies: + tapable: 2.2.2 + + '@types/testing-library__jest-dom@5.14.3': + dependencies: + '@types/jest': 29.4.0 + + '@types/tough-cookie@4.0.0': {} + + '@types/trusted-types@2.0.7': {} + + '@types/unist@3.0.3': {} + + '@types/uuid@9.0.8': {} + + '@types/webpack-env@1.16.0': {} + + '@types/webpack@5.28.5(@swc/core@1.13.5(@swc/helpers@0.5.17))': + dependencies: + '@types/node': 20.16.10 + tapable: 2.2.2 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + + '@types/ws@8.5.13': + dependencies: + '@types/node': 20.16.10 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@16.0.9': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0)(typescript@5.6.3)': + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.6.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3(supports-color@5.5.0) + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + semver: 7.7.2 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3(supports-color@5.5.0) + eslint: 8.57.0 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.6.3) + debug: 4.4.3(supports-color@5.5.0) + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3(supports-color@5.5.0) + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.7.3 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@6.21.0(eslint@8.57.0)(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.6.3) + eslint: 8.57.0 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.2.0': {} + + '@webassemblyjs/ast@1.12.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + + '@webassemblyjs/floating-point-hex-parser@1.11.6': {} + + '@webassemblyjs/helper-api-error@1.11.6': {} + + '@webassemblyjs/helper-buffer@1.12.1': {} + + '@webassemblyjs/helper-numbers@1.11.6': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.11.6 + '@webassemblyjs/helper-api-error': 1.11.6 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.11.6': {} + + '@webassemblyjs/helper-wasm-section@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/wasm-gen': 1.12.1 + + '@webassemblyjs/ieee754@1.11.6': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.11.6': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.11.6': {} + + '@webassemblyjs/wasm-edit@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/helper-wasm-section': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-opt': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + '@webassemblyjs/wast-printer': 1.12.1 + + '@webassemblyjs/wasm-gen@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + + '@webassemblyjs/wasm-opt@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + + '@webassemblyjs/wasm-parser@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-api-error': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + + '@webassemblyjs/wast-printer@1.12.1': + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@xtuc/long': 4.2.2 + + '@xhmikosr/archive-type@7.1.0': + dependencies: + file-type: 20.5.0 + transitivePeerDependencies: + - supports-color + + '@xhmikosr/bin-check@7.1.0': + dependencies: + execa: 5.1.1 + isexe: 2.0.0 + + '@xhmikosr/bin-wrapper@13.2.0': + dependencies: + '@xhmikosr/bin-check': 7.1.0 + '@xhmikosr/downloader': 15.2.0 + '@xhmikosr/os-filter-obj': 3.0.0 + bin-version-check: 5.1.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/decompress-tar@8.1.0': + dependencies: + file-type: 20.5.0 + is-stream: 2.0.1 + tar-stream: 3.1.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/decompress-tarbz2@8.1.0': + dependencies: + '@xhmikosr/decompress-tar': 8.1.0 + file-type: 20.5.0 + is-stream: 2.0.1 + seek-bzip: 2.0.0 + unbzip2-stream: 1.4.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/decompress-targz@8.1.0': + dependencies: + '@xhmikosr/decompress-tar': 8.1.0 + file-type: 20.5.0 + is-stream: 2.0.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/decompress-unzip@7.1.0': + dependencies: + file-type: 20.5.0 + get-stream: 6.0.1 + yauzl: 3.2.0 + transitivePeerDependencies: + - supports-color + + '@xhmikosr/decompress@10.2.0': + dependencies: + '@xhmikosr/decompress-tar': 8.1.0 + '@xhmikosr/decompress-tarbz2': 8.1.0 + '@xhmikosr/decompress-targz': 8.1.0 + '@xhmikosr/decompress-unzip': 7.1.0 + graceful-fs: 4.2.11 + strip-dirs: 3.0.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/downloader@15.2.0': + dependencies: + '@xhmikosr/archive-type': 7.1.0 + '@xhmikosr/decompress': 10.2.0 + content-disposition: 0.5.4 + defaults: 2.0.2 + ext-name: 5.0.0 + file-type: 20.5.0 + filenamify: 6.0.0 + get-stream: 6.0.1 + got: 13.0.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + - supports-color + + '@xhmikosr/os-filter-obj@3.0.0': + dependencies: + arch: 3.0.0 + + '@xobotyi/scrollbar-width@1.9.5': {} + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + abab@2.0.6: {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accept-language-parser@1.5.0: {} + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-globals@7.0.1: + dependencies: + acorn: 8.14.1 + acorn-walk: 8.3.4 + + acorn-import-attributes@1.9.5(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.1 + + acorn@8.14.0: {} + + acorn@8.14.1: {} + + acorn@8.15.0: {} + + adjust-sourcemap-loader@4.0.0: + dependencies: + loader-utils: 2.0.4 + regex-parser: 2.2.11 + + adm-zip@0.5.16: {} + + afterframe@1.0.2: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv-formats@2.1.1(ajv@8.12.0): + optionalDependencies: + ajv: 8.12.0 + + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv-keywords@5.1.0(ajv@8.12.0): + dependencies: + ajv: 8.12.0 + fast-deep-equal: 3.1.3 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.1: + dependencies: + type-fest: 0.11.0 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-html-community@0.0.8: {} + + ansi-html@0.0.9: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arch@3.0.0: {} + + archy@1.0.0: {} + + arg@4.1.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-flatten@1.1.1: {} + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + + array-union@1.0.2: + dependencies: + array-uniq: 1.0.3 + + array-union@2.1.0: {} + + array-uniq@1.0.3: {} + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.0.2 + + array.prototype.flat@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.toreversed@1.1.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.tosorted@1.1.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 + + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + arrify@1.0.1: {} + + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assert-plus@1.0.0: {} + + ast-types-flow@0.0.8: {} + + astral-regex@2.0.0: {} + + async-function@1.0.0: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + atob@2.1.2: {} + + atomic-sleep@1.0.0: {} + + autoprefixer@10.4.16(postcss@8.4.47): + dependencies: + browserslist: 4.25.1 + caniuse-lite: 1.0.30001764 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + aws-sign2@0.7.0: {} + + aws4@1.11.0: {} + + axe-core@4.7.0: {} + + axios-retry@3.9.1: + dependencies: + '@babel/runtime': 7.26.10 + is-retry-allowed: 2.2.0 + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + axobject-query@3.2.1: + dependencies: + dequal: 2.0.3 + + b4a@1.7.3: {} + + babel-jest@29.4.3(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@jest/transform': 29.4.3 + '@types/babel__core': 7.20.0 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.4.3(@babel/core@7.26.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-jest@29.4.3(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@jest/transform': 29.4.3 + '@types/babel__core': 7.20.0 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.4.3(@babel/core@7.26.10) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + optional: true + + babel-loader@9.2.1(@babel/core@7.26.0)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + '@babel/core': 7.26.0 + find-cache-dir: 4.0.0 + schema-utils: 4.3.3 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + babel-plugin-import@1.13.8: + dependencies: + '@babel/helper-module-imports': 7.25.9 + transitivePeerDependencies: + - supports-color + + babel-plugin-inline-react-svg@2.0.2(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/parser': 7.26.2 + lodash.isplainobject: 4.0.6 + resolve: 2.0.0-next.5 + svgo: 2.8.0 + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.26.5 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.4.3: + dependencies: + '@babel/template': 7.26.9 + '@babel/types': 7.26.10 + '@types/babel__core': 7.20.0 + '@types/babel__traverse': 7.18.3 + + babel-plugin-module-resolver@4.1.0: + dependencies: + find-babel-config: 1.2.2 + glob: 7.2.3 + pkg-up: 3.1.0 + reselect: 4.1.8 + resolve: 1.22.10 + + babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.26.0): + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.26.10): + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.10) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0) + core-js-compat: 3.39.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.10) + core-js-compat: 3.41.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.0) + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + babel-plugin-transform-react-remove-prop-types@0.4.24: {} + + babel-plugin-tsconfig-paths-module-resolver@1.0.4: + dependencies: + babel-plugin-module-resolver: 4.1.0 + tsconfig-paths: 3.15.0 + + babel-preset-current-node-syntax@1.0.1(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) + + babel-preset-current-node-syntax@1.0.1(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.10) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.10) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.10) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.10) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.10) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.10) + + babel-preset-jest@29.4.3(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + babel-plugin-jest-hoist: 29.4.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.0) + + babel-preset-jest@29.4.3(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + babel-plugin-jest-hoist: 29.4.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.10) + optional: true + + balanced-match@1.0.0: {} + + balanced-match@2.0.0: {} + + bare-events@2.8.2: {} + + base64-arraybuffer@1.0.2: {} + + base64-js@1.5.1: {} + + base64id@2.0.0: {} + + batch@0.6.1: {} + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + + bem-cn-lite@4.0.0: + dependencies: + bem-cn: 3.0.1 + + bem-cn@3.0.1: {} + + big.js@5.2.2: {} + + bin-version-check@5.1.0: + dependencies: + bin-version: 6.0.0 + semver: 7.7.3 + semver-truncate: 3.0.0 + + bin-version@6.0.0: + dependencies: + execa: 5.1.1 + find-versions: 5.1.0 + + binary-extensions@2.2.0: {} + + blueimp-md5@2.19.0: {} + + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + body-parser@1.20.4: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.14.1 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + bonjour-service@1.2.1: + dependencies: + fast-deep-equal: 3.1.3 + multicast-dns: 7.2.5 + + boolbase@1.0.0: {} + + bowser@2.11.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.0 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.0 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist-load-config@1.0.0: {} + + browserslist-to-es-version@1.1.0: + dependencies: + browserslist: 4.25.1 + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001764 + electron-to-chromium: 1.5.118 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + + browserslist@4.25.1: + dependencies: + caniuse-lite: 1.0.30001727 + electron-to-chromium: 1.5.191 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.1) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + btoa@1.2.1: {} + + buffer-crc32@0.2.13: {} + + buffer-equal-constant-time@1.0.1: {} + + buffer-from@1.1.1: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.0.0 + + bytes@3.1.2: {} + + cacheable-lookup@7.0.0: {} + + cacheable-request@10.2.14: + dependencies: + '@types/http-cache-semantics': 4.0.4 + get-stream: 6.0.1 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + mimic-response: 4.0.0 + normalize-url: 8.1.0 + responselike: 3.0.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase-keys@7.0.2: + dependencies: + camelcase: 6.3.0 + map-obj: 4.3.0 + quick-lru: 5.1.1 + type-fest: 1.4.0 + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-api@3.0.0: + dependencies: + browserslist: 4.25.1 + caniuse-lite: 1.0.30001764 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + + caniuse-lite@1.0.30001727: {} + + caniuse-lite@1.0.30001764: {} + + caseless@0.12.0: {} + + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.2.0: {} + + chalk@5.4.1: {} + + change-emitter@0.1.6: {} + + char-regex@1.0.2: {} + + charenc@0.0.2: {} + + cheerio-select@2.1.0: + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + + cheerio@1.0.0: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.2 + encoding-sniffer: 0.2.0 + htmlparser2: 9.1.0 + parse5: 7.1.2 + parse5-htmlparser2-tree-adapter: 7.1.0 + parse5-parser-stream: 7.1.2 + undici: 6.21.3 + whatwg-mimetype: 4.0.0 + + cheminfo-types@1.10.0: {} + + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.17.21 + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chroma-js@3.1.2: {} + + chrome-trace-event@1.0.3: {} + + ci-info@3.9.0: {} + + circular-dependency-plugin@5.2.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + cjs-module-lexer@1.2.2: {} + + classnames@2.5.1: {} + + clean-stack@2.2.0: {} + + clean-webpack-plugin@4.0.0(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + del: 4.1.1 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + cli-color@2.0.1: + dependencies: + d: 1.0.1 + es5-ext: 0.10.63 + es6-iterator: 2.0.3 + memoizee: 0.4.15 + timers-ext: 0.1.7 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-truncate@2.1.0: + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + + cli-truncate@3.1.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 5.1.2 + + clipboard-copy@3.2.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@2.1.2: {} + + clsx@1.2.1: {} + + clsx@2.1.1: {} + + cluster-key-slot@1.1.0: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colord@2.9.3: {} + + colorette@2.0.19: {} + + colormap@2.3.2: + dependencies: + lerp: 1.0.3 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@10.0.1: {} + + commander@11.1.0: {} + + commander@2.20.3: {} + + commander@6.2.1: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + common-path-prefix@3.0.0: {} + + common-tags@1.8.2: {} + + commondir@1.0.1: {} + + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.7.5: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.0.2 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + connect-history-api-fallback@2.0.0: {} + + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + + consolidated-events@2.0.2: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convert-source-map@1.7.0: + dependencies: + safe-buffer: 5.1.2 + + convert-source-map@2.0.0: {} + + cookie-parser@1.4.7: + dependencies: + cookie: 0.7.2 + cookie-signature: 1.0.6 + + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + + cookie@0.7.2: {} + + cookies@0.9.1: + dependencies: + depd: 2.0.0 + keygrip: 1.1.0 + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + core-js-compat@3.39.0: + dependencies: + browserslist: 4.25.1 + + core-js-compat@3.41.0: + dependencies: + browserslist: 4.25.1 + + core-js-pure@3.39.0: {} + + core-js@3.43.0: {} + + core-util-is@1.0.2: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + + cosmiconfig-typescript-loader@5.0.0(@types/node@20.16.12)(cosmiconfig@8.3.6(typescript@5.6.3))(typescript@5.6.3): + dependencies: + '@types/node': 20.16.12 + cosmiconfig: 8.3.6(typescript@5.6.3) + jiti: 1.21.6 + typescript: 5.6.3 + + cosmiconfig@8.3.6(typescript@5.6.3): + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.6.3 + + cosmiconfig@9.0.0(typescript@5.6.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.6.3 + + create-require@1.1.1: {} + + crelt@1.0.6: {} + + cron-parser@4.9.0: + dependencies: + luxon: 1.28.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypt@0.0.2: {} + + csp-header@5.2.1: {} + + css-blank-pseudo@6.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + css-box-model@1.2.1: + dependencies: + tiny-invariant: 1.1.0 + + css-declaration-sorter@7.2.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + css-functions-list@3.2.2: {} + + css-has-pseudo@6.0.0(postcss@8.4.47): + dependencies: + '@csstools/selector-specificity': 3.0.0(postcss-selector-parser@6.1.2) + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + + css-in-js-utils@3.1.0: + dependencies: + hyphenate-style-name: 1.1.0 + + css-loader@7.1.2(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + icss-utils: 5.1.0(postcss@8.4.47) + postcss: 8.4.47 + postcss-modules-extract-imports: 3.1.0(postcss@8.4.47) + postcss-modules-local-by-default: 4.0.5(postcss@8.4.47) + postcss-modules-scope: 3.2.0(postcss@8.4.47) + postcss-modules-values: 4.0.0(postcss@8.4.47) + postcss-value-parser: 4.2.0 + semver: 7.7.3 + optionalDependencies: + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + css-minimizer-webpack-plugin@7.0.0(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + cssnano: 7.0.6(postcss@8.4.47) + jest-worker: 29.7.0 + postcss: 8.4.47 + schema-utils: 4.3.3 + serialize-javascript: 6.0.2 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + css-prefers-color-scheme@9.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-select@5.1.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 + + css-what@6.1.0: {} + + css.escape@1.5.1: {} + + css@3.0.0: + dependencies: + inherits: 2.0.4 + source-map: 0.6.1 + source-map-resolve: 0.6.0 + + cssdb@7.8.0: {} + + cssesc@3.0.0: {} + + cssfilter@0.0.10: {} + + cssnano-preset-default@7.0.6(postcss@8.4.47): + dependencies: + browserslist: 4.25.1 + css-declaration-sorter: 7.2.0(postcss@8.4.47) + cssnano-utils: 5.0.0(postcss@8.4.47) + postcss: 8.4.47 + postcss-calc: 10.0.2(postcss@8.4.47) + postcss-colormin: 7.0.2(postcss@8.4.47) + postcss-convert-values: 7.0.4(postcss@8.4.47) + postcss-discard-comments: 7.0.3(postcss@8.4.47) + postcss-discard-duplicates: 7.0.1(postcss@8.4.47) + postcss-discard-empty: 7.0.0(postcss@8.4.47) + postcss-discard-overridden: 7.0.0(postcss@8.4.47) + postcss-merge-longhand: 7.0.4(postcss@8.4.47) + postcss-merge-rules: 7.0.4(postcss@8.4.47) + postcss-minify-font-values: 7.0.0(postcss@8.4.47) + postcss-minify-gradients: 7.0.0(postcss@8.4.47) + postcss-minify-params: 7.0.2(postcss@8.4.47) + postcss-minify-selectors: 7.0.4(postcss@8.4.47) + postcss-normalize-charset: 7.0.0(postcss@8.4.47) + postcss-normalize-display-values: 7.0.0(postcss@8.4.47) + postcss-normalize-positions: 7.0.0(postcss@8.4.47) + postcss-normalize-repeat-style: 7.0.0(postcss@8.4.47) + postcss-normalize-string: 7.0.0(postcss@8.4.47) + postcss-normalize-timing-functions: 7.0.0(postcss@8.4.47) + postcss-normalize-unicode: 7.0.2(postcss@8.4.47) + postcss-normalize-url: 7.0.0(postcss@8.4.47) + postcss-normalize-whitespace: 7.0.0(postcss@8.4.47) + postcss-ordered-values: 7.0.1(postcss@8.4.47) + postcss-reduce-initial: 7.0.2(postcss@8.4.47) + postcss-reduce-transforms: 7.0.0(postcss@8.4.47) + postcss-svgo: 7.0.1(postcss@8.4.47) + postcss-unique-selectors: 7.0.3(postcss@8.4.47) + + cssnano-utils@5.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + cssnano@7.0.6(postcss@8.4.47): + dependencies: + cssnano-preset-default: 7.0.6(postcss@8.4.47) + lilconfig: 3.1.2 + postcss: 8.4.47 + + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + cssom@0.3.8: {} + + cssom@0.5.0: {} + + cssstyle@2.3.0: + dependencies: + cssom: 0.3.8 + + csstype@3.1.3: {} + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape-fcose@2.2.0(cytoscape@3.33.1): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.0: {} + + d3-geo@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.0.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.0 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.0.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + d@1.0.1: + dependencies: + es5-ext: 0.10.63 + type: 1.2.0 + + dagre-d3-es@7.0.13: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.21 + + damerau-levenshtein@1.0.8: {} + + dashdash@1.14.1: + dependencies: + assert-plus: 1.0.0 + + data-urls@3.0.2: + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + date-format@4.0.14: {} + + dateformat@4.6.3: {} + + dayjs@1.11.10: {} + + dayjs@1.11.13: {} + + dayjs@1.11.19: {} + + debounce@1.2.1: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + + decamelize@1.2.0: {} + + decamelize@5.0.1: {} + + decimal.js@10.4.3: {} + + decode-uri-component@0.2.2: {} + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + dedent@0.7.0: {} + + deep-diff@0.3.8: {} + + deep-eql@4.1.4: + dependencies: + type-detect: 4.0.8 + + deep-equal@1.0.1: {} + + deep-is@0.1.3: {} + + deepmerge@4.3.1: {} + + default-browser-id@5.0.0: {} + + default-browser@5.2.1: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.0 + + defaults@2.0.2: {} + + defer-to-connect@2.0.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-lazy-prop@2.0.0: {} + + define-lazy-prop@3.0.0: {} + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + del@4.1.1: + dependencies: + '@types/glob': 7.2.0 + globby: 6.1.0 + is-path-cwd: 2.2.0 + is-path-in-cwd: 2.1.0 + p-map: 2.1.0 + pify: 4.0.1 + rimraf: 2.7.1 + + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + + delayed-stream@1.0.0: {} + + delegates@1.0.0: {} + + denque@1.5.1: {} + + depd@1.1.2: {} + + depd@2.0.0: {} + + dequal@2.0.3: {} + + destroy@1.2.0: {} + + detect-libc@1.0.3: + optional: true + + detect-newline@3.1.0: {} + + detect-node@2.1.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff-match-patch@1.0.5: {} + + diff-sequences@27.5.1: {} + + diff-sequences@29.4.3: {} + + diff@4.0.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dnd-core@10.0.2: + dependencies: + '@react-dnd/asap': 4.0.1 + '@react-dnd/invariant': 2.0.0 + redux: 4.2.1 + + dns-packet@5.6.1: + dependencies: + '@leichtgewicht/ip-codec': 2.0.5 + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-accessibility-api@0.5.13: {} + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.26.10 + csstype: 3.1.3 + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domexception@4.0.0: + dependencies: + webidl-conversions: 7.0.0 + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + dompurify@3.2.4: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + dompurify@3.3.1: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + dotenv@16.4.5: {} + + dotenv@8.2.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexer@0.1.2: {} + + eastasianwidth@0.2.0: {} + + ecc-jsbn@0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.118: {} + + electron-to-chromium@1.5.191: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + emojis-list@3.0.0: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + encoding-sniffer@0.2.0: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + engine.io-parser@5.2.3: {} + + engine.io@6.6.4: + dependencies: + '@types/cors': 2.8.19 + '@types/node': 20.19.33 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.7.2 + cors: 2.8.5 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + enhanced-resolve@5.12.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + + enhanced-resolve@5.17.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + + entities@2.2.0: {} + + entities@3.0.1: {} + + entities@4.5.0: {} + + entities@6.0.1: {} + + entities@7.0.1: {} + + env-paths@2.2.1: {} + + envinfo@7.14.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.3.0 + get-symbol-description: 1.0.2 + globalthis: 1.0.3 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.1 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-abstract@1.23.9: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.0.19: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.3 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.1.0 + internal-slot: 1.0.7 + iterator.prototype: 1.1.2 + safe-array-concat: 1.1.2 + + es-module-lexer@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + es5-ext@0.10.63: + dependencies: + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + esniff: 2.0.1 + next-tick: 1.1.0 + + es6-iterator@2.0.3: + dependencies: + d: 1.0.1 + es5-ext: 0.10.63 + es6-symbol: 3.1.3 + + es6-symbol@3.1.3: + dependencies: + d: 1.0.1 + ext: 1.4.0 + + es6-weak-map@2.0.3: + dependencies: + d: 1.0.1 + es5-ext: 0.10.63 + es6-iterator: 2.0.3 + es6-symbol: 3.1.3 + + esbuild@0.25.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + eslint-config-prettier@9.1.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0): + dependencies: + debug: 4.4.3(supports-color@5.5.0) + enhanced-resolve: 5.17.1 + eslint: 8.57.0 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + fast-glob: 3.3.2 + get-tsconfig: 4.7.3 + is-core-module: 2.16.1 + is-glob: 4.0.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.6.3) + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + dependencies: + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.6.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.0): + dependencies: + '@babel/runtime': 7.26.10 + aria-query: 5.3.0 + array-includes: 3.1.8 + array.prototype.flatmap: 1.3.2 + ast-types-flow: 0.0.8 + axe-core: 4.7.0 + axobject-query: 3.2.1 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + es-iterator-helpers: 1.0.19 + eslint: 8.57.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + + eslint-plugin-prettier@5.1.3(@types/eslint@8.44.1)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5): + dependencies: + eslint: 8.57.0 + prettier: 3.2.5 + prettier-linter-helpers: 1.0.0 + synckit: 0.8.8 + optionalDependencies: + '@types/eslint': 8.44.1 + eslint-config-prettier: 9.1.0(eslint@8.57.0) + + eslint-plugin-react-hooks@4.6.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-plugin-react@7.34.1(eslint@8.57.0): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.2 + array.prototype.toreversed: 1.1.2 + array.prototype.tosorted: 1.1.3 + doctrine: 2.1.0 + es-iterator-helpers: 1.0.19 + eslint: 8.57.0 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.8 + object.fromentries: 2.0.8 + object.hasown: 1.1.4 + object.values: 1.2.0 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.11 + + eslint-plugin-security@1.7.1: + dependencies: + safe-regex: 2.1.1 + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.10.0 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@5.5.0) + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + esniff@2.0.1: + dependencies: + d: 1.0.1 + es5-ext: 0.10.63 + event-emitter: 0.3.5 + type: 2.7.2 + + espree@9.6.1: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.5.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + event-emitter@0.3.5: + dependencies: + d: 1.0.1 + es5-ext: 0.10.63 + + event-target-shim@5.0.1: {} + + eventemitter3@4.0.7: {} + + eventemitter3@5.0.1: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@7.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 4.3.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + exit@0.1.2: {} + + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + + expect@27.5.1: + dependencies: + '@jest/types': 27.5.1 + jest-get-type: 27.5.1 + jest-matcher-utils: 27.5.1 + jest-message-util: 27.5.1 + + expect@29.4.3: + dependencies: + '@jest/expect-utils': 29.4.3 + jest-get-type: 29.4.3 + jest-matcher-utils: 29.4.3 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + express-csp-header@5.2.1: + dependencies: + csp-header: 5.2.1 + psl: 1.8.0 + + express@4.21.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + ext-list@2.2.2: + dependencies: + mime-db: 1.54.0 + + ext-name@5.0.0: + dependencies: + ext-list: 2.2.2 + sort-keys-length: 1.0.1 + + ext@1.4.0: + dependencies: + type: 2.7.2 + + extend@3.0.2: {} + + extsprintf@1.3.0: {} + + fast-copy@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-equals@4.0.3: {} + + fast-fifo@1.3.2: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-redact@3.5.0: {} + + fast-safe-stringify@2.1.1: {} + + fast-shallow-equal@1.0.0: {} + + fast-xml-parser@4.2.5: + dependencies: + strnum: 1.0.5 + + fastest-levenshtein@1.0.16: {} + + fastest-stable-stringify@2.0.2: {} + + fastq@1.11.0: + dependencies: + reusify: 1.0.4 + + faye-websocket@0.11.4: + dependencies: + websocket-driver: 0.7.4 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fflate@0.8.2: {} + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + file-entry-cache@7.0.2: + dependencies: + flat-cache: 3.2.0 + + file-type@19.6.0: + dependencies: + get-stream: 9.0.1 + strtok3: 9.1.1 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + + file-type@20.5.0: + dependencies: + '@tokenizer/inflate': 0.2.7 + strtok3: 10.3.4 + token-types: 6.1.1 + uint8array-extras: 1.5.0 + transitivePeerDependencies: + - supports-color + + filename-reserved-regex@3.0.0: {} + + filenamify@6.0.0: + dependencies: + filename-reserved-regex: 3.0.0 + + filesize@10.1.6: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.1.2: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-babel-config@1.2.2: + dependencies: + json5: 1.0.2 + path-exists: 3.0.0 + + find-cache-dir@3.3.2: + dependencies: + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 + + find-cache-dir@4.0.0: + dependencies: + common-path-prefix: 3.0.0 + pkg-dir: 7.0.0 + + find-cache-dir@5.0.0: + dependencies: + common-path-prefix: 3.0.0 + pkg-dir: 7.0.0 + + find-file-up@2.0.1: + dependencies: + resolve-dir: 1.0.1 + + find-pkg@2.0.0: + dependencies: + find-file-up: 2.0.1 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + find-up@6.3.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + + find-versions@5.1.0: + dependencies: + semver-regex: 4.0.5 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.1: {} + + follow-redirects@1.15.6: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + forever-agent@0.6.1: {} + + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.6.3)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + '@babel/code-frame': 7.26.2 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 8.3.6(typescript@5.6.3) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.2 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.7.3 + tapable: 2.2.2 + typescript: 5.6.3 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + form-data-encoder@2.1.4: {} + + form-data@2.3.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + form-data@2.5.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + forwarded@0.2.0: {} + + fraction.js@4.3.7: {} + + fresh@0.5.2: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 + + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 + + fs-monkey@1.0.6: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-port@5.1.1: {} + + get-port@7.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-root-node-polyfill@1.0.0: {} + + get-stream@6.0.1: {} + + get-stream@8.0.1: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.10.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + get-tsconfig@4.7.3: + dependencies: + resolve-pkg-maps: 1.0.0 + + getpass@0.1.7: + dependencies: + assert-plus: 1.0.0 + + github-slugger@1.5.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-modules@2.0.0: + dependencies: + global-prefix: 3.0.0 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + + global-prefix@3.0.0: + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + + globals@11.12.0: {} + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globalthis@1.0.3: + dependencies: + define-properties: 1.2.1 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + + globby@6.1.0: + dependencies: + array-union: 1.0.2 + glob: 7.2.3 + object-assign: 4.1.1 + pify: 2.3.0 + pinkie-promise: 2.0.1 + + globjoin@0.1.4: {} + + gopd@1.2.0: {} + + got@13.0.0: + dependencies: + '@sindresorhus/is': 5.6.0 + '@szmarczak/http-timer': 5.0.1 + cacheable-lookup: 7.0.0 + cacheable-request: 10.2.14 + decompress-response: 6.0.0 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 + http2-wrapper: 2.2.1 + lowercase-keys: 3.0.0 + p-cancelable: 3.0.0 + responselike: 3.0.0 + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + gzip-size@6.0.0: + dependencies: + duplexer: 0.1.2 + + hachure-fill@0.5.2: {} + + handle-thing@2.0.1: {} + + har-schema@2.0.0: {} + + har-validator@5.1.5: + dependencies: + ajv: 6.12.6 + har-schema: 2.0.0 + + hard-rejection@2.1.0: {} + + has-bigints@1.0.2: {} + + has-bigints@1.1.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.0.3: {} + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hashids@2.2.8: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + help-me@5.0.0: {} + + highcharts@8.2.2: {} + + highlight.js@11.9.0: {} + + history@4.10.1: + dependencies: + '@babel/runtime': 7.26.10 + loose-envify: 1.4.0 + resolve-pathname: 3.0.0 + tiny-invariant: 1.1.0 + tiny-warning: 1.0.3 + value-equal: 1.0.1 + + hoist-non-react-statics@2.5.5: {} + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + hpack.js@2.1.6: + dependencies: + inherits: 2.0.4 + obuf: 1.1.2 + readable-stream: 2.3.8 + wbuf: 1.7.3 + + html-dom-parser@5.1.8: + dependencies: + domhandler: 5.0.3 + htmlparser2: 10.1.0 + + html-encoding-sniffer@3.0.0: + dependencies: + whatwg-encoding: 2.0.0 + + html-entities@2.6.0: {} + + html-escaper@2.0.2: {} + + html-react-parser@5.2.17(@types/react@17.0.40)(react@17.0.2): + dependencies: + domhandler: 5.0.3 + html-dom-parser: 5.1.8 + react: 17.0.2 + react-property: 2.0.2 + style-to-js: 1.1.21 + optionalDependencies: + '@types/react': 17.0.40 + + html-tags@3.3.1: {} + + htmlescape@1.1.1: {} + + htmlparser2@10.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 6.0.1 + + htmlparser2@10.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 7.0.1 + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + + htmlparser2@9.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + + http-assert@1.5.0: + dependencies: + deep-equal: 1.0.1 + http-errors: 1.8.1 + + http-cache-semantics@4.2.0: {} + + http-deceiver@1.2.7: {} + + http-errors@1.6.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + + http-errors@1.8.1: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-parser-js@0.5.8: {} + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + http-proxy-middleware@2.0.9(@types/express@4.17.21): + dependencies: + '@types/http-proxy': 1.17.10 + http-proxy: 1.18.1 + is-glob: 4.0.3 + is-plain-obj: 3.0.0 + micromatch: 4.0.8 + optionalDependencies: + '@types/express': 4.17.21 + transitivePeerDependencies: + - debug + + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.6 + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + + http-signature@1.2.0: + dependencies: + assert-plus: 1.0.0 + jsprim: 1.4.2 + sshpk: 1.18.0 + + http2-wrapper@2.2.1: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + human-signals@4.3.1: {} + + human-signals@5.0.0: {} + + husky@8.0.3: {} + + hyperdyperid@1.2.0: {} + + hyphenate-style-name@1.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + icss-utils@5.1.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + ieee754@1.2.1: {} + + ignore-by-default@1.0.1: {} + + ignore@5.3.1: {} + + immer@9.0.21: {} + + immutability-helper@3.1.1: {} + + immutable@4.3.7: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-in-the-middle@1.13.1: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 1.2.2 + module-details-from-path: 1.0.3 + + import-lazy@4.0.0: {} + + import-local@3.1.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + indent-string@5.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.3: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + inline-style-parser@0.2.7: {} + + inline-style-prefixer@7.0.1: + dependencies: + css-in-js-utils: 3.1.0 + + inspect-with-kind@1.0.5: + dependencies: + kind-of: 6.0.3 + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + internmap@1.0.1: {} + + internmap@2.0.3: {} + + ioredis@4.28.0: + dependencies: + cluster-key-slot: 1.1.0 + debug: 4.4.3(supports-color@5.5.0) + denque: 1.5.1 + lodash.defaults: 4.2.0 + lodash.flatten: 4.4.0 + lodash.isarguments: 3.1.0 + p-map: 2.1.0 + redis-commands: 1.7.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + + ipaddr.js@1.9.1: {} + + ipaddr.js@2.2.0: {} + + is-any-array@2.0.1: {} + + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.3.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.2.0 + + is-boolean-object@1.1.2: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-buffer@1.1.6: {} + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-docker@2.2.1: {} + + is-docker@3.0.0: {} + + is-dom@1.1.0: + dependencies: + is-object: 1.0.2 + is-window: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-generator-fn@2.1.0: {} + + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-network-error@1.1.0: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-object@1.0.2: {} + + is-path-cwd@2.2.0: {} + + is-path-in-cwd@2.1.0: + dependencies: + is-path-inside: 2.1.0 + + is-path-inside@2.1.0: + dependencies: + path-is-inside: 1.0.2 + + is-path-inside@3.0.3: {} + + is-plain-obj@1.1.0: {} + + is-plain-obj@3.0.0: {} + + is-plain-object@5.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-promise@2.2.2: {} + + is-regex@1.1.4: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-retry-allowed@2.2.0: {} + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-stream@3.0.0: {} + + is-stream@4.0.1: {} + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.1.0 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-typedarray@1.0.0: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-window@1.0.2: {} + + is-windows@1.0.2: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + isarray@0.0.1: {} + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + ismobilejs@1.1.1: {} + + isomorphic-ws@5.0.0(ws@8.18.0): + dependencies: + ws: 8.18.0 + + isstream@0.1.2: {} + + istanbul-lib-coverage@3.2.0: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.26.10 + '@babel/parser': 7.26.10 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.0: + dependencies: + istanbul-lib-coverage: 3.2.0 + make-dir: 3.1.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + istanbul-lib-coverage: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.5: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.0 + + iterator.prototype@1.1.2: + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + reflect.getprototypeof: 1.0.6 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-changed-files@29.4.3: + dependencies: + execa: 5.1.1 + p-limit: 3.1.0 + + jest-circus@27.5.1: + dependencies: + '@jest/environment': 27.5.1 + '@jest/test-result': 27.5.1 + '@jest/types': 27.5.1 + '@types/node': 20.16.12 + chalk: 4.1.2 + co: 4.6.0 + dedent: 0.7.0 + expect: 27.5.1 + is-generator-fn: 2.1.0 + jest-each: 27.5.1 + jest-matcher-utils: 27.5.1 + jest-message-util: 27.5.1 + jest-runtime: 27.5.1 + jest-snapshot: 27.5.1 + jest-util: 27.5.1 + pretty-format: 27.5.1 + slash: 3.0.0 + stack-utils: 2.0.6 + throat: 6.0.1 + transitivePeerDependencies: + - supports-color + + jest-circus@29.4.3: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.4.3 + '@jest/test-result': 29.4.3 + '@jest/types': 29.6.3 + '@types/node': 20.19.33 + chalk: 4.1.2 + co: 4.6.0 + dedent: 0.7.0 + is-generator-fn: 2.1.0 + jest-each: 29.4.3 + jest-matcher-utils: 29.4.3 + jest-message-util: 29.7.0 + jest-runtime: 29.4.3 + jest-snapshot: 29.4.3 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - supports-color + + jest-cli@29.4.3(@types/node@20.16.12)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)): + dependencies: + '@jest/core': 29.4.3(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)) + '@jest/test-result': 29.4.3 + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + import-local: 3.1.0 + jest-config: 29.4.3(@types/node@20.16.12)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)) + jest-util: 29.7.0 + jest-validate: 29.4.3 + prompts: 2.4.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node + + jest-config@29.4.3(@types/node@20.16.10)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)): + dependencies: + '@babel/core': 7.26.0 + '@jest/test-sequencer': 29.4.3 + '@jest/types': 29.6.3 + babel-jest: 29.4.3(@babel/core@7.26.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.4.3 + jest-environment-node: 29.4.3 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.4.3 + jest-runner: 29.4.3 + jest-util: 29.7.0 + jest-validate: 29.4.3 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.16.10 + ts-node: 10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color + + jest-config@29.4.3(@types/node@20.16.12)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)): + dependencies: + '@babel/core': 7.26.0 + '@jest/test-sequencer': 29.4.3 + '@jest/types': 29.6.3 + babel-jest: 29.4.3(@babel/core@7.26.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.4.3 + jest-environment-node: 29.4.3 + jest-get-type: 29.4.3 + jest-regex-util: 29.4.3 + jest-resolve: 29.4.3 + jest-runner: 29.4.3 + jest-util: 29.7.0 + jest-validate: 29.4.3 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.16.12 + ts-node: 10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color + + jest-diff@27.5.1: + dependencies: + chalk: 4.1.2 + diff-sequences: 27.5.1 + jest-get-type: 27.5.1 + pretty-format: 27.5.1 + + jest-diff@29.4.3: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.4.3 + jest-get-type: 29.4.3 + pretty-format: 29.7.0 + + jest-docblock@29.4.3: + dependencies: + detect-newline: 3.1.0 + + jest-each@27.5.1: + dependencies: + '@jest/types': 27.5.1 + chalk: 4.1.2 + jest-get-type: 27.5.1 + jest-util: 27.5.1 + pretty-format: 27.5.1 + + jest-each@29.4.3: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.4.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-jsdom@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/jsdom': 20.0.1 + '@types/node': 20.16.12 + jest-mock: 29.7.0 + jest-util: 29.7.0 + jsdom: 20.0.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jest-environment-node@29.4.3: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.16.12 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@27.5.1: {} + + jest-get-type@29.4.3: {} + + jest-haste-map@27.5.1: + dependencies: + '@jest/types': 27.5.1 + '@types/graceful-fs': 4.1.5 + '@types/node': 20.19.33 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 27.5.1 + jest-serializer: 27.5.1 + jest-util: 27.5.1 + jest-worker: 27.5.1 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-haste-map@29.4.3: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.5 + '@types/node': 20.19.33 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.4.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-html-reporters@2.1.2: + dependencies: + fs-extra: 9.1.0 + + jest-leak-detector@29.4.3: + dependencies: + jest-get-type: 29.4.3 + pretty-format: 29.7.0 + + jest-matcher-utils@27.5.1: + dependencies: + chalk: 4.1.2 + jest-diff: 27.5.1 + jest-get-type: 27.5.1 + pretty-format: 27.5.1 + + jest-matcher-utils@29.4.3: + dependencies: + chalk: 4.1.2 + jest-diff: 29.4.3 + jest-get-type: 29.4.3 + pretty-format: 29.7.0 + + jest-message-util@27.5.1: + dependencies: + '@babel/code-frame': 7.26.2 + '@jest/types': 27.5.1 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 27.5.1 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.26.2 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@27.5.1: + dependencies: + '@jest/types': 27.5.1 + '@types/node': 20.19.33 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.16.12 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@27.5.1): + optionalDependencies: + jest-resolve: 27.5.1 + + jest-pnp-resolver@1.2.3(jest-resolve@29.4.3): + optionalDependencies: + jest-resolve: 29.4.3 + + jest-regex-util@27.5.1: {} + + jest-regex-util@29.4.3: {} + + jest-resolve-dependencies@29.4.3: + dependencies: + jest-regex-util: 29.4.3 + jest-snapshot: 29.4.3 + transitivePeerDependencies: + - supports-color + + jest-resolve@27.5.1: + dependencies: + '@jest/types': 27.5.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 27.5.1 + jest-pnp-resolver: 1.2.3(jest-resolve@27.5.1) + jest-util: 27.5.1 + jest-validate: 27.5.1 + resolve: 1.22.10 + resolve.exports: 1.1.0 + slash: 3.0.0 + + jest-resolve@29.4.3: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.4.3 + jest-pnp-resolver: 1.2.3(jest-resolve@29.4.3) + jest-util: 29.7.0 + jest-validate: 29.4.3 + resolve: 1.22.10 + resolve.exports: 2.0.0 + slash: 3.0.0 + + jest-runner@29.4.3: + dependencies: + '@jest/console': 29.4.3 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.4.3 + '@jest/transform': 29.4.3 + '@jest/types': 29.6.3 + '@types/node': 20.19.33 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.4.3 + jest-environment-node: 29.4.3 + jest-haste-map: 29.4.3 + jest-leak-detector: 29.4.3 + jest-message-util: 29.7.0 + jest-resolve: 29.4.3 + jest-runtime: 29.4.3 + jest-util: 29.7.0 + jest-watcher: 29.4.3 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@27.5.1: + dependencies: + '@jest/environment': 27.5.1 + '@jest/fake-timers': 27.5.1 + '@jest/globals': 27.5.1 + '@jest/source-map': 27.5.1 + '@jest/test-result': 27.5.1 + '@jest/transform': 27.5.1 + '@jest/types': 27.5.1 + chalk: 4.1.2 + cjs-module-lexer: 1.2.2 + collect-v8-coverage: 1.0.1 + execa: 5.1.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 27.5.1 + jest-message-util: 27.5.1 + jest-mock: 27.5.1 + jest-regex-util: 27.5.1 + jest-resolve: 27.5.1 + jest-snapshot: 27.5.1 + jest-util: 27.5.1 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.4.3: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.4.3 + '@jest/source-map': 29.4.3 + '@jest/test-result': 29.4.3 + '@jest/transform': 29.4.3 + '@jest/types': 29.6.3 + '@types/node': 20.19.33 + chalk: 4.1.2 + cjs-module-lexer: 1.2.2 + collect-v8-coverage: 1.0.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.4.3 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.4.3 + jest-resolve: 29.4.3 + jest-snapshot: 29.4.3 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-serializer@27.5.1: + dependencies: + '@types/node': 20.19.33 + graceful-fs: 4.2.11 + + jest-snapshot@27.5.1: + dependencies: + '@babel/core': 7.26.10 + '@babel/generator': 7.26.10 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + '@jest/transform': 27.5.1 + '@jest/types': 27.5.1 + '@types/babel__traverse': 7.18.3 + '@types/prettier': 2.7.1 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.10) + chalk: 4.1.2 + expect: 27.5.1 + graceful-fs: 4.2.11 + jest-diff: 27.5.1 + jest-get-type: 27.5.1 + jest-haste-map: 27.5.1 + jest-matcher-utils: 27.5.1 + jest-message-util: 27.5.1 + jest-util: 27.5.1 + natural-compare: 1.4.0 + pretty-format: 27.5.1 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.4.3: + dependencies: + '@babel/core': 7.26.10 + '@babel/generator': 7.26.10 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) + '@babel/traverse': 7.26.10 + '@babel/types': 7.26.10 + '@jest/expect-utils': 29.4.3 + '@jest/transform': 29.4.3 + '@jest/types': 29.6.3 + '@types/babel__traverse': 7.18.3 + '@types/prettier': 2.7.1 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.26.10) + chalk: 4.1.2 + expect: 29.4.3 + graceful-fs: 4.2.11 + jest-diff: 29.4.3 + jest-get-type: 29.4.3 + jest-haste-map: 29.4.3 + jest-matcher-utils: 29.4.3 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + jest-util@27.5.1: + dependencies: + '@jest/types': 27.5.1 + '@types/node': 20.16.12 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.16.12 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@27.5.1: + dependencies: + '@jest/types': 27.5.1 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 27.5.1 + leven: 3.1.0 + pretty-format: 27.5.1 + + jest-validate@29.4.3: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.4.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.4.3: + dependencies: + '@jest/test-result': 29.4.3 + '@jest/types': 29.6.3 + '@types/node': 20.19.33 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@27.5.1: + dependencies: + '@types/node': 20.16.12 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest-worker@29.7.0: + dependencies: + '@types/node': 20.16.12 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.4.3(@types/node@20.16.12)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)): + dependencies: + '@jest/core': 29.4.3(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)) + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.4.3(@types/node@20.16.12)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)) + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node + + jiti@1.21.6: {} + + jora@1.0.0-beta.8: + dependencies: + '@discoveryjs/natural-compare': 1.1.0 + + joycon@3.1.1: {} + + js-cookie@2.2.1: {} + + js-sha1@0.6.0: {} + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsbn@0.1.1: {} + + jsdom@20.0.3: + dependencies: + abab: 2.0.6 + acorn: 8.14.1 + acorn-globals: 7.0.1 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.4.3 + domexception: 4.0.0 + escodegen: 2.1.0 + form-data: 4.0.5 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.7 + parse5: 7.1.2 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.3 + w3c-xmlserializer: 4.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + ws: 8.18.3 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.0.2: {} + + json-buffer@3.0.1: {} + + json-cycle@1.5.0: {} + + json-fn@1.1.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema@0.4.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json-stream-stringify@3.0.1: {} + + json-stringify-safe@5.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsondiffpatch@0.6.0: + dependencies: + '@types/diff-match-patch': 1.0.36 + chalk: 5.4.1 + diff-match-patch: 1.0.5 + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonwebtoken@9.0.0: + dependencies: + jws: 3.2.2 + lodash: 4.17.21 + ms: 2.1.3 + semver: 7.7.3 + + jsprim@1.4.2: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + + katex@0.16.21: + dependencies: + commander: 8.3.0 + + katex@0.16.28: + dependencies: + commander: 8.3.0 + + keygrip@1.1.0: + dependencies: + tsscmp: 1.0.6 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + khroma@2.1.0: {} + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + known-css-properties@0.29.0: {} + + koa-compose@4.1.0: {} + + koa@3.0.1: + dependencies: + accepts: 1.3.8 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookies: 0.9.1 + delegates: 1.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + fresh: 0.5.2 + http-assert: 1.5.0 + http-errors: 2.0.1 + koa-compose: 4.1.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + + langium@3.3.1: + dependencies: + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + launch-editor@2.9.1: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.8.1 + + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + + lerp@1.0.3: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@2.1.0: {} + + lilconfig@3.1.2: {} + + lines-and-columns@1.1.6: {} + + lines-and-columns@2.0.4: {} + + linkify-it@4.0.1: + dependencies: + uc.micro: 1.0.6 + + lint-staged@13.2.2: + dependencies: + chalk: 5.2.0 + cli-truncate: 3.1.0 + commander: 10.0.1 + debug: 4.4.3(supports-color@5.5.0) + execa: 7.1.1 + lilconfig: 2.1.0 + listr2: 5.0.8 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-inspect: 1.13.4 + pidtree: 0.6.0 + string-argv: 0.3.1 + yaml: 2.8.1 + transitivePeerDependencies: + - enquirer + - supports-color + + listr2@5.0.8: + dependencies: + cli-truncate: 2.1.0 + colorette: 2.0.19 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.3.0 + rxjs: 7.8.1 + through: 2.3.8 + wrap-ansi: 7.0.0 + + loader-runner@4.3.0: {} + + loader-utils@2.0.4: + dependencies: + big.js: 5.2.2 + emojis-list: 3.0.0 + json5: 2.2.3 + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + + lockfile@1.0.4: + dependencies: + signal-exit: 3.0.7 + + lodash-es@4.17.21: {} + + lodash.camelcase@4.3.0: {} + + lodash.clonedeepwith@4.5.0: {} + + lodash.debounce@4.0.8: {} + + lodash.defaults@4.2.0: {} + + lodash.flatten@4.4.0: {} + + lodash.get@4.4.2: {} + + lodash.has@4.5.2: {} + + lodash.isarguments@3.1.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash.truncate@4.4.2: {} + + lodash.unionby@4.8.0: {} + + lodash.uniq@4.5.0: {} + + lodash@4.17.21: {} + + log-update@4.0.0: + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + + log4js@6.9.1: + dependencies: + date-format: 4.0.14 + debug: 4.4.3(supports-color@5.5.0) + flatted: 3.3.1 + rfdc: 1.4.1 + streamroller: 3.1.5 + transitivePeerDependencies: + - supports-color + + long-timeout@0.1.1: {} + + long@5.3.1: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + lowercase-keys@3.0.0: {} + + lowlight@3.1.0: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.9.0 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lru-queue@0.1.0: + dependencies: + es5-ext: 0.10.63 + + luxon@1.28.1: {} + + lz-string@1.4.4: {} + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + map-obj@1.0.1: {} + + map-obj@4.3.0: {} + + markdown-it-attrs@4.3.1(markdown-it@13.0.2): + dependencies: + markdown-it: 13.0.2 + + markdown-it-color@2.1.1: {} + + markdown-it-deflist@2.1.0: {} + + markdown-it-directive@2.0.5(@types/markdown-it@13.0.7)(markdown-it@13.0.2): + dependencies: + '@types/markdown-it': 13.0.7 + markdown-it: 13.0.2 + + markdown-it-emoji@2.0.2: {} + + markdown-it-ins@3.0.1: {} + + markdown-it-ins@4.0.0: {} + + markdown-it-link-attributes@4.0.1: {} + + markdown-it-mark@3.0.1: {} + + markdown-it-mark@4.0.0: {} + + markdown-it-sub@1.0.0: {} + + markdown-it-sub@2.0.0: {} + + markdown-it-sup@1.0.0: {} + + markdown-it@13.0.2: + dependencies: + argparse: 2.0.1 + entities: 3.0.1 + linkify-it: 4.0.1 + mdurl: 1.0.1 + uc.micro: 1.0.6 + + markdownlint-micromark@0.1.7: {} + + markdownlint-rule-helpers@0.17.2: {} + + markdownlint@0.32.1: + dependencies: + markdown-it: 13.0.2 + markdownlint-micromark: 0.1.7 + + marked@16.4.2: {} + + math-intrinsics@1.1.0: {} + + mathml-tag-names@2.1.3: {} + + md5@2.3.0: + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: 1.1.6 + + mdn-data@2.0.14: {} + + mdn-data@2.0.28: {} + + mdn-data@2.0.30: {} + + mdurl@1.0.1: {} + + media-typer@0.3.0: {} + + media-typer@1.1.0: {} + + memfs@3.5.3: + dependencies: + fs-monkey: 1.0.6 + + memfs@4.17.0: + dependencies: + '@jsonjoy.com/json-pack': 1.1.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.5.0(tslib@2.8.1) + tree-dump: 1.0.2(tslib@2.8.1) + tslib: 2.8.1 + + memoize-one@5.2.1: {} + + memoizee@0.4.15: + dependencies: + d: 1.0.1 + es5-ext: 0.10.63 + es6-weak-map: 2.0.3 + event-emitter: 0.3.5 + is-promise: 2.2.2 + lru-queue: 0.1.0 + next-tick: 1.1.0 + timers-ext: 0.1.7 + + meow@10.1.5: + dependencies: + '@types/minimist': 1.2.5 + camelcase-keys: 7.0.2 + decamelize: 5.0.1 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 8.0.0 + redent: 4.0.0 + trim-newlines: 4.1.1 + type-fest: 1.4.0 + yargs-parser: 20.2.9 + + merge-descriptors@1.0.3: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + mermaid@11.12.2: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.0 + '@mermaid-js/parser': 0.6.3 + '@types/d3': 7.4.3 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.19 + dompurify: 3.3.1 + katex: 0.16.28 + khroma: 2.1.0 + lodash-es: 4.17.21 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + + methods@1.1.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@1.6.0: {} + + mimic-fn@2.1.0: {} + + mimic-fn@4.0.0: {} + + mimic-response@3.1.0: {} + + mimic-response@4.0.0: {} + + min-indent@1.0.1: {} + + mini-css-extract-plugin@2.9.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + schema-utils: 4.3.3 + tapable: 2.2.2 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + minimalistic-assert@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.0.1 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + ml-array-max@1.2.4: + dependencies: + is-any-array: 2.0.1 + + ml-array-min@1.2.3: + dependencies: + is-any-array: 2.0.1 + + ml-array-rescale@1.3.7: + dependencies: + is-any-array: 2.0.1 + ml-array-max: 1.2.4 + ml-array-min: 1.2.3 + + ml-matrix@6.12.1: + dependencies: + is-any-array: 2.0.1 + ml-array-rescale: 1.3.7 + + ml-regression-base@4.0.0: + dependencies: + cheminfo-types: 1.10.0 + is-any-array: 2.0.1 + + ml-regression-polynomial@3.0.2: + dependencies: + ml-matrix: 6.12.1 + ml-regression-base: 4.0.0 + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + mockdate@3.0.5: {} + + module-details-from-path@1.0.3: {} + + moment-timezone-data-webpack-plugin@1.5.1(moment-timezone@0.5.43)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + find-cache-dir: 3.3.2 + make-dir: 3.1.0 + moment-timezone: 0.5.43 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + moment-timezone@0.5.43: + dependencies: + moment: 2.29.4 + + moment@2.29.4: {} + + monaco-editor-webpack-plugin@7.1.0(monaco-editor@0.36.1)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + loader-utils: 2.0.4 + monaco-editor: 0.36.1 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + monaco-editor@0.36.1: {} + + mrmime@2.0.1: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + multicast-dns@7.2.5: + dependencies: + dns-packet: 5.6.1 + thunky: 1.1.0 + + nano-css@5.6.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + css-tree: 1.1.3 + csstype: 3.1.3 + fastest-stable-stringify: 2.0.2 + inline-style-prefixer: 7.0.1 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + rtl-css-js: 1.16.1 + stacktrace-js: 2.0.2 + stylis: 4.3.6 + + nanoid@3.3.8: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + negotiator@0.6.4: {} + + neo-async@2.6.2: {} + + next-tick@1.1.0: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-abort-controller@3.1.1: {} + + node-addon-api@7.1.1: + optional: true + + node-cache@5.1.2: + dependencies: + clone: 2.1.2 + + node-forge@1.3.1: {} + + node-int64@0.4.0: {} + + node-releases@2.0.19: {} + + node-schedule@2.1.1: + dependencies: + cron-parser: 4.9.0 + long-timeout: 0.1.1 + sorted-array-functions: 1.3.0 + + nodemon@3.1.7: + dependencies: + chokidar: 3.6.0 + debug: 4.4.3(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.7.3 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + + normalize-package-data@3.0.3: + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.16.1 + semver: 7.7.3 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + normalize-url@8.1.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + nwsapi@2.2.7: {} + + oauth-sign@0.9.0: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object-sizeof@2.6.5: + dependencies: + buffer: 6.0.3 + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + + object.hasown@1.1.4: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.1.1 + + object.values@1.2.0: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + obuf@1.1.2: {} + + on-exit-leak-free@2.1.2: {} + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.0.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + open@10.1.0: + dependencies: + default-browser: 5.2.1 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + is-wsl: 3.1.0 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + openapi3-ts@4.5.0: + dependencies: + yaml: 2.8.1 + + opener@1.5.2: {} + + optionator@0.9.3: + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.3 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + + orderedmap@2.1.1: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-cancelable@3.0.0: {} + + p-finally@1.0.0: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@4.0.0: + dependencies: + yocto-queue: 1.1.1 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + + p-map@2.1.0: {} + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-map@7.0.2: {} + + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-queue@8.0.1: + dependencies: + eventemitter3: 5.0.1 + p-timeout: 6.1.3 + + p-retry@6.2.0: + dependencies: + '@types/retry': 0.12.2 + is-network-error: 1.1.0 + retry: 0.13.1 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + p-timeout@6.1.3: {} + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + package-manager-detector@1.6.0: {} + + pako@2.1.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.1.6 + + parse-passwd@1.0.0: {} + + parse-srcset@1.0.2: {} + + parse5-htmlparser2-tree-adapter@7.1.0: + dependencies: + domhandler: 5.0.3 + parse5: 7.1.2 + + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.1.2 + + parse5@7.1.2: + dependencies: + entities: 4.5.0 + + parseurl@1.3.3: {} + + path-browserify@1.0.1: {} + + path-data-parser@0.1.0: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-exists@5.0.0: {} + + path-is-absolute@1.0.1: {} + + path-is-inside@1.0.2: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-to-regexp@0.1.12: {} + + path-to-regexp@1.9.0: + dependencies: + isarray: 0.0.1 + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + peek-readable@5.4.2: {} + + pend@1.2.0: {} + + performance-now@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + pify@2.3.0: {} + + pify@4.0.1: {} + + pinkie-promise@2.0.1: + dependencies: + pinkie: 2.0.4 + + pinkie@2.0.4: {} + + pino-abstract-transport@1.2.0: + dependencies: + readable-stream: 4.7.0 + split2: 4.2.0 + + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-pretty@11.3.0: + dependencies: + colorette: 2.0.19 + dateformat: 4.6.3 + fast-copy: 3.0.2 + fast-safe-stringify: 2.1.1 + help-me: 5.0.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pump: 3.0.0 + readable-stream: 4.7.0 + secure-json-parse: 2.7.0 + sonic-boom: 4.2.0 + strip-json-comments: 3.1.1 + + pino-std-serializers@6.2.2: {} + + pino@8.21.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 1.2.0 + pino-std-serializers: 6.2.2 + process-warning: 3.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 3.8.1 + thread-stream: 2.7.0 + + pirates@4.0.5: {} + + piscina@4.9.2: + optionalDependencies: + '@napi-rs/nice': 1.1.1 + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pkg-dir@7.0.0: + dependencies: + find-up: 6.3.0 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-up@3.1.0: + dependencies: + find-up: 3.0.0 + + playwright-core@1.48.2: {} + + playwright@1.48.2: + dependencies: + playwright-core: 1.48.2 + optionalDependencies: + fsevents: 2.3.2 + + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + + possible-typed-array-names@1.0.0: {} + + possible-typed-array-names@1.1.0: {} + + postcss-attribute-case-insensitive@6.0.2(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-calc@10.0.2(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + + postcss-clamp@4.1.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-color-functional-notation@6.0.2(postcss@8.4.47): + dependencies: + '@csstools/postcss-progressive-custom-properties': 3.0.2(postcss@8.4.47) + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-color-hex-alpha@9.0.2(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-color-rebeccapurple@9.0.1(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-colormin@7.0.2(postcss@8.4.47): + dependencies: + browserslist: 4.25.1 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-convert-values@7.0.4(postcss@8.4.47): + dependencies: + browserslist: 4.25.1 + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-custom-media@10.0.2(postcss@8.4.47): + dependencies: + '@csstools/cascade-layer-name-parser': 1.0.5(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + '@csstools/media-query-list-parser': 2.1.5(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + postcss: 8.4.47 + + postcss-custom-properties@13.3.2(postcss@8.4.47): + dependencies: + '@csstools/cascade-layer-name-parser': 1.0.5(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-custom-selectors@7.1.6(postcss@8.4.47): + dependencies: + '@csstools/cascade-layer-name-parser': 1.0.5(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-dir-pseudo-class@8.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-discard-comments@7.0.3(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-discard-duplicates@7.0.1(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-discard-empty@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-discard-overridden@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-double-position-gradients@5.0.2(postcss@8.4.47): + dependencies: + '@csstools/postcss-progressive-custom-properties': 3.0.2(postcss@8.4.47) + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-focus-visible@9.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-focus-within@8.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-font-variant@5.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-gap-properties@5.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-image-set-function@6.0.1(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-lab-function@6.0.7(postcss@8.4.47): + dependencies: + '@csstools/css-color-parser': 1.4.0(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + '@csstools/postcss-progressive-custom-properties': 3.0.2(postcss@8.4.47) + postcss: 8.4.47 + + postcss-loader@8.1.1(@rspack/core@1.5.5(@swc/helpers@0.5.17))(postcss@8.4.47)(typescript@5.6.3)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + cosmiconfig: 9.0.0(typescript@5.6.3) + jiti: 1.21.6 + postcss: 8.4.47 + semver: 7.7.3 + optionalDependencies: + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + transitivePeerDependencies: + - typescript + + postcss-logical@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-media-query-parser@0.2.3: {} + + postcss-merge-longhand@7.0.4(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + stylehacks: 7.0.4(postcss@8.4.47) + + postcss-merge-rules@7.0.4(postcss@8.4.47): + dependencies: + browserslist: 4.25.1 + caniuse-api: 3.0.0 + cssnano-utils: 5.0.0(postcss@8.4.47) + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-minify-font-values@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-minify-gradients@7.0.0(postcss@8.4.47): + dependencies: + colord: 2.9.3 + cssnano-utils: 5.0.0(postcss@8.4.47) + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-minify-params@7.0.2(postcss@8.4.47): + dependencies: + browserslist: 4.25.1 + cssnano-utils: 5.0.0(postcss@8.4.47) + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-minify-selectors@7.0.4(postcss@8.4.47): + dependencies: + cssesc: 3.0.0 + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-modules-extract-imports@3.1.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-modules-local-by-default@4.0.5(postcss@8.4.47): + dependencies: + icss-utils: 5.1.0(postcss@8.4.47) + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + + postcss-modules-scope@3.2.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-modules-values@4.0.0(postcss@8.4.47): + dependencies: + icss-utils: 5.1.0(postcss@8.4.47) + postcss: 8.4.47 + + postcss-nesting@12.0.1(postcss@8.4.47): + dependencies: + '@csstools/selector-specificity': 3.0.0(postcss-selector-parser@6.1.2) + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-normalize-charset@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-normalize-display-values@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-normalize-positions@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-normalize-repeat-style@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-normalize-string@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-normalize-timing-functions@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-normalize-unicode@7.0.2(postcss@8.4.47): + dependencies: + browserslist: 4.25.1 + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-normalize-url@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-normalize-whitespace@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-opacity-percentage@2.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-ordered-values@7.0.1(postcss@8.4.47): + dependencies: + cssnano-utils: 5.0.0(postcss@8.4.47) + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-overflow-shorthand@5.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-page-break@3.0.4(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-place@9.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-prefix-selector@2.1.1(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-preset-env@9.2.0(postcss@8.4.47): + dependencies: + '@csstools/postcss-cascade-layers': 4.0.0(postcss@8.4.47) + '@csstools/postcss-color-function': 3.0.7(postcss@8.4.47) + '@csstools/postcss-color-mix-function': 2.0.7(postcss@8.4.47) + '@csstools/postcss-exponential-functions': 1.0.1(postcss@8.4.47) + '@csstools/postcss-font-format-keywords': 3.0.0(postcss@8.4.47) + '@csstools/postcss-gamut-mapping': 1.0.0(postcss@8.4.47) + '@csstools/postcss-gradients-interpolation-method': 4.0.7(postcss@8.4.47) + '@csstools/postcss-hwb-function': 3.0.6(postcss@8.4.47) + '@csstools/postcss-ic-unit': 3.0.2(postcss@8.4.47) + '@csstools/postcss-initial': 1.0.0(postcss@8.4.47) + '@csstools/postcss-is-pseudo-class': 4.0.3(postcss@8.4.47) + '@csstools/postcss-logical-float-and-clear': 2.0.0(postcss@8.4.47) + '@csstools/postcss-logical-resize': 2.0.0(postcss@8.4.47) + '@csstools/postcss-logical-viewport-units': 2.0.3(postcss@8.4.47) + '@csstools/postcss-media-minmax': 1.1.0(postcss@8.4.47) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 2.0.3(postcss@8.4.47) + '@csstools/postcss-nested-calc': 3.0.0(postcss@8.4.47) + '@csstools/postcss-normalize-display-values': 3.0.1(postcss@8.4.47) + '@csstools/postcss-oklab-function': 3.0.7(postcss@8.4.47) + '@csstools/postcss-progressive-custom-properties': 3.0.2(postcss@8.4.47) + '@csstools/postcss-relative-color-syntax': 2.0.7(postcss@8.4.47) + '@csstools/postcss-scope-pseudo-class': 3.0.0(postcss@8.4.47) + '@csstools/postcss-stepped-value-functions': 3.0.2(postcss@8.4.47) + '@csstools/postcss-text-decoration-shorthand': 3.0.3(postcss@8.4.47) + '@csstools/postcss-trigonometric-functions': 3.0.2(postcss@8.4.47) + '@csstools/postcss-unset-value': 3.0.0(postcss@8.4.47) + autoprefixer: 10.4.16(postcss@8.4.47) + browserslist: 4.25.1 + css-blank-pseudo: 6.0.0(postcss@8.4.47) + css-has-pseudo: 6.0.0(postcss@8.4.47) + css-prefers-color-scheme: 9.0.0(postcss@8.4.47) + cssdb: 7.8.0 + postcss: 8.4.47 + postcss-attribute-case-insensitive: 6.0.2(postcss@8.4.47) + postcss-clamp: 4.1.0(postcss@8.4.47) + postcss-color-functional-notation: 6.0.2(postcss@8.4.47) + postcss-color-hex-alpha: 9.0.2(postcss@8.4.47) + postcss-color-rebeccapurple: 9.0.1(postcss@8.4.47) + postcss-custom-media: 10.0.2(postcss@8.4.47) + postcss-custom-properties: 13.3.2(postcss@8.4.47) + postcss-custom-selectors: 7.1.6(postcss@8.4.47) + postcss-dir-pseudo-class: 8.0.0(postcss@8.4.47) + postcss-double-position-gradients: 5.0.2(postcss@8.4.47) + postcss-focus-visible: 9.0.0(postcss@8.4.47) + postcss-focus-within: 8.0.0(postcss@8.4.47) + postcss-font-variant: 5.0.0(postcss@8.4.47) + postcss-gap-properties: 5.0.0(postcss@8.4.47) + postcss-image-set-function: 6.0.1(postcss@8.4.47) + postcss-lab-function: 6.0.7(postcss@8.4.47) + postcss-logical: 7.0.0(postcss@8.4.47) + postcss-nesting: 12.0.1(postcss@8.4.47) + postcss-opacity-percentage: 2.0.0(postcss@8.4.47) + postcss-overflow-shorthand: 5.0.0(postcss@8.4.47) + postcss-page-break: 3.0.4(postcss@8.4.47) + postcss-place: 9.0.0(postcss@8.4.47) + postcss-pseudo-class-any-link: 9.0.0(postcss@8.4.47) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.47) + postcss-selector-not: 7.0.1(postcss@8.4.47) + postcss-value-parser: 4.2.0 + + postcss-pseudo-class-any-link@9.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-reduce-initial@7.0.2(postcss@8.4.47): + dependencies: + browserslist: 4.25.1 + caniuse-api: 3.0.0 + postcss: 8.4.47 + + postcss-reduce-transforms@7.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + + postcss-replace-overflow-wrap@4.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-resolve-nested-selector@0.1.1: {} + + postcss-safe-parser@6.0.0(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-scss@4.0.9(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-selector-not@7.0.1(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-sorting@8.0.2(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + + postcss-svgo@7.0.1(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-value-parser: 4.2.0 + svgo: 3.3.2 + + postcss-unique-selectors@7.0.3(postcss@8.4.47): + dependencies: + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.47: + dependencies: + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.2.5: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + process-nextick-args@2.0.1: {} + + process-warning@3.0.0: {} + + process@0.11.10: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-expr@2.0.6: {} + + prosemirror-autocomplete@0.4.3: + dependencies: + prosemirror-inputrules: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.6 + + prosemirror-codemark@0.4.2(prosemirror-inputrules@1.5.1)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6): + dependencies: + prosemirror-inputrules: 1.5.1 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.6 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-model@1.25.4: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.6 + + prosemirror-test-builder@1.1.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + + prosemirror-transform@1.11.0: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-utils@1.2.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4): + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + + prosemirror-view@1.41.6: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + protobufjs@7.4.0: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.16.12 + long: 5.3.1 + + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.19.33 + long: 5.3.1 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-from-env@1.1.0: {} + + psl@1.8.0: {} + + pstree.remy@1.1.8: {} + + pump@3.0.0: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + punycode@1.4.1: {} + + punycode@2.1.1: {} + + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + + qs@6.14.1: + dependencies: + side-channel: 1.1.0 + + qs@6.5.3: {} + + querystring@0.2.1: {} + + querystringify@2.2.0: {} + + queue-microtask@1.2.3: {} + + quick-format-unescaped@4.0.4: {} + + quick-lru@5.1.1: {} + + quickjs-emscripten-core@0.31.0: + dependencies: + '@jitl/quickjs-ffi-types': 0.31.0 + + quickjs-emscripten@0.31.0: + dependencies: + '@jitl/quickjs-wasmfile-debug-asyncify': 0.31.0 + '@jitl/quickjs-wasmfile-debug-sync': 0.31.0 + '@jitl/quickjs-wasmfile-release-asyncify': 0.31.0 + '@jitl/quickjs-wasmfile-release-sync': 0.31.0 + quickjs-emscripten-core: 0.31.0 + + raf-schd@4.0.3: {} + + rambda@9.4.2: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + rc-slider@11.1.8(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + rc-util@5.44.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + react-is: 18.3.1 + + react-beautiful-dnd@13.1.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + css-box-model: 1.2.1 + memoize-one: 5.2.1 + raf-schd: 4.0.3 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + react-redux: 7.2.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + redux: 4.2.1 + use-memo-one: 1.1.3(react@17.0.2) + transitivePeerDependencies: + - react-native + + react-dnd-html5-backend@10.0.2: + dependencies: + dnd-core: 10.0.2 + + react-dnd@10.0.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + '@react-dnd/shallowequal': 2.0.0 + '@types/hoist-non-react-statics': 3.3.1 + dnd-core: 10.0.2 + hoist-non-react-statics: 3.3.2 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + react-dom@17.0.2(react@17.0.2): + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react: 17.0.2 + scheduler: 0.20.2 + + react-draggable@4.4.6(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + clsx: 1.2.1 + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + react-error-boundary@3.1.4(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + react: 17.0.2 + + react-grid-layout@1.5.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + clsx: 2.1.1 + fast-equals: 4.0.3 + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + react-draggable: 4.4.6(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react-resizable: 3.0.5(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + resize-observer-polyfill: 1.5.1 + + react-hotkeys-hook@4.5.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + react-inspector@5.1.1(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + is-dom: 1.1.0 + prop-types: 15.8.1 + react: 17.0.2 + + react-intersection-observer@9.4.0(react@17.0.2): + dependencies: + react: 17.0.2 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-lifecycles-compat@3.0.4: {} + + react-list@0.8.18(react@17.0.2): + dependencies: + react: 17.0.2 + + react-monaco-editor@0.52.0(@types/react@17.0.40)(monaco-editor@0.36.1)(react@17.0.2): + dependencies: + '@types/react': 17.0.40 + monaco-editor: 0.36.1 + prop-types: 15.8.1 + react: 17.0.2 + + react-property@2.0.2: {} + + react-recompose@0.33.0(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + change-emitter: 0.1.6 + hoist-non-react-statics: 2.5.5 + react: 17.0.2 + react-lifecycles-compat: 3.0.4 + symbol-observable: 4.0.0 + + react-redux@7.2.4(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + '@types/react-redux': 7.1.16 + hoist-non-react-statics: 3.3.2 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 17.0.2 + react-is: 16.13.1 + optionalDependencies: + react-dom: 17.0.2(react@17.0.2) + + react-refresh@0.14.2: {} + + react-resizable@3.0.5(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + prop-types: 15.8.1 + react: 17.0.2 + react-draggable: 4.4.6(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + transitivePeerDependencies: + - react-dom + + react-router-dom@5.3.4(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + history: 4.10.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 17.0.2 + react-router: 5.3.4(react@17.0.2) + tiny-invariant: 1.1.0 + tiny-warning: 1.0.3 + + react-router@5.3.4(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + history: 4.10.1 + hoist-non-react-statics: 3.3.2 + loose-envify: 1.4.0 + path-to-regexp: 1.9.0 + prop-types: 15.8.1 + react: 17.0.2 + react-is: 16.13.1 + tiny-invariant: 1.1.0 + tiny-warning: 1.0.3 + + react-split-pane@0.1.92(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + react-lifecycles-compat: 3.0.4 + react-style-proptype: 3.2.2 + + react-style-proptype@3.2.2: + dependencies: + prop-types: 15.8.1 + + react-transition-group@4.4.5(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + react-universal-interface@0.6.2(react@17.0.2)(tslib@2.8.1): + dependencies: + react: 17.0.2 + tslib: 2.8.1 + + react-use@17.6.0(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + '@types/js-cookie': 2.2.7 + '@xobotyi/scrollbar-width': 1.9.5 + copy-to-clipboard: 3.3.3 + fast-deep-equal: 3.1.3 + fast-shallow-equal: 1.0.0 + js-cookie: 2.2.1 + nano-css: 5.6.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + react-universal-interface: 0.6.2(react@17.0.2)(tslib@2.8.1) + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + set-harmonic-interval: 1.0.1 + throttle-debounce: 3.0.1 + ts-easing: 0.2.0 + tslib: 2.8.1 + + react-virtualized-auto-sizer@1.0.24(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + react-waypoint@10.3.0(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + consolidated-events: 2.0.2 + prop-types: 15.8.1 + react: 17.0.2 + react-is: 18.3.1 + + react-window@1.8.11(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + '@babel/runtime': 7.26.10 + memoize-one: 5.2.1 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + react@17.0.2: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + + read-pkg-up@8.0.0: + dependencies: + find-up: 5.0.0 + read-pkg: 6.0.0 + type-fest: 1.4.0 + + read-pkg@6.0.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 3.0.3 + parse-json: 5.2.0 + type-fest: 1.4.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.2 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + + real-require@0.2.0: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + redent@4.0.0: + dependencies: + indent-string: 5.0.0 + strip-indent: 4.0.0 + + redis-commands@1.7.0: {} + + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + + redux-devtools-extension@2.13.9(redux@4.2.1): + dependencies: + redux: 4.2.1 + + redux-logger@3.0.6: + dependencies: + deep-diff: 0.3.8 + + redux-thunk@2.4.2(redux@4.2.1): + dependencies: + redux: 4.2.1 + + redux@4.2.1: + dependencies: + '@babel/runtime': 7.26.10 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + reflect.getprototypeof@1.0.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + globalthis: 1.0.3 + which-builtin-type: 1.1.3 + + regenerate-unicode-properties@10.2.0: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.14.0: {} + + regenerator-transform@0.15.2: + dependencies: + '@babel/runtime': 7.26.10 + + regex-parser@2.2.11: {} + + regexp-tree@0.1.27: {} + + regexp.prototype.flags@1.5.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + regexpu-core@6.1.1: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.0 + regjsgen: 0.8.0 + regjsparser: 0.11.2 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.0 + + regjsgen@0.8.0: {} + + regjsparser@0.11.2: + dependencies: + jsesc: 3.0.2 + + request-ip@3.3.0: {} + + request-promise-core@1.1.4(request@2.88.2): + dependencies: + lodash: 4.17.21 + request: 2.88.2 + + request-promise-native@1.0.9(request@2.88.2): + dependencies: + request: 2.88.2 + request-promise-core: 1.1.4(request@2.88.2) + stealthy-require: 1.1.1 + tough-cookie: 2.5.0 + + request@2.88.2: + dependencies: + aws-sign2: 0.7.0 + aws4: 1.11.0 + caseless: 0.12.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 2.3.3 + har-validator: 5.1.5 + http-signature: 1.2.0 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + oauth-sign: 0.9.0 + performance-now: 2.1.0 + qs: 6.5.3 + safe-buffer: 5.2.1 + tough-cookie: 2.5.0 + tunnel-agent: 0.6.0 + uuid: 3.4.0 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + require-in-the-middle@7.5.2: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + module-details-from-path: 1.0.3 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + requires-port@1.0.0: {} + + reselect@4.1.8: {} + + resize-observer-polyfill@1.5.1: {} + + resolve-alpn@1.2.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pathname@3.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve-url-loader@5.0.0: + dependencies: + adjust-sourcemap-loader: 4.0.0 + convert-source-map: 1.7.0 + loader-utils: 2.0.4 + postcss: 8.4.47 + source-map: 0.6.1 + + resolve.exports@1.1.0: {} + + resolve.exports@2.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@1.22.8: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + responselike@3.0.0: + dependencies: + lowercase-keys: 3.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + retry@0.13.1: {} + + reusify@1.0.4: {} + + rfdc@1.3.0: {} + + rfdc@1.4.1: {} + + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rimraf@5.0.10: + dependencies: + glob: 10.4.5 + + robust-orientation@1.2.1: + dependencies: + robust-scale: 1.0.2 + robust-subtract: 1.0.0 + robust-sum: 1.0.0 + two-product: 1.0.2 + + robust-point-in-polygon@1.0.3: + dependencies: + robust-orientation: 1.2.1 + + robust-predicates@3.0.2: {} + + robust-scale@1.0.2: + dependencies: + two-product: 1.0.2 + two-sum: 1.0.0 + + robust-subtract@1.0.0: {} + + robust-sum@1.0.0: {} + + rope-sequence@1.3.4: {} + + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + rslog@1.2.9: {} + + rspack-manifest-plugin@5.0.3(@rspack/core@1.5.5(@swc/helpers@0.5.17)): + dependencies: + '@rspack/lite-tapable': 1.0.1 + optionalDependencies: + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + + rtl-css-js@1.16.1: + dependencies: + '@babel/runtime': 7.26.10 + + run-applescript@7.0.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rw@1.3.3: {} + + rxjs@7.8.1: + dependencies: + tslib: 2.8.1 + + safe-array-concat@1.1.2: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.0.3: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safe-regex@2.1.1: + dependencies: + regexp-tree: 0.1.27 + + safe-stable-stringify@2.5.0: {} + + safer-buffer@2.1.2: {} + + sanitize-html@2.14.0: + dependencies: + deepmerge: 4.3.1 + escape-string-regexp: 4.0.0 + htmlparser2: 8.0.2 + is-plain-object: 5.0.0 + parse-srcset: 1.0.2 + postcss: 8.4.47 + + sass-loader@16.0.3(@rspack/core@1.5.5(@swc/helpers@0.5.17))(sass@1.80.6)(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + neo-async: 2.6.2 + optionalDependencies: + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + sass: 1.80.6 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + sass@1.80.6: + dependencies: + chokidar: 4.0.3 + immutable: 4.3.7 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.0 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.20.2: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + + schema-utils@4.3.3: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + ajv-keywords: 5.1.0(ajv@8.12.0) + + screenfull@5.2.0: {} + + secure-json-parse@2.7.0: {} + + seek-bzip@2.0.0: + dependencies: + commander: 6.2.1 + + select-hose@2.0.0: {} + + selfsigned@2.4.1: + dependencies: + '@types/node-forge': 1.3.11 + node-forge: 1.3.1 + + semver-regex@4.0.5: {} + + semver-truncate@3.0.0: + dependencies: + semver: 7.7.3 + + semver@6.3.1: {} + + semver@7.6.3: {} + + semver@7.7.2: {} + + semver@7.7.3: {} + + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + serve-index@1.9.1: + dependencies: + accepts: 1.3.8 + batch: 0.6.1 + debug: 2.6.9 + escape-html: 1.0.3 + http-errors: 1.6.3 + mime-types: 2.1.35 + parseurl: 1.3.3 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + set-cookie-parser@2.7.1: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-harmonic-interval@1.0.1: {} + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + setprototypeof@1.1.0: {} + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.1: {} + + shimmer@1.2.1: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.7.3 + + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + slice-ansi@3.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slugify@1.6.6: {} + + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + socket.io-adapter@2.5.5: + dependencies: + debug: 4.3.7 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + socket.io@4.8.1: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.7 + engine.io: 6.6.4 + socket.io-adapter: 2.5.5 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + sockjs@0.3.24: + dependencies: + faye-websocket: 0.11.4 + uuid: 8.3.2 + websocket-driver: 0.7.4 + + sonic-boom@3.8.1: + dependencies: + atomic-sleep: 1.0.0 + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + + sort-keys-length@1.0.1: + dependencies: + sort-keys: 1.1.2 + + sort-keys@1.1.2: + dependencies: + is-plain-obj: 1.1.0 + + sorted-array-functions@1.3.0: {} + + source-list-map@2.0.1: {} + + source-map-js@1.2.1: {} + + source-map-loader@5.0.0(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + iconv-lite: 0.6.3 + source-map-js: 1.2.1 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + source-map-resolve@0.6.0: + dependencies: + atob: 2.1.2 + decode-uri-component: 0.2.2 + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.1 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.1 + source-map: 0.6.1 + + source-map@0.5.6: {} + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + source-map@0.7.6: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.17 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.17 + + spdx-license-ids@3.0.17: {} + + spdy-transport@3.0.0: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + detect-node: 2.1.0 + hpack.js: 2.1.6 + obuf: 1.1.2 + readable-stream: 3.6.2 + wbuf: 1.7.3 + transitivePeerDependencies: + - supports-color + + spdy@4.0.2: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + handle-thing: 2.0.1 + http-deceiver: 1.2.7 + select-hose: 2.0.0 + spdy-transport: 3.0.0 + transitivePeerDependencies: + - supports-color + + split2@4.2.0: {} + + sprintf-js@1.0.3: {} + + sshpk@1.18.0: + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + + stable@0.1.8: {} + + stack-generator@2.0.10: + dependencies: + stackframe: 1.3.4 + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stackframe@1.3.4: {} + + stacktrace-gps@3.1.2: + dependencies: + source-map: 0.5.6 + stackframe: 1.3.4 + + stacktrace-js@2.0.2: + dependencies: + error-stack-parser: 2.1.4 + stack-generator: 2.0.10 + stacktrace-gps: 3.1.2 + + standard-as-callback@2.1.0: {} + + statuses@1.5.0: {} + + statuses@2.0.1: {} + + statuses@2.0.2: {} + + stealthy-require@1.1.1: {} + + streamroller@3.1.5: + dependencies: + date-format: 4.0.14 + debug: 4.4.3(supports-color@5.5.0) + fs-extra: 8.1.0 + transitivePeerDependencies: + - supports-color + + streamx@2.23.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-argv@0.3.1: {} + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string.prototype.matchall@4.0.11: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.0.7 + regexp.prototype.flags: 1.5.2 + set-function-name: 2.0.2 + side-channel: 1.0.6 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.1.1 + + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-bom@3.0.0: {} + + strip-bom@4.0.0: {} + + strip-dirs@3.0.0: + dependencies: + inspect-with-kind: 1.0.5 + is-plain-obj: 1.1.0 + + strip-final-newline@2.0.0: {} + + strip-final-newline@3.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-indent@4.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + strnum@1.0.5: {} + + strtok3@10.3.4: + dependencies: + '@tokenizer/token': 0.3.0 + + strtok3@9.1.1: + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 5.4.2 + + style-loader@4.0.0(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + style-mod@4.1.3: {} + + style-search@0.1.0: {} + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + stylehacks@7.0.4(postcss@8.4.47): + dependencies: + browserslist: 4.25.1 + postcss: 8.4.47 + postcss-selector-parser: 6.1.2 + + stylelint-order@6.0.4(stylelint@15.11.0(typescript@5.6.3)): + dependencies: + postcss: 8.4.47 + postcss-sorting: 8.0.2(postcss@8.4.47) + stylelint: 15.11.0(typescript@5.6.3) + + stylelint-prettier@4.1.0(prettier@3.2.5)(stylelint@15.11.0(typescript@5.6.3)): + dependencies: + prettier: 3.2.5 + prettier-linter-helpers: 1.0.0 + stylelint: 15.11.0(typescript@5.6.3) + + stylelint-scss@5.3.2(stylelint@15.11.0(typescript@5.6.3)): + dependencies: + known-css-properties: 0.29.0 + postcss-media-query-parser: 0.2.3 + postcss-resolve-nested-selector: 0.1.1 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + stylelint: 15.11.0(typescript@5.6.3) + + stylelint@15.11.0(typescript@5.6.3): + dependencies: + '@csstools/css-parser-algorithms': 2.3.2(@csstools/css-tokenizer@2.2.1) + '@csstools/css-tokenizer': 2.2.1 + '@csstools/media-query-list-parser': 2.1.5(@csstools/css-parser-algorithms@2.3.2(@csstools/css-tokenizer@2.2.1))(@csstools/css-tokenizer@2.2.1) + '@csstools/selector-specificity': 3.0.0(postcss-selector-parser@6.1.2) + balanced-match: 2.0.0 + colord: 2.9.3 + cosmiconfig: 8.3.6(typescript@5.6.3) + css-functions-list: 3.2.2 + css-tree: 2.3.1 + debug: 4.4.3(supports-color@5.5.0) + fast-glob: 3.3.2 + fastest-levenshtein: 1.0.16 + file-entry-cache: 7.0.2 + global-modules: 2.0.0 + globby: 11.1.0 + globjoin: 0.1.4 + html-tags: 3.3.1 + ignore: 5.3.1 + import-lazy: 4.0.0 + imurmurhash: 0.1.4 + is-plain-object: 5.0.0 + known-css-properties: 0.29.0 + mathml-tag-names: 2.1.3 + meow: 10.1.5 + micromatch: 4.0.8 + normalize-path: 3.0.0 + picocolors: 1.1.1 + postcss: 8.4.47 + postcss-resolve-nested-selector: 0.1.1 + postcss-safe-parser: 6.0.0(postcss@8.4.47) + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + resolve-from: 5.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + style-search: 0.1.0 + supports-hyperlinks: 3.0.0 + svg-tags: 1.0.0 + table: 6.8.2 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + - typescript + + stylis@4.3.6: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@3.0.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svg-parser@2.0.4: {} + + svg-tags@1.0.0: {} + + svgo@2.8.0: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.1.1 + stable: 0.1.8 + + svgo@3.3.2: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 5.1.0 + css-tree: 2.3.1 + css-what: 6.1.0 + csso: 5.0.5 + picocolors: 1.1.1 + + swagger-ui-dist@5.29.0: + dependencies: + '@scarf/scarf': 1.4.0 + + swagger-ui-express@5.0.1(express@4.21.2): + dependencies: + express: 4.21.2 + swagger-ui-dist: 5.29.0 + + swc-loader@0.2.6(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + '@swc/core': 1.13.5(@swc/helpers@0.5.17) + '@swc/counter': 0.1.3 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + symbol-observable@4.0.0: {} + + symbol-tree@3.2.4: {} + + synckit@0.8.8: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.8.1 + + tabbable@6.2.0: {} + + table@6.8.2: + dependencies: + ajv: 8.12.0 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + tapable@2.2.2: {} + + tar-stream@3.1.7: + dependencies: + b4a: 1.7.3 + fast-fifo: 1.3.2 + streamx: 2.23.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + terser-webpack-plugin@5.3.10(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.30.3 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + optionalDependencies: + '@swc/core': 1.13.5(@swc/helpers@0.5.17) + + terser@5.30.3: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.14.1 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + text-decoder@1.2.3: + dependencies: + b4a: 1.7.3 + transitivePeerDependencies: + - react-native-b4a + + text-table@0.2.0: {} + + thingies@1.21.0(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + + thread-stream@2.7.0: + dependencies: + real-require: 0.2.0 + + throat@6.0.1: {} + + throttle-debounce@3.0.1: {} + + through@2.3.8: {} + + thunky@1.1.0: {} + + timers-ext@0.1.7: + dependencies: + es5-ext: 0.10.63 + next-tick: 1.1.0 + + tiny-case@1.0.3: {} + + tiny-invariant@1.1.0: {} + + tiny-warning@1.0.3: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toggle-selection@1.0.6: {} + + toidentifier@1.0.1: {} + + token-types@6.1.1: + dependencies: + '@borewit/text-codec': 0.1.1 + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + + toposort@2.0.2: {} + + totalist@3.0.1: {} + + touch@3.1.1: {} + + tough-cookie@2.5.0: + dependencies: + psl: 1.8.0 + punycode: 2.1.1 + + tough-cookie@4.1.3: + dependencies: + psl: 1.8.0 + punycode: 2.1.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@3.0.0: + dependencies: + punycode: 2.1.1 + + tree-dump@1.0.2(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + + trim-newlines@4.1.1: {} + + ts-api-utils@1.3.0(typescript@5.6.3): + dependencies: + typescript: 5.6.3 + + ts-checker-rspack-plugin@1.1.1(@rspack/core@1.5.5(@swc/helpers@0.5.17))(typescript@5.6.3): + dependencies: + '@babel/code-frame': 7.26.2 + '@rspack/lite-tapable': 1.0.1 + chokidar: 3.6.0 + memfs: 4.17.0 + minimatch: 9.0.5 + picocolors: 1.1.1 + typescript: 5.6.3 + optionalDependencies: + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + + ts-dedent@2.2.0: {} + + ts-easing@0.2.0: {} + + ts-jest@29.0.5(@babel/core@7.26.10)(@jest/types@29.6.3)(babel-jest@29.4.3(@babel/core@7.26.10))(jest@29.4.3(@types/node@20.16.12)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)))(typescript@5.6.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.4.3(@types/node@20.16.12)(ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3)) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.2 + typescript: 5.6.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.10 + '@jest/types': 29.6.3 + babel-jest: 29.4.3(@babel/core@7.26.10) + + ts-node@10.9.2(@swc/core@1.13.5(@swc/helpers@0.5.17))(@types/node@20.16.12)(typescript@5.6.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.8 + '@tsconfig/node12': 1.0.9 + '@tsconfig/node14': 1.0.1 + '@tsconfig/node16': 1.0.2 + '@types/node': 20.16.12 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.6.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.13.5(@swc/helpers@0.5.17) + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tsconfig-to-swcconfig@2.8.1: + dependencies: + '@fastify/deepmerge': 1.3.0 + get-tsconfig: 4.10.0 + + tslib@1.14.1: {} + + tslib@2.8.1: {} + + tsscmp@1.0.6: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + tweetnacl@0.14.5: {} + + two-product@1.0.2: {} + + two-sum@1.0.0: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.11.0: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@1.4.0: {} + + type-fest@2.19.0: {} + + type-fest@3.13.1: + optional: true + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + type@1.2.0: {} + + type@2.7.2: {} + + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.2.0 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.2.0 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.2.0 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typedarray-to-buffer@3.1.5: + dependencies: + is-typedarray: 1.0.0 + + typescript@5.6.3: {} + + uc.micro@1.0.6: {} + + ufo@1.6.3: {} + + uint8array-extras@1.5.0: {} + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.1.0 + which-boxed-primitive: 1.0.2 + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + unbzip2-stream@1.4.3: + dependencies: + buffer: 5.7.1 + through: 2.3.8 + + undefsafe@2.0.5: {} + + undici-types@5.26.5: {} + + undici-types@6.19.8: {} + + undici-types@6.21.0: {} + + undici@6.21.3: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.1.0 + + unicode-match-property-value-ecmascript@2.2.0: {} + + unicode-property-aliases-ecmascript@2.1.0: {} + + universal-cookie@7.2.2: + dependencies: + '@types/cookie': 0.6.0 + cookie: 0.7.2 + + universalify@0.1.2: {} + + universalify@0.2.0: {} + + universalify@2.0.0: {} + + unpipe@1.0.0: {} + + upath@2.0.1: {} + + update-browserslist-db@1.1.3(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + update-browserslist-db@1.1.3(browserslist@4.25.1): + dependencies: + browserslist: 4.25.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uplot@1.6.32: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.1.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + url@0.11.3: + dependencies: + punycode: 1.4.1 + qs: 6.13.0 + + use-memo-one@1.1.3(react@17.0.2): + dependencies: + react: 17.0.2 + + util-deprecate@1.0.2: {} + + utility-types@3.10.0: {} + + utils-merge@1.0.1: {} + + uuid@11.0.5: {} + + uuid@11.1.0: {} + + uuid@3.4.0: {} + + uuid@8.3.2: {} + + uuid@9.0.1: {} + + v8-compile-cache-lib@3.0.1: {} + + v8-to-istanbul@9.1.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.4 + convert-source-map: 1.7.0 + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + value-equal@1.0.1: {} + + vary@1.1.2: {} + + verror@1.10.0: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.3.0 + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + + w3c-keyname@2.2.8: {} + + w3c-xmlserializer@4.0.0: + dependencies: + xml-name-validator: 4.0.0 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + watchpack@2.4.1: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + wbuf@1.7.3: + dependencies: + minimalistic-assert: 1.0.1 + + webidl-conversions@7.0.0: {} + + webpack-assets-manifest@5.2.1(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + chalk: 4.1.2 + deepmerge: 4.3.1 + lockfile: 1.0.4 + lodash.get: 4.4.2 + lodash.has: 4.5.2 + schema-utils: 3.3.0 + tapable: 2.2.2 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + webpack-bundle-analyzer@4.10.2: + dependencies: + '@discoveryjs/json-ext': 0.5.7 + acorn: 8.14.1 + acorn-walk: 8.3.4 + commander: 7.2.0 + debounce: 1.2.1 + escape-string-regexp: 4.0.0 + gzip-size: 6.0.0 + html-escaper: 2.0.2 + opener: 1.5.2 + picocolors: 1.1.1 + sirv: 2.0.4 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + webpack-dev-middleware@7.4.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + colorette: 2.0.19 + memfs: 4.17.0 + mime-types: 2.1.35 + on-finished: 2.4.1 + range-parser: 1.2.1 + schema-utils: 4.3.3 + optionalDependencies: + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + webpack-dev-server@5.2.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + '@types/bonjour': 3.5.13 + '@types/connect-history-api-fallback': 1.5.4 + '@types/express': 4.17.21 + '@types/express-serve-static-core': 4.17.33 + '@types/serve-index': 1.9.4 + '@types/serve-static': 1.15.7 + '@types/sockjs': 0.3.36 + '@types/ws': 8.5.13 + ansi-html-community: 0.0.8 + bonjour-service: 1.2.1 + chokidar: 3.6.0 + colorette: 2.0.19 + compression: 1.7.5 + connect-history-api-fallback: 2.0.0 + express: 4.21.2 + graceful-fs: 4.2.11 + http-proxy-middleware: 2.0.9(@types/express@4.17.21) + ipaddr.js: 2.2.0 + launch-editor: 2.9.1 + open: 10.1.0 + p-retry: 6.2.0 + schema-utils: 4.3.3 + selfsigned: 2.4.1 + serve-index: 1.9.1 + sockjs: 0.3.24 + spdy: 4.0.2 + webpack-dev-middleware: 7.4.2(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + ws: 8.18.3 + optionalDependencies: + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + webpack-manifest-plugin@5.0.0(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + tapable: 2.2.2 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + webpack-sources: 2.3.1 + + webpack-sources@2.3.1: + dependencies: + source-list-map: 2.0.1 + source-map: 0.6.1 + + webpack-sources@3.2.3: {} + + webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + acorn: 8.14.1 + browserslist: 4.25.1 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.17.1 + es-module-lexer: 1.3.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.2 + terser-webpack-plugin: 5.3.10(@swc/core@1.13.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))) + watchpack: 2.4.1 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + websocket-driver@0.7.4: + dependencies: + http-parser-js: 0.5.8 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + + websocket-extensions@0.1.4: {} + + whatwg-encoding@2.0.0: + dependencies: + iconv-lite: 0.6.3 + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@3.0.0: {} + + whatwg-mimetype@4.0.0: {} + + whatwg-url@11.0.0: + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + + which-boxed-primitive@1.0.2: + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.1.3: + dependencies: + function.prototype.name: 1.1.6 + has-tostringtag: 1.0.2 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.0.2 + is-generator-function: 1.0.10 + is-regex: 1.1.4 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.15 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + worker-loader@3.0.8(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + loader-utils: 2.0.4 + schema-utils: 3.3.0 + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + worker-rspack-loader@3.1.2(@rspack/core@1.5.5(@swc/helpers@0.5.17))(webpack@5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17))): + dependencies: + loader-utils: 2.0.4 + schema-utils: 3.3.0 + optionalDependencies: + '@rspack/core': 1.5.5(@swc/helpers@0.5.17) + webpack: 5.96.1(@swc/core@1.13.5(@swc/helpers@0.5.17)) + + workerpool@9.1.1: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + write-file-atomic@3.0.3: + dependencies: + imurmurhash: 0.1.4 + is-typedarray: 1.0.0 + signal-exit: 3.0.7 + typedarray-to-buffer: 3.1.5 + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + ws@7.5.10: {} + + ws@8.17.1: {} + + ws@8.18.0: {} + + ws@8.18.3: {} + + xml-name-validator@4.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yaml@2.8.1: {} + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@3.2.0: + dependencies: + buffer-crc32: 0.2.13 + pend: 1.2.0 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} + + yocto-queue@1.1.1: {} + + yup@1.6.1: + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + + zod@4.3.6: {} diff --git a/scripts/ci/lint-typecheck/features.sh b/scripts/ci/lint-typecheck/features.sh new file mode 100644 index 0000000..e749ffa --- /dev/null +++ b/scripts/ci/lint-typecheck/features.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +ts-node --transpile-only scripts/ci/lint-typecheck/helpers/check-features.ts diff --git a/scripts/ci/lint-typecheck/helpers/check-features.ts b/scripts/ci/lint-typecheck/helpers/check-features.ts new file mode 100644 index 0000000..c33367b --- /dev/null +++ b/scripts/ci/lint-typecheck/helpers/check-features.ts @@ -0,0 +1,40 @@ +import fs from 'fs'; +import path from 'path'; + +import {Feature} from '../../../../src/shared'; + +const featuresListPath = path.resolve( + process.cwd(), + 'src/server/components/features/features-list', +); + +const FEATURES_LIST: any = []; + +fs.readdirSync(featuresListPath).forEach((file: string) => { + if (file === 'index.ts') { + return; + } + + FEATURES_LIST.push(require(path.resolve(featuresListPath, file)).default); +}); + +const FEATURES = FEATURES_LIST.reduce( + (acc: Record, {name}: {name: string}) => { + acc[name] = true; + return acc; + }, + {} as Record, +); + +const missedFeatures: string[] = []; + +Object.values(Feature).forEach((feature) => { + if (typeof FEATURES[feature] === 'undefined') { + missedFeatures.push(feature); + console.error(`Missed config for feature: ${feature}`); + } +}); + +if (missedFeatures.length) { + throw new Error('Missed config for some feature, see output above'); +} diff --git a/scripts/ci/statoscope-comment.jora b/scripts/ci/statoscope-comment.jora new file mode 100644 index 0000000..a538e12 --- /dev/null +++ b/scripts/ci/statoscope-comment.jora @@ -0,0 +1,37 @@ +// variables +$after: resolveInputFile(); +$inputCompilation: $after.compilations.pick(); +$before: resolveReferenceFile(); +$referenceCompilation: $before.compilations.pick(); + +// helpers +$getSizeByChunks: => files.(getAssetSize($$, true)).reduce(=> size + $$, 0); + +// output +{ + buildTime: { + $after: $inputCompilation.time; + $before: $referenceCompilation.time; + $after, + $before, + diff: { + value: $after - $before, + formatted: { type: 'time', a: $before, b: $after } | formatDiff() + ` (${b.percentFrom(a, 2)}%)`, + } + }, + initialSize: { + $after: $inputCompilation.chunks.$getSizeByChunks($inputCompilation.hash); + $before: $referenceCompilation.chunks.$getSizeByChunks($referenceCompilation.hash); + $after, + $before, + diff: { + value: $after - $before, + formatted: { type: 'size', a: $before, b: $after } | formatDiff() + ` (${b.percentFrom(a, 2)}%)`, + } + }, + validation: { + $messages: resolveInputFile().compilations.[hash].(hash.validation_getItems()); + $messages, + total: $messages.size() + } +} \ No newline at end of file diff --git a/scripts/e2e/us-dump.sh b/scripts/e2e/us-dump.sh new file mode 100644 index 0000000..bbaaf04 --- /dev/null +++ b/scripts/e2e/us-dump.sh @@ -0,0 +1,213 @@ +#!/bin/bash + +# exit setup +set -eo pipefail +# [-e] - immediately exit if any command has a non-zero exit status +# [-x] - all executed commands are printed to the terminal [not secure] +# [-o pipefail] - if any command in a pipeline fails, that return code will be used as the return code of the whole pipeline + +SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$0")") + +IS_CLEAR_DELETED="false" +IS_CLEAR_E2E="false" +IS_CLEAR_REVISIONS="false" + +# parse args +for _ in "$@"; do + case ${1} in + --clear-deleted) + IS_CLEAR_DELETED="true" + shift # past argument with no value + ;; + --clear-e2e) + IS_CLEAR_E2E="true" + shift # past argument with no value + ;; + --clear-revisions) + IS_CLEAR_REVISIONS="true" + shift # past argument with no value + ;; + -*) + echo "unknown arg: ${1}" + exit 1 + ;; + *) ;; + esac +done + +echo "" +echo "Start dump UnitedStorage entries..." +echo " - workbooks" +echo " - collections" +echo " - entries" +echo " - revisions" +echo " - links" + +if [ "${IS_CLEAR_DELETED}" = "true" ]; then + echo "+ clear deleted entries automatically..." +fi +if [ "${IS_CLEAR_E2E}" = "true" ]; then + echo "+ clear e2e entries automatically..." +fi +if [ "${IS_CLEAR_REVISIONS}" = "true" ]; then + echo "+ clear not actual entries revisions automatically..." +fi + +COMPOSE_FILE=$(readlink -f "${SCRIPT_DIR}/../../tests/docker-compose.e2e.yml") +DUMP_FILE=$(readlink -f "${SCRIPT_DIR}/../../tests/data/us-e2e-data.sql") + +echo "" +echo "========================" + +echo "BEGIN;" >"${DUMP_FILE}" + +docker --log-level error compose -f "${COMPOSE_FILE}" exec \ + --env "POSTGRES_DUMP_CLEAR_META=true" \ + --env "POSTGRES_DUMP_SKIP_CONFLICT=false" \ + -T postgres \ + /init/us-dump.sh | + sed -E 's|"cypher_text": "[^"]+"|"cypher_text": "{{POSTGRES_PASSWORD}}"|' | + sed -E 's|"host": "[^"]+"|"host": "{{POSTGRES_HOST}}"|' | + sed -E 's|"port": [^,]+,|"port": {{POSTGRES_PORT}},|' | + sed -E 's|"db_name": "[^"]+"|"db_name": "{{POSTGRES_DB}}"|' | + sed -E 's|"username": "[^"]+"|"username": "{{POSTGRES_USER}}"|' \ + >>"${DUMP_FILE}" + +EXIT="$?" + +echo "COMMIT;" >>"${DUMP_FILE}" + +echo "" +echo "========================" + +if [ "${IS_CLEAR_DELETED}" = "true" ]; then + echo "" + echo "Clear deleted entries..." + + DUMP=$(cat "${DUMP_FILE}") + DELETED_ENTRIES=$( + echo "${DUMP}" | + { grep ' public.entries ' || true; } | + { grep '__trash/' || true; } | + { grep -oE '__trash/[0-9]+_' || true; } | + sed 's|__trash/||' | + sed 's|_||' | + tr -d ' ' | + sort | + uniq + ) + + IFS=$'\n' + for DELETED_ENTRY in ${DELETED_ENTRIES}; do + echo " clear deleted entry: ${DELETED_ENTRY}" + DUMP=$(echo "${DUMP}" | { grep -v ", ${DELETED_ENTRY}, " || true; } | { grep -v "(${DELETED_ENTRY}, " || true; }) + done + unset IFS + + echo "${DUMP}" >"${DUMP_FILE}" +fi + +if [ "${IS_CLEAR_E2E}" = "true" ]; then + echo "" + echo "Clear e2e entries..." + + DUMP=$(cat "${DUMP_FILE}") + E2E_ENTRIES=$( + echo "${DUMP}" | + { grep ' public.entries ' || true; } | + { grep 'e2e-entry-' || true; } | + { grep -oE ", '[0-9]+/" || true; } | + sed "s|, '||" | + sed 's|/||' | + tr -d ' ' | + sort | + uniq + ) + + IFS=$'\n' + for E2E_ENTRY in ${E2E_ENTRIES}; do + echo " clear e2e entry: ${E2E_ENTRY}" + DUMP=$(echo "${DUMP}" | { grep -v ", ${E2E_ENTRY}, " || true; } | { grep -v "(${E2E_ENTRY}, " || true; }) + done + unset IFS + + echo "${DUMP}" >"${DUMP_FILE}" +fi + +if [ "${IS_CLEAR_REVISIONS}" = "true" ]; then + echo "" + echo "Clear not actual revisions..." + + DUMP=$(cat "${DUMP_FILE}") + ENTRIES=$( + echo "${DUMP}" | + grep ' public.entries ' | + grep -oE ", '[0-9]+/" | + sed "s|, '||" | + sed 's|/||' | + tr -d ' ' | + sort | + uniq + ) + + IFS=$'\n' + for ENTRY in ${ENTRIES}; do + echo " entry: ${ENTRY}" + REVISIONS=$(echo "${DUMP}" | { grep " public.revisions " || true; } | { grep ", ${ENTRY}, " || true; } | sed 's|INSERT INTO public.revisions .*VALUES|VALUES|' | sed "s|''||g" | sed -E "s|VALUES \('[^']+',||") + REVISIONS_COUNT=$(echo "${REVISIONS}" | wc -l | tr -d ' ') + echo " revisions count: ${REVISIONS_COUNT}" + + if [ "${REVISIONS_COUNT}" == "0" ] || [ "${REVISIONS_COUNT}" == "1" ]; then + continue + fi + echo " clear revisions..." + for REVISION in ${REVISIONS}; do + REVISION_ID=$(echo "${REVISION}" | { grep -oE ", [0-9]+," || true; } | sed 's|,||g' | tr -d ' ') + + ENTRY_REVISION=$(echo "${DUMP}" | { grep ' public.entries ' || true; } | { grep ", ${REVISION_ID}, " || true; } | tr -d ' ') + if [ ! -z "${ENTRY_REVISION}" ]; then + echo " actual revision id: ${REVISION_ID}" + continue + fi + + echo " clear revision id: ${REVISION_ID}" + DUMP=$(echo "${DUMP}" | { grep -v ", ${REVISION_ID}, " || true; }) + done + done + unset IFS + + echo "${DUMP}" >"${DUMP_FILE}" +fi + +E2E_ENTRIES=$(cat "${DUMP_FILE}" | { grep "e2e-entry-" || true; }) +DELETED_ENTRIES=$(cat "${DUMP_FILE}" | { grep "__trash/" || true; }) + +if [ -n "${E2E_ENTRIES}" ]; then + ENTRIES_KEYS=$(echo "${E2E_ENTRIES}" | sed -E "s|INSERT INTO ([^ ]+) .*'([^']*e2e-entry-[^']*)'.*|\1 - \2|" | sed 's|^| - |') + + echo "" + echo "⚠️ WARNING: Found entries with 'e2e-entry-' in key:" >&2 + echo "${ENTRIES_KEYS}" >&2 + echo "These entries might be test entries and should be reviewed..." >&2 +fi + +if [ -n "${DELETED_ENTRIES}" ]; then + ENTRIES_KEYS=$(echo "${DELETED_ENTRIES}" | sed -E "s|INSERT INTO ([^ ]+) .*'([^']*__trash/[^']*)'.*|\1 - \2|" | sed 's|^| - |') + + echo "" + echo "⚠️ WARNING: Found deleted entries:" >&2 + echo "${ENTRIES_KEYS}" >&2 +fi + +# remove empty lines +sed '/^$/N;/^\n$/D' "${DUMP_FILE}" >"${DUMP_FILE}.tmp" && mv "${DUMP_FILE}.tmp" "${DUMP_FILE}" + +if [ "${EXIT}" != "0" ]; then + echo "" + echo "Dump error, exit..." + exit "${EXIT}" +else + echo "" + echo "Dump done, saved at [${DUMP_FILE}]" + exit 0 +fi diff --git a/scripts/maintenance/utils/http-requests.js b/scripts/maintenance/utils/http-requests.js new file mode 100644 index 0000000..6394b34 --- /dev/null +++ b/scripts/maintenance/utils/http-requests.js @@ -0,0 +1,129 @@ +/* eslint-disable */ +'use strict'; + +process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0; + +const https = require('https'); +const http = require('http'); + +// hostname: 'localhost', +// port: 3000, +// path: `/`, +// headers: { +// 'Authorization': `OAuth ${OAUTH_TOKEN}`, +// 'Content-Type': 'application/json', +// }, +async function httpGet({protocol, ...options}) { + return new Promise((resolve, reject) => { + const request = protocol === 'https' ? https : http; + const req = request.get(options, (res) => { + const chunks = []; + + res.on('data', function (chunk) { + chunks.push(chunk); + }).on('end', function () { + let resBody = Buffer.concat(chunks).toString(); + if (res.statusCode === 200 || res.statusCode === 201) { + if (res.headers['content-type'].includes('application/json')) { + resBody = JSON.parse(resBody); + } + resolve(resBody); + } else { + reject(new Error(`Status: ${res.statusCode}, ${resBody}`)); + } + }); + }); + + req.on('error', (e) => { + reject(e); + }); + }); +} + +// hostname: 'localhost, +// port: 3000, +// path: `/`, +// headers: { +// 'Authorization': `OAuth ${OAUTH_TOKEN}`, +// 'Content-Type': 'application/json', +// }, +// body: JSON.stringify(data) +async function httpPost({body, protocol, ...options}) { + return new Promise((resolve, reject) => { + const request = protocol === 'https' ? https : http; + const req = request.request( + { + method: 'POST', + ...options, + }, + (res) => { + const chunks = []; + res.on('data', (data) => chunks.push(data)); + res.on('end', () => { + let resBody = Buffer.concat(chunks).toString(); + if (res.statusCode === 200 || res.statusCode === 201) { + if (res.headers['content-type'].includes('application/json')) { + resBody = JSON.parse(resBody); + } + resolve(resBody); + } else { + reject(new Error(`Status: ${res.statusCode}, ${resBody}`)); + } + }); + }, + ); + req.on('error', (e) => { + reject(e); + }); + if (body) { + req.write(body); + } + req.end(); + }); +} + +// hostname: 'localhost', +// port: 3000, +// path: `/`, +// headers: { +// 'Authorization': `OAuth ${OAUTH_TOKEN}`, +// 'Content-Type': 'application/json', +// }, +async function httpDelete({protocol, ...options}) { + return new Promise((resolve, reject) => { + const request = protocol === 'https' ? https : http; + const req = request.request( + { + method: 'DELETE', + ...options, + }, + (res) => { + const chunks = []; + res.on('data', (data) => chunks.push(data)); + res.on('end', () => { + let resBody = Buffer.concat(chunks).toString(); + if (res.statusCode === 200 || res.statusCode === 201) { + if (res.headers['content-type'].includes('application/json')) { + resBody = JSON.parse(resBody); + } + resolve(resBody); + } else { + reject(new Error(`Status: ${res.statusCode}, ${resBody}`)); + } + }); + }, + ); + + req.on('error', (e) => { + reject(e); + }); + + req.end(); + }); +} + +module.exports = { + httpGet, + httpPost, + httpDelete, +}; diff --git a/scripts/maintenance/utils/logger.js b/scripts/maintenance/utils/logger.js new file mode 100644 index 0000000..1dbbbb8 --- /dev/null +++ b/scripts/maintenance/utils/logger.js @@ -0,0 +1,40 @@ +const fs = require('fs'); + +const MAINTENANCE_LOGS_DIR = process.env.MAINTENANCE_LOGS_DIR; +if (!MAINTENANCE_LOGS_DIR) { + throw new Error('MAINTENANCE_LOGS_DIR directory path should be specified'); +} + +function getTimeNow() { + const now = new Date().toISOString(); + return now.substring(11); +} + +const now = new Date().toISOString(); +const stream = fs.createWriteStream( + `${MAINTENANCE_LOGS_DIR}/dc_${now.replace(/[-:]/g, '').replace(/\./g, '_')}.log`, + { + flags: 'a', + }, +); + +function logger({type = 'info', message}) { + switch (type) { + case 'info': + stream.write(`[INFO] - ${getTimeNow()}: `); + stream.write(message); + break; + case 'error': + stream.write(`[ERROR] - ${getTimeNow()}: `); + stream.write(String(message)); + break; + default: + stream.write(`[INFO] - ${getTimeNow()}: `); + stream.write(message); + } + stream.write('\n'); +} + +module.exports = { + logger, +}; diff --git a/scripts/maintenance/utils/uitls.js b/scripts/maintenance/utils/uitls.js new file mode 100644 index 0000000..ac070cd --- /dev/null +++ b/scripts/maintenance/utils/uitls.js @@ -0,0 +1,7 @@ +function sleep(time) { + return new Promise((res) => setTimeout(res, time)); +} + +module.exports = { + sleep, +}; diff --git a/scripts/setup-dev-env.js b/scripts/setup-dev-env.js new file mode 100644 index 0000000..ad42930 --- /dev/null +++ b/scripts/setup-dev-env.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node +'use strict'; + +const {readFileSync, writeFileSync, openSync} = require('fs'); +const path = require('path'); + +const SECRETS_SECTION_START = '### TEMPLATE SECRETS BEGIN'; +const SECRETS_SECTION_END = '### TEMPLATE SECRETS END'; +const REPLACE_REGEXP = new RegExp(`^${SECRETS_SECTION_START}.*${SECRETS_SECTION_END}$`, 'ms', 's'); + +const installationName = process.env.SETUP_DEV_ENV_INSTALLATION; +const envName = process.env.ENV || 'development'; +const templateName = `${installationName}/${envName}.env`; + +const appPath = path.join(__dirname, '..'); +const templateFilePath = path.join(appPath, `dev/env/${templateName}`); + +const templateContent = readFileSync(templateFilePath).toString(); +const secretsSection = `${SECRETS_SECTION_START}\n${templateContent}\n${SECRETS_SECTION_END}`; + +let currentEnv; + +try { + currentEnv = readFileSync(path.join(appPath, '.env')).toString(); +} catch (__) { + openSync(path.join(appPath, '.env'), 'w'); + + currentEnv = `${SECRETS_SECTION_START}\n${SECRETS_SECTION_END}`; +} + +writeFileSync(path.join(appPath, '.env'), currentEnv.replace(REPLACE_REGEXP, secretsSection)); diff --git a/secrets/.keep b/secrets/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/@types/chartkit-dl.d.ts b/src/@types/chartkit-dl.d.ts new file mode 100644 index 0000000..0492872 --- /dev/null +++ b/src/@types/chartkit-dl.d.ts @@ -0,0 +1,9 @@ +import '../shared/types/chartkit'; + +declare module '../shared/types' { + export interface TableCommonCell { + color?: number; + backgroundColor?: string; + type?: 'number' | 'markup'; + } +} diff --git a/src/@types/express.d.ts b/src/@types/express.d.ts new file mode 100644 index 0000000..1950658 --- /dev/null +++ b/src/@types/express.d.ts @@ -0,0 +1,19 @@ +import type {RenderParams} from '@gravity-ui/app-layout'; + +declare global { + namespace Express { + interface Request { + blackbox?: any; + tvmSelf?: any; + nonce?: string; + } + interface Response { + renderDatalensLayout: (params: RenderParams) => string; + } + } +} +declare module '@gravity-ui/expresskit' { + export interface AppRouteParams { + ui?: boolean; + } +} diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts new file mode 100644 index 0000000..50a2af0 --- /dev/null +++ b/src/@types/global.d.ts @@ -0,0 +1,47 @@ +declare module '*.svg' { + const content: SVGIconSvgrData; + + export default content; +} + +declare module '*.png' { + const path: string; + + export default path; +} + +declare module '*.jpg' { + const path: string; + + export default path; +} + +declare module '*.jpeg' { + const path: string; + + export default path; +} + +declare module '*.webp' { + const path: string; + + export default path; +} + +declare module 'markdown-it-mark' { + import type {PluginSimple} from 'markdown-it'; + const plugin: PluginSimple; + export = plugin; +} + +declare module 'markdown-it-sub' { + import type {PluginSimple} from 'markdown-it'; + const plugin: PluginSimple; + export = plugin; +} + +declare module 'markdown-it-ins' { + import type {PluginSimple} from 'markdown-it'; + const plugin: PluginSimple; + export = plugin; +} diff --git a/src/@types/highcharts-preparers.d.ts b/src/@types/highcharts-preparers.d.ts new file mode 100644 index 0000000..f35b9d0 --- /dev/null +++ b/src/@types/highcharts-preparers.d.ts @@ -0,0 +1,33 @@ +import 'highcharts'; + +declare module 'highcharts' { + interface PointOptionsObject { + colorValue?: string; + colorGuid?: string; + } + + interface SeriesOptions { + colorGuid?: string; + colorValue?: string; + legendTitle?: string; + formattedName?: string; + } + + interface Point { + userOptions: { + legendTitle: string; + formattedName?: string; + }; + yLabel: string; + } + + interface Series { + userOptions: { + legendTitle: string; + }; + } + + interface Axis { + closestPointRange: number; + } +} diff --git a/src/@types/json-fn.d.ts b/src/@types/json-fn.d.ts new file mode 100644 index 0000000..7d4a88b --- /dev/null +++ b/src/@types/json-fn.d.ts @@ -0,0 +1,9 @@ +declare module 'json-fn' { + export function stringify( + value: any, + replacer?: (this: any, key: string, value: any) => any, + space?: string | number, + ): string; + + export function parse(text: string, reviver?: (this: any, key: string, value: any) => any): any; +} diff --git a/src/@types/nodekit.d.ts b/src/@types/nodekit.d.ts new file mode 100644 index 0000000..ad84d95 --- /dev/null +++ b/src/@types/nodekit.d.ts @@ -0,0 +1,155 @@ +import type {Link, Meta} from '@gravity-ui/app-layout'; +import type {Request, Response} from '@gravity-ui/expresskit'; + +import type {CtxUser} from '../server/components/auth/types/user'; +import type {ChartTemplates} from '../server/components/charts-engine/components/chart-generator'; +import type {SourceConfig} from '../server/components/charts-engine/types'; +import type {RedisConfig} from '../server/utils/redis'; +import type {AppEnvironment, LandingPageSettings} from '../shared'; +import type {UserRole} from '../shared/components/auth/constants/role'; +import type {FeatureConfig} from '../shared/types'; + +export interface SharedAppConfig { + endpoints: Endpoints; + features: FeatureConfig; + metrika: MetrikaCounter; + + usMasterToken?: string; + usDynamicMasterTokenPrivateKey?: string; + + regionalEnvConfig?: {allowLanguages?: string[]; defaultLang?: string; langRegion?: string}; + + faviconUrl: string; + + links?: Link[]; + meta?: Meta[]; + + chartkitSettings?: ChartkitGlobalSettings; + defaultColorPaletteId?: string; + serviceName: string; + // CHARTS ENGINE -- START + usEndpoint: string; + getSourcesByEnv: (appEnv: AppEnvironment) => Record; + sources: Record; + appMode?: string; + + enablePreloading?: boolean; + fetchingTimeout: number; + singleFetchingTimeout: number; + runnerExecutionTimeouts?: Record>; + runResponseWhitelist?: string[]; + allowBodyConfig: boolean; + chartsEngineConfig: { + secrets: Record; + enableTelemetry: boolean; + flags?: Record; + usEndpointPostfix: string; + dataFetcherProxiedHeaders?: string[]; + maxWorkers?: number; + includeServicePlan?: boolean; + includeTenantFeatures?: true; + tvmAllowedSources?: Record; + tvmAllowedSourcesWithoutUserTicket?: Record; + privateApiPrefixes?: string[]; + }; + // CHARTS ENGINE -- FINISH + + useIPV6?: boolean; + workers?: number; + + requestIdHeaderName: string; + gatewayProxyHeaders: string[]; + headersMap: Record; + + iamResources?: { + collection: { + roles: { + admin: string; + editor: string; + viewer: string; + limitedViewer?: string; + entryBindingCreator?: string; + limitedEntryBindingCreator?: string; + }; + }; + workbook: { + roles: { + admin: string; + editor: string; + viewer: string; + limitedViewer?: string; + }; + }; + sharedEntry: { + roles: { + admin: string; + editor: string; + viewer: string; + limitedViewer?: string; + entryBindingCreator?: string; + limitedEntryBindingCreator?: string; + }; + }; + }; + + // auth + isAuthEnabled: boolean; + authTokenPublicKey?: string; + authManageLocalUsersDisabled?: boolean; + authSignupDisabled?: boolean; + authCookieName?: string; + // sorted roles from the role with the most rights to the role with the least + orderedAuthRoles?: `${UserRole}`[]; + + chartTemplates: Partial>; + redis: RedisConfig | null; + apiPrefix: string; + preloadList?: string[]; + releaseVersion?: string; + docsUrl?: string; +} + +export interface SharedAppDynamicConfig { + features?: FeatureConfig; +} + +export interface SharedAppContextParams { + userId: string; + gateway: { + reqBody: Request['body']; + requestId: string; + + checkRequestForDeveloperModeAccess: (args: { + ctx: AppContext; + }) => Promise; + markdown: MarkdownContextAction; + + resolveEntryByLink: ( + args: ResolveEntryByLinkComponentArgs, + ) => Promise; + }; + + sources: { + reqBody: Request['body']; + }; + + getAppLayoutSettings: (req: Request, res: Response, name?: string) => AppLayoutSettings; + landingPageSettings?: LandingPageSettings; + + i18n: ServerI18n; + tenantId?: string; + + user?: CtxUser; + + usDynamicMasterToken?: string; + + isEnabledServerFeature: (feature: string) => boolean; +} + +declare module '@gravity-ui/nodekit' { + export interface AppConfig extends SharedAppConfig {} + + export interface AppDynamicConfig extends SharedAppDynamicConfig {} + + export interface AppContextParams extends SharedAppContextParams {} +} diff --git a/src/@types/svg.d.ts b/src/@types/svg.d.ts new file mode 100644 index 0000000..d694a65 --- /dev/null +++ b/src/@types/svg.d.ts @@ -0,0 +1,4 @@ +declare module '*.svg' { + const value: any; + export = value; +} diff --git a/src/@types/window.d.ts b/src/@types/window.d.ts new file mode 100644 index 0000000..bb00e32 --- /dev/null +++ b/src/@types/window.d.ts @@ -0,0 +1,20 @@ +import type moment from 'moment'; +import type {compose} from 'redux'; + +import type {AvailableYMActions, DLGlobalData} from '../shared'; + +declare global { + interface Window { + sdk: unknown; + moment: moment; + DL: DLGlobalData; + ym?: ( + counterId: string | number, + action: AvailableYMActions, + targetId: string, + params?: Record, + ) => void; + __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: typeof compose; + clipboardData: DataTransfer; + } +} diff --git a/src/i18n-keysets/.arcignore b/src/i18n-keysets/.arcignore new file mode 100644 index 0000000..2dc3750 --- /dev/null +++ b/src/i18n-keysets/.arcignore @@ -0,0 +1 @@ +.sync diff --git a/src/i18n-keysets/.gitignore b/src/i18n-keysets/.gitignore new file mode 100644 index 0000000..2dc3750 --- /dev/null +++ b/src/i18n-keysets/.gitignore @@ -0,0 +1 @@ +.sync diff --git a/src/i18n-keysets/auth.dialog-change-password/context.json b/src/i18n-keysets/auth.dialog-change-password/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-change-password/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/auth.dialog-change-password/en.json b/src/i18n-keysets/auth.dialog-change-password/en.json new file mode 100644 index 0000000..f4b69cf --- /dev/null +++ b/src/i18n-keysets/auth.dialog-change-password/en.json @@ -0,0 +1,14 @@ +{ + "button_cancel": "Cancel", + "button_save": "Save", + "label_admin-notification": "Set a temporary password and ask the user to change it in the account settings", + "label_error-incorrect-old-password": "Incorrect current password", + "label_error-passwords-not-match": "These passwords don't match", + "label_error-required-fields": "You must fill out the required fields", + "label_new-password": "New password", + "label_old-password": "Current password", + "label_password": "Password", + "label_repeat-password": "Repeat password", + "label_success-change": "Password successfully changed", + "title_change-password": "Change password" +} diff --git a/src/i18n-keysets/auth.dialog-change-password/keyset.json b/src/i18n-keysets/auth.dialog-change-password/keyset.json new file mode 100644 index 0000000..401f5d5 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-change-password/keyset.json @@ -0,0 +1,60 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_admin-notification": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-incorrect-old-password": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-passwords-not-match": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-required-fields": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-password": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_old-password": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_password": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_repeat-password": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_success-change": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_change-password": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/auth.dialog-change-password/ru.json b/src/i18n-keysets/auth.dialog-change-password/ru.json new file mode 100644 index 0000000..e389150 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-change-password/ru.json @@ -0,0 +1,14 @@ +{ + "button_cancel": "Отменить", + "button_save": "Сохранить", + "label_admin-notification": "Задайте временный пароль и попросите пользователя сменить его в настройках учётной записи", + "label_error-incorrect-old-password": "Текущий пароль указан неверно", + "label_error-passwords-not-match": "Пароли не совпадают", + "label_error-required-fields": "Поле обязательно для заполнения", + "label_new-password": "Новый пароль", + "label_old-password": "Текущий пароль", + "label_password": "Пароль", + "label_repeat-password": "Новый пароль ещё раз", + "label_success-change": "Пароль успешно изменён", + "title_change-password": "Смена пароля" +} diff --git a/src/i18n-keysets/auth.dialog-change-user-role/context.json b/src/i18n-keysets/auth.dialog-change-user-role/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-change-user-role/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/auth.dialog-change-user-role/en.json b/src/i18n-keysets/auth.dialog-change-user-role/en.json new file mode 100644 index 0000000..bc597d3 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-change-user-role/en.json @@ -0,0 +1,10 @@ +{ + "action_user-roles-change-apply": "Save", + "action_user-roles-change-cancel": "Cancel", + "label_base-role": "Base role for service access", + "label_includes-permissions-from": "Includes permissions granted by the role", + "label_roles-change-failure": "An error occurred while editing the user's access rights", + "label_roles-change-success": "Role assigned successfully", + "label_select-role": "Select role", + "title_update-role": "Setting up access rights" +} diff --git a/src/i18n-keysets/auth.dialog-change-user-role/keyset.json b/src/i18n-keysets/auth.dialog-change-user-role/keyset.json new file mode 100644 index 0000000..00f0acd --- /dev/null +++ b/src/i18n-keysets/auth.dialog-change-user-role/keyset.json @@ -0,0 +1,44 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "action_user-roles-change-apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_user-roles-change-cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_base-role": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_includes-permissions-from": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_roles-change-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_roles-change-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-role": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_update-role": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/auth.dialog-change-user-role/ru.json b/src/i18n-keysets/auth.dialog-change-user-role/ru.json new file mode 100644 index 0000000..7637f7e --- /dev/null +++ b/src/i18n-keysets/auth.dialog-change-user-role/ru.json @@ -0,0 +1,10 @@ +{ + "action_user-roles-change-apply": "Сохранить", + "action_user-roles-change-cancel": "Отменить", + "label_base-role": "Базовая роль для доступа к сервису", + "label_includes-permissions-from": "Включает разрешения, предоставляемые ролью", + "label_roles-change-failure": "При редактировании прав доступа пользователя произошла ошибка", + "label_roles-change-success": "Роль успешно назначена", + "label_select-role": "Выберите роль", + "title_update-role": "Настройка прав доступа" +} diff --git a/src/i18n-keysets/auth.dialog-delete-user/context.json b/src/i18n-keysets/auth.dialog-delete-user/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-delete-user/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/auth.dialog-delete-user/en.json b/src/i18n-keysets/auth.dialog-delete-user/en.json new file mode 100644 index 0000000..c6c6ee3 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-delete-user/en.json @@ -0,0 +1,8 @@ +{ + "action_user-profile-deletion-cancel": "Cancel", + "action_user-profile-deletion-confirm": "Delete user", + "label_delete-user-confirmation": "Are you sure you want to delete the user? After deletion, it will not be able to use the DataLens service.", + "label_delete-user-failure": "An error occurred when deleting the user", + "label_delete-user-success": "User deleted successfully", + "title_delete-user": "Deleting a user" +} diff --git a/src/i18n-keysets/auth.dialog-delete-user/keyset.json b/src/i18n-keysets/auth.dialog-delete-user/keyset.json new file mode 100644 index 0000000..ee2f850 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-delete-user/keyset.json @@ -0,0 +1,36 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "action_user-profile-deletion-cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_user-profile-deletion-confirm": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-user-confirmation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-user-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-user-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_delete-user": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/auth.dialog-delete-user/ru.json b/src/i18n-keysets/auth.dialog-delete-user/ru.json new file mode 100644 index 0000000..d7e8bd7 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-delete-user/ru.json @@ -0,0 +1,8 @@ +{ + "action_user-profile-deletion-cancel": "Отменить", + "action_user-profile-deletion-confirm": "Удалить пользователя", + "label_delete-user-confirmation": "Вы действительно хотите удалить пользователя? После удаления он не сможет пользоваться сервисом DataLens.", + "label_delete-user-failure": "При удалении пользователя произошла ошибка", + "label_delete-user-success": "Пользователь успешно удалён", + "title_delete-user": "Удаление пользователя" +} diff --git a/src/i18n-keysets/auth.dialog-edit-profile/context.json b/src/i18n-keysets/auth.dialog-edit-profile/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-edit-profile/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/auth.dialog-edit-profile/en.json b/src/i18n-keysets/auth.dialog-edit-profile/en.json new file mode 100644 index 0000000..76097c0 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-edit-profile/en.json @@ -0,0 +1,6 @@ +{ + "button_cancel": "Cancel", + "button_save": "Save", + "label_success-edit": "Profile edited successfully", + "title_edit-profile": "Edit profile" +} diff --git a/src/i18n-keysets/auth.dialog-edit-profile/keyset.json b/src/i18n-keysets/auth.dialog-edit-profile/keyset.json new file mode 100644 index 0000000..e9793ad --- /dev/null +++ b/src/i18n-keysets/auth.dialog-edit-profile/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_success-edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_edit-profile": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/auth.dialog-edit-profile/ru.json b/src/i18n-keysets/auth.dialog-edit-profile/ru.json new file mode 100644 index 0000000..01f6b28 --- /dev/null +++ b/src/i18n-keysets/auth.dialog-edit-profile/ru.json @@ -0,0 +1,6 @@ +{ + "button_cancel": "Отменить", + "button_save": "Сохранить", + "label_success-edit": "Профиль пользователя успешно сохранён", + "title_edit-profile": "Редактирование профиля" +} diff --git a/src/i18n-keysets/auth.form-controls/context.json b/src/i18n-keysets/auth.form-controls/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/auth.form-controls/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/auth.form-controls/en.json b/src/i18n-keysets/auth.form-controls/en.json new file mode 100644 index 0000000..dfe5376 --- /dev/null +++ b/src/i18n-keysets/auth.form-controls/en.json @@ -0,0 +1,10 @@ +{ + "button_generate-password": "Generate password", + "label_email": "Email", + "label_first-name": "First name", + "label_last-name": "Last name", + "label_login": "Username", + "label_password": "Password", + "label_repeat-password": "Repeat password", + "label_roles": "Role" +} diff --git a/src/i18n-keysets/auth.form-controls/keyset.json b/src/i18n-keysets/auth.form-controls/keyset.json new file mode 100644 index 0000000..360aea5 --- /dev/null +++ b/src/i18n-keysets/auth.form-controls/keyset.json @@ -0,0 +1,44 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_generate-password": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_email": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_first-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_last-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_login": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_password": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_repeat-password": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_roles": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/auth.form-controls/ru.json b/src/i18n-keysets/auth.form-controls/ru.json new file mode 100644 index 0000000..22d622b --- /dev/null +++ b/src/i18n-keysets/auth.form-controls/ru.json @@ -0,0 +1,10 @@ +{ + "button_generate-password": "Сгенерировать пароль", + "label_email": "Почта", + "label_first-name": "Имя", + "label_last-name": "Фамилия", + "label_login": "Логин", + "label_password": "Пароль", + "label_repeat-password": "Подтвердите пароль", + "label_roles": "Роль" +} diff --git a/src/i18n-keysets/auth.sign-in/context.json b/src/i18n-keysets/auth.sign-in/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/auth.sign-in/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/auth.sign-in/en.json b/src/i18n-keysets/auth.sign-in/en.json new file mode 100644 index 0000000..f198a32 --- /dev/null +++ b/src/i18n-keysets/auth.sign-in/en.json @@ -0,0 +1,9 @@ +{ + "button_sign-in": "Sign in", + "label_error-incorrect-fields": "Incorrect username or password. Check that everything is correct and try again.", + "label_error-required-fields": "Enter username and password", + "label_login-placeholder": "Username", + "label_password-placeholder": "Password", + "label_sign-up-hint": "Don't have an account?", + "label_sing-up-link": "Sign up" +} diff --git a/src/i18n-keysets/auth.sign-in/keyset.json b/src/i18n-keysets/auth.sign-in/keyset.json new file mode 100644 index 0000000..0f0675a --- /dev/null +++ b/src/i18n-keysets/auth.sign-in/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_sign-in": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-incorrect-fields": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-required-fields": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_login-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_password-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sign-up-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sing-up-link": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/auth.sign-in/ru.json b/src/i18n-keysets/auth.sign-in/ru.json new file mode 100644 index 0000000..38b0556 --- /dev/null +++ b/src/i18n-keysets/auth.sign-in/ru.json @@ -0,0 +1,9 @@ +{ + "button_sign-in": "Войти", + "label_error-incorrect-fields": "Неверный логин или пароль. Проверьте правильность ввода и попробуйте ещё раз.", + "label_error-required-fields": "Укажите логин и пароль", + "label_login-placeholder": "Логин", + "label_password-placeholder": "Пароль", + "label_sign-up-hint": "У вас ещё нет учётной записи?", + "label_sing-up-link": "Зарегистрироваться" +} diff --git a/src/i18n-keysets/auth.sign-up/context.json b/src/i18n-keysets/auth.sign-up/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/auth.sign-up/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/auth.sign-up/en.json b/src/i18n-keysets/auth.sign-up/en.json new file mode 100644 index 0000000..4ddd3d0 --- /dev/null +++ b/src/i18n-keysets/auth.sign-up/en.json @@ -0,0 +1,5 @@ +{ + "button_back": "Back", + "button_sign-up": "Sign up", + "title_sign-up": "Sign up" +} diff --git a/src/i18n-keysets/auth.sign-up/keyset.json b/src/i18n-keysets/auth.sign-up/keyset.json new file mode 100644 index 0000000..6f61776 --- /dev/null +++ b/src/i18n-keysets/auth.sign-up/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_back": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_sign-up": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_sign-up": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/auth.sign-up/ru.json b/src/i18n-keysets/auth.sign-up/ru.json new file mode 100644 index 0000000..7fc0b2a --- /dev/null +++ b/src/i18n-keysets/auth.sign-up/ru.json @@ -0,0 +1,5 @@ +{ + "button_back": "Назад", + "button_sign-up": "Зарегистрироваться", + "title_sign-up": "Регистрация" +} diff --git a/src/i18n-keysets/auth.user-form-validation/context.json b/src/i18n-keysets/auth.user-form-validation/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/auth.user-form-validation/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/auth.user-form-validation/en.json b/src/i18n-keysets/auth.user-form-validation/en.json new file mode 100644 index 0000000..d3be8cf --- /dev/null +++ b/src/i18n-keysets/auth.user-form-validation/en.json @@ -0,0 +1,13 @@ +{ + "label_error-email-invalid": "Incorrect email format", + "label_error-first-name-max": "First name can be up to {{max}} characters in length", + "label_error-last-name-max": "Last name can be up to {{max}} characters in length", + "label_error-login-email-invalid": "Incorrect email address format in login", + "label_error-login-invalid": "The login can consist of Latin letters, numbers, and the symbols - and _. It must start with a letter and end with a letter or a number. It can also be in the correct email format.", + "label_error-login-max": "Login can be up to {{max}} characters in length", + "label_error-login-min": "Login must be at least {{min}} characters in length", + "label_error-password-invalid": "Create a strong password:

– minimum length: {{min}} characters

– contains at least one upper and lowercase letter and a number

– contains at least one special character: !@#$%^&*-_", + "label_error-password-max": "Password can be up to {{max}} characters in length", + "label_error-password-not-match": "Passwords do not match", + "label_error-required-fields": "Please fill in all required fields" +} diff --git a/src/i18n-keysets/auth.user-form-validation/keyset.json b/src/i18n-keysets/auth.user-form-validation/keyset.json new file mode 100644 index 0000000..0233e53 --- /dev/null +++ b/src/i18n-keysets/auth.user-form-validation/keyset.json @@ -0,0 +1,56 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_error-email-invalid": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-first-name-max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-last-name-max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-login-email-invalid": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-login-invalid": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-login-max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-login-min": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-password-invalid": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-password-max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-password-not-match": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-required-fields": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/auth.user-form-validation/ru.json b/src/i18n-keysets/auth.user-form-validation/ru.json new file mode 100644 index 0000000..0260bdf --- /dev/null +++ b/src/i18n-keysets/auth.user-form-validation/ru.json @@ -0,0 +1,13 @@ +{ + "label_error-email-invalid": "Некорректный формат электронной почты", + "label_error-first-name-max": "Максимальная длина имени — {{max}} символов", + "label_error-last-name-max": "Максимальная длина фамилии — {{max}} символов", + "label_error-login-email-invalid": "Некорректный формат электронной почты в логине", + "label_error-login-invalid": "Логин может состоять из латинских букв, цифр и символов - и _. Начинается с буквы и заканчивается буквой или цифрой. Также может быть в формате корректной почты.", + "label_error-login-max": "Максимальная длина логина — {{max}} символов", + "label_error-login-min": "Минимальная длина логина — {{min}} символов", + "label_error-password-invalid": "Придумайте надёжный пароль:

– минимальная длина — {{min}} символов

– содержит заглавную, строчную латинскую букву и цифру

– содержит специальный символ: !@#$%^&*-_", + "label_error-password-max": "Максимальная длина пароля — {{max}} символов", + "label_error-password-not-match": "Пароли не совпадают", + "label_error-required-fields": "Пожалуйста, заполните все обязательные поля" +} diff --git a/src/i18n-keysets/auth.user-profile.view/context.json b/src/i18n-keysets/auth.user-profile.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/auth.user-profile.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/auth.user-profile.view/en.json b/src/i18n-keysets/auth.user-profile.view/en.json new file mode 100644 index 0000000..c13d147 --- /dev/null +++ b/src/i18n-keysets/auth.user-profile.view/en.json @@ -0,0 +1,16 @@ +{ + "action_assign-role": "Assign role", + "action_change-password": "Change password", + "action_delete-user": "Delete user", + "action_edit-profile": "Edit account", + "label_email": "Email", + "label_failed-to-load-user": "An error occurred while loading the account. The user may have been deleted", + "label_first-name": "First name", + "label_last-name": "Last name", + "label_login": "Login", + "label_role": "Role", + "label_user-id": "ID", + "title_administration": "Administration", + "title_permissions": "Permissions", + "title_profile": "Account" +} diff --git a/src/i18n-keysets/auth.user-profile.view/keyset.json b/src/i18n-keysets/auth.user-profile.view/keyset.json new file mode 100644 index 0000000..c27c1db --- /dev/null +++ b/src/i18n-keysets/auth.user-profile.view/keyset.json @@ -0,0 +1,68 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "action_assign-role": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_change-password": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_delete-user": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_edit-profile": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_email": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_failed-to-load-user": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_first-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_last-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_login": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_role": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_user-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_administration": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_permissions": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_profile": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/auth.user-profile.view/ru.json b/src/i18n-keysets/auth.user-profile.view/ru.json new file mode 100644 index 0000000..ddf2376 --- /dev/null +++ b/src/i18n-keysets/auth.user-profile.view/ru.json @@ -0,0 +1,16 @@ +{ + "action_assign-role": "Назначить роль", + "action_change-password": "Сменить пароль", + "action_delete-user": "Удалить пользователя", + "action_edit-profile": "Редактировать профиль", + "label_email": "Почта", + "label_failed-to-load-user": "При загрузке учётной записи произошла ошибка, возможно, пользователь был удалён", + "label_first-name": "Имя", + "label_last-name": "Фамилия", + "label_login": "Логин", + "label_role": "Роль", + "label_user-id": "Идентификатор", + "title_administration": "Администрирование", + "title_permissions": "Права доступа", + "title_profile": "Профиль" +} diff --git a/src/i18n-keysets/chartkit.button-spin-input/context.json b/src/i18n-keysets/chartkit.button-spin-input/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.button-spin-input/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.button-spin-input/en.json b/src/i18n-keysets/chartkit.button-spin-input/en.json new file mode 100644 index 0000000..a47a4f2 --- /dev/null +++ b/src/i18n-keysets/chartkit.button-spin-input/en.json @@ -0,0 +1,3 @@ +{ + "error": "Error" +} diff --git a/src/i18n-keysets/chartkit.button-spin-input/keyset.json b/src/i18n-keysets/chartkit.button-spin-input/keyset.json new file mode 100644 index 0000000..d246e12 --- /dev/null +++ b/src/i18n-keysets/chartkit.button-spin-input/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "error": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.button-spin-input/ru.json b/src/i18n-keysets/chartkit.button-spin-input/ru.json new file mode 100644 index 0000000..2fa244f --- /dev/null +++ b/src/i18n-keysets/chartkit.button-spin-input/ru.json @@ -0,0 +1,3 @@ +{ + "error": "Ошибка" +} diff --git a/src/i18n-keysets/chartkit.control.items/context.json b/src/i18n-keysets/chartkit.control.items/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.control.items/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.control.items/en.json b/src/i18n-keysets/chartkit.control.items/en.json new file mode 100644 index 0000000..7c200ad --- /dev/null +++ b/src/i18n-keysets/chartkit.control.items/en.json @@ -0,0 +1,6 @@ +{ + "apply": "Apply", + "close": "Close", + "close-confirm": "You have unapplied changes. Are you sure you want to close the dialog?", + "value_undefined": "Not defined" +} diff --git a/src/i18n-keysets/chartkit.control.items/keyset.json b/src/i18n-keysets/chartkit.control.items/keyset.json new file mode 100644 index 0000000..f40d648 --- /dev/null +++ b/src/i18n-keysets/chartkit.control.items/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "close-confirm": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_undefined": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.control.items/ru.json b/src/i18n-keysets/chartkit.control.items/ru.json new file mode 100644 index 0000000..96c5cb9 --- /dev/null +++ b/src/i18n-keysets/chartkit.control.items/ru.json @@ -0,0 +1,6 @@ +{ + "apply": "Применить", + "close": "Закрыть", + "close-confirm": "У вас есть непримененные изменения. Вы уверены, что хотите закрыть диалог?", + "value_undefined": "Не определено" +} diff --git a/src/i18n-keysets/chartkit.control/context.json b/src/i18n-keysets/chartkit.control/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.control/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.control/en.json b/src/i18n-keysets/chartkit.control/en.json new file mode 100644 index 0000000..36ed19d --- /dev/null +++ b/src/i18n-keysets/chartkit.control/en.json @@ -0,0 +1,3 @@ +{ + "label_error": "An error occured while executing the selector" +} diff --git a/src/i18n-keysets/chartkit.control/keyset.json b/src/i18n-keysets/chartkit.control/keyset.json new file mode 100644 index 0000000..b96e963 --- /dev/null +++ b/src/i18n-keysets/chartkit.control/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_error": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.control/ru.json b/src/i18n-keysets/chartkit.control/ru.json new file mode 100644 index 0000000..72d8f54 --- /dev/null +++ b/src/i18n-keysets/chartkit.control/ru.json @@ -0,0 +1,3 @@ +{ + "label_error": "При выполнении селектора произошла ошибка" +} diff --git a/src/i18n-keysets/chartkit.custom-error/context.json b/src/i18n-keysets/chartkit.custom-error/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.custom-error/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.custom-error/en.json b/src/i18n-keysets/chartkit.custom-error/en.json new file mode 100644 index 0000000..bdac87f --- /dev/null +++ b/src/i18n-keysets/chartkit.custom-error/en.json @@ -0,0 +1,14 @@ +{ + "close": "Close", + "copy": "Copy", + "draw-anyway": "Draw anyway", + "error": "Error", + "error-data-provider": "Data fetching error", + "error-no-data": "No data", + "error-timeout": "The server response waiting time has been exceeded", + "error-too-many-lines": "Too many series on the chart", + "error-ui-sandbox-timeout": "The allowed chart execution time has been exceeded", + "error-unknown-extension": "Unknown chart type", + "more": "More", + "retry": "Retry" +} diff --git a/src/i18n-keysets/chartkit.custom-error/keyset.json b/src/i18n-keysets/chartkit.custom-error/keyset.json new file mode 100644 index 0000000..ddce2c3 --- /dev/null +++ b/src/i18n-keysets/chartkit.custom-error/keyset.json @@ -0,0 +1,60 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "draw-anyway": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-data-provider": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-no-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-timeout": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-too-many-lines": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-ui-sandbox-timeout": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-unknown-extension": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "more": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "retry": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.custom-error/ru.json b/src/i18n-keysets/chartkit.custom-error/ru.json new file mode 100644 index 0000000..62e21b7 --- /dev/null +++ b/src/i18n-keysets/chartkit.custom-error/ru.json @@ -0,0 +1,14 @@ +{ + "close": "Закрыть", + "copy": "Копировать", + "draw-anyway": "Отобразить", + "error": "Ошибка", + "error-data-provider": "Ошибка загрузки данных", + "error-no-data": "Нет данных", + "error-timeout": "Превышено время ожидания ответа от сервера", + "error-too-many-lines": "Превышено рекомендуемое число рядов", + "error-ui-sandbox-timeout": "Превышено допустимое время исполнения чарта", + "error-unknown-extension": "Неизвестный тип чарта", + "more": "Подробнее", + "retry": "Повторить" +} diff --git a/src/i18n-keysets/chartkit.data-provider/context.json b/src/i18n-keysets/chartkit.data-provider/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.data-provider/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.data-provider/en.json b/src/i18n-keysets/chartkit.data-provider/en.json new file mode 100644 index 0000000..39a617f --- /dev/null +++ b/src/i18n-keysets/chartkit.data-provider/en.json @@ -0,0 +1,22 @@ +{ + "categories": "Categories", + "date": "Date", + "error-authorization": "Authorization error", + "error-chyt-table-access": "No access to YT table", + "error-data-fetching": "Data fetching error", + "error-execution": "Chart execution error", + "error-network": "Network error", + "error-no-view-rights": "No view rights", + "error-not-found": "Not found", + "error-not-loaded": "Chart loading error", + "error-oversize-segments": "The maximum number of splits is {{maxNumber}}", + "error-oversize-table": "Maximum number of {{type}} exceeded", + "error-oversize_cells": "cells", + "error-oversize_columns": "columns", + "error-processing": "Chart processing error", + "error-runtime-timeout": "Response processing timeout", + "error-secrets-access": "No write access on chart with secrets", + "error-too-many-rows": "Too many series on the chart", + "error-unsupported-data-type": "Unsupported data type", + "error-unsupported-type": "Unsupported chart type" +} diff --git a/src/i18n-keysets/chartkit.data-provider/keyset.json b/src/i18n-keysets/chartkit.data-provider/keyset.json new file mode 100644 index 0000000..214d197 --- /dev/null +++ b/src/i18n-keysets/chartkit.data-provider/keyset.json @@ -0,0 +1,92 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "categories": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-authorization": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-chyt-table-access": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-data-fetching": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-execution": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-network": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-no-view-rights": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-not-found": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-not-loaded": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-oversize-segments": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-oversize-table": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-oversize_cells": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-oversize_columns": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-processing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-runtime-timeout": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-secrets-access": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-too-many-rows": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-unsupported-data-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error-unsupported-type": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.data-provider/ru.json b/src/i18n-keysets/chartkit.data-provider/ru.json new file mode 100644 index 0000000..ea558a0 --- /dev/null +++ b/src/i18n-keysets/chartkit.data-provider/ru.json @@ -0,0 +1,22 @@ +{ + "categories": "Категории", + "date": "Дата", + "error-authorization": "Ошибка авторизации", + "error-chyt-table-access": "Нет доступа к таблице в YT", + "error-data-fetching": "Ошибка загрузки данных", + "error-execution": "Ошибка выполнения чарта", + "error-network": "Ошибка сети", + "error-no-view-rights": "Нет прав на просмотр", + "error-not-found": "Чарт не найден", + "error-not-loaded": "Не удалось загрузить чарт", + "error-oversize-segments": "Максимально доступное количество сплитов — {{maxNumber}}", + "error-oversize-table": "Превышено максимальное количество {{type}}", + "error-oversize_cells": "ячеек", + "error-oversize_columns": "столбцов", + "error-processing": "Ошибка обработки чарта", + "error-runtime-timeout": "Превышено время обработки ответа", + "error-secrets-access": "Чарт с секретом недоступен для записи", + "error-too-many-rows": "Превышено рекомендуемое число рядов", + "error-unsupported-data-type": "Неподдерживаемый тип данных", + "error-unsupported-type": "Неподдерживаемый тип чарта" +} diff --git a/src/i18n-keysets/chartkit.error-details/context.json b/src/i18n-keysets/chartkit.error-details/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.error-details/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.error-details/en.json b/src/i18n-keysets/chartkit.error-details/en.json new file mode 100644 index 0000000..b175419 --- /dev/null +++ b/src/i18n-keysets/chartkit.error-details/en.json @@ -0,0 +1,5 @@ +{ + "close": "Close", + "copy": "Copy", + "more": "More" +} diff --git a/src/i18n-keysets/chartkit.error-details/keyset.json b/src/i18n-keysets/chartkit.error-details/keyset.json new file mode 100644 index 0000000..98dbfdf --- /dev/null +++ b/src/i18n-keysets/chartkit.error-details/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "more": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.error-details/ru.json b/src/i18n-keysets/chartkit.error-details/ru.json new file mode 100644 index 0000000..8671a82 --- /dev/null +++ b/src/i18n-keysets/chartkit.error-details/ru.json @@ -0,0 +1,5 @@ +{ + "close": "Закрыть", + "copy": "Копировать", + "more": "Подробнее" +} diff --git a/src/i18n-keysets/chartkit.insights/context.json b/src/i18n-keysets/chartkit.insights/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.insights/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.insights/en.json b/src/i18n-keysets/chartkit.insights/en.json new file mode 100644 index 0000000..a6ca529 --- /dev/null +++ b/src/i18n-keysets/chartkit.insights/en.json @@ -0,0 +1,3 @@ +{ + "ok": "Ok" +} diff --git a/src/i18n-keysets/chartkit.insights/keyset.json b/src/i18n-keysets/chartkit.insights/keyset.json new file mode 100644 index 0000000..3f867eb --- /dev/null +++ b/src/i18n-keysets/chartkit.insights/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "ok": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.insights/ru.json b/src/i18n-keysets/chartkit.insights/ru.json new file mode 100644 index 0000000..d7486e5 --- /dev/null +++ b/src/i18n-keysets/chartkit.insights/ru.json @@ -0,0 +1,3 @@ +{ + "ok": "Ок" +} diff --git a/src/i18n-keysets/chartkit.menu.download-dialog/context.json b/src/i18n-keysets/chartkit.menu.download-dialog/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.download-dialog/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.menu.download-dialog/en.json b/src/i18n-keysets/chartkit.menu.download-dialog/en.json new file mode 100644 index 0000000..d6f4bb0 --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.download-dialog/en.json @@ -0,0 +1,15 @@ +{ + "button_cancel": "Cancel", + "button_download": "Save", + "label_decimal-delimiter": "Fraction delimiter", + "label_encoding": "Encoding", + "label_hint": "You can skip this dialog by clicking on the menu element while holding CMD/CTRL", + "label_title-csv": "Save as CSV", + "label_title-xlsx": "Save as XLSX", + "label_values-delimiter": "Delimiter", + "value_comma": "comma", + "value_dot": "dot", + "value_semicolon": "semicolon", + "value_space": "space", + "value_tab": "tab" +} diff --git a/src/i18n-keysets/chartkit.menu.download-dialog/keyset.json b/src/i18n-keysets/chartkit.menu.download-dialog/keyset.json new file mode 100644 index 0000000..0b42d3b --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.download-dialog/keyset.json @@ -0,0 +1,64 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_download": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_decimal-delimiter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_encoding": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-csv": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-xlsx": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_values-delimiter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_comma": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_dot": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_semicolon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_space": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_tab": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.menu.download-dialog/ru.json b/src/i18n-keysets/chartkit.menu.download-dialog/ru.json new file mode 100644 index 0000000..effbb84 --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.download-dialog/ru.json @@ -0,0 +1,15 @@ +{ + "button_cancel": "Отмена", + "button_download": "Сохранить", + "label_decimal-delimiter": "Десятичный разделитель", + "label_encoding": "Кодировка", + "label_hint": "Этот диалог можно пропустить, кликнув по элементу меню с зажатым CMD/CTRL", + "label_title-csv": "Сохранить как CSV", + "label_title-xlsx": "Сохранить как XLSX", + "label_values-delimiter": "Разделитель значений", + "value_comma": "запятая", + "value_dot": "точка", + "value_semicolon": "точка с запятой", + "value_space": "пробел", + "value_tab": "табуляция" +} diff --git a/src/i18n-keysets/chartkit.menu.export/context.json b/src/i18n-keysets/chartkit.menu.export/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.export/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.menu.export/en.json b/src/i18n-keysets/chartkit.menu.export/en.json new file mode 100644 index 0000000..c1c1f2b --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.export/en.json @@ -0,0 +1,21 @@ +{ + "export_failed": "Data export error", + "export_limit_error": "You can export data up to {{limit}}; your data size is {{length}}.", + "export_success": "Copied to clipboard", + "format_csv": "CSV", + "format_image": "Image", + "format_markdown": "Markdown", + "format_wiki": "Wiki", + "format_xlsx": "XLSX", + "label_data-export-forbidden": "Data export is forbidden by the owner of the connection or dataset", + "label_export-forbidden.DISABLED_EXPORT_CONNECTION": "The connection owner has forbidden data export", + "label_export-forbidden.DISABLED_EXPORT_DATASET": "The dataset owner has forbidden data export", + "label_export-forbidden.DISABLED_EXPORT_TENANT": "The organization's administrator has prohibited data export", + "label_export-forbidden.PROHIBITED_EXPORT_CONNECTION": "Data export is not available on this connection type", + "label_export-forbidden.PROHIBITED_EXPORT_PIVOT_TABLE": "Data export is unavailable for this chart type", + "label_export-forbidden.PROHIBITED_EXPORT_QL_CHART": "Data export is unavailable for this chart type", + "label_export-forbidden.PROHIBITED_EXPORT_TENANT": "Data export is unavailable in this organization", + "label_export-forbidden.PROHIBITED_EXPORT_TYPED_QUERY": "Data export is unavailable for this chart type", + "menu-export": "Save as", + "menu-screenshot": "Save as image" +} diff --git a/src/i18n-keysets/chartkit.menu.export/keyset.json b/src/i18n-keysets/chartkit.menu.export/keyset.json new file mode 100644 index 0000000..70161d4 --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.export/keyset.json @@ -0,0 +1,88 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "export_failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "export_limit_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "export_success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "format_csv": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "format_image": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "format_markdown": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "format_wiki": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "format_xlsx": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_data-export-forbidden": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-forbidden.DISABLED_EXPORT_CONNECTION": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-forbidden.DISABLED_EXPORT_DATASET": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-forbidden.DISABLED_EXPORT_TENANT": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-forbidden.PROHIBITED_EXPORT_CONNECTION": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-forbidden.PROHIBITED_EXPORT_PIVOT_TABLE": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-forbidden.PROHIBITED_EXPORT_QL_CHART": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-forbidden.PROHIBITED_EXPORT_TENANT": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-forbidden.PROHIBITED_EXPORT_TYPED_QUERY": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu-export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu-screenshot": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.menu.export/ru.json b/src/i18n-keysets/chartkit.menu.export/ru.json new file mode 100644 index 0000000..e507eaa --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.export/ru.json @@ -0,0 +1,21 @@ +{ + "export_failed": "Ошибка экспорта данных", + "export_limit_error": "Для экспорта доступны данные объёмом до {{limit}}, объём ваших данных — {{length}}.", + "export_success": "Скопировано в буфер обмена", + "format_csv": "CSV", + "format_image": "Изображение", + "format_markdown": "Markdown", + "format_wiki": "Wiki", + "format_xlsx": "XLSX", + "label_data-export-forbidden": "Владелец подключения или датасета запретил экспорт данных", + "label_export-forbidden.DISABLED_EXPORT_CONNECTION": "Владелец подключения запретил экспорт данных", + "label_export-forbidden.DISABLED_EXPORT_DATASET": "Владелец датасета запретил экспорт данных", + "label_export-forbidden.DISABLED_EXPORT_TENANT": "Администратор организации запретил экспорт данных", + "label_export-forbidden.PROHIBITED_EXPORT_CONNECTION": "Экспорт данных недоступен в подключении данного типа", + "label_export-forbidden.PROHIBITED_EXPORT_PIVOT_TABLE": "Экспорт данных недоступен для данного типа чарта", + "label_export-forbidden.PROHIBITED_EXPORT_QL_CHART": "Экспорт данных недоступен для данного типа чарта", + "label_export-forbidden.PROHIBITED_EXPORT_TENANT": "Экспорт данных недоступен в данной организации", + "label_export-forbidden.PROHIBITED_EXPORT_TYPED_QUERY": "Экспорт данных недоступен для данного типа чарта", + "menu-export": "Сохранить как", + "menu-screenshot": "Сохранить как изображение" +} diff --git a/src/i18n-keysets/chartkit.menu.inspector/context.json b/src/i18n-keysets/chartkit.menu.inspector/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.inspector/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.menu.inspector/en.json b/src/i18n-keysets/chartkit.menu.inspector/en.json new file mode 100644 index 0000000..a6cbdc4 --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.inspector/en.json @@ -0,0 +1,24 @@ +{ + "button_close": "Close", + "label_api-waiting": "Maps API", + "label_api-waiting-hint": "Maps modules loading time", + "label_caption": "Inspector", + "label_columns": "Columns", + "label_config-resolving": "Config resolving", + "label_data-fetching": "Data fetching", + "label_data-fetching-danger": "Data loading time exceeds recommended. Optimize the query or limit the amount of requested data.", + "label_data-fetching-warning": "Data loading time exceeds recommended. Optimize the query or limit the amount of requested data.", + "label_js-execution": "Execution", + "label_js-execution-danger": "Processing time exceeds recommended. Simplify the chart or limit the amount of requested data.", + "label_js-execution-warning": "Processing time exceeds recommended. Simplify the chart or limit the amount of requested data.", + "label_points": "Points", + "label_rows": "Rows", + "label_series": "Series", + "label_source-info": "Query to data source", + "label_source-request-data": "Request body", + "label_source-request-query": "Request query", + "label_source-request-result": "Query result", + "label_source-response-body": "Response", + "label_sources": "Data sources", + "label_widget-rendering": "Rendering" +} diff --git a/src/i18n-keysets/chartkit.menu.inspector/keyset.json b/src/i18n-keysets/chartkit.menu.inspector/keyset.json new file mode 100644 index 0000000..185614f --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.inspector/keyset.json @@ -0,0 +1,100 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_api-waiting": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_api-waiting-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_columns": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_config-resolving": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_data-fetching": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_data-fetching-danger": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_data-fetching-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_js-execution": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_js-execution-danger": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_js-execution-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_points": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_rows": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_series": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-request-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-request-query": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-request-result": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-response-body": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sources": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_widget-rendering": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.menu.inspector/ru.json b/src/i18n-keysets/chartkit.menu.inspector/ru.json new file mode 100644 index 0000000..2eb820e --- /dev/null +++ b/src/i18n-keysets/chartkit.menu.inspector/ru.json @@ -0,0 +1,24 @@ +{ + "button_close": "Закрыть", + "label_api-waiting": "API карт", + "label_api-waiting-hint": "Время на загрузку модулей необходимых для работы карт", + "label_caption": "Инспектор", + "label_columns": "Колонки", + "label_config-resolving": "Загрузка конфига", + "label_data-fetching": "Загрузка данных", + "label_data-fetching-danger": "Время загрузки данных превышает рекомендуемое. Оптимизируйте запрос либо ограничьте объем запрашиваемых данных.", + "label_data-fetching-warning": "Время загрузки данных превышает рекомендуемое. Оптимизируйте запрос либо ограничьте объем запрашиваемых данных.", + "label_js-execution": "Выполнение", + "label_js-execution-danger": "Время обработки данных значительно превышает рекомендуемое. Упростите чарт либо ограничьте объем запрашиваемых данных.", + "label_js-execution-warning": "Время обработки данных превышает рекомендуемое. Упростите чарт либо ограничьте объем запрашиваемых данных.", + "label_points": "Точки", + "label_rows": "Строки", + "label_series": "Линии", + "label_source-info": "Запрос в источник данных", + "label_source-request-data": "Тело запроса", + "label_source-request-query": "Параметры запроса", + "label_source-request-result": "Результат запроса", + "label_source-response-body": "Ответ", + "label_sources": "Источники данных", + "label_widget-rendering": "Рендеринг" +} diff --git a/src/i18n-keysets/chartkit.menu/context.json b/src/i18n-keysets/chartkit.menu/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit.menu/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit.menu/en.json b/src/i18n-keysets/chartkit.menu/en.json new file mode 100644 index 0000000..26ceaeb --- /dev/null +++ b/src/i18n-keysets/chartkit.menu/en.json @@ -0,0 +1,33 @@ +{ + "add-smoothing": "Add smoothing", + "add-trend": "Add trend", + "button_apply": "Apply", + "button_cancel": "Cancel", + "compare-last-day": "Compare with yesterday", + "compare-last-year": "Compare with last year", + "data-export": "Export data", + "embedded": "Embed code", + "get-code": "Share", + "hide-comments": "Hide comments", + "hide-compare": "Disable compare", + "hide-full-period": "Disable full period", + "hide-holidays": "Hide weekends", + "hide-normalize": "Disable", + "manage-comments": "Comments", + "modeling": "Modeling", + "modeling-settings": "Configure", + "normalize": "First point normalization", + "normalize-division": "Division", + "normalize-subtraction": "Substraction", + "open-as-table": "Open as a table", + "open-edit": "Edit", + "open-fullscreen": "Open in full screen", + "open-in-window": "Open in a new tab", + "remove-smoothing": "Remove smoothing", + "remove-trend": "Remove trend", + "show-comments": "Show comments", + "show-full-period": "Show full period", + "show-holidays": "Show weekends", + "show-sources": "Sources", + "title_select-action": "Select action" +} diff --git a/src/i18n-keysets/chartkit.menu/keyset.json b/src/i18n-keysets/chartkit.menu/keyset.json new file mode 100644 index 0000000..8acb3db --- /dev/null +++ b/src/i18n-keysets/chartkit.menu/keyset.json @@ -0,0 +1,136 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "add-smoothing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "add-trend": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "compare-last-day": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "compare-last-year": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "data-export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "embedded": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "get-code": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "hide-comments": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "hide-compare": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "hide-full-period": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "hide-holidays": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "hide-normalize": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "manage-comments": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "modeling": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "modeling-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "normalize": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "normalize-division": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "normalize-subtraction": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "open-as-table": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "open-edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "open-fullscreen": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "open-in-window": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "remove-smoothing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "remove-trend": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "show-comments": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "show-full-period": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "show-holidays": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "show-sources": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_select-action": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit.menu/ru.json b/src/i18n-keysets/chartkit.menu/ru.json new file mode 100644 index 0000000..afe1255 --- /dev/null +++ b/src/i18n-keysets/chartkit.menu/ru.json @@ -0,0 +1,33 @@ +{ + "add-smoothing": "Добавить сглаживание", + "add-trend": "Добавить тренд", + "button_apply": "Применить", + "button_cancel": "Отменить", + "compare-last-day": "Сравнить со вчерашним днем", + "compare-last-year": "Сравнить с прошлым годом", + "data-export": "Экспорт данных", + "embedded": "Код для вставки", + "get-code": "Поделиться", + "hide-comments": "Скрыть комментарии", + "hide-compare": "Отключить сравнение", + "hide-full-period": "Отключить весь период", + "hide-holidays": "Скрыть выходные", + "hide-normalize": "Отключить", + "manage-comments": "Комментарии", + "modeling": "Моделирование", + "modeling-settings": "Настроить", + "normalize": "Нормализация на 1-ю точку", + "normalize-division": "Делением", + "normalize-subtraction": "Вычитанием", + "open-as-table": "Открыть как таблицу", + "open-edit": "Редактировать", + "open-fullscreen": "Открыть на весь экран", + "open-in-window": "Открыть в новой вкладке", + "remove-smoothing": "Убрать сглаживание", + "remove-trend": "Убрать тренд", + "show-comments": "Показать комментарии", + "show-full-period": "Посмотреть весь период", + "show-holidays": "Показать выходные", + "show-sources": "Источники", + "title_select-action": "Выберите действие" +} diff --git a/src/i18n-keysets/chartkit/context.json b/src/i18n-keysets/chartkit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/chartkit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/chartkit/en.json b/src/i18n-keysets/chartkit/en.json new file mode 100644 index 0000000..1d8c3c2 --- /dev/null +++ b/src/i18n-keysets/chartkit/en.json @@ -0,0 +1,6 @@ +{ + "categories": "Categories", + "date": "Date", + "smoothing": "smoothing", + "trend": "trend" +} diff --git a/src/i18n-keysets/chartkit/keyset.json b/src/i18n-keysets/chartkit/keyset.json new file mode 100644 index 0000000..6c542e5 --- /dev/null +++ b/src/i18n-keysets/chartkit/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "categories": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "smoothing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "trend": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/chartkit/ru.json b/src/i18n-keysets/chartkit/ru.json new file mode 100644 index 0000000..99930cf --- /dev/null +++ b/src/i18n-keysets/chartkit/ru.json @@ -0,0 +1,6 @@ +{ + "categories": "Категории", + "date": "Дата", + "smoothing": "сглаживание", + "trend": "тренд" +} diff --git a/src/i18n-keysets/collections/context.json b/src/i18n-keysets/collections/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/collections/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/collections/en.json b/src/i18n-keysets/collections/en.json new file mode 100644 index 0000000..e6ef3a7 --- /dev/null +++ b/src/i18n-keysets/collections/en.json @@ -0,0 +1,30 @@ +{ + "action_access": "Access", + "action_cancel": "Cancel", + "action_clear-filters": "Reset filters", + "action_connect-your-data": "Connect your data", + "action_copy": "Copy", + "action_create": "Create", + "action_create-collection": "Collection", + "action_create-collection-hint": "Groups workbooks and other collections", + "action_create-workbook": "Workbook", + "action_create-workbook-hint": "Stores related connections, datasets, charts, and dashboards", + "action_delete": "Delete", + "action_edit": "Edit", + "action_export": "Export", + "action_move": "Move", + "action_reset-all": "Reset all", + "action_select": "Select", + "action_select-all": "Select all", + "label_empty-list": "No collections or workbooks", + "label_last-modified": "Last modified", + "label_not-found": "No results", + "label_root-title": "Collections and workbooks", + "label_status-deleting": "Deleting", + "label_status-importing": "Importing", + "label_title": "Name", + "section_create-first": "You can create a workbook and connect your data", + "section_incorrect-filters": "Try other values in filters", + "shared-actions-menu-notice": "Objects for reuse and binding to workbooks", + "shared-actions-menu-title": "Common objects" +} diff --git a/src/i18n-keysets/collections/keyset.json b/src/i18n-keysets/collections/keyset.json new file mode 100644 index 0000000..24d1f56 --- /dev/null +++ b/src/i18n-keysets/collections/keyset.json @@ -0,0 +1,124 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "action_access": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_clear-filters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_connect-your-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-collection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-collection-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-workbook-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_move": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_reset-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_select": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_select-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-list": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_last-modified": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_not-found": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_root-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status-deleting": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status-importing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_create-first": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_incorrect-filters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "shared-actions-menu-notice": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "shared-actions-menu-title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/collections/ru.json b/src/i18n-keysets/collections/ru.json new file mode 100644 index 0000000..0473a63 --- /dev/null +++ b/src/i18n-keysets/collections/ru.json @@ -0,0 +1,30 @@ +{ + "action_access": "Доступ", + "action_cancel": "Отменить", + "action_clear-filters": "Очистить фильтры", + "action_connect-your-data": "Подключить свои данные", + "action_copy": "Копировать", + "action_create": "Создать", + "action_create-collection": "Коллекцию", + "action_create-collection-hint": "Группирует воркбуки и другие коллекции", + "action_create-workbook": "Воркбук", + "action_create-workbook-hint": "Содержит подключения, датасеты, чарты и дашборды", + "action_delete": "Удалить", + "action_edit": "Редактировать", + "action_export": "Экспортировать", + "action_move": "Переместить", + "action_reset-all": "Снять все", + "action_select": "Выбрать", + "action_select-all": "Выбрать все", + "label_empty-list": "Нет коллекций и воркбуков", + "label_last-modified": "Последнее изменение", + "label_not-found": "Ничего не найдено", + "label_root-title": "Коллекции и воркбуки", + "label_status-deleting": "Удаляется", + "label_status-importing": "Импортируется", + "label_title": "Название", + "section_create-first": "Вы можете создать воркбук и подключить свои данные", + "section_incorrect-filters": "Попробуйте указать другие значения в фильтрах", + "shared-actions-menu-notice": "Объекты для переиспользования и привязки к воркбукам", + "shared-actions-menu-title": "Общие объекты" +} diff --git a/src/i18n-keysets/common.components/context.json b/src/i18n-keysets/common.components/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/common.components/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/common.components/en.json b/src/i18n-keysets/common.components/en.json new file mode 100644 index 0000000..4e64e49 --- /dev/null +++ b/src/i18n-keysets/common.components/en.json @@ -0,0 +1,3 @@ +{ + "label_details": "View hint" +} diff --git a/src/i18n-keysets/common.components/keyset.json b/src/i18n-keysets/common.components/keyset.json new file mode 100644 index 0000000..86cb3f2 --- /dev/null +++ b/src/i18n-keysets/common.components/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_details": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/common.components/ru.json b/src/i18n-keysets/common.components/ru.json new file mode 100644 index 0000000..6657bd7 --- /dev/null +++ b/src/i18n-keysets/common.components/ru.json @@ -0,0 +1,3 @@ +{ + "label_details": "Посмотреть подсказку" +} diff --git a/src/i18n-keysets/common.data-types/context.json b/src/i18n-keysets/common.data-types/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/common.data-types/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/common.data-types/en.json b/src/i18n-keysets/common.data-types/en.json new file mode 100644 index 0000000..cae34e1 --- /dev/null +++ b/src/i18n-keysets/common.data-types/en.json @@ -0,0 +1,14 @@ +{ + "label_array_float": "Array of fractional numbers", + "label_array_int": "Array of integers", + "label_array_str": "Array of strings", + "label_date": "Date", + "label_float": "Fractional number", + "label_genericdatetime": "Date and time", + "label_geopoint": "Geopoint", + "label_geopolygon": "Geopolygon", + "label_integer": "Integer", + "label_markup": "Markup", + "label_string": "String", + "label_unsupported": "Unsupported type" +} diff --git a/src/i18n-keysets/common.data-types/keyset.json b/src/i18n-keysets/common.data-types/keyset.json new file mode 100644 index 0000000..5f19e0a --- /dev/null +++ b/src/i18n-keysets/common.data-types/keyset.json @@ -0,0 +1,60 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_array_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_array_int": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_array_str": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_genericdatetime": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_geopoint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_geopolygon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_integer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_markup": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_string": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unsupported": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/common.data-types/ru.json b/src/i18n-keysets/common.data-types/ru.json new file mode 100644 index 0000000..6b22429 --- /dev/null +++ b/src/i18n-keysets/common.data-types/ru.json @@ -0,0 +1,14 @@ +{ + "label_array_float": "Массив дробных чисел", + "label_array_int": "Массив целых чисел", + "label_array_str": "Массив строк", + "label_date": "Дата", + "label_float": "Дробное число", + "label_genericdatetime": "Дата и время", + "label_geopoint": "Геоточка", + "label_geopolygon": "Геополигон", + "label_integer": "Целое число", + "label_markup": "Разметка", + "label_string": "Строка", + "label_unsupported": "Неподдерживаемый тип" +} diff --git a/src/i18n-keysets/common.errors/context.json b/src/i18n-keysets/common.errors/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/common.errors/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/common.errors/en.json b/src/i18n-keysets/common.errors/en.json new file mode 100644 index 0000000..c1ec7c4 --- /dev/null +++ b/src/i18n-keysets/common.errors/en.json @@ -0,0 +1,7 @@ +{ + "label_error-access-message": "This token cannot be used with the specified parameters", + "label_error-invalid-format-token-message": "Invalid token format", + "label_error-invalid-token-message": "This token is not suitable for this embed", + "label_error-outdated-message": "Embed settings expired", + "label_error-token-not-found-message": "The embed has no token" +} diff --git a/src/i18n-keysets/common.errors/keyset.json b/src/i18n-keysets/common.errors/keyset.json new file mode 100644 index 0000000..df7bab8 --- /dev/null +++ b/src/i18n-keysets/common.errors/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_error-access-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-invalid-format-token-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-invalid-token-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-outdated-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-token-not-found-message": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/common.errors/ru.json b/src/i18n-keysets/common.errors/ru.json new file mode 100644 index 0000000..da7d0ac --- /dev/null +++ b/src/i18n-keysets/common.errors/ru.json @@ -0,0 +1,7 @@ +{ + "label_error-access-message": "Невозможно использовать токен с заданными параметрами", + "label_error-invalid-format-token-message": "Неверный формат токена", + "label_error-invalid-token-message": "Токен не подходит для данного встраивания", + "label_error-outdated-message": "Настройки встраивания устарели", + "label_error-token-not-found-message": "Во встраивании отсутствует токен" +} diff --git a/src/i18n-keysets/common.read-only/context.json b/src/i18n-keysets/common.read-only/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/common.read-only/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/common.read-only/en.json b/src/i18n-keysets/common.read-only/en.json new file mode 100644 index 0000000..34ae7ad --- /dev/null +++ b/src/i18n-keysets/common.read-only/en.json @@ -0,0 +1,3 @@ +{ + "toast_editing-warning": "Due to maintenance work, the service is currently read-only" +} diff --git a/src/i18n-keysets/common.read-only/keyset.json b/src/i18n-keysets/common.read-only/keyset.json new file mode 100644 index 0000000..eff62ba --- /dev/null +++ b/src/i18n-keysets/common.read-only/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "toast_editing-warning": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/common.read-only/ru.json b/src/i18n-keysets/common.read-only/ru.json new file mode 100644 index 0000000..92904bc --- /dev/null +++ b/src/i18n-keysets/common.read-only/ru.json @@ -0,0 +1,3 @@ +{ + "toast_editing-warning": "Ведутся технические работы, сервис доступен только для чтения" +} diff --git a/src/i18n-keysets/component.action-panel.view/context.json b/src/i18n-keysets/component.action-panel.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.action-panel.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.action-panel.view/en.json b/src/i18n-keysets/component.action-panel.view/en.json new file mode 100644 index 0000000..3552314 --- /dev/null +++ b/src/i18n-keysets/component.action-panel.view/en.json @@ -0,0 +1,9 @@ +{ + "button_open-navigation": "Open navigation", + "button_redo": "Restore", + "button_switch-public": "Public access", + "button_undo": "Undo", + "label_history-changes": "Change history", + "label_history-changes-date-limit": "You can view the history for the past three months", + "label_history-changes-date-limit-dataset": "You can view the history since" +} diff --git a/src/i18n-keysets/component.action-panel.view/keyset.json b/src/i18n-keysets/component.action-panel.view/keyset.json new file mode 100644 index 0000000..0780d0a --- /dev/null +++ b/src/i18n-keysets/component.action-panel.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_open-navigation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_redo": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_switch-public": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_undo": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_history-changes": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_history-changes-date-limit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_history-changes-date-limit-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.action-panel.view/ru.json b/src/i18n-keysets/component.action-panel.view/ru.json new file mode 100644 index 0000000..66213d9 --- /dev/null +++ b/src/i18n-keysets/component.action-panel.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_open-navigation": "Открыть навигацию", + "button_redo": "Восстановить", + "button_switch-public": "Публичный доступ", + "button_undo": "Отменить", + "label_history-changes": "История изменений", + "label_history-changes-date-limit": "Доступна история за последние три месяца", + "label_history-changes-date-limit-dataset": "Просмотр истории доступен с" +} diff --git a/src/i18n-keysets/component.aside-header-settings.view/context.json b/src/i18n-keysets/component.aside-header-settings.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.aside-header-settings.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.aside-header-settings.view/en.json b/src/i18n-keysets/component.aside-header-settings.view/en.json new file mode 100644 index 0000000..d7e5301 --- /dev/null +++ b/src/i18n-keysets/component.aside-header-settings.view/en.json @@ -0,0 +1,17 @@ +{ + "field_interface-theme": "Interface theme", + "field_interface-theme-contrast": "Contrast", + "field_language": "Language", + "label_language-en": "English", + "label_language-ru": "Русский", + "label_not-found": "No results", + "label_placeholder-search": "Search settings", + "label_title": "Settings", + "section_appearance": "Appearance", + "section_language": "Language", + "value_theme-contrast-enhanced": "High", + "value_theme-contrast-normal": "Normal", + "value_theme-dark": "Dark", + "value_theme-light": "Light", + "value_theme-system": "System" +} diff --git a/src/i18n-keysets/component.aside-header-settings.view/keyset.json b/src/i18n-keysets/component.aside-header-settings.view/keyset.json new file mode 100644 index 0000000..79ff84d --- /dev/null +++ b/src/i18n-keysets/component.aside-header-settings.view/keyset.json @@ -0,0 +1,72 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "field_interface-theme": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_interface-theme-contrast": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_language": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_language-en": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_language-ru": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_not-found": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-search": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_appearance": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_language": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_theme-contrast-enhanced": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_theme-contrast-normal": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_theme-dark": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_theme-light": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_theme-system": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.aside-header-settings.view/ru.json b/src/i18n-keysets/component.aside-header-settings.view/ru.json new file mode 100644 index 0000000..1bad0ea --- /dev/null +++ b/src/i18n-keysets/component.aside-header-settings.view/ru.json @@ -0,0 +1,17 @@ +{ + "field_interface-theme": "Тема интерфейса", + "field_interface-theme-contrast": "Контрастность", + "field_language": "Язык", + "label_language-en": "English", + "label_language-ru": "Русский", + "label_not-found": "Ничего не найдено", + "label_placeholder-search": "Найти настройки", + "label_title": "Настройки", + "section_appearance": "Оформление", + "section_language": "Язык", + "value_theme-contrast-enhanced": "Высокая", + "value_theme-contrast-normal": "Стандартная", + "value_theme-dark": "Темная", + "value_theme-light": "Светлая", + "value_theme-system": "Системная" +} diff --git a/src/i18n-keysets/component.aside-header.view/context.json b/src/i18n-keysets/component.aside-header.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.aside-header.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.aside-header.view/en.json b/src/i18n-keysets/component.aside-header.view/en.json new file mode 100644 index 0000000..32aea65 --- /dev/null +++ b/src/i18n-keysets/component.aside-header.view/en.json @@ -0,0 +1,15 @@ +{ + "label_about": "About", + "label_account": "Account", + "label_app-version": "Version", + "label_collections": "Collections and workbooks", + "label_docs": "Documentation", + "label_gallery": "DataLens Gallery", + "label_github": "GitHub", + "label_logout": "Log out", + "label_main": "Help", + "label_profile-settings": "Profile settings", + "label_settings": "Settings", + "switch_collections": "Collections and workbooks", + "switch_service-settings": "Service settings" +} diff --git a/src/i18n-keysets/component.aside-header.view/keyset.json b/src/i18n-keysets/component.aside-header.view/keyset.json new file mode 100644 index 0000000..7c40c1a --- /dev/null +++ b/src/i18n-keysets/component.aside-header.view/keyset.json @@ -0,0 +1,64 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_about": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_account": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_app-version": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_collections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_docs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gallery": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_github": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_logout": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_main": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_profile-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_collections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_service-settings": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.aside-header.view/ru.json b/src/i18n-keysets/component.aside-header.view/ru.json new file mode 100644 index 0000000..16050d0 --- /dev/null +++ b/src/i18n-keysets/component.aside-header.view/ru.json @@ -0,0 +1,15 @@ +{ + "label_about": "О сервисе", + "label_account": "Учётная запись", + "label_app-version": "Версия", + "label_collections": "Коллекции и воркбуки", + "label_docs": "Документация", + "label_gallery": "Галерея", + "label_github": "GitHub", + "label_logout": "Выйти", + "label_main": "Помощь", + "label_profile-settings": "Настройки профиля", + "label_settings": "Настройки", + "switch_collections": "Коллекции и воркбуки", + "switch_service-settings": "Настройки сервиса" +} diff --git a/src/i18n-keysets/component.chart-modeling-settings/context.json b/src/i18n-keysets/component.chart-modeling-settings/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.chart-modeling-settings/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.chart-modeling-settings/en.json b/src/i18n-keysets/component.chart-modeling-settings/en.json new file mode 100644 index 0000000..7eb9de3 --- /dev/null +++ b/src/i18n-keysets/component.chart-modeling-settings/en.json @@ -0,0 +1,20 @@ +{ + "label_additional-settings": "Additional settings", + "label_auto": "Auto", + "label_cubic": "Cubic", + "label_linear": "Linear", + "label_link-series": "Group lines", + "label_method": "Function", + "label_null-data-warning": "The modeling is inaccurate, as the data is partially or completely missing", + "label_null-data-warning-short": "Model inaccuracy", + "label_quadratic": "Quadratic", + "label_shape-and-width": "Line shape and width", + "label_simple-moving-average": "Simple moving average", + "label_smoothing": "Smoothing", + "label_trend": "Trend", + "label_window-size": "Window (number of points)", + "title_modeling": "Modeling", + "tooltip_smoothing-method": "A simple moving average is the arithmetic mean at point t over the interval specified in the Window field. The window includes the points up to and including point t.", + "tooltip_smoothing-window-size": "The number of points for calculating the arithmetic mean, including the point t itself.", + "tooltip_trend-method": "**Linear**: A linear regression, a straight line, helps to generally assess growth or decline in a selected interval.\n**Quadratic**: Use if the source data clearly shows a nonlinear dependence in the data.\n**Cubic**: Use if more than one inflection is clearly visible in the source data.\n\nWhen making decisions, use the quadratic and cubic functions carefully. Follow the recommendations above." +} diff --git a/src/i18n-keysets/component.chart-modeling-settings/keyset.json b/src/i18n-keysets/component.chart-modeling-settings/keyset.json new file mode 100644 index 0000000..e2b4981 --- /dev/null +++ b/src/i18n-keysets/component.chart-modeling-settings/keyset.json @@ -0,0 +1,84 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_additional-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_auto": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_cubic": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_linear": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_link-series": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_method": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_null-data-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_null-data-warning-short": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_quadratic": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_shape-and-width": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_simple-moving-average": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_smoothing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_trend": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_window-size": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_modeling": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_smoothing-method": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_smoothing-window-size": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_trend-method": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.chart-modeling-settings/ru.json b/src/i18n-keysets/component.chart-modeling-settings/ru.json new file mode 100644 index 0000000..bd6b2d4 --- /dev/null +++ b/src/i18n-keysets/component.chart-modeling-settings/ru.json @@ -0,0 +1,20 @@ +{ + "label_additional-settings": "Дополнительные настройки", + "label_auto": "Авто", + "label_cubic": "Кубическая", + "label_linear": "Линейная", + "label_link-series": "Группировать линии", + "label_method": "Функция", + "label_null-data-warning": "Моделирование построено неточно, так как данные частично или полностью отсутствуют", + "label_null-data-warning-short": "Неточность моделирования", + "label_quadratic": "Квадратичная", + "label_shape-and-width": "Форма и толщина линий", + "label_simple-moving-average": "Простое скользящее среднее", + "label_smoothing": "Сглаживание", + "label_trend": "Тренд", + "label_window-size": "Окно (количество точек)", + "title_modeling": "Моделирование", + "tooltip_smoothing-method": "Простое скользящее среднее — среднее арифметическое в точке t за интервал, указанный в поле «Окно». Окно включает точки, предшествующие t, и саму точку t.", + "tooltip_smoothing-window-size": "Количество точек для расчёта среднего арифметического, включая саму точку t.", + "tooltip_trend-method": "**Линейная** — линейная регрессия, представляет собой прямую линию, помогает в целом оценить рост или падение на выбранном интервале.\n**Квадратичная** — используйте, если на исходных данных явно видна нелинейная зависимость в данных.\n**Кубическая** — используйте, если на исходных данных явно видно больше одного перегиба.\n\nПри принятии решений с осторожностью используйте квадратичную и кубическую функции. Ориентируйтесь на рекомендации выше." +} diff --git a/src/i18n-keysets/component.chart-save-controls/context.json b/src/i18n-keysets/component.chart-save-controls/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.chart-save-controls/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.chart-save-controls/en.json b/src/i18n-keysets/component.chart-save-controls/en.json new file mode 100644 index 0000000..5dffe19 --- /dev/null +++ b/src/i18n-keysets/component.chart-save-controls/en.json @@ -0,0 +1,8 @@ +{ + "button_apply": "Apply", + "button_save": "Save", + "button_save-as-draft": "Save as draft", + "button_save-as-new": "Save as copy", + "button_save-as-published": "Save and make actual", + "button_saved": "Saved" +} diff --git a/src/i18n-keysets/component.chart-save-controls/keyset.json b/src/i18n-keysets/component.chart-save-controls/keyset.json new file mode 100644 index 0000000..accf833 --- /dev/null +++ b/src/i18n-keysets/component.chart-save-controls/keyset.json @@ -0,0 +1,36 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-published": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_saved": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.chart-save-controls/ru.json b/src/i18n-keysets/component.chart-save-controls/ru.json new file mode 100644 index 0000000..569c493 --- /dev/null +++ b/src/i18n-keysets/component.chart-save-controls/ru.json @@ -0,0 +1,8 @@ +{ + "button_apply": "Применить", + "button_save": "Сохранить", + "button_save-as-draft": "Сохранить как черновик", + "button_save-as-new": "Сохранить как копию", + "button_save-as-published": "Сохранить и сделать актуальной", + "button_saved": "Сохранено" +} diff --git a/src/i18n-keysets/component.chartkit-alerts.view/context.json b/src/i18n-keysets/component.chartkit-alerts.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.chartkit-alerts.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.chartkit-alerts.view/en.json b/src/i18n-keysets/component.chartkit-alerts.view/en.json new file mode 100644 index 0000000..a29c2f4 --- /dev/null +++ b/src/i18n-keysets/component.chartkit-alerts.view/en.json @@ -0,0 +1,68 @@ +{ + "button_add": "Add", + "button_cancel": "Cancel", + "button_close": "Close", + "button_details": "More", + "button_get": "Get", + "button_retry": "Repeat", + "button_save": "Save", + "context_timezone": "DataLens is not yet able to work with timezones: all dates on the charts are displayed in UTC+0.



If you shift the time series using a formula in the dataset or a code in Editor, you must select the appropriate time zone here.



Example: if you add 3 hours to the datetime values in the dataset, select +03:00 here.", + "field_aggregation-function": "Aggregation function", + "field_description": "Description", + "field_name": "Name", + "field_oauth": "OAuth token", + "field_recipients": "Recipients", + "field_timezone": "Timezone", + "label_add": "Add", + "label_approve-alert-remove": "Are you sure you want to delete alert?", + "label_approve-draft": "Are you sure you want to discard changes?", + "label_condition": "Condition", + "label_critical-error": "Critical error", + "label_error": "An error occurred", + "label_new-alert": "New alert", + "label_optional": "optional", + "label_params-absolute-date": "The chart parameters use fixed dates.

Make sure that you use relative dates for the alert.", + "label_series": "Series", + "label_unsupported-chart": "You can't create an alert on this chart", + "label_unsupported-datetime": "There should be a continuous time series on the X-axis", + "label_unsupported-series-type": "The series type is not supported", + "label_unsupported-widget-type": "The chart type is not supported", + "label_unsupported-y-axis-type": "The Y-axis type should be linear", + "label_validation-error": "Validation error", + "label_y-axis": "Y-axis", + "section_additional-settings": "Additional settings", + "section_alert-settings": "Alert parameters", + "section_general": "General", + "section_metrics": "Metrics", + "section_notifications": "Notification channels", + "section_title": "Alerts", + "toast_alert-created": "Alert created", + "toast_alert-removed": "Alert deleted", + "toast_alert-updated": "Alert updated", + "toast_create-alert-failed": "Failed to create alert", + "toast_existed": "Entry already exists", + "toast_remove-alert-failed": "Failed to delete alert", + "toast_update-alert-failed": "Failed to update alert", + "validation_invalid": "Incorrect value in field", + "validation_no-recipients": "You must have at least one recipient", + "validation_not-empty": "The field cannot be empty", + "validation_not-number": "Value must be a number", + "validation_series-max": "The number of series should not exceed {{max}}", + "validation_series-selected": "Select a series", + "validation_threshold-error-no-alarm": "You must specify Alarm threshold", + "value_aggregation-AVG": "Average", + "value_aggregation-COUNT": "Count", + "value_aggregation-LAST_NON_NAN": "Last", + "value_aggregation-MAX": "Maximum", + "value_aggregation-MIN": "Minimum", + "value_aggregation-SUM": "Sum", + "value_all-series": "All", + "value_condition-EQ": "Equals to", + "value_condition-GT": "Greater than", + "value_condition-GTE": "Greater than or equals", + "value_condition-LT": "Less than", + "value_condition-LTE": "Less than or equals", + "value_condition-NE": "Not equals to", + "value_select-series": "Select", + "value_y-axis-number": "Аxis {{num}}" +} diff --git a/src/i18n-keysets/component.chartkit-alerts.view/keyset.json b/src/i18n-keysets/component.chartkit-alerts.view/keyset.json new file mode 100644 index 0000000..61c935a --- /dev/null +++ b/src/i18n-keysets/component.chartkit-alerts.view/keyset.json @@ -0,0 +1,276 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_details": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_get": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_timezone": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_aggregation-function": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_oauth": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_recipients": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_timezone": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_approve-alert-remove": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_approve-draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_condition": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_critical-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-alert": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_optional": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_params-absolute-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_series": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unsupported-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unsupported-datetime": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unsupported-series-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unsupported-widget-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unsupported-y-axis-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_validation-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_y-axis": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_additional-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_alert-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_general": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_metrics": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_notifications": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_alert-created": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_alert-removed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_alert-updated": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_create-alert-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_existed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_remove-alert-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_update-alert-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_invalid": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_no-recipients": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_not-empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_not-number": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_series-max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_series-selected": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_threshold-error-no-alarm": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_aggregation-AVG": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_aggregation-COUNT": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_aggregation-LAST_NON_NAN": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_aggregation-MAX": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_aggregation-MIN": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_aggregation-SUM": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_all-series": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_condition-EQ": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_condition-GT": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_condition-GTE": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_condition-LT": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_condition-LTE": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_condition-NE": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_select-series": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_y-axis-number": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.chartkit-alerts.view/ru.json b/src/i18n-keysets/component.chartkit-alerts.view/ru.json new file mode 100644 index 0000000..4c8a2bf --- /dev/null +++ b/src/i18n-keysets/component.chartkit-alerts.view/ru.json @@ -0,0 +1,68 @@ +{ + "button_add": "Добавить", + "button_cancel": "Отмена", + "button_close": "Закрыть", + "button_details": "Подробнее", + "button_get": "Получить", + "button_retry": "Повторить", + "button_save": "Сохранить", + "context_timezone": "DataLens пока не умеет работать с таймзонами: все даты на чартах выводятся в UTC+0.



Если вы сдвигаете временной ряд формулой в датасете или кодом в Editor, необходимо выбрать тут соответствующую таймзону.



Пример: если в датасете вы прибавляете 3 часа к datetime-значениям, здесь нужно выбрать +03:00.", + "field_aggregation-function": "Функция агрегации", + "field_description": "Описание", + "field_name": "Название", + "field_oauth": "OAuth-токен", + "field_recipients": "Получатели", + "field_timezone": "Таймзона", + "label_add": "Добавить", + "label_approve-alert-remove": "Вы действительно хотите удалить алерт?", + "label_approve-draft": "Вы уверены, что хотите выйти?

Данные будут утеряны.", + "label_condition": "Условие срабатывания", + "label_critical-error": "Критическая ошибка", + "label_error": "Произошла ошибка", + "label_new-alert": "Новый алерт", + "label_optional": "опционально", + "label_params-absolute-date": "В параметрах чарта используются фиксированные даты.

Убедитесь, что для алерта вы используете относительные даты.", + "label_series": "Серии", + "label_unsupported-chart": "Нельзя создать алерт на данном чарте", + "label_unsupported-datetime": "По оси X должен быть непрерывный временной ряд", + "label_unsupported-series-type": "Тип серии не поддерживается", + "label_unsupported-widget-type": "Тип чарта не поддерживается", + "label_unsupported-y-axis-type": "Тип оси Y должен быть «Линейный»", + "label_validation-error": "Ошибка валидации", + "label_y-axis": "Ось Y", + "section_additional-settings": "Дополнительные настройки", + "section_alert-settings": "Настройки алерта", + "section_general": "Основные", + "section_metrics": "Метрики", + "section_notifications": "Каналы уведомлений", + "section_title": "Алерты", + "toast_alert-created": "Алерт создан", + "toast_alert-removed": "Алерт удален", + "toast_alert-updated": "Алерт обновлен", + "toast_create-alert-failed": "Не удалось создать алерт", + "toast_existed": "Запись уже существует", + "toast_remove-alert-failed": "Не удалось удалить алерт", + "toast_update-alert-failed": "Не удалось обновить алерт", + "validation_invalid": "Поле заполнено неправильно", + "validation_no-recipients": "Должен быть хотя бы один получатель", + "validation_not-empty": "Поле должно быть заполнено", + "validation_not-number": "Значение должно быть числом", + "validation_series-max": "Количество серий не должно превышать {{max}}", + "validation_series-selected": "Выберите серию", + "validation_threshold-error-no-alarm": "Необходимо указать порог для статуса «Alarm»", + "value_aggregation-AVG": "Среднее", + "value_aggregation-COUNT": "Количество", + "value_aggregation-LAST_NON_NAN": "Последнее", + "value_aggregation-MAX": "Максимум", + "value_aggregation-MIN": "Минимум", + "value_aggregation-SUM": "Сумма", + "value_all-series": "Все", + "value_condition-EQ": "Равно", + "value_condition-GT": "Больше", + "value_condition-GTE": "Больше или равно", + "value_condition-LT": "Меньше", + "value_condition-LTE": "Меньше или равно", + "value_condition-NE": "Не равно", + "value_select-series": "Выбрать", + "value_y-axis-number": "Ось {{num}}" +} diff --git a/src/i18n-keysets/component.chartkit-error.codes/context.json b/src/i18n-keysets/component.chartkit-error.codes/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.chartkit-error.codes/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.chartkit-error.codes/en.json b/src/i18n-keysets/component.chartkit-error.codes/en.json new file mode 100644 index 0000000..d908829 --- /dev/null +++ b/src/i18n-keysets/component.chartkit-error.codes/en.json @@ -0,0 +1,34 @@ +{ + "403": "No access to view data", + "404": "Data source not found", + "ERR.CHARTS.CONFIG_LOADING_ERROR": "No view rights for chart", + "ERR.CHARTS.REQUEST_SIZE_LIMIT_EXCEEDED": "Data size limit exceeded", + "ERR.CHARTS.UNSUPPORTED_DATA_TYPE": "Unsupported data type", + "ERR.DS_API": "Datasets internal error", + "ERR.DS_API.CLIQUE_STOPPED": "CHYT clique is not running", + "ERR.DS_API.DATABASE_UNAVAILABLE": "Database unavailable", + "ERR.DS_API.DB": "Source error", + "ERR.DS_API.DB.CHYT": "CHYT error", + "ERR.DS_API.DB.CHYT.INVALID_SORTED_JOIN": "Incorrect connection between tables", + "ERR.DS_API.DB.COLUMN_DOES_NOT_EXIST": "Using non-existent field", + "ERR.DS_API.DB.DATA_PREPARATION_NOT_FINISHED": "Data preparation is not completed yet", + "ERR.DS_API.DB.INVALID_QUERY": "Invalid SQL query", + "ERR.DS_API.DB.MEMORY_LIMIT_EXCEEDED": "Memory limit has been exceeded during query execution", + "ERR.DS_API.DB.METRICA": "Metrika error", + "ERR.DS_API.DB.SOURCE_DOES_NOT_EXIST": "Using non-existent table", + "ERR.DS_API.DB.SOURCE_ERROR.CLOSED_PREMATURELY": "The data source has terminated the connection", + "ERR.DS_API.DB.SOURCE_ERROR.INVALID_RESPONSE": "The data source is not responding", + "ERR.DS_API.DB.SOURCE_ERROR.TIMEOUT": "Query execution time exceeded in the data source", + "ERR.DS_API.FIELD.NOT_FOUND": "Using non-existent field", + "ERR.DS_API.FILTER.INVALID_VALUE": "Incorrect filter value", + "ERR.DS_API.FORMULA": "Error in formula", + "ERR.DS_API.NO_AVAILABLE_SUBPRODUCTS": "Your subscription to an item from the Marketplace has expired", + "ERR.DS_API.REFERENCED_ENTRY_ACCESS_DENIED_CONNECTION": "No access to connection", + "ERR.DS_API.ROW_COUNT_LIMIT": "Too many rows", + "ERR.DS_API.SOURCE_CONFIG.TABLE_NOT_CONFIGURED": "Invalid table configuration", + "ERR.DS_API.US.ACCESS_DENIED_DATASET": "No access to dataset", + "ERR.DS_API.US.BAD_REQUEST": "Internal error", + "ERR.DS_API.US.OBJ_NOT_FOUND": "Dataset not found", + "ERR.DS_API.US.WORKBOOK_ISOLATION_INTERRUPTION_DATASET": "Dataset not found in the current workbook", + "ERR.UNKNOWN": "Internal error" +} diff --git a/src/i18n-keysets/component.chartkit-error.codes/keyset.json b/src/i18n-keysets/component.chartkit-error.codes/keyset.json new file mode 100644 index 0000000..9a2eff9 --- /dev/null +++ b/src/i18n-keysets/component.chartkit-error.codes/keyset.json @@ -0,0 +1,140 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "403": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "404": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.CHARTS.CONFIG_LOADING_ERROR": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.CHARTS.REQUEST_SIZE_LIMIT_EXCEEDED": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.CHARTS.UNSUPPORTED_DATA_TYPE": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.CLIQUE_STOPPED": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DATABASE_UNAVAILABLE": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.CHYT": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.CHYT.INVALID_SORTED_JOIN": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.COLUMN_DOES_NOT_EXIST": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.DATA_PREPARATION_NOT_FINISHED": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.INVALID_QUERY": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.MEMORY_LIMIT_EXCEEDED": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.METRICA": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.SOURCE_DOES_NOT_EXIST": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.SOURCE_ERROR.CLOSED_PREMATURELY": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.SOURCE_ERROR.INVALID_RESPONSE": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.DB.SOURCE_ERROR.TIMEOUT": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.FIELD.NOT_FOUND": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.FILTER.INVALID_VALUE": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.FORMULA": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.NO_AVAILABLE_SUBPRODUCTS": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.REFERENCED_ENTRY_ACCESS_DENIED_CONNECTION": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.ROW_COUNT_LIMIT": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.SOURCE_CONFIG.TABLE_NOT_CONFIGURED": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.US.ACCESS_DENIED_DATASET": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.US.BAD_REQUEST": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.US.OBJ_NOT_FOUND": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.DS_API.US.WORKBOOK_ISOLATION_INTERRUPTION_DATASET": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "ERR.UNKNOWN": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.chartkit-error.codes/ru.json b/src/i18n-keysets/component.chartkit-error.codes/ru.json new file mode 100644 index 0000000..4dfd1e0 --- /dev/null +++ b/src/i18n-keysets/component.chartkit-error.codes/ru.json @@ -0,0 +1,34 @@ +{ + "403": "Нет прав на просмотр данных", + "404": "Источник не найден", + "ERR.CHARTS.CONFIG_LOADING_ERROR": "Нет прав на просмотр чарта", + "ERR.CHARTS.REQUEST_SIZE_LIMIT_EXCEEDED": "Превышен лимит объема данных", + "ERR.CHARTS.UNSUPPORTED_DATA_TYPE": "Неподдерживаемый тип данных", + "ERR.DS_API": "Внутренняя ошибка датасетов", + "ERR.DS_API.CLIQUE_STOPPED": "CHYT-клика не запущена", + "ERR.DS_API.DATABASE_UNAVAILABLE": "База данных недоступна", + "ERR.DS_API.DB": "Ошибка источника", + "ERR.DS_API.DB.CHYT": "Ошибка CHYT", + "ERR.DS_API.DB.CHYT.INVALID_SORTED_JOIN": "Связь между таблицами настроена некорректно", + "ERR.DS_API.DB.COLUMN_DOES_NOT_EXIST": "Используется несуществующее поле", + "ERR.DS_API.DB.DATA_PREPARATION_NOT_FINISHED": "Подготовка данных ещё не завершилась", + "ERR.DS_API.DB.INVALID_QUERY": "Некорректный SQL-запрос", + "ERR.DS_API.DB.MEMORY_LIMIT_EXCEEDED": "Лимит памяти был превышен во время выполнения запроса", + "ERR.DS_API.DB.METRICA": "Ошибка Метрики", + "ERR.DS_API.DB.SOURCE_DOES_NOT_EXIST": "Используется несуществующая таблица", + "ERR.DS_API.DB.SOURCE_ERROR.CLOSED_PREMATURELY": "Источник данных прервал соединение", + "ERR.DS_API.DB.SOURCE_ERROR.INVALID_RESPONSE": "Источник данных не отвечает", + "ERR.DS_API.DB.SOURCE_ERROR.TIMEOUT": "Превышено время выполнения запроса в источнике данных", + "ERR.DS_API.FIELD.NOT_FOUND": "Используется несуществующее поле", + "ERR.DS_API.FILTER.INVALID_VALUE": "Некорректное значение фильтра", + "ERR.DS_API.FORMULA": "Ошибка в формуле", + "ERR.DS_API.NO_AVAILABLE_SUBPRODUCTS": "Истекла подписка на товар из Marketplace", + "ERR.DS_API.REFERENCED_ENTRY_ACCESS_DENIED_CONNECTION": "Нет доступа к подключению", + "ERR.DS_API.ROW_COUNT_LIMIT": "Превышено допустимое количество строк", + "ERR.DS_API.SOURCE_CONFIG.TABLE_NOT_CONFIGURED": "Нарушена конфигурация таблицы", + "ERR.DS_API.US.ACCESS_DENIED_DATASET": "Нет доступа к датасету", + "ERR.DS_API.US.BAD_REQUEST": "Внутренняя ошибка", + "ERR.DS_API.US.OBJ_NOT_FOUND": "Датасет не найден", + "ERR.DS_API.US.WORKBOOK_ISOLATION_INTERRUPTION_DATASET": "Датасет не найден в текущем воркбуке", + "ERR.UNKNOWN": "Внутренняя ошибка" +} diff --git a/src/i18n-keysets/component.chartkit-error.view/context.json b/src/i18n-keysets/component.chartkit-error.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.chartkit-error.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.chartkit-error.view/en.json b/src/i18n-keysets/component.chartkit-error.view/en.json new file mode 100644 index 0000000..960ec63 --- /dev/null +++ b/src/i18n-keysets/component.chartkit-error.view/en.json @@ -0,0 +1,4 @@ +{ + "button_details": "More", + "button_report": "Report a problem" +} diff --git a/src/i18n-keysets/component.chartkit-error.view/keyset.json b/src/i18n-keysets/component.chartkit-error.view/keyset.json new file mode 100644 index 0000000..33e28e9 --- /dev/null +++ b/src/i18n-keysets/component.chartkit-error.view/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_details": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_report": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.chartkit-error.view/ru.json b/src/i18n-keysets/component.chartkit-error.view/ru.json new file mode 100644 index 0000000..9544603 --- /dev/null +++ b/src/i18n-keysets/component.chartkit-error.view/ru.json @@ -0,0 +1,4 @@ +{ + "button_details": "Подробнее", + "button_report": "Сообщить о проблеме" +} diff --git a/src/i18n-keysets/component.collection-breadcrumbs/context.json b/src/i18n-keysets/component.collection-breadcrumbs/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.collection-breadcrumbs/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.collection-breadcrumbs/en.json b/src/i18n-keysets/component.collection-breadcrumbs/en.json new file mode 100644 index 0000000..eba0b96 --- /dev/null +++ b/src/i18n-keysets/component.collection-breadcrumbs/en.json @@ -0,0 +1,3 @@ +{ + "label_root-title": "Collections and workbooks" +} diff --git a/src/i18n-keysets/component.collection-breadcrumbs/keyset.json b/src/i18n-keysets/component.collection-breadcrumbs/keyset.json new file mode 100644 index 0000000..8834286 --- /dev/null +++ b/src/i18n-keysets/component.collection-breadcrumbs/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_root-title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.collection-breadcrumbs/ru.json b/src/i18n-keysets/component.collection-breadcrumbs/ru.json new file mode 100644 index 0000000..8c0dbd2 --- /dev/null +++ b/src/i18n-keysets/component.collection-breadcrumbs/ru.json @@ -0,0 +1,3 @@ +{ + "label_root-title": "Коллекции и воркбуки" +} diff --git a/src/i18n-keysets/component.collection-filters/context.json b/src/i18n-keysets/component.collection-filters/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.collection-filters/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.collection-filters/en.json b/src/i18n-keysets/component.collection-filters/en.json new file mode 100644 index 0000000..c66b58a --- /dev/null +++ b/src/i18n-keysets/component.collection-filters/en.json @@ -0,0 +1,13 @@ +{ + "label_filter-by-ownership-all": "All", + "label_filter-by-ownership-only-my": "Only mine", + "label_filter-by-type-all": "All types", + "label_filter-by-type-only-collections": "Only collections", + "label_filter-by-type-only-entries": "Only common objects", + "label_filter-by-type-only-workbooks": "Only workbooks", + "label_filter-string-placeholder": "Search by name", + "label_sort-first-alphabet-asc": "Sort A to Z", + "label_sort-first-alphabet-desc": "Sort Z to A", + "label_sort-first-new": "Newest first", + "label_sort-first-old": "Oldest first" +} diff --git a/src/i18n-keysets/component.collection-filters/keyset.json b/src/i18n-keysets/component.collection-filters/keyset.json new file mode 100644 index 0000000..0d8c8b8 --- /dev/null +++ b/src/i18n-keysets/component.collection-filters/keyset.json @@ -0,0 +1,56 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_filter-by-ownership-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-by-ownership-only-my": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-by-type-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-by-type-only-collections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-by-type-only-entries": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-by-type-only-workbooks": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-string-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sort-first-alphabet-asc": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sort-first-alphabet-desc": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sort-first-new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sort-first-old": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.collection-filters/ru.json b/src/i18n-keysets/component.collection-filters/ru.json new file mode 100644 index 0000000..240319f --- /dev/null +++ b/src/i18n-keysets/component.collection-filters/ru.json @@ -0,0 +1,13 @@ +{ + "label_filter-by-ownership-all": "Все", + "label_filter-by-ownership-only-my": "Только мои", + "label_filter-by-type-all": "Все типы", + "label_filter-by-type-only-collections": "Только коллекции", + "label_filter-by-type-only-entries": "Только общие объекты", + "label_filter-by-type-only-workbooks": "Только воркбуки", + "label_filter-string-placeholder": "Искать по названию", + "label_sort-first-alphabet-asc": "Сортировка от А до Я", + "label_sort-first-alphabet-desc": "Сортировка от Я до А", + "label_sort-first-new": "Сначала новые", + "label_sort-first-old": "Сначала старые" +} diff --git a/src/i18n-keysets/component.collections-structure/context.json b/src/i18n-keysets/component.collections-structure/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.collections-structure/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.collections-structure/en.json b/src/i18n-keysets/component.collections-structure/en.json new file mode 100644 index 0000000..331b21e --- /dev/null +++ b/src/i18n-keysets/component.collections-structure/en.json @@ -0,0 +1,54 @@ +{ + "action_cancel": "Cancel", + "action_copy": "Copy", + "action_create": "Create", + "action_create-collection": "Create collection", + "action_create-workbook": "Create workbook", + "action_delete": "Delete", + "action_edit-collection": "Edit collection", + "action_edit-workbook": "Edit workbook", + "action_exit": "Close", + "action_move": "Move", + "action_save": "Save", + "action_stay": "Remain", + "button_choose-file": "Select a file", + "button_deploy": "Deploy", + "label_collection-already-exists-error": "A collection with that name already exists", + "label_connection": "connection", + "label_copy": "Copy to", + "label_copy-denied-title": "Insufficient permissions to copy", + "label_create-denied-title": "Insufficient permissions to create object", + "label_create-entry-in-workbook": "Choose a workbook to create a", + "label_dashboard": "dashboard", + "label_dataset": "dataset", + "label_delete-collection": "Delete collection", + "label_delete-collections-for-delete": "Collections to be deleted", + "label_delete-collections-workbooks": "Delete collections and workbooks", + "label_delete-connections-for-delete": "Connections to be deleted", + "label_delete-datasets-for-delete": "Datasets to be deleted", + "label_delete-workbook": "Delete workbook", + "label_delete-workbooks-for-delete": "Workbooks to be deleted", + "label_deploy-denied-title": "Insufficient permissions to create a workbook", + "label_description": "Description", + "label_empty-collection": "This collection is empty or nothing matching the filter conditions was found", + "label_error-file-type": "File must be in JSON format", + "label_help-import-hint": "Use a workbook configuration file in JSON format. You can get such a file by exporting the workbook.", + "label_import-exit-description": "This will not affect the import process. The import window can be opened again by clicking on the workbook in the list.", + "label_import-from-file": "Import from file", + "label_move": "Move to", + "label_move-denied-title": "Insufficient permissions to move object", + "label_ql": "QL chart", + "label_report": "report", + "label_title": "Name", + "label_wizard": "chart", + "label_workbook-already-exists-error": "A workbook with that name already exists", + "section_delete-collection": "Are you sure you want to delete the collection \"{{name}}\"?", + "section_delete-workbook": "Are you sure you want to delete the workbook \"{{name}}\"?", + "title_deploy": "Deploy to", + "title_import-exit": "Do you want to close the import window?", + "toast_collection-already-exists-error": "A collection with that name already exists", + "toast_get-gallery-file-error": "Deployment error, please contact the support service", + "toast_get-json-error": "An error occurred while trying to get a JSON from the file", + "toast_outdated-workbook-info": "The workbook information is outdated. Please refresh the page.", + "toast_workbook-already-exists-error": "A workbook with that name already exists" +} diff --git a/src/i18n-keysets/component.collections-structure/keyset.json b/src/i18n-keysets/component.collections-structure/keyset.json new file mode 100644 index 0000000..d3b1d20 --- /dev/null +++ b/src/i18n-keysets/component.collections-structure/keyset.json @@ -0,0 +1,220 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "action_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-collection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_edit-collection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_edit-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_exit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_move": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_stay": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_choose-file": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_deploy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_collection-already-exists-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_copy-denied-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_create-denied-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_create-entry-in-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-collection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-collections-for-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-collections-workbooks": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-connections-for-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-datasets-for-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-workbooks-for-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_deploy-denied-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-collection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-file-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_help-import-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_import-exit-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_import-from-file": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_move": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_move-denied-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_ql": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_report": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_wizard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_workbook-already-exists-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_delete-collection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_delete-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_deploy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_import-exit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_collection-already-exists-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_get-gallery-file-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_get-json-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_outdated-workbook-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_workbook-already-exists-error": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.collections-structure/ru.json b/src/i18n-keysets/component.collections-structure/ru.json new file mode 100644 index 0000000..afef7bc --- /dev/null +++ b/src/i18n-keysets/component.collections-structure/ru.json @@ -0,0 +1,54 @@ +{ + "action_cancel": "Отменить", + "action_copy": "Копировать", + "action_create": "Создать", + "action_create-collection": "Создать коллекцию", + "action_create-workbook": "Создать воркбук", + "action_delete": "Удалить", + "action_edit-collection": "Редактирование коллекции", + "action_edit-workbook": "Редактирование воркбука", + "action_exit": "Закрыть", + "action_move": "Переместить", + "action_save": "Сохранить", + "action_stay": "Остаться", + "button_choose-file": "Выбрать файл", + "button_deploy": "Развернуть", + "label_collection-already-exists-error": "Коллекция с таким названием уже существует", + "label_connection": "подключения", + "label_copy": "Куда копировать", + "label_copy-denied-title": "Недостаточно прав для копирования", + "label_create-denied-title": "Недостаточно прав для создания объекта", + "label_create-entry-in-workbook": "Выберите воркбук для создания", + "label_dashboard": "дашборда", + "label_dataset": "датасета", + "label_delete-collection": "Удаление коллекции", + "label_delete-collections-for-delete": "Будут удалены коллекции", + "label_delete-collections-workbooks": "Удаление коллекций и воркбуков", + "label_delete-connections-for-delete": "Будут удалены подключения", + "label_delete-datasets-for-delete": "Будут удалены датасеты", + "label_delete-workbook": "Удаление воркбука", + "label_delete-workbooks-for-delete": "Будут удалены воркбуки", + "label_deploy-denied-title": "Недостаточно прав для создания воркбука", + "label_description": "Описание", + "label_empty-collection": "В коллекции пусто или не удалось ничего найти по условиям в фильтрах", + "label_error-file-type": "Файл должен быть в формате JSON", + "label_help-import-hint": "Используйте файл с конфигурацией воркбука в формате JSON. Получить такой файл можно при экспорте воркбука.", + "label_import-exit-description": "На процесс импорта это не повлияет. Окно импорта можно снова открыть, нажав на воркбук в списке.", + "label_import-from-file": "Импорт из файла", + "label_move": "Куда переместить", + "label_move-denied-title": "Недостаточно прав для перемещения", + "label_ql": "QL-чарта", + "label_report": "отчёта", + "label_title": "Название", + "label_wizard": "чарта", + "label_workbook-already-exists-error": "Воркбук с таким названием уже существует", + "section_delete-collection": "Вы действительно хотите удалить коллекцию \"{{name}}\"?", + "section_delete-workbook": "Вы действительно хотите удалить воркбук \"{{name}}\"?", + "title_deploy": "Куда развернуть", + "title_import-exit": "Закрыть окно импорта?", + "toast_collection-already-exists-error": "Коллекция с таким названием уже существует", + "toast_get-gallery-file-error": "Ошибка при развёртывании, обратитесь в поддержку", + "toast_get-json-error": "Ошибка при попытке получить JSON из файла", + "toast_outdated-workbook-info": "Информация о воркбуке устарела. Пожалуйста, обновите страницу.", + "toast_workbook-already-exists-error": "Воркбук с таким названием уже существует" +} diff --git a/src/i18n-keysets/component.color-palette-editor/context.json b/src/i18n-keysets/component.color-palette-editor/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.color-palette-editor/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.color-palette-editor/en.json b/src/i18n-keysets/component.color-palette-editor/en.json new file mode 100644 index 0000000..b1e6d4b --- /dev/null +++ b/src/i18n-keysets/component.color-palette-editor/en.json @@ -0,0 +1,29 @@ +{ + "button_apply": "Save and apply", + "button_cancel": "Cancel", + "button_close": "Cancel", + "button_delete-cancel": "Cancel", + "button_delete-confirm": "Delete", + "button_delete-title": "Delete palette", + "button_save": "Add palette", + "label_add-color": "Add color", + "label_add-color-disabled": "You can't add more than {{count}} colors", + "label_add-palette": "Add palette", + "label_bars-custom-color-error": "Use HEX code to indicate a color", + "label_default-palette": "Default palette", + "label_delete-message": "You are going to delete the palette. Charts that use it will switch to the system palette. Are you sure you want to delete it?", + "label_delete-palette": "Delete", + "label_edit-palette": "Edit", + "label_gradient-palette-description": "They are used with numerical indicators.", + "label_gradient-palette-title": "Gradient color palettes", + "label_header-caption": "Configure color palettes", + "label_palette-description": "They are used for discrete values: names of indicators, measurement values.", + "label_palette-name": "Palette name", + "label_palette-title": "Color palettes", + "toast_create-failed": "Failed to create palette", + "toast_delete-failed": "Failed to delete palette", + "toast_update-default-palette-failed": "Error changing default palette", + "toast_update-default-palette-success": "The default palette was applied to all available charts.", + "toast_update-failed": "Failed to update palette", + "tooltip_disabled-due-rights": "Available only to the administrator" +} diff --git a/src/i18n-keysets/component.color-palette-editor/keyset.json b/src/i18n-keysets/component.color-palette-editor/keyset.json new file mode 100644 index 0000000..7449a10 --- /dev/null +++ b/src/i18n-keysets/component.color-palette-editor/keyset.json @@ -0,0 +1,120 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_delete-cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_delete-confirm": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_delete-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-color-disabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-palette": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-custom-color-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-palette": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-palette": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_edit-palette": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gradient-palette-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gradient-palette-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_header-caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_palette-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_palette-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_palette-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_create-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_delete-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_update-default-palette-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_update-default-palette-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_update-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_disabled-due-rights": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.color-palette-editor/ru.json b/src/i18n-keysets/component.color-palette-editor/ru.json new file mode 100644 index 0000000..12de24f --- /dev/null +++ b/src/i18n-keysets/component.color-palette-editor/ru.json @@ -0,0 +1,29 @@ +{ + "button_apply": "Сохранить и применить", + "button_cancel": "Отменить", + "button_close": "Закрыть", + "button_delete-cancel": "Отмена", + "button_delete-confirm": "Удалить", + "button_delete-title": "Удалить палитру", + "button_save": "Добавить палитру", + "label_add-color": "Добавить цвет", + "label_add-color-disabled": "Невозможно добавить более {{count}} цветов", + "label_add-palette": "Добавить палитру", + "label_bars-custom-color-error": "Для обозначения цвета используйте HEX-код", + "label_default-palette": "Палитра по умолчанию", + "label_delete-message": "Вы собираетесь удалить палитру. Чарты которые её используют переключатся на системную палитру. Вы уверены что хотите удалить?", + "label_delete-palette": "Удалить", + "label_edit-palette": "Редактировать", + "label_gradient-palette-description": "Используются с числовыми показателями.", + "label_gradient-palette-title": "Градиентные цветовые палитры", + "label_header-caption": "Настройка цветовых палитр", + "label_palette-description": "Используются для дискретных значений: названий показателей, значений измерений.", + "label_palette-name": "Название палитры", + "label_palette-title": "Цветовые палитры", + "toast_create-failed": "Не удалось создать палитру", + "toast_delete-failed": "Не удалось удалить палитру", + "toast_update-default-palette-failed": "Ошибка при изменении палитры по умолчанию", + "toast_update-default-palette-success": "Палитра по умолчанию применена ко всем доступным чартам", + "toast_update-failed": "Не удалось обновить палитру", + "tooltip_disabled-due-rights": "Доступно только администратору" +} diff --git a/src/i18n-keysets/component.copy-entry-to-workbook-dialog/context.json b/src/i18n-keysets/component.copy-entry-to-workbook-dialog/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.copy-entry-to-workbook-dialog/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.copy-entry-to-workbook-dialog/en.json b/src/i18n-keysets/component.copy-entry-to-workbook-dialog/en.json new file mode 100644 index 0000000..0f05f06 --- /dev/null +++ b/src/i18n-keysets/component.copy-entry-to-workbook-dialog/en.json @@ -0,0 +1,24 @@ +{ + "action_cancel": "Cancel", + "action_copy": "Copy", + "action_show-all": "Show all", + "label_charts": "Charts", + "label_connections": "Connections", + "label_copy-warning": "The object and all related objects will be copied to the workbook.", + "label_current-entry": "Selected object", + "label_dashboards": "Dashboards", + "label_datasets": "Datasets", + "label_file-connection-warning": "Copying file connections is not currently supported.", + "label_linked-objects": "Linked objects", + "label_no-linked-objects": "Empty", + "label_objects": [ + "{{count}} object", + "{{count}} objects", + "{{count}} objects", + "0 objects", + "", + "" + ], + "label_unknown-error": "An error occured during copying. Please try again later.", + "title": "Copy objects to workbooks" +} diff --git a/src/i18n-keysets/component.copy-entry-to-workbook-dialog/keyset.json b/src/i18n-keysets/component.copy-entry-to-workbook-dialog/keyset.json new file mode 100644 index 0000000..454c49f --- /dev/null +++ b/src/i18n-keysets/component.copy-entry-to-workbook-dialog/keyset.json @@ -0,0 +1,72 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "action_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_show-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_charts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_copy-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_current-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dashboards": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_datasets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_file-connection-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_linked-objects": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-linked-objects": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_objects": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unknown-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.copy-entry-to-workbook-dialog/ru.json b/src/i18n-keysets/component.copy-entry-to-workbook-dialog/ru.json new file mode 100644 index 0000000..6ba166e --- /dev/null +++ b/src/i18n-keysets/component.copy-entry-to-workbook-dialog/ru.json @@ -0,0 +1,24 @@ +{ + "action_cancel": "Отменить", + "action_copy": "Копировать", + "action_show-all": "Показать все", + "label_charts": "Чарты", + "label_connections": "Подключения", + "label_copy-warning": "Объект и все связанные объекты будут скопированы в воркбук.", + "label_current-entry": "Выбранный объект", + "label_dashboards": "Дашборды", + "label_datasets": "Датасеты", + "label_file-connection-warning": "Копирование файловых соединений не поддерживается.", + "label_linked-objects": "Связанные объекты", + "label_no-linked-objects": "Пусто", + "label_objects": [ + "{{count}} объект", + "{{count}} объекта", + "{{count}} объектов", + "0 объектов", + "", + "" + ], + "label_unknown-error": "При копировании возникла ошибка. Пожалуйста, повторите попытку позже.", + "title": "Копирование в воркбук" +} diff --git a/src/i18n-keysets/component.dashkit.view/context.json b/src/i18n-keysets/component.dashkit.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dashkit.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dashkit.view/en.json b/src/i18n-keysets/component.dashkit.view/en.json new file mode 100644 index 0000000..25c84c3 --- /dev/null +++ b/src/i18n-keysets/component.dashkit.view/en.json @@ -0,0 +1,9 @@ +{ + "button_retry": "Repeat", + "label_copy": "Copy", + "label_delete": "Delete", + "label_edit": "Edit", + "label_error": "Error", + "label_render-markdown-error": "Failed to render markdown", + "label_settings": "Settings" +} diff --git a/src/i18n-keysets/component.dashkit.view/keyset.json b/src/i18n-keysets/component.dashkit.view/keyset.json new file mode 100644 index 0000000..be1f2af --- /dev/null +++ b/src/i18n-keysets/component.dashkit.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_render-markdown-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_settings": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dashkit.view/ru.json b/src/i18n-keysets/component.dashkit.view/ru.json new file mode 100644 index 0000000..c02be55 --- /dev/null +++ b/src/i18n-keysets/component.dashkit.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_retry": "Повторить", + "label_copy": "Копировать", + "label_delete": "Удалить", + "label_edit": "Редактировать", + "label_error": "Ошибка", + "label_render-markdown-error": "Ошибка преобразования в markdown", + "label_settings": "Настройки" +} diff --git a/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/context.json b/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/en.json b/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/en.json new file mode 100644 index 0000000..8818b45 --- /dev/null +++ b/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/en.json @@ -0,0 +1,12 @@ +{ + "apply-btn": "Next", + "cancel-btn": "Cancel", + "dialog-title": "Enter link to connection", + "documentation-link-text": "documentation", + "entry-error": "Invalid object", + "info-message": "The connection and the workbook must be located in the same organization, and you must have the rights to bind the connection specified. Read more in the", + "input-label": "Link:", + "link-error": "Invalid link", + "no-access-for-binding-create": "Insufficient rights to bind object", + "required-error": "Add link" +} diff --git a/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/keyset.json b/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/keyset.json new file mode 100644 index 0000000..17ebf1b --- /dev/null +++ b/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/keyset.json @@ -0,0 +1,52 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "apply-btn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "cancel-btn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "documentation-link-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "entry-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "info-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "input-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "link-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "no-access-for-binding-create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "required-error": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/ru.json b/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/ru.json new file mode 100644 index 0000000..ed42944 --- /dev/null +++ b/src/i18n-keysets/component.dialog-add-shared-entry-from-link.view/ru.json @@ -0,0 +1,12 @@ +{ + "apply-btn": "Далее", + "cancel-btn": "Отмена", + "dialog-title": "Укажите ссылку на подключение", + "documentation-link-text": "документации", + "entry-error": "Недопустимый объект", + "info-message": "Подключение и воркбук должны находиться в одной организации, и у вас должны быть права на привязку указанного подключения. Подробнее в", + "input-label": "Ссылка:", + "link-error": "Некорректная ссылка", + "no-access-for-binding-create": "Недостаточно прав для привязки объекта", + "required-error": "Добавьте ссылку" +} diff --git a/src/i18n-keysets/component.dialog-aliases.view/context.json b/src/i18n-keysets/component.dialog-aliases.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-aliases.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-aliases.view/en.json b/src/i18n-keysets/component.dialog-aliases.view/en.json new file mode 100644 index 0000000..5bae9fb --- /dev/null +++ b/src/i18n-keysets/component.dialog-aliases.view/en.json @@ -0,0 +1,14 @@ +{ + "button_add-alias": "Add alias", + "button_apply": "Apply", + "button_cancel": "Cancel", + "label_card": "You cannot create aliases between fields of the same dataset or between fields with the same ID in different datasets.", + "label_card-used-params": "You cannot add an alias to a widget without the set parameters", + "label_collapse-label": "List of all aliases added", + "label_empty": "No aliases configured", + "label_info_1": "Aliases allow you to use matching fields to link widgets based on different datasets, as well as manual selectors.", + "label_info_2": "Aliases' field types must match.", + "label_link-type": "Current link type", + "label_required-add-alias": "Add an alias to change the link type", + "title_add-alias": "Alias setup" +} diff --git a/src/i18n-keysets/component.dialog-aliases.view/keyset.json b/src/i18n-keysets/component.dialog-aliases.view/keyset.json new file mode 100644 index 0000000..9b26e68 --- /dev/null +++ b/src/i18n-keysets/component.dialog-aliases.view/keyset.json @@ -0,0 +1,60 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-alias": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_card": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_card-used-params": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_collapse-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_info_1": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_info_2": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_link-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_required-add-alias": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_add-alias": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-aliases.view/ru.json b/src/i18n-keysets/component.dialog-aliases.view/ru.json new file mode 100644 index 0000000..7d67e03 --- /dev/null +++ b/src/i18n-keysets/component.dialog-aliases.view/ru.json @@ -0,0 +1,14 @@ +{ + "button_add-alias": "Добавить алиас", + "button_apply": "Применить", + "button_cancel": "Отмена", + "label_card": "Нельзя создавать алиасы для полей одного датасета или для полей с одинаковыми идентификаторами из разных датасетов.", + "label_card-used-params": "Невозможно добавить алиас для виджета без заданных параметров.", + "label_collapse-label": "Добавленные алиасы", + "label_empty": "Нет настроенных алиасов", + "label_info_1": "Алиас позволяет связывать виджеты, созданные на основе разных датасетов через соответствие полей, а также настраивать связи с ручным селектором.", + "label_info_2": "Типы полей у алиасов должны совпадать.", + "label_link-type": "Текущий тип связи", + "label_required-add-alias": "Чтобы изменить тип связи, добавьте алиас", + "title_add-alias": "Настройка алиасов" +} diff --git a/src/i18n-keysets/component.dialog-collection-no-create-permission.view/context.json b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-collection-no-create-permission.view/en.json b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/en.json new file mode 100644 index 0000000..539fd36 --- /dev/null +++ b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/en.json @@ -0,0 +1,5 @@ +{ + "button_ok": "Ok", + "label_title": "Insufficient permissions", + "section_message": "To create collections and workbooks, you need the datalens.editor role. Please contact your administrator." +} diff --git a/src/i18n-keysets/component.dialog-collection-no-create-permission.view/keyset.json b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/keyset.json new file mode 100644 index 0000000..d5c531a --- /dev/null +++ b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_ok": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_message": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-collection-no-create-permission.view/ru.json b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/ru.json new file mode 100644 index 0000000..b3b88d7 --- /dev/null +++ b/src/i18n-keysets/component.dialog-collection-no-create-permission.view/ru.json @@ -0,0 +1,5 @@ +{ + "button_ok": "Понятно", + "label_title": "Недостаточно прав", + "section_message": "Для создания коллекций и воркбуков необходима роль datalens.editor, обратитесь к администратору." +} diff --git a/src/i18n-keysets/component.dialog-copy-entry.view/context.json b/src/i18n-keysets/component.dialog-copy-entry.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-copy-entry.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-copy-entry.view/en.json b/src/i18n-keysets/component.dialog-copy-entry.view/en.json new file mode 100644 index 0000000..4eac607 --- /dev/null +++ b/src/i18n-keysets/component.dialog-copy-entry.view/en.json @@ -0,0 +1,15 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel", + "dialog_dash-deprecation-copy-text": "The way the selectors behave on the dashboards you previously created may be different from the way they do on new dashboards. For consistency, you may want to update your dashboard configuration.", + "label_copy": "copy", + "label_default-name": "New dataset", + "label_entry-name-already-exists": "That name is already in use, try another one", + "label_error-on-template-apply": "An error occurred while deploying the template", + "label_placeholder": "Name", + "section_caption": "Where to copy?", + "section_caption-dashboard": "Duplicate dashboard", + "section_caption-dataset": "Duplicate dataset", + "section_caption-widget": "Duplicate chart", + "toast_copy-failed": "Failed to copy" +} diff --git a/src/i18n-keysets/component.dialog-copy-entry.view/keyset.json b/src/i18n-keysets/component.dialog-copy-entry.view/keyset.json new file mode 100644 index 0000000..25ffbdf --- /dev/null +++ b/src/i18n-keysets/component.dialog-copy-entry.view/keyset.json @@ -0,0 +1,64 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog_dash-deprecation-copy-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-name-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-on-template-apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption-dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption-widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_copy-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-copy-entry.view/ru.json b/src/i18n-keysets/component.dialog-copy-entry.view/ru.json new file mode 100644 index 0000000..b3b2bb6 --- /dev/null +++ b/src/i18n-keysets/component.dialog-copy-entry.view/ru.json @@ -0,0 +1,15 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отменить", + "dialog_dash-deprecation-copy-text": "Поведение селекторов на созданных ранее дашбордах может отличаться от поведения на новых дашбордах. Для консистентности рекомендуем обновить конфигурацию вашего дашборда.", + "label_copy": "копия", + "label_default-name": "Новый датасет", + "label_entry-name-already-exists": "Название уже используется, придумайте другое", + "label_error-on-template-apply": "При разворачивании темплейта произошла ошибка", + "label_placeholder": "Введите имя", + "section_caption": "Куда скопировать?", + "section_caption-dashboard": "Дублировать дашборд", + "section_caption-dataset": "Дублировать датасет", + "section_caption-widget": "Дублировать чарт", + "toast_copy-failed": "Не удалось скопировать" +} diff --git a/src/i18n-keysets/component.dialog-copy-workbook.view/context.json b/src/i18n-keysets/component.dialog-copy-workbook.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-copy-workbook.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-copy-workbook.view/en.json b/src/i18n-keysets/component.dialog-copy-workbook.view/en.json new file mode 100644 index 0000000..9e88ebc --- /dev/null +++ b/src/i18n-keysets/component.dialog-copy-workbook.view/en.json @@ -0,0 +1,5 @@ +{ + "button_cancel": "Cancel", + "button_done": "Done", + "section_caption": "Copy workbook" +} diff --git a/src/i18n-keysets/component.dialog-copy-workbook.view/keyset.json b/src/i18n-keysets/component.dialog-copy-workbook.view/keyset.json new file mode 100644 index 0000000..40a6e7e --- /dev/null +++ b/src/i18n-keysets/component.dialog-copy-workbook.view/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_done": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-copy-workbook.view/ru.json b/src/i18n-keysets/component.dialog-copy-workbook.view/ru.json new file mode 100644 index 0000000..b6941d4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-copy-workbook.view/ru.json @@ -0,0 +1,5 @@ +{ + "button_cancel": "Отменить", + "button_done": "Готово", + "section_caption": "Копировать воркбук" +} diff --git a/src/i18n-keysets/component.dialog-create-dashboard.view/context.json b/src/i18n-keysets/component.dialog-create-dashboard.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-dashboard.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-create-dashboard.view/en.json b/src/i18n-keysets/component.dialog-create-dashboard.view/en.json new file mode 100644 index 0000000..a24a0a9 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-dashboard.view/en.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Create", + "button_cancel": "Cancel", + "label_default-name": "New dashboard", + "label_entry-name-already-exists": "That name is already in use, try another one", + "label_placeholder": "Name", + "section_caption": "Create dashboard", + "toast_create-dashboard-failed": "Failed to create dashboard" +} diff --git a/src/i18n-keysets/component.dialog-create-dashboard.view/keyset.json b/src/i18n-keysets/component.dialog-create-dashboard.view/keyset.json new file mode 100644 index 0000000..9db35bb --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-dashboard.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-name-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_create-dashboard-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-create-dashboard.view/ru.json b/src/i18n-keysets/component.dialog-create-dashboard.view/ru.json new file mode 100644 index 0000000..a2f8e61 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-dashboard.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Создать", + "button_cancel": "Отменить", + "label_default-name": "Новый дашборд", + "label_entry-name-already-exists": "Название уже используется, придумайте другое", + "label_placeholder": "Название", + "section_caption": "Создать дашборд", + "toast_create-dashboard-failed": "Не удалось создать дашборд" +} diff --git a/src/i18n-keysets/component.dialog-create-editor-chart.view/context.json b/src/i18n-keysets/component.dialog-create-editor-chart.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-editor-chart.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-create-editor-chart.view/en.json b/src/i18n-keysets/component.dialog-create-editor-chart.view/en.json new file mode 100644 index 0000000..5edc119 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-editor-chart.view/en.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Save", + "button_cancel": "Cancel", + "label_default-name": "New chart", + "label_entry-name-already-exists": "That name is already in use, try another one", + "label_placeholder": "New chart", + "section_caption": "Save chart", + "toast_create-editor-chart-failed": "Failed to save chart" +} diff --git a/src/i18n-keysets/component.dialog-create-editor-chart.view/keyset.json b/src/i18n-keysets/component.dialog-create-editor-chart.view/keyset.json new file mode 100644 index 0000000..6a94afd --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-editor-chart.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-name-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_create-editor-chart-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-create-editor-chart.view/ru.json b/src/i18n-keysets/component.dialog-create-editor-chart.view/ru.json new file mode 100644 index 0000000..ccbb9be --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-editor-chart.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Сохранить", + "button_cancel": "Отменить", + "label_default-name": "Новый чарт", + "label_entry-name-already-exists": "Название уже используется, придумайте другое", + "label_placeholder": "Новый чарт", + "section_caption": "Сохранить чарт", + "toast_create-editor-chart-failed": "Не удалось сохранить чарт" +} diff --git a/src/i18n-keysets/component.dialog-create-folder.view/context.json b/src/i18n-keysets/component.dialog-create-folder.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-folder.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-create-folder.view/en.json b/src/i18n-keysets/component.dialog-create-folder.view/en.json new file mode 100644 index 0000000..7441199 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-folder.view/en.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Done", + "button_cancel": "Cancel", + "label_default-name": "New folder", + "label_folder-already-exists": "A folder with this name already exists, try another one", + "label_placeholder": "Folder name", + "section_caption": "Create folder", + "toast_create-folder-failed": "Failed to create folder" +} diff --git a/src/i18n-keysets/component.dialog-create-folder.view/keyset.json b/src/i18n-keysets/component.dialog-create-folder.view/keyset.json new file mode 100644 index 0000000..aa835de --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-folder.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_folder-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_create-folder-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-create-folder.view/ru.json b/src/i18n-keysets/component.dialog-create-folder.view/ru.json new file mode 100644 index 0000000..44ff947 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-folder.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Готово", + "button_cancel": "Отменить", + "label_default-name": "Новая папка", + "label_folder-already-exists": "Папка с таким названием уже есть, придумайте другое", + "label_placeholder": "Введите имя папки", + "section_caption": "Создать папку", + "toast_create-folder-failed": "Не удалось создать папку" +} diff --git a/src/i18n-keysets/component.dialog-create-workbook-entry.view/context.json b/src/i18n-keysets/component.dialog-create-workbook-entry.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-workbook-entry.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-create-workbook-entry.view/en.json b/src/i18n-keysets/component.dialog-create-workbook-entry.view/en.json new file mode 100644 index 0000000..6b9fa09 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-workbook-entry.view/en.json @@ -0,0 +1,3 @@ +{ + "label_name": "Name" +} diff --git a/src/i18n-keysets/component.dialog-create-workbook-entry.view/keyset.json b/src/i18n-keysets/component.dialog-create-workbook-entry.view/keyset.json new file mode 100644 index 0000000..65dd282 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-workbook-entry.view/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_name": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-create-workbook-entry.view/ru.json b/src/i18n-keysets/component.dialog-create-workbook-entry.view/ru.json new file mode 100644 index 0000000..54ad885 --- /dev/null +++ b/src/i18n-keysets/component.dialog-create-workbook-entry.view/ru.json @@ -0,0 +1,3 @@ +{ + "label_name": "Название" +} diff --git a/src/i18n-keysets/component.dialog-dash-meta/context.json b/src/i18n-keysets/component.dialog-dash-meta/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-dash-meta/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-dash-meta/en.json b/src/i18n-keysets/component.dialog-dash-meta/en.json new file mode 100644 index 0000000..9f407ea --- /dev/null +++ b/src/i18n-keysets/component.dialog-dash-meta/en.json @@ -0,0 +1,8 @@ +{ + "button_cancel": "Cancel", + "button_clear": "Clear", + "button_contact-service": "Contact support", + "button_edit": "Edit", + "button_save": "Save", + "context_fill-text": "Enter text" +} diff --git a/src/i18n-keysets/component.dialog-dash-meta/keyset.json b/src/i18n-keysets/component.dialog-dash-meta/keyset.json new file mode 100644 index 0000000..69a2e72 --- /dev/null +++ b/src/i18n-keysets/component.dialog-dash-meta/keyset.json @@ -0,0 +1,36 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_clear": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_contact-service": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_fill-text": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-dash-meta/ru.json b/src/i18n-keysets/component.dialog-dash-meta/ru.json new file mode 100644 index 0000000..ce30df4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-dash-meta/ru.json @@ -0,0 +1,8 @@ +{ + "button_cancel": "Отменить", + "button_clear": "Очистить", + "button_contact-service": "Обратиться в поддержку", + "button_edit": "Редактировать", + "button_save": "Сохранить", + "context_fill-text": "Введите текст" +} diff --git a/src/i18n-keysets/component.dialog-delete-entry.view/context.json b/src/i18n-keysets/component.dialog-delete-entry.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-delete-entry.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-delete-entry.view/en.json b/src/i18n-keysets/component.dialog-delete-entry.view/en.json new file mode 100644 index 0000000..e089f88 --- /dev/null +++ b/src/i18n-keysets/component.dialog-delete-entry.view/en.json @@ -0,0 +1,7 @@ +{ + "button_cancel": "Cancel", + "button_delete": "Delete", + "label_delete": "Delete", + "section_caption": "Are you sure?", + "toast_delete-failed": "Failed to delete" +} diff --git a/src/i18n-keysets/component.dialog-delete-entry.view/keyset.json b/src/i18n-keysets/component.dialog-delete-entry.view/keyset.json new file mode 100644 index 0000000..11867d1 --- /dev/null +++ b/src/i18n-keysets/component.dialog-delete-entry.view/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_delete-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-delete-entry.view/ru.json b/src/i18n-keysets/component.dialog-delete-entry.view/ru.json new file mode 100644 index 0000000..d411690 --- /dev/null +++ b/src/i18n-keysets/component.dialog-delete-entry.view/ru.json @@ -0,0 +1,7 @@ +{ + "button_cancel": "Отменить", + "button_delete": "Удалить", + "label_delete": "Удалить", + "section_caption": "Вы уверены?", + "toast_delete-failed": "Не удалось удалить" +} diff --git a/src/i18n-keysets/component.dialog-edit-favorites-alias.view/context.json b/src/i18n-keysets/component.dialog-edit-favorites-alias.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-edit-favorites-alias.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-edit-favorites-alias.view/en.json b/src/i18n-keysets/component.dialog-edit-favorites-alias.view/en.json new file mode 100644 index 0000000..18f476b --- /dev/null +++ b/src/i18n-keysets/component.dialog-edit-favorites-alias.view/en.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Done", + "button_cancel": "Cancel", + "button_delete": "Delete label", + "caption_add": "Add label", + "caption_rename": "Rename label", + "label_placeholder": "New name", + "toast_rename-favorites-alias-failed": "Failed to rename label" +} diff --git a/src/i18n-keysets/component.dialog-edit-favorites-alias.view/keyset.json b/src/i18n-keysets/component.dialog-edit-favorites-alias.view/keyset.json new file mode 100644 index 0000000..7659493 --- /dev/null +++ b/src/i18n-keysets/component.dialog-edit-favorites-alias.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "caption_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "caption_rename": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_rename-favorites-alias-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-edit-favorites-alias.view/ru.json b/src/i18n-keysets/component.dialog-edit-favorites-alias.view/ru.json new file mode 100644 index 0000000..898ebb2 --- /dev/null +++ b/src/i18n-keysets/component.dialog-edit-favorites-alias.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Готово", + "button_cancel": "Отменить", + "button_delete": "Удалить лейбл", + "caption_add": "Добавить лейбл", + "caption_rename": "Изменить лейбл", + "label_placeholder": "Введите новое имя", + "toast_rename-favorites-alias-failed": "Не удалось переименовать лейбл" +} diff --git a/src/i18n-keysets/component.dialog-edit-warning.view/context.json b/src/i18n-keysets/component.dialog-edit-warning.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-edit-warning.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-edit-warning.view/en.json b/src/i18n-keysets/component.dialog-edit-warning.view/en.json new file mode 100644 index 0000000..5cef181 --- /dev/null +++ b/src/i18n-keysets/component.dialog-edit-warning.view/en.json @@ -0,0 +1,6 @@ +{ + "button_edit": "Edit current version", + "button_show-in-history": "Show in history", + "label_dash-warning-text": "The change history contains not actual versions of the dashboard {{name}} saved later than the current version.", + "label_dash-warning-title": "Later versions are available for the dashboard" +} diff --git a/src/i18n-keysets/component.dialog-edit-warning.view/keyset.json b/src/i18n-keysets/component.dialog-edit-warning.view/keyset.json new file mode 100644 index 0000000..ea37bf4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-edit-warning.view/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_show-in-history": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dash-warning-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dash-warning-title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-edit-warning.view/ru.json b/src/i18n-keysets/component.dialog-edit-warning.view/ru.json new file mode 100644 index 0000000..4ffa7ac --- /dev/null +++ b/src/i18n-keysets/component.dialog-edit-warning.view/ru.json @@ -0,0 +1,6 @@ +{ + "button_edit": "Редактировать текущую версию", + "button_show-in-history": "Показать в истории", + "label_dash-warning-text": "История изменений содержит неактуальные версии дашборда {{name}}, которые были сохранены позже текущей версии.", + "label_dash-warning-title": "Доступны более поздние версии дашбода" +} diff --git a/src/i18n-keysets/component.dialog-entry-description/context.json b/src/i18n-keysets/component.dialog-entry-description/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-entry-description/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-entry-description/en.json b/src/i18n-keysets/component.dialog-entry-description/en.json new file mode 100644 index 0000000..9f407ea --- /dev/null +++ b/src/i18n-keysets/component.dialog-entry-description/en.json @@ -0,0 +1,8 @@ +{ + "button_cancel": "Cancel", + "button_clear": "Clear", + "button_contact-service": "Contact support", + "button_edit": "Edit", + "button_save": "Save", + "context_fill-text": "Enter text" +} diff --git a/src/i18n-keysets/component.dialog-entry-description/keyset.json b/src/i18n-keysets/component.dialog-entry-description/keyset.json new file mode 100644 index 0000000..69a2e72 --- /dev/null +++ b/src/i18n-keysets/component.dialog-entry-description/keyset.json @@ -0,0 +1,36 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_clear": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_contact-service": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_fill-text": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-entry-description/ru.json b/src/i18n-keysets/component.dialog-entry-description/ru.json new file mode 100644 index 0000000..ce30df4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-entry-description/ru.json @@ -0,0 +1,8 @@ +{ + "button_cancel": "Отменить", + "button_clear": "Очистить", + "button_contact-service": "Обратиться в поддержку", + "button_edit": "Редактировать", + "button_save": "Сохранить", + "context_fill-text": "Введите текст" +} diff --git a/src/i18n-keysets/component.dialog-move-entry.view/context.json b/src/i18n-keysets/component.dialog-move-entry.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-move-entry.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-move-entry.view/en.json b/src/i18n-keysets/component.dialog-move-entry.view/en.json new file mode 100644 index 0000000..fbee88f --- /dev/null +++ b/src/i18n-keysets/component.dialog-move-entry.view/en.json @@ -0,0 +1,7 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel", + "label_entry-name-already-exists": "That name is already in use, try another one", + "section_caption": "Where to move?", + "toast_move-failed": "Failed to move" +} diff --git a/src/i18n-keysets/component.dialog-move-entry.view/keyset.json b/src/i18n-keysets/component.dialog-move-entry.view/keyset.json new file mode 100644 index 0000000..b05d42b --- /dev/null +++ b/src/i18n-keysets/component.dialog-move-entry.view/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-name-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_move-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-move-entry.view/ru.json b/src/i18n-keysets/component.dialog-move-entry.view/ru.json new file mode 100644 index 0000000..4c416fc --- /dev/null +++ b/src/i18n-keysets/component.dialog-move-entry.view/ru.json @@ -0,0 +1,7 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отменить", + "label_entry-name-already-exists": "Название уже используется, придумайте другое", + "section_caption": "Куда переместить?", + "toast_move-failed": "Не удалось переместить" +} diff --git a/src/i18n-keysets/component.dialog-parameter/context.json b/src/i18n-keysets/component.dialog-parameter/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-parameter/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-parameter/en.json b/src/i18n-keysets/component.dialog-parameter/en.json new file mode 100644 index 0000000..49062f4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-parameter/en.json @@ -0,0 +1,26 @@ +{ + "button_cancel": "Cancel", + "button_create": "Add", + "button_edit": "Save", + "button_reset": "Initial value", + "label_default-value-float-error": "Value must be a float", + "label_default-value-int-error": "Value must be an integer", + "label_default-value-uint-error": "Value must be an unsigned integer", + "label_required-field-error": "This is a required field", + "label_select-validation-type": "Value validation", + "label_select-validation-type-default": "Default", + "label_select-validation-type-default-hint": "Standard check for invalid characters", + "label_select-validation-type-regex": "Regular expression", + "label_switch-template": "Allow use in source configuration", + "label_switch-template-warn": "The parameter will be created, but it cannot be used when configuring the source", + "label_textarea-regex-placeholder": "Regular Python expression", + "parameter_default-value": "Default value", + "parameter_false": "False", + "parameter_name": "Name", + "parameter_name-error": "Incorrect parameter name", + "parameter_name-note": "Parameter name restrictions: \n* Lowercase Latin letters, digits, hyphens, and underscores are acceptable. The first character can't be an underscore: `_name`. \n* Parameter name length should not exceed 36 characters. \n* Reserved parameter names that cannot be used: `tab`, `state`, `mode`, `focus`, `grid`, `tz`, `from`, `to`.", + "parameter_true": "True", + "parameter_type": "Type", + "title_create-parameter": "Add parameter", + "title_edit-parameter": "Edit parameter" +} diff --git a/src/i18n-keysets/component.dialog-parameter/keyset.json b/src/i18n-keysets/component.dialog-parameter/keyset.json new file mode 100644 index 0000000..378c750 --- /dev/null +++ b/src/i18n-keysets/component.dialog-parameter/keyset.json @@ -0,0 +1,108 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_reset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-value-float-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-value-int-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-value-uint-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_required-field-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-validation-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-validation-type-default": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-validation-type-default-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-validation-type-regex": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_switch-template": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_switch-template-warn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_textarea-regex-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "parameter_default-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "parameter_false": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "parameter_name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "parameter_name-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "parameter_name-note": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "parameter_true": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "parameter_type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_create-parameter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_edit-parameter": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-parameter/ru.json b/src/i18n-keysets/component.dialog-parameter/ru.json new file mode 100644 index 0000000..a5dce52 --- /dev/null +++ b/src/i18n-keysets/component.dialog-parameter/ru.json @@ -0,0 +1,26 @@ +{ + "button_cancel": "Отмена", + "button_create": "Добавить", + "button_edit": "Сохранить", + "button_reset": "Исходное значение", + "label_default-value-float-error": "Значение должно быть дробным числом", + "label_default-value-int-error": "Значение должно быть целым числом", + "label_default-value-uint-error": "Значение должно быть целым числом без знака", + "label_required-field-error": "Обязательное поле", + "label_select-validation-type": "Валидация значений", + "label_select-validation-type-default": "По умолчанию", + "label_select-validation-type-default-hint": "Стандартная проверка недопустимых символов", + "label_select-validation-type-regex": "Регулярное выражение", + "label_switch-template": "Разрешить использовать в настройке источника", + "label_switch-template-warn": "Параметр будет создан, но его нельзя будет использовать при настройке источника", + "label_textarea-regex-placeholder": "Регулярное выражение Python", + "parameter_default-value": "Значение по умолчанию", + "parameter_false": "False", + "parameter_name": "Название", + "parameter_name-error": "Некорректное название параметра", + "parameter_name-note": "Ограничения на имя параметра: \n* Допускаются только латинские буквы (прописные и строчные), цифры, знак тире `-` и нижнее подчеркивание `_`. \n* Длина имени не должна превышать 36 символов. \n* Не допускается наименование параметра, начинающегося с символа нижнего подчеркивания: `_name`. \n* Зарезервированные имена параметров, которые нельзя использовать: `tab`, `state`, `mode`, `focus`, `grid`, `tz`, `from`, `to`.", + "parameter_true": "True", + "parameter_type": "Тип", + "title_create-parameter": "Добавление параметра", + "title_edit-parameter": "Редактирование параметра" +} diff --git a/src/i18n-keysets/component.dialog-related-entities.view/context.json b/src/i18n-keysets/component.dialog-related-entities.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-related-entities.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-related-entities.view/en.json b/src/i18n-keysets/component.dialog-related-entities.view/en.json new file mode 100644 index 0000000..efd88b5 --- /dev/null +++ b/src/i18n-keysets/component.dialog-related-entities.view/en.json @@ -0,0 +1,15 @@ +{ + "label_button-retry": "Retry", + "label_current-object": "Current object", + "label_entities-count": "Number of related objects:", + "label_no-relatives": "There are no related objects", + "label_request-error": "Data request error", + "label_scope-chart": "Charts", + "label_scope-connection": "Connections", + "label_scope-dash": "Dashboards", + "label_scope-dataset": "Datasets", + "label_scope-other": "Other", + "label_title": "Related objects", + "value_includes": "Contains objects", + "value_where-contained": "Used in objects" +} diff --git a/src/i18n-keysets/component.dialog-related-entities.view/keyset.json b/src/i18n-keysets/component.dialog-related-entities.view/keyset.json new file mode 100644 index 0000000..d864b8e --- /dev/null +++ b/src/i18n-keysets/component.dialog-related-entities.view/keyset.json @@ -0,0 +1,64 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_button-retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_current-object": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entities-count": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-relatives": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_request-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scope-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scope-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scope-dash": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scope-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scope-other": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_includes": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_where-contained": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-related-entities.view/ru.json b/src/i18n-keysets/component.dialog-related-entities.view/ru.json new file mode 100644 index 0000000..b916570 --- /dev/null +++ b/src/i18n-keysets/component.dialog-related-entities.view/ru.json @@ -0,0 +1,15 @@ +{ + "label_button-retry": "Повторить", + "label_current-object": "Текущий объект", + "label_entities-count": "Количество связанных объектов:", + "label_no-relatives": "Связанные объекты отсутствуют", + "label_request-error": "При запросе данных произошла ошибка", + "label_scope-chart": "Чарты", + "label_scope-connection": "Подключения", + "label_scope-dash": "Дашборды", + "label_scope-dataset": "Датасеты", + "label_scope-other": "Другое", + "label_title": "Связанные объекты", + "value_includes": "Содержит объекты", + "value_where-contained": "Используется в объектах" +} diff --git a/src/i18n-keysets/component.dialog-relations.view/context.json b/src/i18n-keysets/component.dialog-relations.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-relations.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-relations.view/en.json b/src/i18n-keysets/component.dialog-relations.view/en.json new file mode 100644 index 0000000..0a5dd4a --- /dev/null +++ b/src/i18n-keysets/component.dialog-relations.view/en.json @@ -0,0 +1,56 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel", + "button_clear-alias": "Delete invalid aliases?", + "button_delete-alias": "Delete alias", + "button_disconnect": "Disable", + "button_incoming": "Ingoing", + "button_not-linked": "Not linked", + "button_outcoming": "Outgoing", + "button_show-related-widgets": "Show linked widgets", + "label_add-alias": "Alias setup", + "label_alias-already-exists": "This alias already exists.", + "label_alias-detail-1": "Linked widgets", + "label_alias-detail-2": "with parameter", + "label_alias-same-dataset": "You cannot create an alias with a connection between fields of the same dataset.", + "label_alias-same-dataset-field": "Fields with the same IDs are already connected, there is no need to create an alias.", + "label_all": "all", + "label_both": "Both", + "label_by-alias": "by alias", + "label_by-aliases": "by aliases", + "label_by-dataset-fields": "by dataset fields", + "label_by-field": "by field", + "label_by-fields": "by fields", + "label_by-parameter": "by parameter", + "label_by-parameters": "by parameters", + "label_charts": "charts", + "label_choose-field-for-alias": "Choose field for alias", + "label_choose-fields-from-list": "Choose fields from list", + "label_dataset": "dataset", + "label_datasets": "of the dataset", + "label_empty-state": "Select widget", + "label_ignore": "Not linked", + "label_input": "Input", + "label_invalid-alias": "Field in the alias created doesn't belong to any dashboard widget parameters. Delete it to allow links to work correctly.", + "label_invalid-alias-text": "The aliases don't belong to any dashboard widget parameters. Delete them to allow links to work correctly.", + "label_invalid-alias-title": "Alias error", + "label_need-default-editor-chart": "To add an alias with an Editor chart, set parameters in the chart settings on the dashboard", + "label_need-default-external-control": "To add an alias with an external selector, add default parameters in its settings", + "label_no-connections": "No elements to link", + "label_no-widget-info": "Unable to load widget data", + "label_output": "Output", + "label_placeholder-search": "Search widget list", + "label_relation-type": "Link type", + "label_selectors": "selectors", + "label_unknown": "Unspecified", + "label_widget": "Widget", + "title-error": "Unable to display widget title", + "title_empty-selected-widgets": "No widget selected", + "title_links": "Links", + "value_link-affect": "affects", + "value_link-both": "Widgets affect each other", + "value_link-ignore": "Widgets don't affect each other", + "value_link-unknown": "Insufficient data for link detection", + "value_quote-left": "\"", + "value_quote-right": "\"" +} diff --git a/src/i18n-keysets/component.dialog-relations.view/keyset.json b/src/i18n-keysets/component.dialog-relations.view/keyset.json new file mode 100644 index 0000000..c7c0378 --- /dev/null +++ b/src/i18n-keysets/component.dialog-relations.view/keyset.json @@ -0,0 +1,228 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_clear-alias": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_delete-alias": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_disconnect": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_incoming": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_not-linked": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_outcoming": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_show-related-widgets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-alias": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alias-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alias-detail-1": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alias-detail-2": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alias-same-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alias-same-dataset-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_both": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_by-alias": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_by-aliases": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_by-dataset-fields": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_by-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_by-fields": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_by-parameter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_by-parameters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_charts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_choose-field-for-alias": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_choose-fields-from-list": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_datasets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-state": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_ignore": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_input": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-alias": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-alias-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-alias-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_need-default-editor-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_need-default-external-control": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-widget-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_output": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-search": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_relation-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_selectors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unknown": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_empty-selected-widgets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_links": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_link-affect": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_link-both": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_link-ignore": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_link-unknown": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_quote-left": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_quote-right": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-relations.view/ru.json b/src/i18n-keysets/component.dialog-relations.view/ru.json new file mode 100644 index 0000000..bda69bd --- /dev/null +++ b/src/i18n-keysets/component.dialog-relations.view/ru.json @@ -0,0 +1,56 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отмена", + "button_clear-alias": "Удалить алиасы с ошибками?", + "button_delete-alias": "Удалить алиас", + "button_disconnect": "Отключить", + "button_incoming": "Входящие", + "button_not-linked": "Не связаны", + "button_outcoming": "Исходящие", + "button_show-related-widgets": "Отобразить связанные виджеты", + "label_add-alias": "Настройка алиасов", + "label_alias-already-exists": "Такой алиас уже есть в списке.", + "label_alias-detail-1": "Связанные виджеты", + "label_alias-detail-2": "c параметром", + "label_alias-same-dataset": "Запрещено создание алиаса со связью между полями одного датасета.", + "label_alias-same-dataset-field": "Поля с одинаковыми идентификаторами уже связаны, создание алиаса избыточно.", + "label_all": "все", + "label_both": "Двусторонняя", + "label_by-alias": "по алиасу", + "label_by-aliases": "по алиасам", + "label_by-dataset-fields": "по полям датасета", + "label_by-field": "по полю", + "label_by-fields": "по полям", + "label_by-parameter": "по параметру", + "label_by-parameters": "по параметрам", + "label_charts": "чарты", + "label_choose-field-for-alias": "Выберите поле для алиаса", + "label_choose-fields-from-list": "Выберите поля из списка", + "label_dataset": "датасет", + "label_datasets": "датасета", + "label_empty-state": "Выберите виджет", + "label_ignore": "Не связаны", + "label_input": "Входящая", + "label_invalid-alias": "В созданном алиасе содержится поле, которое не является параметром ни одного виджета на дашборде. Для корректной работы связей удалите из алиаса это поле.", + "label_invalid-alias-text": "Некоторые алиасы содержат поля, которые не являются параметрами ни одного виджета на дашборде. Для корректной работы связей удалите алиасы с ошибками.", + "label_invalid-alias-title": "Ошибка в алиасах", + "label_need-default-editor-chart": "Чтобы добавить алиас с чартом Editor, в настройках этого чарта на дашборде задайте параметры", + "label_need-default-external-control": "Чтобы добавить алиас с внешним селектором, необходимо в настройках селектора задать параметры по умолчанию", + "label_no-connections": "Нет элементов для связей", + "label_no-widget-info": "Не удалось загрузить информацию о виджете", + "label_output": "Исходящая", + "label_placeholder-search": "Поиск по списку виджетов", + "label_relation-type": "Тип связи", + "label_selectors": "селекторы", + "label_unknown": "Не определена", + "label_widget": "Виджет", + "title-error": "Невозможно отобразить название виджета", + "title_empty-selected-widgets": "Виджет не выбран", + "title_links": "Связи", + "value_link-affect": "влияет на", + "value_link-both": "Виджеты влияют друг на друга", + "value_link-ignore": "Виджеты не влияют друг на друга", + "value_link-unknown": "Недостаточно данных для определения связи", + "value_quote-left": "«", + "value_quote-right": "»" +} diff --git a/src/i18n-keysets/component.dialog-rename-entry.view/context.json b/src/i18n-keysets/component.dialog-rename-entry.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-rename-entry.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-rename-entry.view/en.json b/src/i18n-keysets/component.dialog-rename-entry.view/en.json new file mode 100644 index 0000000..0a93fa7 --- /dev/null +++ b/src/i18n-keysets/component.dialog-rename-entry.view/en.json @@ -0,0 +1,8 @@ +{ + "button_apply": "Done", + "button_cancel": "Cancel", + "label_entry-name-already-exists": "That name is already in use, try another one", + "label_placeholder": "New name", + "section_caption": "Rename", + "toast_rename-failed": "Failed to rename" +} diff --git a/src/i18n-keysets/component.dialog-rename-entry.view/keyset.json b/src/i18n-keysets/component.dialog-rename-entry.view/keyset.json new file mode 100644 index 0000000..9354e62 --- /dev/null +++ b/src/i18n-keysets/component.dialog-rename-entry.view/keyset.json @@ -0,0 +1,36 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-name-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_rename-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-rename-entry.view/ru.json b/src/i18n-keysets/component.dialog-rename-entry.view/ru.json new file mode 100644 index 0000000..ef5a633 --- /dev/null +++ b/src/i18n-keysets/component.dialog-rename-entry.view/ru.json @@ -0,0 +1,8 @@ +{ + "button_apply": "Готово", + "button_cancel": "Отменить", + "label_entry-name-already-exists": "Название уже используется, придумайте другое", + "label_placeholder": "Введите новое имя", + "section_caption": "Переименовать", + "toast_rename-failed": "Не удалось переименовать" +} diff --git a/src/i18n-keysets/component.dialog-revisions-diff.view/context.json b/src/i18n-keysets/component.dialog-revisions-diff.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-revisions-diff.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-revisions-diff.view/en.json b/src/i18n-keysets/component.dialog-revisions-diff.view/en.json new file mode 100644 index 0000000..c3563be --- /dev/null +++ b/src/i18n-keysets/component.dialog-revisions-diff.view/en.json @@ -0,0 +1,11 @@ +{ + "button_open-revision-in-new-window": "Open in a new window", + "field_split-diff": "Divide", + "label_author": "Author", + "label_compare-with": "compare with", + "label_status": "Status", + "label_without-diff": "No changes", + "toast_fetch-initial-diff-failed": "Failed to load diff changes", + "toast_get-revisions-failed": "Failed to load change history", + "toast_get-script-failed": "Failed to load script" +} diff --git a/src/i18n-keysets/component.dialog-revisions-diff.view/keyset.json b/src/i18n-keysets/component.dialog-revisions-diff.view/keyset.json new file mode 100644 index 0000000..3448848 --- /dev/null +++ b/src/i18n-keysets/component.dialog-revisions-diff.view/keyset.json @@ -0,0 +1,48 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_open-revision-in-new-window": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_split-diff": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_author": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_compare-with": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_without-diff": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_fetch-initial-diff-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_get-revisions-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_get-script-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-revisions-diff.view/ru.json b/src/i18n-keysets/component.dialog-revisions-diff.view/ru.json new file mode 100644 index 0000000..fcbb861 --- /dev/null +++ b/src/i18n-keysets/component.dialog-revisions-diff.view/ru.json @@ -0,0 +1,11 @@ +{ + "button_open-revision-in-new-window": "Открыть в новом окне", + "field_split-diff": "Разделить", + "label_author": "Автор", + "label_compare-with": "сравнить с", + "label_status": "Статус", + "label_without-diff": "Нет изменений", + "toast_fetch-initial-diff-failed": "Не удалось загрузить различия версий", + "toast_get-revisions-failed": "Не удалось загрузить историю изменений", + "toast_get-script-failed": "Не удалось загрузить скрипт" +} diff --git a/src/i18n-keysets/component.dialog-revisions.view/context.json b/src/i18n-keysets/component.dialog-revisions.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-revisions.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-revisions.view/en.json b/src/i18n-keysets/component.dialog-revisions.view/en.json new file mode 100644 index 0000000..54180a1 --- /dev/null +++ b/src/i18n-keysets/component.dialog-revisions.view/en.json @@ -0,0 +1,22 @@ +{ + "button_actions": "Actions", + "button_show-revisions-diff": "View differences between versions", + "label_current-changes": "Current changes", + "label_no-changes": "No changes yet", + "label_progress-open": "Opening", + "label_progress-publishing": "Publishing", + "label_status-current": "Current", + "label_status-draft": "Draft", + "label_status-published": "Actual", + "label_status-tooltip-current": "Current", + "label_status-tooltip-disable": "Not available for view", + "label_status-tooltip-draft": "Draft", + "label_status-tooltip-not-actual": "Not actual", + "label_status-tooltip-published": "Actual", + "label_tooltip-published": "Published", + "section_title": "Change history", + "toast_get-revisions-failed": "Failed to load change history", + "toast_warning-has-no-saved-changes": "Any unsaved changes will be lost.", + "value_open-revision": "Open", + "value_publish-revision": "Make actual" +} diff --git a/src/i18n-keysets/component.dialog-revisions.view/keyset.json b/src/i18n-keysets/component.dialog-revisions.view/keyset.json new file mode 100644 index 0000000..115f2de --- /dev/null +++ b/src/i18n-keysets/component.dialog-revisions.view/keyset.json @@ -0,0 +1,92 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_actions": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_show-revisions-diff": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_current-changes": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-changes": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_progress-open": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_progress-publishing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status-current": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status-draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status-published": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status-tooltip-current": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status-tooltip-disable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status-tooltip-draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status-tooltip-not-actual": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_status-tooltip-published": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tooltip-published": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_get-revisions-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_warning-has-no-saved-changes": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_open-revision": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_publish-revision": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-revisions.view/ru.json b/src/i18n-keysets/component.dialog-revisions.view/ru.json new file mode 100644 index 0000000..545c59b --- /dev/null +++ b/src/i18n-keysets/component.dialog-revisions.view/ru.json @@ -0,0 +1,22 @@ +{ + "button_actions": "Actions", + "button_show-revisions-diff": "Посмотреть различия между версиями", + "label_current-changes": "Текущие изменения", + "label_no-changes": "Изменения отсутствуют", + "label_progress-open": "Открытие", + "label_progress-publishing": "Публикация", + "label_status-current": "Текущая", + "label_status-draft": "Черновик", + "label_status-published": "Актуальная", + "label_status-tooltip-current": "Текущая", + "label_status-tooltip-disable": "Недоступно для просмотра", + "label_status-tooltip-draft": "Черновик", + "label_status-tooltip-not-actual": "Неактуальная", + "label_status-tooltip-published": "Актуальная", + "label_tooltip-published": "Был опубликован", + "section_title": "История изменений", + "toast_get-revisions-failed": "Не удалось загрузить историю изменений", + "toast_warning-has-no-saved-changes": "Все не сохраненные изменения будут утеряны.", + "value_open-revision": "Открыть", + "value_publish-revision": "Сделать актуальной" +} diff --git a/src/i18n-keysets/component.dialog-save-as-new-entry.view/context.json b/src/i18n-keysets/component.dialog-save-as-new-entry.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-save-as-new-entry.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-save-as-new-entry.view/en.json b/src/i18n-keysets/component.dialog-save-as-new-entry.view/en.json new file mode 100644 index 0000000..4912e28 --- /dev/null +++ b/src/i18n-keysets/component.dialog-save-as-new-entry.view/en.json @@ -0,0 +1,10 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel", + "label_copy": "copy", + "label_default-name": "New", + "label_entry-name-already-exists": "That name is already in use, try another one", + "label_placeholder": "Name", + "section_caption": "Save as new", + "toast_save-as-new-failed": "Failed to save as new" +} diff --git a/src/i18n-keysets/component.dialog-save-as-new-entry.view/keyset.json b/src/i18n-keysets/component.dialog-save-as-new-entry.view/keyset.json new file mode 100644 index 0000000..83e74b8 --- /dev/null +++ b/src/i18n-keysets/component.dialog-save-as-new-entry.view/keyset.json @@ -0,0 +1,44 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-name-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_save-as-new-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-save-as-new-entry.view/ru.json b/src/i18n-keysets/component.dialog-save-as-new-entry.view/ru.json new file mode 100644 index 0000000..883156d --- /dev/null +++ b/src/i18n-keysets/component.dialog-save-as-new-entry.view/ru.json @@ -0,0 +1,10 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отменить", + "label_copy": "копия", + "label_default-name": "Новый", + "label_entry-name-already-exists": "Название уже используется, придумайте другое", + "label_placeholder": "Введите имя", + "section_caption": "Сохранить как новый", + "toast_save-as-new-failed": "Не удалось сохранить как новый" +} diff --git a/src/i18n-keysets/component.dialog-save-widget.view/context.json b/src/i18n-keysets/component.dialog-save-widget.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-save-widget.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-save-widget.view/en.json b/src/i18n-keysets/component.dialog-save-widget.view/en.json new file mode 100644 index 0000000..309d455 --- /dev/null +++ b/src/i18n-keysets/component.dialog-save-widget.view/en.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Save", + "button_cancel": "Cancel", + "label_entry-name-already-exists": "That name is already in use, try another one", + "label_placeholder": "Name", + "label_widget-name-default": "New chart", + "section_caption": "Save chart", + "toast_save-widget-failed": "Failed to save chart" +} diff --git a/src/i18n-keysets/component.dialog-save-widget.view/keyset.json b/src/i18n-keysets/component.dialog-save-widget.view/keyset.json new file mode 100644 index 0000000..36962b2 --- /dev/null +++ b/src/i18n-keysets/component.dialog-save-widget.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-name-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_widget-name-default": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_save-widget-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-save-widget.view/ru.json b/src/i18n-keysets/component.dialog-save-widget.view/ru.json new file mode 100644 index 0000000..3944853 --- /dev/null +++ b/src/i18n-keysets/component.dialog-save-widget.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Сохранить", + "button_cancel": "Отменить", + "label_entry-name-already-exists": "Название уже используется, придумайте другое", + "label_placeholder": "Введите название", + "label_widget-name-default": "Новый чарт", + "section_caption": "Сохранить чарт", + "toast_save-widget-failed": "Не удалось сохранить чарт" +} diff --git a/src/i18n-keysets/component.dialog-select-shared-entry.view/context.json b/src/i18n-keysets/component.dialog-select-shared-entry.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-select-shared-entry.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-select-shared-entry.view/en.json b/src/i18n-keysets/component.dialog-select-shared-entry.view/en.json new file mode 100644 index 0000000..a2ce584 --- /dev/null +++ b/src/i18n-keysets/component.dialog-select-shared-entry.view/en.json @@ -0,0 +1,4 @@ +{ + "or": "or", + "past-link-btn": "Enter link" +} diff --git a/src/i18n-keysets/component.dialog-select-shared-entry.view/keyset.json b/src/i18n-keysets/component.dialog-select-shared-entry.view/keyset.json new file mode 100644 index 0000000..28c93b3 --- /dev/null +++ b/src/i18n-keysets/component.dialog-select-shared-entry.view/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "or": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "past-link-btn": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-select-shared-entry.view/ru.json b/src/i18n-keysets/component.dialog-select-shared-entry.view/ru.json new file mode 100644 index 0000000..71412b5 --- /dev/null +++ b/src/i18n-keysets/component.dialog-select-shared-entry.view/ru.json @@ -0,0 +1,4 @@ +{ + "or": "или", + "past-link-btn": "Указать ссылку" +} diff --git a/src/i18n-keysets/component.dialog-set-actual-confirm.view/context.json b/src/i18n-keysets/component.dialog-set-actual-confirm.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-set-actual-confirm.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-set-actual-confirm.view/en.json b/src/i18n-keysets/component.dialog-set-actual-confirm.view/en.json new file mode 100644 index 0000000..bcb019e --- /dev/null +++ b/src/i18n-keysets/component.dialog-set-actual-confirm.view/en.json @@ -0,0 +1,6 @@ +{ + "button_cancel": "Cancel", + "button_confirm": "Yes, make actual", + "label_text": "A copy of the version you are currently viewing will be created. But you can always return to previous versions.", + "label_title": "Make actual?" +} diff --git a/src/i18n-keysets/component.dialog-set-actual-confirm.view/keyset.json b/src/i18n-keysets/component.dialog-set-actual-confirm.view/keyset.json new file mode 100644 index 0000000..ec784f7 --- /dev/null +++ b/src/i18n-keysets/component.dialog-set-actual-confirm.view/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_confirm": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-set-actual-confirm.view/ru.json b/src/i18n-keysets/component.dialog-set-actual-confirm.view/ru.json new file mode 100644 index 0000000..3d7cf46 --- /dev/null +++ b/src/i18n-keysets/component.dialog-set-actual-confirm.view/ru.json @@ -0,0 +1,6 @@ +{ + "button_cancel": "Отмена", + "button_confirm": "Да, сделать", + "label_text": "Будет создана копия версии, которую вы просматриваете сейчас. При этом вы всегда можете вернуться к предыдущим версиям.", + "label_title": "Сделать актуальной?" +} diff --git a/src/i18n-keysets/component.dialog-shared-entry-bindings.view/context.json b/src/i18n-keysets/component.dialog-shared-entry-bindings.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-bindings.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-shared-entry-bindings.view/en.json b/src/i18n-keysets/component.dialog-shared-entry-bindings.view/en.json new file mode 100644 index 0000000..2910575 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-bindings.view/en.json @@ -0,0 +1,28 @@ +{ + "alert-message-info": "Deletion will not affect links in other objects", + "alert-message-warning": "Before deleting, ensure that this will not affect links in other objects", + "alert-title-info": "{{entry}} is not in use anywhere", + "alert-title-warning": "{{entry}} is in use in several {{relation}}", + "apply-delete": "Delete", + "cancel-delete": "Cancel", + "connection": "Connection", + "dataset": "Dataset", + "dialog-error": "An error occurred", + "label-attachment-source": "In use in workbooks", + "label-attachment-target": "Contains connections", + "label-current-entry": "Selected object", + "list-action-change-permissions": "Change rights", + "list-action-unbind": "Unbind", + "list-empty": "Bindings missing", + "list-search-placeholder": "Name", + "list-title": "Bound objects", + "list-title-connection": "Connections", + "list-title-workbook": "Workbooks", + "refresh-btn": "Update", + "relation-dataset-delete": "datasets", + "relation-workbook-delete": "workbooks", + "relations-delete": "workbooks and datasets", + "retry-btn": "Try again", + "title-dialog": "Manage bindings", + "title-dialog-delete": "Delete {{entry}}?" +} diff --git a/src/i18n-keysets/component.dialog-shared-entry-bindings.view/keyset.json b/src/i18n-keysets/component.dialog-shared-entry-bindings.view/keyset.json new file mode 100644 index 0000000..81f0036 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-bindings.view/keyset.json @@ -0,0 +1,116 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "alert-message-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "alert-message-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "alert-title-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "alert-title-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "apply-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "cancel-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-attachment-source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-attachment-target": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-current-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list-action-change-permissions": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list-action-unbind": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list-empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list-search-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list-title-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list-title-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "refresh-btn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "relation-dataset-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "relation-workbook-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "relations-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "retry-btn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title-dialog": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title-dialog-delete": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-shared-entry-bindings.view/ru.json b/src/i18n-keysets/component.dialog-shared-entry-bindings.view/ru.json new file mode 100644 index 0000000..11cca4a --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-bindings.view/ru.json @@ -0,0 +1,28 @@ +{ + "alert-message-info": "Удаление не нарушит связи в других объектах", + "alert-message-warning": "Перед удалением убедитесь, что не нарушатся связи в других объектах", + "alert-title-info": "{{entry}} нигде не используется", + "alert-title-warning": "{{entry}} используется в некоторых {{relation}}", + "apply-delete": "Удалить", + "cancel-delete": "Отмена", + "connection": "Подключение", + "dataset": "Датасет", + "dialog-error": "Произошла ошибка", + "label-attachment-source": "Используется в воркбуках", + "label-attachment-target": "Содержит подключения", + "label-current-entry": "Выбранный объект", + "list-action-change-permissions": "Изменить права", + "list-action-unbind": "Отвязать", + "list-empty": "Привязки отсутствуют", + "list-search-placeholder": "Название", + "list-title": "Привязанные объекты", + "list-title-connection": "Подключения", + "list-title-workbook": "Воркбуки", + "refresh-btn": "Обновить", + "relation-dataset-delete": "датасетах", + "relation-workbook-delete": "воркбуках", + "relations-delete": "воркбуках и датасетах", + "retry-btn": "Попробовать снова", + "title-dialog": "Управление привязками", + "title-dialog-delete": "Удалить {{entry}}?" +} diff --git a/src/i18n-keysets/component.dialog-shared-entry-permissions.view/context.json b/src/i18n-keysets/component.dialog-shared-entry-permissions.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-permissions.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-shared-entry-permissions.view/en.json b/src/i18n-keysets/component.dialog-shared-entry-permissions.view/en.json new file mode 100644 index 0000000..bc39c78 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-permissions.view/en.json @@ -0,0 +1,13 @@ +{ + "apply-text": "Apply", + "cancel-text": "Cancel", + "delegate-message": "Connection rights will not be checked within a workbook", + "delegate-title": "Delegate access rights", + "documentation-link-text": "documentation", + "label-current-entry": "Selected object", + "label-relation-entity": "Where it's used", + "not-delegate-message": "To view the data, you must grant the rights to the original connection", + "not-delegate-title": "Do not delegate access rights", + "notice-text": "Incorrect settings can lead to unexpected results. Read more in the", + "title-dialog": "Access setup" +} diff --git a/src/i18n-keysets/component.dialog-shared-entry-permissions.view/keyset.json b/src/i18n-keysets/component.dialog-shared-entry-permissions.view/keyset.json new file mode 100644 index 0000000..0203e99 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-permissions.view/keyset.json @@ -0,0 +1,56 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "apply-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "cancel-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "delegate-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "delegate-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "documentation-link-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-current-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-relation-entity": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "not-delegate-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "not-delegate-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "notice-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title-dialog": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-shared-entry-permissions.view/ru.json b/src/i18n-keysets/component.dialog-shared-entry-permissions.view/ru.json new file mode 100644 index 0000000..130d0cc --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-permissions.view/ru.json @@ -0,0 +1,13 @@ +{ + "apply-text": "Применить", + "cancel-text": "Отмена", + "delegate-message": "Внутри воркбука права на подключение проверяться не будут", + "delegate-title": "Делегировать права доступа", + "documentation-link-text": "документации", + "label-current-entry": "Выбранный объект", + "label-relation-entity": "Где используется", + "not-delegate-message": "Для просмотра данных необходимо выдать права на оригинал подключения", + "not-delegate-title": "Не делегировать права доступа", + "notice-text": "Неправильная настройка может привести к непредвиденным результатам. Подробнее в", + "title-dialog": "Настройка доступа" +} diff --git a/src/i18n-keysets/component.dialog-shared-entry-unbind.view/context.json b/src/i18n-keysets/component.dialog-shared-entry-unbind.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-unbind.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-shared-entry-unbind.view/en.json b/src/i18n-keysets/component.dialog-shared-entry-unbind.view/en.json new file mode 100644 index 0000000..c36db16 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-unbind.view/en.json @@ -0,0 +1,19 @@ +{ + "apply-btn": "Unbind", + "cancel-btn": "Cancel", + "dialog-title": "Unbind {{entry}}?", + "entries-list-title-connection": "Connections", + "label-current-entry": "Selected object", + "label-of-shared-dataset": "dataset", + "label-relation-dataset": "dataset", + "label-relation-entity": "Where it's used", + "label-relation-workbook": "workbook", + "label-shared-connection": "Connection", + "label-shared-dataset": "Dataset", + "label-workbook": "Workbook", + "message-alert": "If {{relation}} are built on data from {{entry}}, they will stop working.", + "message-relation-alert": "charts", + "message-relations-alert": "datasets and charts", + "open-relation": "Open {{relation}}", + "title-alert": "{{entry}} is in use in {{relation}}" +} diff --git a/src/i18n-keysets/component.dialog-shared-entry-unbind.view/keyset.json b/src/i18n-keysets/component.dialog-shared-entry-unbind.view/keyset.json new file mode 100644 index 0000000..1d68661 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-unbind.view/keyset.json @@ -0,0 +1,80 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "apply-btn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "cancel-btn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "entries-list-title-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-current-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-of-shared-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-relation-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-relation-entity": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-relation-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-shared-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-shared-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "message-alert": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "message-relation-alert": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "message-relations-alert": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "open-relation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title-alert": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-shared-entry-unbind.view/ru.json b/src/i18n-keysets/component.dialog-shared-entry-unbind.view/ru.json new file mode 100644 index 0000000..9e99213 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-entry-unbind.view/ru.json @@ -0,0 +1,19 @@ +{ + "apply-btn": "Отвязать", + "cancel-btn": "Отмена", + "dialog-title": "Отвязать {{entry}}?", + "entries-list-title-connection": "Подключения", + "label-current-entry": "Выбранный объект", + "label-of-shared-dataset": "датасета", + "label-relation-dataset": "датасете", + "label-relation-entity": "Где используется", + "label-relation-workbook": "воркбуке", + "label-shared-connection": "Подключение", + "label-shared-dataset": "Датасет", + "label-workbook": "Воркбук", + "message-alert": "Если на данных из {{entry}} построены {{relation}}, они перестанут работать.", + "message-relation-alert": "чарты", + "message-relations-alert": "датасеты и чарты", + "open-relation": "Открыть {{relation}}", + "title-alert": "{{entry}} используется в {{relation}}" +} diff --git a/src/i18n-keysets/component.dialog-shared-related-entities.view/context.json b/src/i18n-keysets/component.dialog-shared-related-entities.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-related-entities.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-shared-related-entities.view/en.json b/src/i18n-keysets/component.dialog-shared-related-entities.view/en.json new file mode 100644 index 0000000..d7c5c82 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-related-entities.view/en.json @@ -0,0 +1,21 @@ +{ + "alert-message-info": "Deletion will not affect charts in Wizard, but may affect charts in Editor", + "alert-message-warning": "Before deleting, ensure that this will not affect links in objects. If you have charts in Editor, you will have to check them manually.", + "alert-title-info": "{{entry}} is not in use anywhere", + "alert-title-warning": "{{entry}} is in use in several {{relation}}", + "apply-text": "Delete", + "cancel-text": "Cancel", + "dialog-error": "An error occurred", + "dialog-title": "Linked objects in workbook", + "label-current-entry": "Selected object", + "label-shared-connection": "Connection", + "label-shared-dataset": "Dataset", + "refresh-btn": "Update", + "related-entities-list-empty": "No linked objects in workbook", + "relation-chart": "charts", + "relation-dash": "dashboards", + "relation-dataset": "datasets", + "relations": "linked objects", + "retry-btn": "Try again", + "title-dialog-delete": "Delete {{entry}}?" +} diff --git a/src/i18n-keysets/component.dialog-shared-related-entities.view/keyset.json b/src/i18n-keysets/component.dialog-shared-related-entities.view/keyset.json new file mode 100644 index 0000000..96cf559 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-related-entities.view/keyset.json @@ -0,0 +1,88 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "alert-message-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "alert-message-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "alert-title-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "alert-title-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "apply-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "cancel-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-current-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-shared-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-shared-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "refresh-btn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "related-entities-list-empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "relation-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "relation-dash": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "relation-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "relations": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "retry-btn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title-dialog-delete": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-shared-related-entities.view/ru.json b/src/i18n-keysets/component.dialog-shared-related-entities.view/ru.json new file mode 100644 index 0000000..a85f868 --- /dev/null +++ b/src/i18n-keysets/component.dialog-shared-related-entities.view/ru.json @@ -0,0 +1,21 @@ +{ + "alert-message-info": "Удаление не повлияет на чарты в Wizard, но может повлиять на чарты в Editor", + "alert-message-warning": "Перед удалением убедитесь, что не нарушатся связи в объектах. Если у вас есть чарты в Editor, их придётся проверить вручную.", + "alert-title-info": "{{entry}} нигде не используется", + "alert-title-warning": "{{entry}} используется в некоторых {{relation}}", + "apply-text": "Удалить", + "cancel-text": "Отмена", + "dialog-error": "Произошла ошибка", + "dialog-title": "Связанные объекты в воркбуке", + "label-current-entry": "Выбранный объект", + "label-shared-connection": "Подключение", + "label-shared-dataset": "Датасет", + "refresh-btn": "Обновить", + "related-entities-list-empty": "Связанные объекты в воркбуке отсутствуют", + "relation-chart": "чартах", + "relation-dash": "дашбордах", + "relation-dataset": "датасетах", + "relations": "связанных объектах", + "retry-btn": "Попробовать снова", + "title-dialog-delete": "Удалить {{entry}}?" +} diff --git a/src/i18n-keysets/component.dialog-switch-public.view/context.json b/src/i18n-keysets/component.dialog-switch-public.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dialog-switch-public.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dialog-switch-public.view/en.json b/src/i18n-keysets/component.dialog-switch-public.view/en.json new file mode 100644 index 0000000..7d410c9 --- /dev/null +++ b/src/i18n-keysets/component.dialog-switch-public.view/en.json @@ -0,0 +1,45 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel", + "button_continue": "Continue", + "button_copy": "Copy link", + "button_disable-all": "Disable all", + "button_enable-all": "Enable all", + "button_refresh": "Update", + "button_repeat": "Repeat", + "label_access-by-link": "Access via link", + "label_alert-publish-text": "The object will be available to any user on the internet.", + "label_alert-publish-text-many": "The objects will be available to any user on the internet.", + "label_alert-unpublish-text": "The object is publicly available. Are you sure?", + "label_alert-unpublish-text-many": "Some objects are publicly available. Are you sure?", + "label_author-description": "When publishing a {{subject}}, you can specify a link to its source or author. The link will be displayed at the bottom of the {{subject}}.", + "label_author-link": "Link", + "label_author-link-error": "Invalid link format", + "label_author-subject-dash": "dashboard", + "label_author-subject-widget": "chart", + "label_author-text": "Link text", + "label_dataset-deny-publish": "Dataset rejected publication change", + "label_loading-failed": "Failed to load", + "label_main-attention": "Important: Once you apply the settings, your {{subject}} and all its enabled related objects will become available to all internet users.", + "label_main-subject-dash": "dashboard", + "label_main-subject-widget": "chart", + "label_no-permission-alert": "To change the publication status, you need {{permission}} permission", + "label_placeholder-publishing-not-enabled": "Publication is not enabled", + "label_some-entries-not-ready": "Some objects are not ready for publication", + "md_label_unavaible-publication-attention": "You cannot embed this object because it uses connections that are either unavailable for publication or deleted.", + "section_alert-publish-title": "Make public?", + "section_alert-publish-title-many": "Make public?", + "section_alert-unpublish-title": "Unpublish?", + "section_alert-unpublish-title-many": "Unpublish objects?", + "section_author": "Author or link to source", + "section_connection-relations": "Connections", + "section_current-entry": "Current object", + "section_dataset-relations": "Datasets", + "section_main-title": "Configuring public access", + "section_object-relations": "Linked objects", + "section_widget-relations": "Charts", + "toast_get-publication-preview-failed": "Failed to load", + "toast_link-copied": "Link copied", + "toast_switch-publication-status-failed": "Failed to change public access", + "toast_switch-publication-status-success": "Public access changed" +} diff --git a/src/i18n-keysets/component.dialog-switch-public.view/keyset.json b/src/i18n-keysets/component.dialog-switch-public.view/keyset.json new file mode 100644 index 0000000..6692ebf --- /dev/null +++ b/src/i18n-keysets/component.dialog-switch-public.view/keyset.json @@ -0,0 +1,184 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_continue": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_disable-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_enable-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_refresh": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_repeat": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_access-by-link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alert-publish-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alert-publish-text-many": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alert-unpublish-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alert-unpublish-text-many": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_author-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_author-link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_author-link-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_author-subject-dash": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_author-subject-widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_author-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dataset-deny-publish": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_loading-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_main-attention": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_main-subject-dash": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_main-subject-widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-permission-alert": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-publishing-not-enabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_some-entries-not-ready": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "md_label_unavaible-publication-attention": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_alert-publish-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_alert-publish-title-many": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_alert-unpublish-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_alert-unpublish-title-many": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_author": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_connection-relations": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_current-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_dataset-relations": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_main-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_object-relations": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_widget-relations": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_get-publication-preview-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_link-copied": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_switch-publication-status-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_switch-publication-status-success": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dialog-switch-public.view/ru.json b/src/i18n-keysets/component.dialog-switch-public.view/ru.json new file mode 100644 index 0000000..23862f9 --- /dev/null +++ b/src/i18n-keysets/component.dialog-switch-public.view/ru.json @@ -0,0 +1,45 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отменить", + "button_continue": "Продолжить", + "button_copy": "Скопировать ссылку", + "button_disable-all": "Выключить всё", + "button_enable-all": "Включить всё", + "button_refresh": "Обновить", + "button_repeat": "Повторить", + "label_access-by-link": "Доступ по ссылке", + "label_alert-publish-text": "Объект станет доступен любому пользователю в интернете.", + "label_alert-publish-text-many": "Объекты станут доступны любому пользователю в интернете.", + "label_alert-unpublish-text": "Объект находится в публичном доступе. Вы уверены?", + "label_alert-unpublish-text-many": "Некоторые объекты находятся в публичном доступе. Вы уверены?", + "label_author-description": "При публикации {{subject}} можно указать ссылку на его источник или автора. Ссылка будет отображаться внизу {{subject}}.", + "label_author-link": "Ссылка", + "label_author-link-error": "Неверный формат ссылки", + "label_author-subject-dash": "дашборда", + "label_author-subject-widget": "чарта", + "label_author-text": "Текст ссылки", + "label_dataset-deny-publish": "Датасет отклонил возможность смены публикации", + "label_loading-failed": "Не удалось загрузить", + "label_main-attention": "Внимание! После применения настроек {{subject}} и все включённые связанные объекты будут доступны любому пользователю в интернете.", + "label_main-subject-dash": "дашборд", + "label_main-subject-widget": "чарт", + "label_no-permission-alert": "Для изменения статуса публикации нужны права {{permission}}", + "label_placeholder-publishing-not-enabled": "Публикация не включена", + "label_some-entries-not-ready": "Некоторые объекты не готовы к публикации", + "md_label_unavaible-publication-attention": "Нельзя опубликовать объект, потому что в нём используются недоступные для публикации или удалённые подключения.", + "section_alert-publish-title": "Сделать публичным?", + "section_alert-publish-title-many": "Сделать публичными?", + "section_alert-unpublish-title": "Снять с публикации?", + "section_alert-unpublish-title-many": "Снять объекты с публикации?", + "section_author": "Авторство или ссылка на источник", + "section_connection-relations": "Подключения", + "section_current-entry": "Текущий объект", + "section_dataset-relations": "Датасеты", + "section_main-title": "Настройка публичного доступа", + "section_object-relations": "Связанные объекты", + "section_widget-relations": "Чарты", + "toast_get-publication-preview-failed": "Не удалось загрузить", + "toast_link-copied": "Ссылка скопирована", + "toast_switch-publication-status-failed": "Не удалось изменить публичный доступ", + "toast_switch-publication-status-success": "Публичный доступ был изменен" +} diff --git a/src/i18n-keysets/component.dl-dataset-field-list.view/context.json b/src/i18n-keysets/component.dl-dataset-field-list.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dl-dataset-field-list.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dl-dataset-field-list.view/en.json b/src/i18n-keysets/component.dl-dataset-field-list.view/en.json new file mode 100644 index 0000000..6d77413 --- /dev/null +++ b/src/i18n-keysets/component.dl-dataset-field-list.view/en.json @@ -0,0 +1,3 @@ +{ + "label_filter-placeholder": "Field" +} diff --git a/src/i18n-keysets/component.dl-dataset-field-list.view/keyset.json b/src/i18n-keysets/component.dl-dataset-field-list.view/keyset.json new file mode 100644 index 0000000..d27675c --- /dev/null +++ b/src/i18n-keysets/component.dl-dataset-field-list.view/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_filter-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dl-dataset-field-list.view/ru.json b/src/i18n-keysets/component.dl-dataset-field-list.view/ru.json new file mode 100644 index 0000000..700bf92 --- /dev/null +++ b/src/i18n-keysets/component.dl-dataset-field-list.view/ru.json @@ -0,0 +1,3 @@ +{ + "label_filter-placeholder": "Поле" +} diff --git a/src/i18n-keysets/component.dl-dialog-confirm.view/context.json b/src/i18n-keysets/component.dl-dialog-confirm.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-confirm.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dl-dialog-confirm.view/en.json b/src/i18n-keysets/component.dl-dialog-confirm.view/en.json new file mode 100644 index 0000000..832cbcc --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-confirm.view/en.json @@ -0,0 +1,7 @@ +{ + "button-save-chart_apply": "Save and continue", + "button-save-chart_cancel": "Cancel", + "button_apply": "Apply", + "button_cancel": "Cancel", + "confirm-header": "Confirm action" +} diff --git a/src/i18n-keysets/component.dl-dialog-confirm.view/keyset.json b/src/i18n-keysets/component.dl-dialog-confirm.view/keyset.json new file mode 100644 index 0000000..29b44f8 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-confirm.view/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button-save-chart_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button-save-chart_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "confirm-header": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dl-dialog-confirm.view/ru.json b/src/i18n-keysets/component.dl-dialog-confirm.view/ru.json new file mode 100644 index 0000000..c67d94a --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-confirm.view/ru.json @@ -0,0 +1,7 @@ +{ + "button-save-chart_apply": "Сохранить и продолжить", + "button-save-chart_cancel": "Отмена", + "button_apply": "Да", + "button_cancel": "Нет", + "confirm-header": "Подтвердите действие" +} diff --git a/src/i18n-keysets/component.dl-dialog-filter.view/context.json b/src/i18n-keysets/component.dl-dialog-filter.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-filter.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dl-dialog-filter.view/en.json b/src/i18n-keysets/component.dl-dialog-filter.view/en.json new file mode 100644 index 0000000..ab9a4c1 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-filter.view/en.json @@ -0,0 +1,16 @@ +{ + "button_add": "Add", + "button_apply": "Apply filter", + "button_cancel": "Cancel", + "button_left-column-action": "Select all", + "button_right-column-action": "Clear", + "label_filter-placeholder": "Search", + "label_header-title": "Select a field", + "label_load-limit-reached-warn": "The first 1000 values are shown. Refine your search using the Search field.", + "label_operation": "Operation", + "label_select-column-left-item-desc": "Select", + "label_select-column-left-title": "Available", + "label_select-column-right-item-desc": "Delete", + "label_select-column-right-title": "Selected", + "label_value": "Value" +} diff --git a/src/i18n-keysets/component.dl-dialog-filter.view/keyset.json b/src/i18n-keysets/component.dl-dialog-filter.view/keyset.json new file mode 100644 index 0000000..8264456 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-filter.view/keyset.json @@ -0,0 +1,68 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_left-column-action": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_right-column-action": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_header-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_load-limit-reached-warn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-column-left-item-desc": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-column-left-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-column-right-item-desc": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-column-right-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_value": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dl-dialog-filter.view/ru.json b/src/i18n-keysets/component.dl-dialog-filter.view/ru.json new file mode 100644 index 0000000..2675339 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-filter.view/ru.json @@ -0,0 +1,16 @@ +{ + "button_add": "Добавить", + "button_apply": "Применить фильтр", + "button_cancel": "Отменить", + "button_left-column-action": "Выбрать все", + "button_right-column-action": "Очистить", + "label_filter-placeholder": "Найти", + "label_header-title": "Выберите поле", + "label_load-limit-reached-warn": "Показана первая 1000 значений. Уточните поиск с помощью поля Найти.", + "label_operation": "Операция", + "label_select-column-left-item-desc": "Выбрать", + "label_select-column-left-title": "Доступны", + "label_select-column-right-item-desc": "Удалить", + "label_select-column-right-title": "Выбраны", + "label_value": "Значение" +} diff --git a/src/i18n-keysets/component.dl-dialog-ql-parameter.view/context.json b/src/i18n-keysets/component.dl-dialog-ql-parameter.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-ql-parameter.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dl-dialog-ql-parameter.view/en.json b/src/i18n-keysets/component.dl-dialog-ql-parameter.view/en.json new file mode 100644 index 0000000..183a4dc --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-ql-parameter.view/en.json @@ -0,0 +1,6 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel", + "label_header-title": "Parameter value", + "label_value": "Value" +} diff --git a/src/i18n-keysets/component.dl-dialog-ql-parameter.view/keyset.json b/src/i18n-keysets/component.dl-dialog-ql-parameter.view/keyset.json new file mode 100644 index 0000000..3603342 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-ql-parameter.view/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_header-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_value": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dl-dialog-ql-parameter.view/ru.json b/src/i18n-keysets/component.dl-dialog-ql-parameter.view/ru.json new file mode 100644 index 0000000..8285f7d --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-ql-parameter.view/ru.json @@ -0,0 +1,6 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отменить", + "label_header-title": "Значение параметра", + "label_value": "Значение" +} diff --git a/src/i18n-keysets/component.dl-dialog-warning.view/context.json b/src/i18n-keysets/component.dl-dialog-warning.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-warning.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dl-dialog-warning.view/en.json b/src/i18n-keysets/component.dl-dialog-warning.view/en.json new file mode 100644 index 0000000..505ee11 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-warning.view/en.json @@ -0,0 +1,3 @@ +{ + "button_apply": "Close" +} diff --git a/src/i18n-keysets/component.dl-dialog-warning.view/keyset.json b/src/i18n-keysets/component.dl-dialog-warning.view/keyset.json new file mode 100644 index 0000000..77996e0 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-warning.view/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dl-dialog-warning.view/ru.json b/src/i18n-keysets/component.dl-dialog-warning.view/ru.json new file mode 100644 index 0000000..0423fb4 --- /dev/null +++ b/src/i18n-keysets/component.dl-dialog-warning.view/ru.json @@ -0,0 +1,3 @@ +{ + "button_apply": "Закрыть" +} diff --git a/src/i18n-keysets/component.dl-field-editor.view/context.json b/src/i18n-keysets/component.dl-field-editor.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.dl-field-editor.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.dl-field-editor.view/en.json b/src/i18n-keysets/component.dl-field-editor.view/en.json new file mode 100644 index 0000000..80038c3 --- /dev/null +++ b/src/i18n-keysets/component.dl-field-editor.view/en.json @@ -0,0 +1,52 @@ +{ + "button_cancel": "Cancel", + "button_create": "Create", + "button_documentation": "Reference", + "button_save": "Save", + "button_save-as": "Save as a new field", + "label_aggregation": "Aggregation", + "label_confirm-message": "Are you sure you want to exit? Data will be lost.", + "label_doc-formula-is-empty": "Select a function from the list on the left to display its description", + "label_doc-formula-is-empty-description-examples-link": "Examples on the Demo Dashboard", + "label_doc-formula-is-empty-description-syntax-link": "Learn more about the syntax", + "label_doc-list-placeholder": "Function", + "label_field-already-exist": "A field with the specified name already exists.", + "label_field-id": "Field ID", + "label_field-id-hint": "The field ID is used to pass filtering parameters for this field on the dashboard or the chart URL. It is allowed to use Latin letters in lowercase, as well as the characters \"_\" and \"-\".", + "label_field-name": "Name", + "label_field-name-placeholder": "Field name", + "label_field-name-placeholder-new": "New field", + "label_field-source": "Source field", + "label_field-type": "Field type", + "label_hide-in-wizard": "Don't display in wizard", + "label_invalid-id": "Invalid id format", + "label_show-in-wizard": "Display in wizard", + "label_source": "Source", + "label_source-data": "Data source", + "label_source-is-empty": "Select a source for the field", + "label_title-is-empty": "The field name must not be empty", + "value_array_float": "Array of fractional numbers", + "value_array_int": "Array of integers", + "value_array_str": "Array of strings", + "value_auto": "Auto", + "value_avg": "Average", + "value_boolean": "Logical", + "value_count": "Count", + "value_countunique": "Count (Distinct)", + "value_date": "Date", + "value_float": "Fractional number", + "value_formula": "Formula", + "value_genericdatetime": "Date and time", + "value_geopoint": "Geopoint", + "value_geopolygon": "Geopolygon", + "value_integer": "Integer", + "value_max": "Maximum", + "value_min": "Minimum", + "value_none": "No", + "value_source-from-field": "Field from source", + "value_string": "String", + "value_sum": "Sum", + "value_tree_float": "Tree of fractional numbers", + "value_tree_int": "Tree of integers", + "value_tree_str": "Tree of strings" +} diff --git a/src/i18n-keysets/component.dl-field-editor.view/keyset.json b/src/i18n-keysets/component.dl-field-editor.view/keyset.json new file mode 100644 index 0000000..7933e05 --- /dev/null +++ b/src/i18n-keysets/component.dl-field-editor.view/keyset.json @@ -0,0 +1,212 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_documentation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_aggregation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_confirm-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_doc-formula-is-empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_doc-formula-is-empty-description-examples-link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_doc-formula-is-empty-description-syntax-link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_doc-list-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-already-exist": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-id-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-name-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-name-placeholder-new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_hide-in-wizard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_show-in-wizard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-is-empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-is-empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_array_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_array_int": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_array_str": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_auto": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_avg": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_boolean": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_count": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_countunique": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_formula": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_genericdatetime": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geopoint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geopolygon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_integer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_min": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_none": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_source-from-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_string": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_sum": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_tree_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_tree_int": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_tree_str": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.dl-field-editor.view/ru.json b/src/i18n-keysets/component.dl-field-editor.view/ru.json new file mode 100644 index 0000000..9365a6d --- /dev/null +++ b/src/i18n-keysets/component.dl-field-editor.view/ru.json @@ -0,0 +1,52 @@ +{ + "button_cancel": "Отменить", + "button_create": "Создать", + "button_documentation": "Справочник", + "button_save": "Сохранить", + "button_save-as": "Сохранить как новое поле", + "label_aggregation": "Агрегация", + "label_confirm-message": "Вы уверены, что хотите выйти? Данные будут утеряны.", + "label_doc-formula-is-empty": "Выберите функцию в списке слева, чтобы просмотреть её описание", + "label_doc-formula-is-empty-description-examples-link": "Примеры на демодашборде", + "label_doc-formula-is-empty-description-syntax-link": "Подробнее о синтаксисе", + "label_doc-list-placeholder": "Функция", + "label_field-already-exist": "Поле с таким именем уже существует", + "label_field-id": "ID поля", + "label_field-id-hint": "ID поля используется для передачи параметров фильтрации по этому полю на дашборде или URL чарта. Допускается использовать латинские буквы в нижнем регистре, а также символы \"_\" и \"-\".", + "label_field-name": "Имя", + "label_field-name-placeholder": "Название поля", + "label_field-name-placeholder-new": "Новое поле", + "label_field-source": "Поле источника", + "label_field-type": "Тип поля", + "label_hide-in-wizard": "Не показывать в визарде", + "label_invalid-id": "Неправильный формат id", + "label_show-in-wizard": "Показывать в визарде", + "label_source": "Источник", + "label_source-data": "Источник данных", + "label_source-is-empty": "Выберите источник для поля", + "label_title-is-empty": "Имя поля не должно быть пустым", + "value_array_float": "Массив дробных чисел", + "value_array_int": "Массив целых чисел", + "value_array_str": "Массив строк", + "value_auto": "Авто", + "value_avg": "Среднее", + "value_boolean": "Логический", + "value_count": "Количество", + "value_countunique": "Количество уникальных", + "value_date": "Дата", + "value_float": "Дробное число", + "value_formula": "Формула", + "value_genericdatetime": "Дата и время", + "value_geopoint": "Геоточка", + "value_geopolygon": "Геополигон", + "value_integer": "Целое число", + "value_max": "Максимум", + "value_min": "Минимум", + "value_none": "Нет", + "value_source-from-field": "Поле из источника", + "value_string": "Строка", + "value_sum": "Сумма", + "value_tree_float": "Дерево дробных чисел", + "value_tree_int": "Дерево целых чисел", + "value_tree_str": "Дерево строк" +} diff --git a/src/i18n-keysets/component.editor-console.view/context.json b/src/i18n-keysets/component.editor-console.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.editor-console.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.editor-console.view/en.json b/src/i18n-keysets/component.editor-console.view/en.json new file mode 100644 index 0000000..f0c5620 --- /dev/null +++ b/src/i18n-keysets/component.editor-console.view/en.json @@ -0,0 +1,4 @@ +{ + "label_failed-loading-logs": "Failed to load logs.", + "label_max-length-array": "The object is cropped. The maximum length of the array is {{length}}." +} diff --git a/src/i18n-keysets/component.editor-console.view/keyset.json b/src/i18n-keysets/component.editor-console.view/keyset.json new file mode 100644 index 0000000..d531392 --- /dev/null +++ b/src/i18n-keysets/component.editor-console.view/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_failed-loading-logs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_max-length-array": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.editor-console.view/ru.json b/src/i18n-keysets/component.editor-console.view/ru.json new file mode 100644 index 0000000..7d16329 --- /dev/null +++ b/src/i18n-keysets/component.editor-console.view/ru.json @@ -0,0 +1,4 @@ +{ + "label_failed-loading-logs": "Не удалось загрузить логи", + "label_max-length-array": "Объект обрезан. Максимальная длина массива {{length}}." +} diff --git a/src/i18n-keysets/component.editor-controls/context.json b/src/i18n-keysets/component.editor-controls/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.editor-controls/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.editor-controls/en.json b/src/i18n-keysets/component.editor-controls/en.json new file mode 100644 index 0000000..b4d8b18 --- /dev/null +++ b/src/i18n-keysets/component.editor-controls/en.json @@ -0,0 +1,5 @@ +{ + "button_diff": "View diff", + "button_docs": "View documentation", + "button_search": "Open search" +} diff --git a/src/i18n-keysets/component.editor-controls/keyset.json b/src/i18n-keysets/component.editor-controls/keyset.json new file mode 100644 index 0000000..8268eab --- /dev/null +++ b/src/i18n-keysets/component.editor-controls/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_diff": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_docs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_search": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.editor-controls/ru.json b/src/i18n-keysets/component.editor-controls/ru.json new file mode 100644 index 0000000..ea6c0dd --- /dev/null +++ b/src/i18n-keysets/component.editor-controls/ru.json @@ -0,0 +1,5 @@ +{ + "button_diff": "Посмотреть изменения", + "button_docs": "Посмотреть документацию", + "button_search": "Открыть поиск" +} diff --git a/src/i18n-keysets/component.editor-search.view/context.json b/src/i18n-keysets/component.editor-search.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.editor-search.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.editor-search.view/en.json b/src/i18n-keysets/component.editor-search.view/en.json new file mode 100644 index 0000000..89a69f0 --- /dev/null +++ b/src/i18n-keysets/component.editor-search.view/en.json @@ -0,0 +1,5 @@ +{ + "button_match-case-title": "Case-sensitive", + "label_not-found-search": "Not found", + "label_placeholder-search": "Search" +} diff --git a/src/i18n-keysets/component.editor-search.view/keyset.json b/src/i18n-keysets/component.editor-search.view/keyset.json new file mode 100644 index 0000000..c519077 --- /dev/null +++ b/src/i18n-keysets/component.editor-search.view/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_match-case-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_not-found-search": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-search": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.editor-search.view/ru.json b/src/i18n-keysets/component.editor-search.view/ru.json new file mode 100644 index 0000000..7cbb707 --- /dev/null +++ b/src/i18n-keysets/component.editor-search.view/ru.json @@ -0,0 +1,5 @@ +{ + "button_match-case-title": "С учетом регистра", + "label_not-found-search": "Ничего не найдено", + "label_placeholder-search": "Поиск" +} diff --git a/src/i18n-keysets/component.entity-row.view/context.json b/src/i18n-keysets/component.entity-row.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.entity-row.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.entity-row.view/en.json b/src/i18n-keysets/component.entity-row.view/en.json new file mode 100644 index 0000000..64f899e --- /dev/null +++ b/src/i18n-keysets/component.entity-row.view/en.json @@ -0,0 +1,5 @@ +{ + "delegated": "Rights delegated", + "not-delegated": "No rights delegated", + "relation-tooltip-text": "Go to linked objects in workbook" +} diff --git a/src/i18n-keysets/component.entity-row.view/keyset.json b/src/i18n-keysets/component.entity-row.view/keyset.json new file mode 100644 index 0000000..a9e4c11 --- /dev/null +++ b/src/i18n-keysets/component.entity-row.view/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "delegated": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "not-delegated": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "relation-tooltip-text": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.entity-row.view/ru.json b/src/i18n-keysets/component.entity-row.view/ru.json new file mode 100644 index 0000000..9a3d2f4 --- /dev/null +++ b/src/i18n-keysets/component.entity-row.view/ru.json @@ -0,0 +1,5 @@ +{ + "delegated": "Права делегированы", + "not-delegated": "Без делегации прав", + "relation-tooltip-text": "Перейти к связанным объектам в воркбуке" +} diff --git a/src/i18n-keysets/component.entry-context-menu.view/context.json b/src/i18n-keysets/component.entry-context-menu.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.entry-context-menu.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.entry-context-menu.view/en.json b/src/i18n-keysets/component.entry-context-menu.view/en.json new file mode 100644 index 0000000..9a699a0 --- /dev/null +++ b/src/i18n-keysets/component.entry-context-menu.view/en.json @@ -0,0 +1,20 @@ +{ + "toast_copy-error": "Copy error", + "toast_copy-id-success": "ID copied to clipboard", + "toast_copy-link-success": "Link copied to clipboard", + "value_access": "Permissions", + "value_add-favorites-alias": "Add label", + "value_copy": "Copy", + "value_copy-id": "Copy ID", + "value_copy-link": "Copy link", + "value_copy-success": "Object copied successfully", + "value_delete": "Delete", + "value_duplicate": "Duplicate", + "value_edit-favorites-alias": "Rename label", + "value_migrate-to-workbook": "Migrate to workbook", + "value_move": "Move", + "value_rename": "Rename", + "value_revisions": "Change history", + "value_share": "Share", + "value_show-related-entities": "Related objects" +} diff --git a/src/i18n-keysets/component.entry-context-menu.view/keyset.json b/src/i18n-keysets/component.entry-context-menu.view/keyset.json new file mode 100644 index 0000000..0c494dd --- /dev/null +++ b/src/i18n-keysets/component.entry-context-menu.view/keyset.json @@ -0,0 +1,84 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "toast_copy-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_copy-id-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_copy-link-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_access": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_add-favorites-alias": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_copy-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_copy-link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_copy-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_duplicate": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_edit-favorites-alias": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_migrate-to-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_move": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_rename": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_revisions": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_share": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_show-related-entities": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.entry-context-menu.view/ru.json b/src/i18n-keysets/component.entry-context-menu.view/ru.json new file mode 100644 index 0000000..45ba50b --- /dev/null +++ b/src/i18n-keysets/component.entry-context-menu.view/ru.json @@ -0,0 +1,20 @@ +{ + "toast_copy-error": "При копировании произошла ошибка", + "toast_copy-id-success": "ID скопирован в буфер обмена", + "toast_copy-link-success": "Ссылка скопирована в буфер обмена", + "value_access": "Права доступа", + "value_add-favorites-alias": "Добавить лейбл", + "value_copy": "Копировать", + "value_copy-id": "Копировать ID", + "value_copy-link": "Копировать ссылку", + "value_copy-success": "Объект успешно скопирован", + "value_delete": "Удалить", + "value_duplicate": "Дублировать", + "value_edit-favorites-alias": "Изменить лейбл", + "value_migrate-to-workbook": "Мигрировать в воркбук", + "value_move": "Переместить", + "value_rename": "Переименовать", + "value_revisions": "История изменений", + "value_share": "Поделиться", + "value_show-related-entities": "Связанные объекты" +} diff --git a/src/i18n-keysets/component.entry-label.view/context.json b/src/i18n-keysets/component.entry-label.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.entry-label.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.entry-label.view/en.json b/src/i18n-keysets/component.entry-label.view/en.json new file mode 100644 index 0000000..c54e5f1 --- /dev/null +++ b/src/i18n-keysets/component.entry-label.view/en.json @@ -0,0 +1,6 @@ +{ + "label_draft": "Draft", + "label_latest": "Latest", + "label_locked": "Locked by {{user}}", + "label_version": "v{{version}}" +} diff --git a/src/i18n-keysets/component.entry-label.view/keyset.json b/src/i18n-keysets/component.entry-label.view/keyset.json new file mode 100644 index 0000000..1b8d66c --- /dev/null +++ b/src/i18n-keysets/component.entry-label.view/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_latest": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_locked": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_version": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.entry-label.view/ru.json b/src/i18n-keysets/component.entry-label.view/ru.json new file mode 100644 index 0000000..c54e5f1 --- /dev/null +++ b/src/i18n-keysets/component.entry-label.view/ru.json @@ -0,0 +1,6 @@ +{ + "label_draft": "Draft", + "label_latest": "Latest", + "label_locked": "Locked by {{user}}", + "label_version": "v{{version}}" +} diff --git a/src/i18n-keysets/component.entry-row.view/context.json b/src/i18n-keysets/component.entry-row.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.entry-row.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.entry-row.view/en.json b/src/i18n-keysets/component.entry-row.view/en.json new file mode 100644 index 0000000..075962f --- /dev/null +++ b/src/i18n-keysets/component.entry-row.view/en.json @@ -0,0 +1,3 @@ +{ + "label_no-access": "No access to the object" +} diff --git a/src/i18n-keysets/component.entry-row.view/keyset.json b/src/i18n-keysets/component.entry-row.view/keyset.json new file mode 100644 index 0000000..72acf1c --- /dev/null +++ b/src/i18n-keysets/component.entry-row.view/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_no-access": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.entry-row.view/ru.json b/src/i18n-keysets/component.entry-row.view/ru.json new file mode 100644 index 0000000..4db096a --- /dev/null +++ b/src/i18n-keysets/component.entry-row.view/ru.json @@ -0,0 +1,3 @@ +{ + "label_no-access": "Нет доступа к объекту" +} diff --git a/src/i18n-keysets/component.entry-suggest.view/context.json b/src/i18n-keysets/component.entry-suggest.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.entry-suggest.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.entry-suggest.view/en.json b/src/i18n-keysets/component.entry-suggest.view/en.json new file mode 100644 index 0000000..95f4b7d --- /dev/null +++ b/src/i18n-keysets/component.entry-suggest.view/en.json @@ -0,0 +1,12 @@ +{ + "label_empty-state-not-found-description": "Try entering another value", + "label_empty-state-not-found-title": "Nothing found", + "label_empty-state-template-description": "Start entering an entity name", + "label_empty-state-template-title": "There is nothing yet", + "label_option-all": "All types", + "label_option-connections": "Connections", + "label_option-datasets": "Datasets", + "label_option-widgets": "Charts", + "label_search-filter-placeholder": "Search by name", + "label_type-select-placeholder": "Select type" +} diff --git a/src/i18n-keysets/component.entry-suggest.view/keyset.json b/src/i18n-keysets/component.entry-suggest.view/keyset.json new file mode 100644 index 0000000..3892c13 --- /dev/null +++ b/src/i18n-keysets/component.entry-suggest.view/keyset.json @@ -0,0 +1,52 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_empty-state-not-found-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-state-not-found-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-state-template-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-state-template-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_option-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_option-connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_option-datasets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_option-widgets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_search-filter-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_type-select-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.entry-suggest.view/ru.json b/src/i18n-keysets/component.entry-suggest.view/ru.json new file mode 100644 index 0000000..af77f6c --- /dev/null +++ b/src/i18n-keysets/component.entry-suggest.view/ru.json @@ -0,0 +1,12 @@ +{ + "label_empty-state-not-found-description": "Попробуйте ввести другое значение", + "label_empty-state-not-found-title": "Ничего не найдено", + "label_empty-state-template-description": "Начните вводить название объекта", + "label_empty-state-template-title": "Пока ничего нет", + "label_option-all": "Все типы", + "label_option-connections": "Подключения", + "label_option-datasets": "Датасеты", + "label_option-widgets": "Чарты", + "label_search-filter-placeholder": "Искать по названию", + "label_type-select-placeholder": "Выберите тип" +} diff --git a/src/i18n-keysets/component.error-content.view/context.json b/src/i18n-keysets/component.error-content.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.error-content.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.error-content.view/en.json b/src/i18n-keysets/component.error-content.view/en.json new file mode 100644 index 0000000..c945c88 --- /dev/null +++ b/src/i18n-keysets/component.error-content.view/en.json @@ -0,0 +1,7 @@ +{ + "button_access-rights": "Request permissions", + "button_console": "Open console", + "button_copy": "Copy {{subject}}", + "label_error-general": "Something went wrong", + "toast_copied": "Copied" +} diff --git a/src/i18n-keysets/component.error-content.view/keyset.json b/src/i18n-keysets/component.error-content.view/keyset.json new file mode 100644 index 0000000..0505df9 --- /dev/null +++ b/src/i18n-keysets/component.error-content.view/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_access-rights": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_console": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-general": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_copied": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.error-content.view/ru.json b/src/i18n-keysets/component.error-content.view/ru.json new file mode 100644 index 0000000..bce3aed --- /dev/null +++ b/src/i18n-keysets/component.error-content.view/ru.json @@ -0,0 +1,7 @@ +{ + "button_access-rights": "Запросить права", + "button_console": "Перейти в консоль", + "button_copy": "Копировать {{subject}}", + "label_error-general": "Что-то пошло не так", + "toast_copied": "Скопировано" +} diff --git a/src/i18n-keysets/component.error-dialog.view/context.json b/src/i18n-keysets/component.error-dialog.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.error-dialog.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.error-dialog.view/en.json b/src/i18n-keysets/component.error-dialog.view/en.json new file mode 100644 index 0000000..d60d5ec --- /dev/null +++ b/src/i18n-keysets/component.error-dialog.view/en.json @@ -0,0 +1,18 @@ +{ + "button_close": "Close", + "button_copy": "Copy", + "button_report": "Report a problem", + "button_retry": "Retry", + "label_db-request": "Sent query:", + "label_db-response": "Database response:", + "label_error": "Error", + "label_export-id": "Export ID:", + "label_full-backend-response": "Debug info:", + "label_import-id": "Import ID:", + "label_request-id": "Request ID:", + "label_trace-id": "Trace ID:", + "tab_db-request": "Sent query", + "tab_db-response": "Database response", + "tab_debug": "Debug info", + "tab_doc": "Documentation" +} diff --git a/src/i18n-keysets/component.error-dialog.view/keyset.json b/src/i18n-keysets/component.error-dialog.view/keyset.json new file mode 100644 index 0000000..ca15b0e --- /dev/null +++ b/src/i18n-keysets/component.error-dialog.view/keyset.json @@ -0,0 +1,76 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_report": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_db-request": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_db-response": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_full-backend-response": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_import-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_request-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_trace-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_db-request": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_db-response": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_debug": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_doc": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.error-dialog.view/ru.json b/src/i18n-keysets/component.error-dialog.view/ru.json new file mode 100644 index 0000000..df7b000 --- /dev/null +++ b/src/i18n-keysets/component.error-dialog.view/ru.json @@ -0,0 +1,18 @@ +{ + "button_close": "Закрыть", + "button_copy": "Скопировать", + "button_report": "Сообщить о проблеме", + "button_retry": "Повторить", + "label_db-request": "Отправленный запрос:", + "label_db-response": "Ответ БД:", + "label_error": "Ошибка", + "label_export-id": "Export-ID:", + "label_full-backend-response": "Отладочная информация:", + "label_import-id": "Import-ID:", + "label_request-id": "Request-ID:", + "label_trace-id": "Trace-ID:", + "tab_db-request": "Отправленный запрос", + "tab_db-response": "Ответ БД", + "tab_debug": "Отладочная информация", + "tab_doc": "Документация" +} diff --git a/src/i18n-keysets/component.expandable-panel.view/context.json b/src/i18n-keysets/component.expandable-panel.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.expandable-panel.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.expandable-panel.view/en.json b/src/i18n-keysets/component.expandable-panel.view/en.json new file mode 100644 index 0000000..0d4fb7b --- /dev/null +++ b/src/i18n-keysets/component.expandable-panel.view/en.json @@ -0,0 +1,4 @@ +{ + "button_title-collapse": "Collapse", + "button_title-expand": "Expand" +} diff --git a/src/i18n-keysets/component.expandable-panel.view/keyset.json b/src/i18n-keysets/component.expandable-panel.view/keyset.json new file mode 100644 index 0000000..de7f3ad --- /dev/null +++ b/src/i18n-keysets/component.expandable-panel.view/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_title-collapse": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_title-expand": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.expandable-panel.view/ru.json b/src/i18n-keysets/component.expandable-panel.view/ru.json new file mode 100644 index 0000000..f458247 --- /dev/null +++ b/src/i18n-keysets/component.expandable-panel.view/ru.json @@ -0,0 +1,4 @@ +{ + "button_title-collapse": "Свернуть", + "button_title-expand": "Развернуть" +} diff --git a/src/i18n-keysets/component.field-editor.view/context.json b/src/i18n-keysets/component.field-editor.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.field-editor.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.field-editor.view/en.json b/src/i18n-keysets/component.field-editor.view/en.json new file mode 100644 index 0000000..becfa8f --- /dev/null +++ b/src/i18n-keysets/component.field-editor.view/en.json @@ -0,0 +1,51 @@ +{ + "button_add-description": "Add description", + "button_cancel": "Cancel", + "button_create": "Create", + "button_manual": "Reference", + "button_no": "No", + "button_save": "Save", + "button_yes": "Yes", + "field_aggregation": "Aggregation", + "field_data-type": "Data type", + "field_description": "Description", + "field_field-name": "Name", + "field_field-type": "Field type", + "field_not-display-in-wizard": "Don't display in wizard", + "field_source": "Source", + "field_source-data": "Data source", + "field_source-field": "Source field", + "label_confirm-question-on-error": "Are you sure you want to exit?", + "label_data-will-be-lost": "Data will be lost", + "label_field-already-exist": "A field with the specified name already exists.", + "label_information-about-syntax-on-wiki": "Function Syntax Help", + "label_placeholder-field": "Field", + "label_source-is-empty": "Select a source for the field", + "label_title-is-empty": "The field name must not be empty", + "section_title": "Field settings", + "value_array_float": "Array of fractional numbers", + "value_array_int": "Array of integers", + "value_array_str": "Array of strings", + "value_auto": "Auto", + "value_avg": "Average", + "value_boolean": "Logical", + "value_count": "Count", + "value_countunique": "Count Unique", + "value_date": "Date", + "value_float": "Fractional number", + "value_formula": "Formula", + "value_function": "Functions", + "value_genericdatetime": "Date and time", + "value_geopoint": "Geopoint", + "value_geopolygon": "Geopolygon", + "value_integer": "Integer", + "value_max": "Maximum", + "value_min": "Minimum", + "value_none": "No", + "value_source-from-field": "Field from source", + "value_string": "String", + "value_sum": "Sum", + "value_tree_float": "Tree of fractional numbers", + "value_tree_int": "Tree of integers", + "value_tree_str": "Tree of strings" +} diff --git a/src/i18n-keysets/component.field-editor.view/keyset.json b/src/i18n-keysets/component.field-editor.view/keyset.json new file mode 100644 index 0000000..87505b9 --- /dev/null +++ b/src/i18n-keysets/component.field-editor.view/keyset.json @@ -0,0 +1,208 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_manual": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_no": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_yes": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_aggregation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_data-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_field-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_field-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_not-display-in-wizard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_source-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_source-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_confirm-question-on-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_data-will-be-lost": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-already-exist": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_information-about-syntax-on-wiki": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-is-empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-is-empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_array_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_array_int": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_array_str": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_auto": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_avg": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_boolean": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_count": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_countunique": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_formula": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_function": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_genericdatetime": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geopoint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geopolygon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_integer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_min": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_none": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_source-from-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_string": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_sum": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_tree_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_tree_int": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_tree_str": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.field-editor.view/ru.json b/src/i18n-keysets/component.field-editor.view/ru.json new file mode 100644 index 0000000..960009c --- /dev/null +++ b/src/i18n-keysets/component.field-editor.view/ru.json @@ -0,0 +1,51 @@ +{ + "button_add-description": "Добавить описание", + "button_cancel": "Отменить", + "button_create": "Создать", + "button_manual": "Справочник", + "button_no": "Нет", + "button_save": "Сохранить", + "button_yes": "Да", + "field_aggregation": "Агрегация", + "field_data-type": "Тип данных", + "field_description": "Описание", + "field_field-name": "Имя", + "field_field-type": "Тип поля", + "field_not-display-in-wizard": "Не отображать в визарде", + "field_source": "Источник", + "field_source-data": "Источник данных", + "field_source-field": "Поле источника", + "label_confirm-question-on-error": "Вы уверены что хотите выйти?", + "label_data-will-be-lost": "Данные будут утеряны", + "label_field-already-exist": "Поле с таким именем уже существует", + "label_information-about-syntax-on-wiki": "Справка по синтаксису функций", + "label_placeholder-field": "Поле", + "label_source-is-empty": "Для поля должен быть выбран источник", + "label_title-is-empty": "Имя поля не должно быть пустым", + "section_title": "Настройка поля", + "value_array_float": "Массив дробных чисел", + "value_array_int": "Массив целых чисел", + "value_array_str": "Массив строк", + "value_auto": "Авто", + "value_avg": "Среднее", + "value_boolean": "Логический", + "value_count": "Количество", + "value_countunique": "Количество уникальных", + "value_date": "Дата", + "value_float": "Дробное число", + "value_formula": "Формула", + "value_function": "Функция", + "value_genericdatetime": "Дата и время", + "value_geopoint": "Геоточка", + "value_geopolygon": "Геополигон", + "value_integer": "Целое число", + "value_max": "Максимум", + "value_min": "Минимум", + "value_none": "Нет", + "value_source-from-field": "Поле из источника", + "value_string": "Строка", + "value_sum": "Сумма", + "value_tree_float": "Дерево дробных чисел", + "value_tree_int": "Дерево целых чисел", + "value_tree_str": "Дерево строк" +} diff --git a/src/i18n-keysets/component.formatted-value.view/context.json b/src/i18n-keysets/component.formatted-value.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.formatted-value.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.formatted-value.view/en.json b/src/i18n-keysets/component.formatted-value.view/en.json new file mode 100644 index 0000000..a02e186 --- /dev/null +++ b/src/i18n-keysets/component.formatted-value.view/en.json @@ -0,0 +1,7 @@ +{ + "label_memory-b": "Byte", + "label_memory-gb": "GB", + "label_memory-kb": "KB", + "label_memory-mb": "MB", + "label_memory-tb": "TB" +} diff --git a/src/i18n-keysets/component.formatted-value.view/keyset.json b/src/i18n-keysets/component.formatted-value.view/keyset.json new file mode 100644 index 0000000..c2bbe3e --- /dev/null +++ b/src/i18n-keysets/component.formatted-value.view/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_memory-b": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_memory-gb": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_memory-kb": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_memory-mb": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_memory-tb": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.formatted-value.view/ru.json b/src/i18n-keysets/component.formatted-value.view/ru.json new file mode 100644 index 0000000..ca10b65 --- /dev/null +++ b/src/i18n-keysets/component.formatted-value.view/ru.json @@ -0,0 +1,7 @@ +{ + "label_memory-b": "Байт", + "label_memory-gb": "ГБ", + "label_memory-kb": "КБ", + "label_memory-mb": "МБ", + "label_memory-tb": "ТБ" +} diff --git a/src/i18n-keysets/component.iam-access-dialog/context.json b/src/i18n-keysets/component.iam-access-dialog/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.iam-access-dialog/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.iam-access-dialog/en.json b/src/i18n-keysets/component.iam-access-dialog/en.json new file mode 100644 index 0000000..9ba0aa2 --- /dev/null +++ b/src/i18n-keysets/component.iam-access-dialog/en.json @@ -0,0 +1,32 @@ +{ + "action_add-user": "Add user", + "action_cancel": "Cancel", + "action_choose-user": "Select user", + "action_choose-user-or-group": "Select user or group", + "action_close": "Close", + "action_revoke-access": "Revoke role", + "action_save": "Save", + "button_bindings-list-retry": "Reload list", + "label_access_error": "Insufficient rights", + "label_all": "All", + "label_groups": "Groups", + "label_invitee": "Invitee accounts", + "label_no-data": "Empty", + "label_object": "Object", + "label_revoke-access": "Revoke role", + "label_role": "Role", + "label_user": "User", + "label_user-accounts": "Users", + "placeholder_search": "Filter by user", + "role-entry-binding-creator": "Delegation of rights", + "role-limited-entry-binding-creator": "No delegation of rights", + "role_admin": "Admin", + "role_editor": "Editor", + "role_limited-viewer": "Limited viewer", + "role_viewer": "Viewer", + "section_direct_accesses": "Direct permissions", + "section_inherited_accesses": "Inherited permissions", + "section_revoke-access": "Are you sure you want to revoke this role from {{userName}}? The user will lose access to all objects located in {{objectName}}.", + "title_access-list": "Manage access", + "title_add-user": "Add user" +} diff --git a/src/i18n-keysets/component.iam-access-dialog/keyset.json b/src/i18n-keysets/component.iam-access-dialog/keyset.json new file mode 100644 index 0000000..6c999e9 --- /dev/null +++ b/src/i18n-keysets/component.iam-access-dialog/keyset.json @@ -0,0 +1,132 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "action_add-user": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_choose-user": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_choose-user-or-group": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_revoke-access": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_bindings-list-retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_access_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_groups": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invitee": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_object": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_revoke-access": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_role": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_user": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_user-accounts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_search": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "role-entry-binding-creator": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "role-limited-entry-binding-creator": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "role_admin": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "role_editor": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "role_limited-viewer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "role_viewer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_direct_accesses": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_inherited_accesses": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_revoke-access": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_access-list": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_add-user": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.iam-access-dialog/ru.json b/src/i18n-keysets/component.iam-access-dialog/ru.json new file mode 100644 index 0000000..00e60e4 --- /dev/null +++ b/src/i18n-keysets/component.iam-access-dialog/ru.json @@ -0,0 +1,32 @@ +{ + "action_add-user": "Добавить пользователя", + "action_cancel": "Отменить", + "action_choose-user": "Выбрать пользователя", + "action_choose-user-or-group": "Выбрать пользователя или группу", + "action_close": "Закрыть", + "action_revoke-access": "Отозвать роль", + "action_save": "Сохранить", + "button_bindings-list-retry": "Обновить список", + "label_access_error": "Недостаточно прав", + "label_all": "Все", + "label_groups": "Группы", + "label_invitee": "Приглашенные аккаунты", + "label_no-data": "Пусто", + "label_object": "Объект", + "label_revoke-access": "Отозвать роль", + "label_role": "Роль", + "label_user": "Пользователь", + "label_user-accounts": "Пользователи", + "placeholder_search": "Фильтр по пользователю", + "role-entry-binding-creator": "Делегация прав", + "role-limited-entry-binding-creator": "Без делегации прав", + "role_admin": "Администрирование", + "role_editor": "Редактирование", + "role_limited-viewer": "Ограниченный просмотр", + "role_viewer": "Просмотр", + "section_direct_accesses": "Прямые права", + "section_inherited_accesses": "Наследуемые права", + "section_revoke-access": "Вы уверены, что хотите отозвать роль у {{userName}}? Пользователь потеряет доступ ко всем объектам находящимся в {{objectName}}.", + "title_access-list": "Управление доступом", + "title_add-user": "Добавление пользователя" +} diff --git a/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/context.json b/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/en.json b/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/en.json new file mode 100644 index 0000000..20fea63 --- /dev/null +++ b/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/en.json @@ -0,0 +1,5 @@ +{ + "button_back-to-main-page": "Go to main page", + "button_switch-to-desktop": "Open desktop version", + "label_error-inaccessible-on-mobile-title": "This page is inaccessible via mobile" +} diff --git a/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/keyset.json b/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/keyset.json new file mode 100644 index 0000000..ecd959a --- /dev/null +++ b/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_back-to-main-page": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_switch-to-desktop": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-inaccessible-on-mobile-title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/ru.json b/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/ru.json new file mode 100644 index 0000000..2d9e762 --- /dev/null +++ b/src/i18n-keysets/component.inaccessible-on-mobile-inset.view/ru.json @@ -0,0 +1,5 @@ +{ + "button_back-to-main-page": "Вернуться на главную", + "button_switch-to-desktop": "Открыть десктопную версию", + "label_error-inaccessible-on-mobile-title": "Содержимое страницы недоступно в мобильной версии" +} diff --git a/src/i18n-keysets/component.list-with-menu.view/context.json b/src/i18n-keysets/component.list-with-menu.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.list-with-menu.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.list-with-menu.view/en.json b/src/i18n-keysets/component.list-with-menu.view/en.json new file mode 100644 index 0000000..cf9ce5e --- /dev/null +++ b/src/i18n-keysets/component.list-with-menu.view/en.json @@ -0,0 +1,5 @@ +{ + "button_copy": "Copy to clipboard", + "button_delete": "Delete", + "button_duplicate": "Duplicate" +} diff --git a/src/i18n-keysets/component.list-with-menu.view/keyset.json b/src/i18n-keysets/component.list-with-menu.view/keyset.json new file mode 100644 index 0000000..d3b1727 --- /dev/null +++ b/src/i18n-keysets/component.list-with-menu.view/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_duplicate": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.list-with-menu.view/ru.json b/src/i18n-keysets/component.list-with-menu.view/ru.json new file mode 100644 index 0000000..fe86846 --- /dev/null +++ b/src/i18n-keysets/component.list-with-menu.view/ru.json @@ -0,0 +1,5 @@ +{ + "button_copy": "Копировать в буфер", + "button_delete": "Удалить", + "button_duplicate": "Дублировать" +} diff --git a/src/i18n-keysets/component.migrate-to-workbook-dialog/context.json b/src/i18n-keysets/component.migrate-to-workbook-dialog/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.migrate-to-workbook-dialog/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.migrate-to-workbook-dialog/en.json b/src/i18n-keysets/component.migrate-to-workbook-dialog/en.json new file mode 100644 index 0000000..ae4c79a --- /dev/null +++ b/src/i18n-keysets/component.migrate-to-workbook-dialog/en.json @@ -0,0 +1,27 @@ +{ + "action_back": "Back", + "action_cancel": "Cancel", + "action_next": "Next", + "action_show-all": "Show all", + "label_charts": "Charts", + "label_connections": "Connections", + "label_current-entry": "Selected object", + "label_dashboards": "Dashboards", + "label_datasets": "Datasets", + "label_entry-already-in-workbook": "Migrated with other objects", + "label_linked-objects": "Linked objects", + "label_no-linked-objects": "Empty", + "label_objects": [ + "{{count}} object", + "{{count}} objects", + "{{count}} objects", + "0 objects", + "", + "" + ], + "label_unknown-error": "An error occured during migration. Please try again later.", + "md_label_copy-extension-warning": "Objects were detected that do not support copying: connection to Google Sheets or to a file (CSV). Use a transfer", + "md_label_copy-warning": "The object and all related objects will be moved from folders to the workbook. It is impossible to return them from the workbook to folders. After the transfer, you will become the administrator of the workbook and will be able to assign access rights to other users and user groups", + "title_copy": "Copy to workbook", + "title_transfer": "Transfer to workbook" +} diff --git a/src/i18n-keysets/component.migrate-to-workbook-dialog/keyset.json b/src/i18n-keysets/component.migrate-to-workbook-dialog/keyset.json new file mode 100644 index 0000000..84da4bd --- /dev/null +++ b/src/i18n-keysets/component.migrate-to-workbook-dialog/keyset.json @@ -0,0 +1,84 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "action_back": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_next": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_show-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_charts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_current-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dashboards": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_datasets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-already-in-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_linked-objects": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-linked-objects": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_objects": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unknown-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "md_label_copy-extension-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "md_label_copy-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_transfer": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.migrate-to-workbook-dialog/ru.json b/src/i18n-keysets/component.migrate-to-workbook-dialog/ru.json new file mode 100644 index 0000000..6e25345 --- /dev/null +++ b/src/i18n-keysets/component.migrate-to-workbook-dialog/ru.json @@ -0,0 +1,27 @@ +{ + "action_back": "Назад", + "action_cancel": "Отменить", + "action_next": "Далее", + "action_show-all": "Показать все", + "label_charts": "Чарты", + "label_connections": "Подключения", + "label_current-entry": "Выбранный объект", + "label_dashboards": "Дашборды", + "label_datasets": "Датасеты", + "label_entry-already-in-workbook": "Перенесен с другими объектами", + "label_linked-objects": "Связанные объекты", + "label_no-linked-objects": "Пусто", + "label_objects": [ + "{{count}} объект", + "{{count}} объекта", + "{{count}} объектов", + "0 объектов", + "", + "" + ], + "label_unknown-error": "При переносе возникла ошибка. Пожалуйста, повторите попытку позже.", + "md_label_copy-extension-warning": "Обнаружены объекты, для которых копирование не поддержано: подключение к Google Sheets или к файлу (CSV). Воспользуйтесь переносом.", + "md_label_copy-warning": "Объект и все связанные объекты будут перенесены из папок в воркбук. Вернуть объекты из воркбука в папки невозможно. После переноса вы станете администратором воркбука и сможете назначать права доступа для других пользователей и групп пользователей", + "title_copy": "Копирование в воркбук", + "title_transfer": "Перенос в воркбук" +} diff --git a/src/i18n-keysets/component.monaco.view/context.json b/src/i18n-keysets/component.monaco.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.monaco.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.monaco.view/en.json b/src/i18n-keysets/component.monaco.view/en.json new file mode 100644 index 0000000..b08f086 --- /dev/null +++ b/src/i18n-keysets/component.monaco.view/en.json @@ -0,0 +1,3 @@ +{ + "label_failed-to-load": "Failed to load editor" +} diff --git a/src/i18n-keysets/component.monaco.view/keyset.json b/src/i18n-keysets/component.monaco.view/keyset.json new file mode 100644 index 0000000..e3e71c8 --- /dev/null +++ b/src/i18n-keysets/component.monaco.view/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_failed-to-load": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.monaco.view/ru.json b/src/i18n-keysets/component.monaco.view/ru.json new file mode 100644 index 0000000..49256df --- /dev/null +++ b/src/i18n-keysets/component.monaco.view/ru.json @@ -0,0 +1,3 @@ +{ + "label_failed-to-load": "Не удалось загрузить редактор" +} diff --git a/src/i18n-keysets/component.navigation-prompt/context.json b/src/i18n-keysets/component.navigation-prompt/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.navigation-prompt/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.navigation-prompt/en.json b/src/i18n-keysets/component.navigation-prompt/en.json new file mode 100644 index 0000000..a8f628a --- /dev/null +++ b/src/i18n-keysets/component.navigation-prompt/en.json @@ -0,0 +1,3 @@ +{ + "label_prompt-message": "There are unsaved changes on the page. Are you sure?" +} diff --git a/src/i18n-keysets/component.navigation-prompt/keyset.json b/src/i18n-keysets/component.navigation-prompt/keyset.json new file mode 100644 index 0000000..52cb123 --- /dev/null +++ b/src/i18n-keysets/component.navigation-prompt/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_prompt-message": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.navigation-prompt/ru.json b/src/i18n-keysets/component.navigation-prompt/ru.json new file mode 100644 index 0000000..b98a784 --- /dev/null +++ b/src/i18n-keysets/component.navigation-prompt/ru.json @@ -0,0 +1,3 @@ +{ + "label_prompt-message": "На странице есть несохраненные изменения. Вы уверены?" +} diff --git a/src/i18n-keysets/component.navigation.view/context.json b/src/i18n-keysets/component.navigation.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.navigation.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.navigation.view/en.json b/src/i18n-keysets/component.navigation.view/en.json new file mode 100644 index 0000000..0f11bb8 --- /dev/null +++ b/src/i18n-keysets/component.navigation.view/en.json @@ -0,0 +1,82 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel", + "button_close": "Close", + "button_continue-anyway": "Continue anyway", + "button_create": "Create", + "button_create-connection": "Create connection", + "button_create-dashboard": "Create dashboard", + "button_create-dataset": "Create dataset", + "button_create-folder": "Create folder", + "button_create-widget": "Create chart", + "button_delete": "Delete", + "button_details": "Details", + "button_done": "Done", + "button_goto-folder": "Go to the folder", + "button_move": "Move", + "button_request-rights": "Request permissions", + "button_retry": "Retry", + "label-description_empty-connections": "Connect your data sources: databases, files, and services", + "label-description_empty-dashboards": "Build pages with collections of charts, tables, and filters", + "label-description_empty-datasets": "Create datasets based on your connections", + "label-description_empty-reports": "Collect documents for presentation, export, or printing", + "label-description_empty-widgets": "Visualize data as charts, tables, or maps", + "label_access-denied-folder": "Access denied", + "label_batch-move-access-error-description": "Insufficient permissions to move files. Contact file administrator.", + "label_batch-move-success-description": "Files moved successfully", + "label_batch-selected-entries-count": "Selected objects — {{value}}", + "label_empty": "Empty", + "label_empty-connections": "Create your first connection", + "label_empty-dashboards": "Create your first dashboard", + "label_empty-datasets": "Create your first dataset", + "label_empty-favorites": "No favorites yet", + "label_empty-folder": "There is nothing in the folder yet", + "label_empty-reports": "Create your first report", + "label_empty-widgets": "Create your first chart", + "label_failed-load-navigation": "Failed to load navigation", + "label_mobile-navigation-filters-all": "All", + "label_mobile-navigation-filters-favorites": "Favorites", + "label_mobile-navigation-filters-mine": "Mine", + "label_not-found": "Nothing found", + "label_original-name": "Original name", + "label_placeholder-filter-by-name": "Filter by name", + "label_placeholder-search": "Search", + "label_quick-access": "Quick access", + "label_table-column-author": "Author", + "label_table-column-date": "Date", + "label_table-column-entry-name": "Name", + "section_batch-move-access-error": "Access error", + "section_batch-move-error": "Error moving", + "section_batch-move-progress": "Move files …", + "section_batch-move-select-destination": "Where to move?", + "section_sidebar-title": "Navigation", + "switch_connections": "Connections", + "switch_dashboards": "Dashboards", + "switch_datasets": "Datasets", + "switch_favorites": "Favorites", + "switch_latest": "Latest", + "switch_personal-folder": "Personal directory", + "switch_root": "All objects", + "switch_widgets": "Charts", + "toast_failed-add-favorite": "Failed to add to favorites", + "toast_failed-remove-favorite": "Failed to delete from favorites", + "tooltip_table-move-denied": "Only objects in folders can be moved", + "value_create-connection": "Connection", + "value_create-dashboard": "Dashboard", + "value_create-dashboard-new": "Dashboard (new)", + "value_create-dataset": "Dataset", + "value_create-editor": "Chart in Editor", + "value_create-folder": "Directory", + "value_create-monitoringql": "Chart Monitoring", + "value_create-promql": "Chart Prometheus", + "value_create-ql": "QL Chart", + "value_create-sql": "Chart SQL", + "value_create-widget": "Chart", + "value_create-wizard": "Chart in Wizard", + "value_date-ascending": "First old", + "value_date-descending": "First new", + "value_name-ascending": "A-Z", + "value_name-descending": "Z-A", + "value_ownership-all": "All", + "value_ownership-only-mine": "Only mine" +} diff --git a/src/i18n-keysets/component.navigation.view/keyset.json b/src/i18n-keysets/component.navigation.view/keyset.json new file mode 100644 index 0000000..5790c68 --- /dev/null +++ b/src/i18n-keysets/component.navigation.view/keyset.json @@ -0,0 +1,332 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_continue-anyway": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-folder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_details": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_done": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_goto-folder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_move": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_request-rights": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-description_empty-connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-description_empty-dashboards": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-description_empty-datasets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-description_empty-reports": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-description_empty-widgets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_access-denied-folder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_batch-move-access-error-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_batch-move-success-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_batch-selected-entries-count": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-dashboards": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-datasets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-favorites": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-folder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-reports": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-widgets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_failed-load-navigation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mobile-navigation-filters-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mobile-navigation-filters-favorites": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mobile-navigation-filters-mine": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_not-found": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_original-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-filter-by-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-search": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_quick-access": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_table-column-author": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_table-column-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_table-column-entry-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_batch-move-access-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_batch-move-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_batch-move-progress": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_batch-move-select-destination": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_sidebar-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_dashboards": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_datasets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_favorites": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_latest": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_personal-folder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_root": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_widgets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_failed-add-favorite": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_failed-remove-favorite": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_table-move-denied": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-dashboard-new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-editor": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-folder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-monitoringql": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-promql": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-ql": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-sql": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_create-wizard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date-ascending": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date-descending": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_name-ascending": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_name-descending": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_ownership-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_ownership-only-mine": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.navigation.view/ru.json b/src/i18n-keysets/component.navigation.view/ru.json new file mode 100644 index 0000000..3db698c --- /dev/null +++ b/src/i18n-keysets/component.navigation.view/ru.json @@ -0,0 +1,82 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отменить", + "button_close": "Закрыть", + "button_continue-anyway": "Все равно продолжить", + "button_create": "Создать", + "button_create-connection": "Создать подключение", + "button_create-dashboard": "Создать дашборд", + "button_create-dataset": "Создать датасет", + "button_create-folder": "Создать папку", + "button_create-widget": "Создать чарт", + "button_delete": "Удалить", + "button_details": "Подробнее", + "button_done": "Готово", + "button_goto-folder": "Перейти к папке", + "button_move": "Переместить", + "button_request-rights": "Запросить права", + "button_retry": "Повторить", + "label-description_empty-connections": "Подключайте свои источники данных: базы данных, файлы и сервисы", + "label-description_empty-dashboards": "Создавайте страницы с наборами диаграмм, таблиц и фильтров", + "label-description_empty-datasets": "Формируйте наборы данных на основе подключений", + "label-description_empty-reports": "Собирайте документы для презентации, экспорта или печати", + "label-description_empty-widgets": "Визуализируйте данные в виде диаграммы, таблицы или карты", + "label_access-denied-folder": "Доступ запрещён", + "label_batch-move-access-error-description": "Недостаточно прав для перемещения файлов. Обратитесь к администратору файлов.", + "label_batch-move-success-description": "Файлы успешно перемещены", + "label_batch-selected-entries-count": "Выбрано объектов — {{value}}", + "label_empty": "Пусто", + "label_empty-connections": "Создайте ваше первое подключение", + "label_empty-dashboards": "Создайте ваш первый дашборд", + "label_empty-datasets": "Создайте ваш первый датасет", + "label_empty-favorites": "В избранном пока ничего нет", + "label_empty-folder": "В папке пока ничего нет", + "label_empty-reports": "Создайте ваш первый отчёт", + "label_empty-widgets": "Создайте ваш первый чарт", + "label_failed-load-navigation": "Не удалось загрузить навигацию", + "label_mobile-navigation-filters-all": "Все", + "label_mobile-navigation-filters-favorites": "Избранные", + "label_mobile-navigation-filters-mine": "Мои", + "label_not-found": "Ничего не найдено", + "label_original-name": "Оригинальное имя", + "label_placeholder-filter-by-name": "Фильтр по имени", + "label_placeholder-search": "Поиск", + "label_quick-access": "Быстрый доступ", + "label_table-column-author": "Автор", + "label_table-column-date": "Дата", + "label_table-column-entry-name": "Название", + "section_batch-move-access-error": "Ошибка доступа", + "section_batch-move-error": "Ошибка перемещения", + "section_batch-move-progress": "Перемещение файлов ...", + "section_batch-move-select-destination": "Куда переместить?", + "section_sidebar-title": "Навигация", + "switch_connections": "Подключения", + "switch_dashboards": "Дашборды", + "switch_datasets": "Датасеты", + "switch_favorites": "Избранное", + "switch_latest": "Последние", + "switch_personal-folder": "Личная папка", + "switch_root": "Все объекты", + "switch_widgets": "Чарты", + "toast_failed-add-favorite": "Не удалось добавить в избранное", + "toast_failed-remove-favorite": "Не удалось удалить из избранного", + "tooltip_table-move-denied": "Перемещение доступно только для объектов из папок", + "value_create-connection": "Подключение", + "value_create-dashboard": "Дашборд", + "value_create-dashboard-new": "Дашборд (new)", + "value_create-dataset": "Датасет", + "value_create-editor": "Чарт в Editor", + "value_create-folder": "Папку", + "value_create-monitoringql": "Чарт Monitoring", + "value_create-promql": "Чарт Prometheus", + "value_create-ql": "QL-чарт", + "value_create-sql": "Чарт SQL", + "value_create-widget": "Чарт", + "value_create-wizard": "Чарт в Wizard", + "value_date-ascending": "Сначала старые", + "value_date-descending": "Сначала новые", + "value_name-ascending": "А-Я", + "value_name-descending": "Я-А", + "value_ownership-all": "Все", + "value_ownership-only-mine": "Только мои" +} diff --git a/src/i18n-keysets/component.need-reset-dialog.view/context.json b/src/i18n-keysets/component.need-reset-dialog.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.need-reset-dialog.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.need-reset-dialog.view/en.json b/src/i18n-keysets/component.need-reset-dialog.view/en.json new file mode 100644 index 0000000..f1cb844 --- /dev/null +++ b/src/i18n-keysets/component.need-reset-dialog.view/en.json @@ -0,0 +1,5 @@ +{ + "button_apply": "Refresh", + "label_description": "It looks like you have performed logout in another tab, try refreshing this tab", + "label_title": "Need refresh" +} diff --git a/src/i18n-keysets/component.need-reset-dialog.view/keyset.json b/src/i18n-keysets/component.need-reset-dialog.view/keyset.json new file mode 100644 index 0000000..ed16afc --- /dev/null +++ b/src/i18n-keysets/component.need-reset-dialog.view/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.need-reset-dialog.view/ru.json b/src/i18n-keysets/component.need-reset-dialog.view/ru.json new file mode 100644 index 0000000..2b51f9d --- /dev/null +++ b/src/i18n-keysets/component.need-reset-dialog.view/ru.json @@ -0,0 +1,5 @@ +{ + "button_apply": "Перезагрузить страницу", + "label_description": "Похоже вы разлогинились, попробуйте перезагрузить страницу", + "label_title": "Необходима перезагрузка" +} diff --git a/src/i18n-keysets/component.number-field-formatting.view/context.json b/src/i18n-keysets/component.number-field-formatting.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.number-field-formatting.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.number-field-formatting.view/en.json b/src/i18n-keysets/component.number-field-formatting.view/en.json new file mode 100644 index 0000000..55603b2 --- /dev/null +++ b/src/i18n-keysets/component.number-field-formatting.view/en.json @@ -0,0 +1,18 @@ +{ + "field_format": "Format", + "field_postfix": "Postfix", + "field_precision": "Precision", + "field_prefix": "Prefix", + "field_rank-delimiter": "Show thousands separator", + "field_units": "Units", + "value_format-number": "Number", + "value_format-percent": "Percent", + "value_rank-delimiter-hide": "Hide", + "value_rank-delimiter-show": "Show", + "value_unit-auto": "Auto", + "value_unit-b": "Billions, B", + "value_unit-k": "Thousands, K", + "value_unit-m": "Millions, M", + "value_unit-null": "Null", + "value_unit-t": "Trillions, T" +} diff --git a/src/i18n-keysets/component.number-field-formatting.view/keyset.json b/src/i18n-keysets/component.number-field-formatting.view/keyset.json new file mode 100644 index 0000000..7e09a4f --- /dev/null +++ b/src/i18n-keysets/component.number-field-formatting.view/keyset.json @@ -0,0 +1,76 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "field_format": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_postfix": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_precision": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_prefix": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_rank-delimiter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_units": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_format-number": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_format-percent": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_rank-delimiter-hide": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_rank-delimiter-show": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_unit-auto": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_unit-b": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_unit-k": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_unit-m": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_unit-null": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_unit-t": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.number-field-formatting.view/ru.json b/src/i18n-keysets/component.number-field-formatting.view/ru.json new file mode 100644 index 0000000..e21f121 --- /dev/null +++ b/src/i18n-keysets/component.number-field-formatting.view/ru.json @@ -0,0 +1,18 @@ +{ + "field_format": "Формат", + "field_postfix": "Постфикс", + "field_precision": "Знаков после запятой", + "field_prefix": "Префикс", + "field_rank-delimiter": "Отображать группы разрядов", + "field_units": "Размерность", + "value_format-number": "Число", + "value_format-percent": "Процент", + "value_rank-delimiter-hide": "Слитно", + "value_rank-delimiter-show": "С разделителем", + "value_unit-auto": "Авто", + "value_unit-b": "Миллиарды, B", + "value_unit-k": "Тысячи, K", + "value_unit-m": "Миллионы, M", + "value_unit-null": "Нет", + "value_unit-t": "Триллионы, T" +} diff --git a/src/i18n-keysets/component.operations/context.json b/src/i18n-keysets/component.operations/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.operations/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.operations/en.json b/src/i18n-keysets/component.operations/en.json new file mode 100644 index 0000000..2dfb38d --- /dev/null +++ b/src/i18n-keysets/component.operations/en.json @@ -0,0 +1,27 @@ +{ + "label_operation-all-values": "No selected values", + "label_operation-contains": "Contains (case-insensetive)", + "label_operation-contains-case": "Contain (case-sensetive)", + "label_operation-date-in": "Belongs to a range", + "label_operation-endswith": "Ends with (case-insensetive)", + "label_operation-endswith-case": "Ends with (case-sensetive)", + "label_operation-equals": "Equal to", + "label_operation-gt": "Greater than", + "label_operation-gte": "Greater than or equal to", + "label_operation-in": "Belongs to a set", + "label_operation-is-not-null": "Not empty", + "label_operation-is-null": "Empty", + "label_operation-leneq": "Length equal to", + "label_operation-lengt": "Length greater than", + "label_operation-lengte": "Length greater than or equal to", + "label_operation-lenlt": "Length less than", + "label_operation-lenlte": "Length less than or equal to", + "label_operation-lt": "Less than", + "label_operation-lte": "Less than or equal to", + "label_operation-nequals": "Not equal to", + "label_operation-nin": "Does not belong to a set", + "label_operation-notcontains": "Does not contain (case-insensetive)", + "label_operation-notcontains-case": "Does not contain (case-sensetive)", + "label_operation-startswith": "Starts with (case-insensetive)", + "label_operation-startswith-case": "Starts with (case-sensetive)" +} diff --git a/src/i18n-keysets/component.operations/keyset.json b/src/i18n-keysets/component.operations/keyset.json new file mode 100644 index 0000000..3d35a09 --- /dev/null +++ b/src/i18n-keysets/component.operations/keyset.json @@ -0,0 +1,112 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_operation-all-values": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-contains": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-contains-case": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-date-in": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-endswith": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-endswith-case": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-equals": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-gt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-gte": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-in": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-is-not-null": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-is-null": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-leneq": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-lengt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-lengte": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-lenlt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-lenlte": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-lt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-lte": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-nequals": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-nin": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-notcontains": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-notcontains-case": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-startswith": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_operation-startswith-case": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.operations/ru.json b/src/i18n-keysets/component.operations/ru.json new file mode 100644 index 0000000..a964a9a --- /dev/null +++ b/src/i18n-keysets/component.operations/ru.json @@ -0,0 +1,27 @@ +{ + "label_operation-all-values": "Нет выбранных значений", + "label_operation-contains": "Содержит (без учета регистра)", + "label_operation-contains-case": "Содержит (с учетом регистра)", + "label_operation-date-in": "Принадлежит диапазону", + "label_operation-endswith": "Заканчивается на (без учета регистра)", + "label_operation-endswith-case": "Заканчивается на (с учетом регистра)", + "label_operation-equals": "Равно", + "label_operation-gt": "Больше", + "label_operation-gte": "Больше или равно", + "label_operation-in": "Принадлежит множеству", + "label_operation-is-not-null": "Не пусто", + "label_operation-is-null": "Пусто", + "label_operation-leneq": "Длина равна", + "label_operation-lengt": "Длина больше", + "label_operation-lengte": "Длина больше или равна", + "label_operation-lenlt": "Длина меньше", + "label_operation-lenlte": "Длина меньше или равна", + "label_operation-lt": "Меньше", + "label_operation-lte": "Меньше или равно", + "label_operation-nequals": "Не равно", + "label_operation-nin": "Не принадлежит множеству", + "label_operation-notcontains": "Не содержит (без учета регистра)", + "label_operation-notcontains-case": "Не содержит (с учетом регистра)", + "label_operation-startswith": "Начинается на (без учета регистра)", + "label_operation-startswith-case": "Начинается на (с учетом регистра)" +} diff --git a/src/i18n-keysets/component.path-select.view/context.json b/src/i18n-keysets/component.path-select.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.path-select.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.path-select.view/en.json b/src/i18n-keysets/component.path-select.view/en.json new file mode 100644 index 0000000..db81f0f --- /dev/null +++ b/src/i18n-keysets/component.path-select.view/en.json @@ -0,0 +1,3 @@ +{ + "label_default-placeholder": "Name" +} diff --git a/src/i18n-keysets/component.path-select.view/keyset.json b/src/i18n-keysets/component.path-select.view/keyset.json new file mode 100644 index 0000000..6ae9d70 --- /dev/null +++ b/src/i18n-keysets/component.path-select.view/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_default-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.path-select.view/ru.json b/src/i18n-keysets/component.path-select.view/ru.json new file mode 100644 index 0000000..c132f9c --- /dev/null +++ b/src/i18n-keysets/component.path-select.view/ru.json @@ -0,0 +1,3 @@ +{ + "label_default-placeholder": "Название" +} diff --git a/src/i18n-keysets/component.permission-select.view/context.json b/src/i18n-keysets/component.permission-select.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.permission-select.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.permission-select.view/en.json b/src/i18n-keysets/component.permission-select.view/en.json new file mode 100644 index 0000000..efde121 --- /dev/null +++ b/src/i18n-keysets/component.permission-select.view/en.json @@ -0,0 +1,7 @@ +{ + "field_admin": "Admin", + "field_execute": "Execute", + "field_none": "—", + "field_read": "Read", + "field_write": "Write" +} diff --git a/src/i18n-keysets/component.permission-select.view/keyset.json b/src/i18n-keysets/component.permission-select.view/keyset.json new file mode 100644 index 0000000..ef76fec --- /dev/null +++ b/src/i18n-keysets/component.permission-select.view/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "field_admin": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_execute": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_none": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_read": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_write": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.permission-select.view/ru.json b/src/i18n-keysets/component.permission-select.view/ru.json new file mode 100644 index 0000000..5bdf72e --- /dev/null +++ b/src/i18n-keysets/component.permission-select.view/ru.json @@ -0,0 +1,7 @@ +{ + "field_admin": "Администрирование", + "field_execute": "Исполнение", + "field_none": "—", + "field_read": "Просмотр", + "field_write": "Редактирование" +} diff --git a/src/i18n-keysets/component.relative-dates-picker.view/context.json b/src/i18n-keysets/component.relative-dates-picker.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.relative-dates-picker.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.relative-dates-picker.view/en.json b/src/i18n-keysets/component.relative-dates-picker.view/en.json new file mode 100644 index 0000000..64295ae --- /dev/null +++ b/src/i18n-keysets/component.relative-dates-picker.view/en.json @@ -0,0 +1,104 @@ +{ + "label_absolute-date": "Exact date", + "label_end-control-title": "End", + "label_include-current-day": "Including the current day", + "label_invalid-date": "Invalid date", + "label_invalid-range": "The end date precedes the start date", + "label_preset": "Preset", + "label_preset-last_-0_days": "Today", + "label_preset-last_-0_days-range": "Today", + "label_preset-last_-13_days": "14 days ago", + "label_preset-last_-13_days-range": "Last 14 days", + "label_preset-last_-179_days": "180 days ago", + "label_preset-last_-179_days-range": "Last 180 days", + "label_preset-last_-1_days": "Yesterday", + "label_preset-last_-1_days-range": "Yesterday", + "label_preset-last_-27_days": "28 days ago", + "label_preset-last_-27_days-range": "Last 28 days", + "label_preset-last_-2_days": "3 days ago", + "label_preset-last_-2_days-range": "Last 3 days", + "label_preset-last_-364_days": "365 days ago", + "label_preset-last_-364_days-range": "Last 365 days", + "label_preset-last_-6_days": "7 days ago", + "label_preset-last_-6_days-range": "Last 7 days", + "label_preset-last_-89_days": "90 days ago", + "label_preset-last_-89_days-range": "Last 90 days", + "label_relative-date": "Offset from the current date", + "label_scale-M": [ + "Month", + "Months", + "Months", + "Months", + "", + "" + ], + "label_scale-Q": [ + "Quarter", + "Quarters", + "Quarters", + "Quarters", + "", + "" + ], + "label_scale-d": [ + "Day", + "Days", + "Days", + "Days", + "", + "" + ], + "label_scale-h": [ + "Hour", + "Hours", + "Hours", + "Hours", + "", + "" + ], + "label_scale-m": [ + "Minute", + "Minutes", + "Minutes", + "Minutes", + "", + "" + ], + "label_scale-ms": [ + "Millisecond", + "Milliseconds", + "Milliseconds", + "Milliseconds", + "", + "" + ], + "label_scale-s": [ + "Second", + "Seconds", + "Seconds", + "Seconds", + "", + "" + ], + "label_scale-w": [ + "Week", + "Weeks", + "Weeks", + "Weeks", + "", + "" + ], + "label_scale-y": [ + "Year", + "Years", + "Years", + "Years", + "", + "" + ], + "label_sign+": "Plus", + "label_sign-": "Minus", + "label_single-control-title": "Value", + "label_start-control-title": "Start", + "label_tooltip-utc": "The date is specified in UTC" +} diff --git a/src/i18n-keysets/component.relative-dates-picker.view/keyset.json b/src/i18n-keysets/component.relative-dates-picker.view/keyset.json new file mode 100644 index 0000000..e174550 --- /dev/null +++ b/src/i18n-keysets/component.relative-dates-picker.view/keyset.json @@ -0,0 +1,168 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_absolute-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_end-control-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_include-current-day": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-0_days": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-0_days-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-13_days": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-13_days-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-179_days": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-179_days-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-1_days": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-1_days-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-27_days": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-27_days-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-2_days": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-2_days-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-364_days": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-364_days-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-6_days": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-6_days-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-89_days": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preset-last_-89_days-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_relative-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-M": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-Q": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-d": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-h": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-m": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-ms": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-s": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-w": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-y": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sign+": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sign-": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_single-control-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_start-control-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tooltip-utc": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.relative-dates-picker.view/ru.json b/src/i18n-keysets/component.relative-dates-picker.view/ru.json new file mode 100644 index 0000000..7869311 --- /dev/null +++ b/src/i18n-keysets/component.relative-dates-picker.view/ru.json @@ -0,0 +1,104 @@ +{ + "label_absolute-date": "Точная дата", + "label_end-control-title": "Конец", + "label_include-current-day": "Включая текущий день", + "label_invalid-date": "Недопустимая дата", + "label_invalid-range": "Дата окончания предшествует дате начала", + "label_preset": "Пресет", + "label_preset-last_-0_days": "Сегодня", + "label_preset-last_-0_days-range": "Сегодня", + "label_preset-last_-13_days": "14 дней назад", + "label_preset-last_-13_days-range": "Последние 14 дней", + "label_preset-last_-179_days": "180 дней назад", + "label_preset-last_-179_days-range": "Последние 180 дней", + "label_preset-last_-1_days": "Вчера", + "label_preset-last_-1_days-range": "Вчера", + "label_preset-last_-27_days": "28 дней назад", + "label_preset-last_-27_days-range": "Последние 28 дней", + "label_preset-last_-2_days": "3 дня назад", + "label_preset-last_-2_days-range": "Последние 3 дня", + "label_preset-last_-364_days": "365 дней назад", + "label_preset-last_-364_days-range": "Последние 365 дней", + "label_preset-last_-6_days": "7 дней назад", + "label_preset-last_-6_days-range": "Последниe 7 дней", + "label_preset-last_-89_days": "90 дней назад", + "label_preset-last_-89_days-range": "Последние 90 дней", + "label_relative-date": "Смещение от текущей даты", + "label_scale-M": [ + "Месяц", + "Месяца", + "Месяцев", + "Месяцев", + "", + "" + ], + "label_scale-Q": [ + "Квартал", + "Квартала", + "Кварталов", + "Кварталов", + "", + "" + ], + "label_scale-d": [ + "День", + "Дня", + "Дней", + "Дней", + "", + "" + ], + "label_scale-h": [ + "Час", + "Часа", + "Часов", + "Часов", + "", + "" + ], + "label_scale-m": [ + "Минута", + "Минуты", + "Минут", + "Минут", + "", + "" + ], + "label_scale-ms": [ + "Миллисекунда", + "Миллисекунды", + "Миллисекунд", + "Миллисекунд", + "", + "" + ], + "label_scale-s": [ + "Секунда", + "Секунды", + "Секунд", + "Секунд", + "", + "" + ], + "label_scale-w": [ + "Неделя", + "Недели", + "Недель", + "Недель", + "", + "" + ], + "label_scale-y": [ + "Год", + "Года", + "Лет", + "Лет", + "", + "" + ], + "label_sign+": "Плюс", + "label_sign-": "Минус", + "label_single-control-title": "Значение", + "label_start-control-title": "Начало", + "label_tooltip-utc": "Дата указана в UTC" +} diff --git a/src/i18n-keysets/component.revisions-panel.view/context.json b/src/i18n-keysets/component.revisions-panel.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.revisions-panel.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.revisions-panel.view/en.json b/src/i18n-keysets/component.revisions-panel.view/en.json new file mode 100644 index 0000000..5fbffc1 --- /dev/null +++ b/src/i18n-keysets/component.revisions-panel.view/en.json @@ -0,0 +1,30 @@ +{ + "button_cancel": "Cancel", + "button_confirm-deprecation": "Update", + "button_edit-anyway": "Edit anyway", + "button_make-actual": "Make actual", + "button_open-actual": "Open actual", + "button_open-draft": "Open draft", + "button_open-revisions": "Show in history", + "context-menu_copy-id": "Copy ID", + "label_already-editing-entry": "This {{scope}} is already being edited", + "label_by": "by", + "label_chart": "chart", + "label_connection": "connection", + "label_dash": "dashboard", + "label_dataset": "dataset", + "label_draft-version": "This is a draft version", + "label_editor": "editor", + "label_entry-is-editing": "{{scope}} is being edited", + "label_entry-is-editing-by": "is already editing this {{scope}}", + "label_later-warning-text": "The change history contains a draft version of the {{scope}}", + "label_not-actual": "This is not actual version", + "label_of-chart": "of chart", + "label_of-connection": "of connection", + "label_of-dash": "of dashboard", + "label_of-dataset": "of dataset", + "label_of-editor": "of editor", + "label_unexpected-error": "Error", + "toast_copy-error": "Copy error", + "toast_copy-id-success": "ID copied to clipboard" +} diff --git a/src/i18n-keysets/component.revisions-panel.view/keyset.json b/src/i18n-keysets/component.revisions-panel.view/keyset.json new file mode 100644 index 0000000..1d337dc --- /dev/null +++ b/src/i18n-keysets/component.revisions-panel.view/keyset.json @@ -0,0 +1,124 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_confirm-deprecation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-anyway": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_make-actual": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_open-actual": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_open-draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_open-revisions": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context-menu_copy-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_already-editing-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_by": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dash": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_draft-version": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_editor": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-is-editing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-is-editing-by": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_later-warning-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_not-actual": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_of-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_of-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_of-dash": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_of-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_of-editor": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unexpected-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_copy-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_copy-id-success": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.revisions-panel.view/ru.json b/src/i18n-keysets/component.revisions-panel.view/ru.json new file mode 100644 index 0000000..ed45afe --- /dev/null +++ b/src/i18n-keysets/component.revisions-panel.view/ru.json @@ -0,0 +1,30 @@ +{ + "button_cancel": "Отмена", + "button_confirm-deprecation": "Обновить", + "button_edit-anyway": "Все равно редактировать", + "button_make-actual": "Сделать актуальной", + "button_open-actual": "Открыть актуальную", + "button_open-draft": "Открыть черновик", + "button_open-revisions": "Показать в истории", + "context-menu_copy-id": "Копировать ID", + "label_already-editing-entry": "Этот {{scope}} уже редактируется", + "label_by": "от", + "label_chart": "чарт", + "label_connection": "подключение", + "label_dash": "дашборд", + "label_dataset": "датасет", + "label_draft-version": "Это черновая версия", + "label_editor": "редактор", + "label_entry-is-editing": "{{scope}} уже редактируется", + "label_entry-is-editing-by": "уже редактирует этот {{scope}}", + "label_later-warning-text": "История изменений содержит черновую версию {{scope}}", + "label_not-actual": "Это неактуальная версия", + "label_of-chart": "чарта", + "label_of-connection": "подключения", + "label_of-dash": "дашборда", + "label_of-dataset": "датасета", + "label_of-editor": "редактора", + "label_unexpected-error": "Ошибка", + "toast_copy-error": "При копировании произошла ошибка", + "toast_copy-id-success": "ID скопирован в буфер обмена" +} diff --git a/src/i18n-keysets/component.select-migration-to-workbook-dialog/context.json b/src/i18n-keysets/component.select-migration-to-workbook-dialog/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.select-migration-to-workbook-dialog/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.select-migration-to-workbook-dialog/en.json b/src/i18n-keysets/component.select-migration-to-workbook-dialog/en.json new file mode 100644 index 0000000..aaeee15 --- /dev/null +++ b/src/i18n-keysets/component.select-migration-to-workbook-dialog/en.json @@ -0,0 +1,19 @@ +{ + "action_cancel": "Cancel", + "action_сhoose": "Choose", + "description_copy": "Copy all objects with links. Original objects remain in directories.", + "description_transfer": "Transfer all recursively linked object into one common workbook.", + "label": "NEW", + "list_first_item_copy": "Only search links for current object", + "list_first_item_transfer": "Recursive search of all links", + "list_first_item_transfer_help_text": "Recursive link search for the current object and all linked objects: “down“ to the connection and “up“ to the dashboard", + "list_fourth_item_copy": "Currently unavailable for CSV and Google Sheets", + "list_fourth_item_transfer": "Available for CSV and Google Sheets", + "list_second_item_copy": "Objects in directories remain", + "list_second_item_transfer": "Objects in directories will not remain", + "list_third_item_copy": "Links to objects without changes", + "list_third_item_transfer": "Links to objects lead to workbooks", + "title": "Choose a method of migrating to the workbook", + "title_copy": "Copy", + "title_transfer": "Transfer" +} diff --git a/src/i18n-keysets/component.select-migration-to-workbook-dialog/keyset.json b/src/i18n-keysets/component.select-migration-to-workbook-dialog/keyset.json new file mode 100644 index 0000000..0299a33 --- /dev/null +++ b/src/i18n-keysets/component.select-migration-to-workbook-dialog/keyset.json @@ -0,0 +1,80 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "action_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_сhoose": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "description_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "description_transfer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list_first_item_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list_first_item_transfer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list_first_item_transfer_help_text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list_fourth_item_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list_fourth_item_transfer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list_second_item_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list_second_item_transfer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list_third_item_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "list_third_item_transfer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_transfer": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.select-migration-to-workbook-dialog/ru.json b/src/i18n-keysets/component.select-migration-to-workbook-dialog/ru.json new file mode 100644 index 0000000..88f39c8 --- /dev/null +++ b/src/i18n-keysets/component.select-migration-to-workbook-dialog/ru.json @@ -0,0 +1,19 @@ +{ + "action_cancel": "Отменить", + "action_сhoose": "Выбрать", + "description_copy": "Копирование объекта со связями. Оригиналы объектов останутся в папках.", + "description_transfer": "Перенос всех рекурсивно связанных объектов в один общий воркбук.", + "label": "NEW", + "list_first_item_copy": "Поиск связей только для текущего объекта", + "list_first_item_transfer": "Рекурсивный поиск всех связей", + "list_first_item_transfer_help_text": "Рекурсивный поиск связей для текущего объекта и всех связанных объектов: “вниз“ до подключения и “вверх“ до дашборда", + "list_fourth_item_copy": "Пока недоступно для CSV и Google Sheets", + "list_fourth_item_transfer": "Доступно для CSV и Google Sheets", + "list_second_item_copy": "Объекты в папках остаются", + "list_second_item_transfer": "Объекты в папках не остаются", + "list_third_item_copy": "Ссылки на объекты без изменений", + "list_third_item_transfer": "Ссылки на объекты ведут в воркбуки", + "title": "Выберите способ миграции в воркбук", + "title_copy": "Копирование", + "title_transfer": "Перенос" +} diff --git a/src/i18n-keysets/component.toaster.view/context.json b/src/i18n-keysets/component.toaster.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.toaster.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.toaster.view/en.json b/src/i18n-keysets/component.toaster.view/en.json new file mode 100644 index 0000000..4d842f6 --- /dev/null +++ b/src/i18n-keysets/component.toaster.view/en.json @@ -0,0 +1,3 @@ +{ + "label_details": "Details" +} diff --git a/src/i18n-keysets/component.toaster.view/keyset.json b/src/i18n-keysets/component.toaster.view/keyset.json new file mode 100644 index 0000000..86cb3f2 --- /dev/null +++ b/src/i18n-keysets/component.toaster.view/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_details": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.toaster.view/ru.json b/src/i18n-keysets/component.toaster.view/ru.json new file mode 100644 index 0000000..7189405 --- /dev/null +++ b/src/i18n-keysets/component.toaster.view/ru.json @@ -0,0 +1,3 @@ +{ + "label_details": "Подробнее" +} diff --git a/src/i18n-keysets/component.user-menu.view/context.json b/src/i18n-keysets/component.user-menu.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.user-menu.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.user-menu.view/en.json b/src/i18n-keysets/component.user-menu.view/en.json new file mode 100644 index 0000000..f990bab --- /dev/null +++ b/src/i18n-keysets/component.user-menu.view/en.json @@ -0,0 +1,3 @@ +{ + "label_logout": "Log out" +} diff --git a/src/i18n-keysets/component.user-menu.view/keyset.json b/src/i18n-keysets/component.user-menu.view/keyset.json new file mode 100644 index 0000000..2b31636 --- /dev/null +++ b/src/i18n-keysets/component.user-menu.view/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_logout": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.user-menu.view/ru.json b/src/i18n-keysets/component.user-menu.view/ru.json new file mode 100644 index 0000000..c629546 --- /dev/null +++ b/src/i18n-keysets/component.user-menu.view/ru.json @@ -0,0 +1,3 @@ +{ + "label_logout": "Выйти" +} diff --git a/src/i18n-keysets/component.users-suggest.view/context.json b/src/i18n-keysets/component.users-suggest.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.users-suggest.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.users-suggest.view/en.json b/src/i18n-keysets/component.users-suggest.view/en.json new file mode 100644 index 0000000..67158af --- /dev/null +++ b/src/i18n-keysets/component.users-suggest.view/en.json @@ -0,0 +1,7 @@ +{ + "label_empty-state-not-found-description": "Check the search query for typos", + "label_empty-state-not-found-title": "Nothing was found", + "label_empty-state-template-description": "Search users by name or login", + "label_empty-state-template-title": "There is nothing yet", + "label_filter-placeholder": "Search" +} diff --git a/src/i18n-keysets/component.users-suggest.view/keyset.json b/src/i18n-keysets/component.users-suggest.view/keyset.json new file mode 100644 index 0000000..bbcbc42 --- /dev/null +++ b/src/i18n-keysets/component.users-suggest.view/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_empty-state-not-found-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-state-not-found-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-state-template-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-state-template-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.users-suggest.view/ru.json b/src/i18n-keysets/component.users-suggest.view/ru.json new file mode 100644 index 0000000..842f005 --- /dev/null +++ b/src/i18n-keysets/component.users-suggest.view/ru.json @@ -0,0 +1,7 @@ +{ + "label_empty-state-not-found-description": "Проверьте, нет ли опечаток в поисковом запросе", + "label_empty-state-not-found-title": "Ничего не найдено", + "label_empty-state-template-description": "Ищите по имени или логину", + "label_empty-state-template-title": "Пока ничего нет", + "label_filter-placeholder": "Поиск" +} diff --git a/src/i18n-keysets/component.view-error.view/context.json b/src/i18n-keysets/component.view-error.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.view-error.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.view-error.view/en.json b/src/i18n-keysets/component.view-error.view/en.json new file mode 100644 index 0000000..a60948e --- /dev/null +++ b/src/i18n-keysets/component.view-error.view/en.json @@ -0,0 +1,6 @@ +{ + "button_details": "Details", + "button_retry": "Retry", + "label_access-error": "Access error", + "label_error": "Error" +} diff --git a/src/i18n-keysets/component.view-error.view/keyset.json b/src/i18n-keysets/component.view-error.view/keyset.json new file mode 100644 index 0000000..d3f00c2 --- /dev/null +++ b/src/i18n-keysets/component.view-error.view/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_details": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_access-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.view-error.view/ru.json b/src/i18n-keysets/component.view-error.view/ru.json new file mode 100644 index 0000000..e1e2456 --- /dev/null +++ b/src/i18n-keysets/component.view-error.view/ru.json @@ -0,0 +1,6 @@ +{ + "button_details": "Подробнее", + "button_retry": "Повторить", + "label_access-error": "Ошибка доступа", + "label_error": "Ошибка" +} diff --git a/src/i18n-keysets/component.workbook-export-dialog.view/context.json b/src/i18n-keysets/component.workbook-export-dialog.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.workbook-export-dialog.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.workbook-export-dialog.view/en.json b/src/i18n-keysets/component.workbook-export-dialog.view/en.json new file mode 100644 index 0000000..5eb9002 --- /dev/null +++ b/src/i18n-keysets/component.workbook-export-dialog.view/en.json @@ -0,0 +1,22 @@ +{ + "button_back-to-export": "Back", + "button_begin-export": "Export", + "button_cancel": "Cancel", + "button_cancel-export": "Cancel export", + "button_close": "Close", + "button_close-export": "Close", + "button_download": "Download", + "label_cancel-export-description": "The workbook configuration file will not be created", + "label_close-export-description": "If you do not download the file now, you will need to export it again", + "label_info-documentation": "How do workbooks export work?", + "label_info-file": "We are exporting the workbook configuration to a file", + "label_info-secrets": "You will need to re-enter your passwords during the import", + "label_info-time": "The export may take several minutes", + "label_info-versions": "In other versions of DataLens, some connections will not work", + "label_success-export": "Download file with workbook configuration", + "title_cancel-export": "Cancel the export?", + "title_close-export": "Close the window with the finished export file?", + "title_export": "Workbook export", + "title_export-success": "Export file is ready", + "title_fatal-error": "Workbook export error" +} diff --git a/src/i18n-keysets/component.workbook-export-dialog.view/keyset.json b/src/i18n-keysets/component.workbook-export-dialog.view/keyset.json new file mode 100644 index 0000000..1e8a907 --- /dev/null +++ b/src/i18n-keysets/component.workbook-export-dialog.view/keyset.json @@ -0,0 +1,92 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_back-to-export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_begin-export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel-export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_close-export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_download": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_cancel-export-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_close-export-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_info-documentation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_info-file": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_info-secrets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_info-time": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_info-versions": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_success-export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_cancel-export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_close-export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_export-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_fatal-error": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.workbook-export-dialog.view/ru.json b/src/i18n-keysets/component.workbook-export-dialog.view/ru.json new file mode 100644 index 0000000..2b01338 --- /dev/null +++ b/src/i18n-keysets/component.workbook-export-dialog.view/ru.json @@ -0,0 +1,22 @@ +{ + "button_back-to-export": "Вернуться", + "button_begin-export": "Экспортировать", + "button_cancel": "Отменить", + "button_cancel-export": "Отменить экспорт", + "button_close": "Закрыть", + "button_close-export": "Закрыть", + "button_download": "Скачать", + "label_cancel-export-description": "Файл с конфигурацией ворбука не будет создан", + "label_close-export-description": "Если не скачать файл сейчас, потребуется заново выполнить экспорт", + "label_info-documentation": "Как работает экспорт воркбуков?", + "label_info-file": "Экспортируем конфигурацию воркбука в файл", + "label_info-secrets": "При импорте потребуется заново ввести пароли", + "label_info-time": "Экспорт может занять несколько минут", + "label_info-versions": "В других версиях DataLens некоторые подключения не будут работать", + "label_success-export": "Скачайте файл с конфигурацией воркбука", + "title_cancel-export": "Отменить экспорт?", + "title_close-export": "Закрыть окно с готовым файлом экспорта?", + "title_export": "Экспорт воркбука", + "title_export-success": "Воркбук экспортирован", + "title_fatal-error": "Ошибка при экспорте воркбука" +} diff --git a/src/i18n-keysets/component.workbook-export.notifications/context.json b/src/i18n-keysets/component.workbook-export.notifications/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.workbook-export.notifications/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.workbook-export.notifications/en.json b/src/i18n-keysets/component.workbook-export.notifications/en.json new file mode 100644 index 0000000..e76337a --- /dev/null +++ b/src/i18n-keysets/component.workbook-export.notifications/en.json @@ -0,0 +1,23 @@ +{ + "button_show-details": "Show details", + "label_bad-connection-type": "These connection types are not supported in the current version of the service", + "label_error-export-connection": "Failed to export connection", + "label_error-export-dataset": "Failed to export dataset", + "label_error-import-connection": "Failed to import connection", + "label_error-import-dataset": "Failed to import dataset", + "label_export-check-connection-credentials": "In connections, the values of fields with passwords and tokens are not exported. They should be checked and replaced if necessary after being imported", + "label_export-rls": "RLS is not guaranteed to work correctly in exported datasets", + "label_import-check-connections-credentials": "Check the connection settings and re-enter passwords and tokens if necessary", + "label_import-check-delegation": "Connection configuration is not complete, change or save the connection settings again", + "label_import-rls": "Check RLS functionality in datasets", + "label_info-documentation-notifications": "Error and warning descriptions", + "label_invalid-entry-data": "Workbook configuration was corrupted", + "label_invalid-entry-scope": "The workbook contains entities with an incorrect type", + "label_invalid-error": "Unknown error", + "label_invalid-version": "The DataLens installation does not support the workbook configuration", + "label_missing-linked-entry": "The workbook contains entities linked to nonexistent entities", + "label_scope-not-available-for-installation": "The workbook contains entities not supported in your DataLens installation", + "label_unexpected-workflow-error": "Unexpected error during operation", + "label_unsupported-objects": "Objects of this type are not available for export and import", + "title_error-details": "Error details" +} diff --git a/src/i18n-keysets/component.workbook-export.notifications/keyset.json b/src/i18n-keysets/component.workbook-export.notifications/keyset.json new file mode 100644 index 0000000..e4183bc --- /dev/null +++ b/src/i18n-keysets/component.workbook-export.notifications/keyset.json @@ -0,0 +1,96 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_show-details": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bad-connection-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-export-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-export-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-import-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-import-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-check-connection-credentials": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_export-rls": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_import-check-connections-credentials": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_import-check-delegation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_import-rls": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_info-documentation-notifications": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-entry-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-entry-scope": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-version": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_missing-linked-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scope-not-available-for-installation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unexpected-workflow-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unsupported-objects": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_error-details": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.workbook-export.notifications/ru.json b/src/i18n-keysets/component.workbook-export.notifications/ru.json new file mode 100644 index 0000000..16cff72 --- /dev/null +++ b/src/i18n-keysets/component.workbook-export.notifications/ru.json @@ -0,0 +1,23 @@ +{ + "button_show-details": "Показать детали", + "label_bad-connection-type": "Некоторые типы подключений не поддерживаются в текущей версии сервиса", + "label_error-export-connection": "Не удалось экспортировать подключение", + "label_error-export-dataset": "Не удалось экспортировать датасет", + "label_error-import-connection": "Не удалось импортировать подключение", + "label_error-import-dataset": "Не удалось импортировать датасет", + "label_export-check-connection-credentials": "В подключениях значения полей с паролями и токенами не экспортируются, после импорта их нужно проверить и при необходимости заменить", + "label_export-rls": "В экспортированных датасетах не гарантируется корректная работа RLS", + "label_import-check-connections-credentials": "Проверьте настройки подключений и при необходимости заново укажите пароли и токены", + "label_import-check-delegation": "Настройка подключений не завершена, измените или повторно сохраните настройки подключений", + "label_import-rls": "Убедитесь, что RLS в датасетах работает корректно", + "label_info-documentation-notifications": "Описание ошибок и предупреждений", + "label_invalid-entry-data": "Конфигурация воркбука нарушена", + "label_invalid-entry-scope": "В воркбуке содержатся объекты с некорректным типом", + "label_invalid-error": "Неизвестная ошибка", + "label_invalid-version": "Инсталляция DataLens не поддерживает конфигурацию воркбука", + "label_missing-linked-entry": "В воркбуке есть объекты, связанные с несуществующими объектами", + "label_scope-not-available-for-installation": "В воркбуке есть объекты, которые не поддерживаются в вашей инсталляции DataLens", + "label_unexpected-workflow-error": "Операция прервана из-за неизвестной ошибки", + "label_unsupported-objects": "Объекты данного типа недоступны для экспорта и импорта", + "title_error-details": "Детали ошибки" +} diff --git a/src/i18n-keysets/component.workbook-import-view.view/context.json b/src/i18n-keysets/component.workbook-import-view.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.workbook-import-view.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.workbook-import-view.view/en.json b/src/i18n-keysets/component.workbook-import-view.view/en.json new file mode 100644 index 0000000..5342069 --- /dev/null +++ b/src/i18n-keysets/component.workbook-import-view.view/en.json @@ -0,0 +1,9 @@ +{ + "button_cancel": "Cancel", + "button_close": "Close", + "button_go-to-workbook": "Open workbook", + "label_success-import": "The import of the workbook configuration was successful", + "title_creating-workbook": "Creating workbook", + "title_error-creating": "Workbook creation error", + "title_workbook-created": "Workbook created" +} diff --git a/src/i18n-keysets/component.workbook-import-view.view/keyset.json b/src/i18n-keysets/component.workbook-import-view.view/keyset.json new file mode 100644 index 0000000..af17bbd --- /dev/null +++ b/src/i18n-keysets/component.workbook-import-view.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_go-to-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_success-import": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_creating-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_error-creating": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_workbook-created": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.workbook-import-view.view/ru.json b/src/i18n-keysets/component.workbook-import-view.view/ru.json new file mode 100644 index 0000000..338bd27 --- /dev/null +++ b/src/i18n-keysets/component.workbook-import-view.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_cancel": "Отменить", + "button_close": "Закрыть", + "button_go-to-workbook": "Открыть воркбук", + "label_success-import": "Импорт конфигурации воркбука прошёл успешно", + "title_creating-workbook": "Создаём воркбук", + "title_error-creating": "Ошибка при импорте воркбука", + "title_workbook-created": "Воркбук создан" +} diff --git a/src/i18n-keysets/component.workbook.navigation.view/context.json b/src/i18n-keysets/component.workbook.navigation.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.workbook.navigation.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.workbook.navigation.view/en.json b/src/i18n-keysets/component.workbook.navigation.view/en.json new file mode 100644 index 0000000..fb4ae3a --- /dev/null +++ b/src/i18n-keysets/component.workbook.navigation.view/en.json @@ -0,0 +1,8 @@ +{ + "entries-list-title-connection": "Connections", + "entries-list-title-dataset": "Datasets", + "label_no-data": "No data", + "label_placeholder-filter-by-name": "Filter by name", + "shared-list-title": "Bound {{entry}}", + "workbook-list-title": "{{entry}} in workbook" +} diff --git a/src/i18n-keysets/component.workbook.navigation.view/keyset.json b/src/i18n-keysets/component.workbook.navigation.view/keyset.json new file mode 100644 index 0000000..76a6a96 --- /dev/null +++ b/src/i18n-keysets/component.workbook.navigation.view/keyset.json @@ -0,0 +1,36 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "entries-list-title-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "entries-list-title-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-filter-by-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "shared-list-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "workbook-list-title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.workbook.navigation.view/ru.json b/src/i18n-keysets/component.workbook.navigation.view/ru.json new file mode 100644 index 0000000..9d04fa8 --- /dev/null +++ b/src/i18n-keysets/component.workbook.navigation.view/ru.json @@ -0,0 +1,8 @@ +{ + "entries-list-title-connection": "Подключения", + "entries-list-title-dataset": "Датасеты", + "label_no-data": "Нет данных", + "label_placeholder-filter-by-name": "Фильтр по имени", + "shared-list-title": "Привязанные {{entry}}", + "workbook-list-title": "{{entry}} в воркбуке" +} diff --git a/src/i18n-keysets/component.wysiwyg-editor.view/context.json b/src/i18n-keysets/component.wysiwyg-editor.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/component.wysiwyg-editor.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/component.wysiwyg-editor.view/en.json b/src/i18n-keysets/component.wysiwyg-editor.view/en.json new file mode 100644 index 0000000..9758b41 --- /dev/null +++ b/src/i18n-keysets/component.wysiwyg-editor.view/en.json @@ -0,0 +1,3 @@ +{ + "label_failed-to-load": "Failed to load field for text entry" +} diff --git a/src/i18n-keysets/component.wysiwyg-editor.view/keyset.json b/src/i18n-keysets/component.wysiwyg-editor.view/keyset.json new file mode 100644 index 0000000..e3e71c8 --- /dev/null +++ b/src/i18n-keysets/component.wysiwyg-editor.view/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_failed-to-load": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/component.wysiwyg-editor.view/ru.json b/src/i18n-keysets/component.wysiwyg-editor.view/ru.json new file mode 100644 index 0000000..76f42eb --- /dev/null +++ b/src/i18n-keysets/component.wysiwyg-editor.view/ru.json @@ -0,0 +1,3 @@ +{ + "label_failed-to-load": "Не удалось загрузить поле для ввода текста" +} diff --git a/src/i18n-keysets/components.common.Datepicker/context.json b/src/i18n-keysets/components.common.Datepicker/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/components.common.Datepicker/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/components.common.Datepicker/en.json b/src/i18n-keysets/components.common.Datepicker/en.json new file mode 100644 index 0000000..48a4e0f --- /dev/null +++ b/src/i18n-keysets/components.common.Datepicker/en.json @@ -0,0 +1,54 @@ +{ + "apply_button_text": "Apply", + "error_date": "Specify date", + "error_dates_range": "Specify both dates of the range", + "month_long_1": "January", + "month_long_10": "October", + "month_long_11": "November", + "month_long_12": "December", + "month_long_2": "February", + "month_long_3": "March", + "month_long_4": "April", + "month_long_5": "May", + "month_long_6": "June", + "month_long_7": "July", + "month_long_8": "August", + "month_long_9": "September", + "month_short_1": "Jan", + "month_short_10": "Oct", + "month_short_11": "Nov", + "month_short_12": "Dec", + "month_short_2": "Feb", + "month_short_3": "Mar", + "month_short_4": "Apr", + "month_short_5": "May", + "month_short_6": "Jun", + "month_short_7": "Jul", + "month_short_8": "Aug", + "month_short_9": "Sep", + "placeholder_letter_H": "h", + "placeholder_letter_M": "M", + "placeholder_letter_S": "ms", + "placeholder_letter_d": "D", + "placeholder_letter_h": "h", + "placeholder_letter_m": "m", + "placeholder_letter_s": "s", + "placeholder_letter_y": "Y", + "preset_current": "Current", + "preset_day_30": "30 days", + "preset_day_365": "365 days", + "preset_day_7": "7 days", + "preset_day_90": "90 days", + "preset_day_today": "Today", + "preset_day_two_days_ago": "Two days ago", + "preset_day_yesterday": "Yesterday", + "preset_past": "Past", + "preset_week_current": "Current", + "preset_week_past": "Past", + "tab_day": "Day", + "tab_month": "Month", + "tab_quarter": "Quarter", + "tab_week": "Week", + "tab_year": "Year", + "value_required": "This field is required" +} diff --git a/src/i18n-keysets/components.common.Datepicker/keyset.json b/src/i18n-keysets/components.common.Datepicker/keyset.json new file mode 100644 index 0000000..ac055c1 --- /dev/null +++ b/src/i18n-keysets/components.common.Datepicker/keyset.json @@ -0,0 +1,220 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "apply_button_text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_dates_range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_1": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_10": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_11": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_12": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_2": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_3": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_4": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_5": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_6": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_7": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_8": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_long_9": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_1": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_10": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_11": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_12": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_2": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_3": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_4": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_5": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_6": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_7": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_8": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "month_short_9": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_letter_H": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_letter_M": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_letter_S": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_letter_d": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_letter_h": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_letter_m": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_letter_s": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_letter_y": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_current": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_day_30": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_day_365": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_day_7": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_day_90": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_day_today": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_day_two_days_ago": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_day_yesterday": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_past": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_week_current": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "preset_week_past": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_day": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_month": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_quarter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_week": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_year": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_required": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/components.common.Datepicker/ru.json b/src/i18n-keysets/components.common.Datepicker/ru.json new file mode 100644 index 0000000..e3d0193 --- /dev/null +++ b/src/i18n-keysets/components.common.Datepicker/ru.json @@ -0,0 +1,54 @@ +{ + "apply_button_text": "Применить", + "error_date": "Укажите дату", + "error_dates_range": "Укажите обе даты диапазона", + "month_long_1": "Январь", + "month_long_10": "Октябрь", + "month_long_11": "Ноябрь", + "month_long_12": "Декабрь", + "month_long_2": "Февраль", + "month_long_3": "Март", + "month_long_4": "Апрель", + "month_long_5": "Май", + "month_long_6": "Июнь", + "month_long_7": "Июль", + "month_long_8": "Август", + "month_long_9": "Сентябрь", + "month_short_1": "Янв", + "month_short_10": "Окт", + "month_short_11": "Ноя", + "month_short_12": "Дек", + "month_short_2": "Фев", + "month_short_3": "Мар", + "month_short_4": "Апр", + "month_short_5": "Май", + "month_short_6": "Июн", + "month_short_7": "Июл", + "month_short_8": "Авг", + "month_short_9": "Сен", + "placeholder_letter_H": "ч", + "placeholder_letter_M": "М", + "placeholder_letter_S": "мс", + "placeholder_letter_d": "Д", + "placeholder_letter_h": "ч", + "placeholder_letter_m": "м", + "placeholder_letter_s": "с", + "placeholder_letter_y": "Г", + "preset_current": "Текущий", + "preset_day_30": "30 дней", + "preset_day_365": "365 дней", + "preset_day_7": "7 дней", + "preset_day_90": "90 дней", + "preset_day_today": "Сегодня", + "preset_day_two_days_ago": "Позавчера", + "preset_day_yesterday": "Вчера", + "preset_past": "Прошедший", + "preset_week_current": "Текущая", + "preset_week_past": "Прошедшая", + "tab_day": "День", + "tab_month": "Месяц", + "tab_quarter": "Квартал", + "tab_week": "Неделя", + "tab_year": "Год", + "value_required": "Обязательно для заполнения" +} diff --git a/src/i18n-keysets/components.common.SimpleDatepicker/context.json b/src/i18n-keysets/components.common.SimpleDatepicker/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/components.common.SimpleDatepicker/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/components.common.SimpleDatepicker/en.json b/src/i18n-keysets/components.common.SimpleDatepicker/en.json new file mode 100644 index 0000000..332ae59 --- /dev/null +++ b/src/i18n-keysets/components.common.SimpleDatepicker/en.json @@ -0,0 +1,11 @@ +{ + "apply_button_text": "Apply", + "date_and_time_input_error": "Enter the date and time in the format \"{{format}}\"", + "date_input_error": "Enter the date in the format \"{{format}}\"", + "default_date_placeholder": "YYYY-MM-DD", + "default_ru_date_placeholder": "DD.MM.YYYY", + "relative_input_error": "Enter the date in a special format or in the format \"{{format}}\"", + "time_input_error": "Enter the time in the format \"{{format}}\"", + "time_placeholder": "HH:MM", + "time_with_sec_placeholder": "HH:MM:SS" +} diff --git a/src/i18n-keysets/components.common.SimpleDatepicker/keyset.json b/src/i18n-keysets/components.common.SimpleDatepicker/keyset.json new file mode 100644 index 0000000..3f42c88 --- /dev/null +++ b/src/i18n-keysets/components.common.SimpleDatepicker/keyset.json @@ -0,0 +1,48 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "apply_button_text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "date_and_time_input_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "date_input_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "default_date_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "default_ru_date_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "relative_input_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "time_input_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "time_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "time_with_sec_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/components.common.SimpleDatepicker/ru.json b/src/i18n-keysets/components.common.SimpleDatepicker/ru.json new file mode 100644 index 0000000..d3dbb6a --- /dev/null +++ b/src/i18n-keysets/components.common.SimpleDatepicker/ru.json @@ -0,0 +1,11 @@ +{ + "apply_button_text": "Применить", + "date_and_time_input_error": "Введите дату и время в формате \"{{format}}\"", + "date_input_error": "Введите дату в формате \"{{format}}\"", + "default_date_placeholder": "ГГГГ-ММ-ДД", + "default_ru_date_placeholder": "ДД.ММ.ГГГГ", + "relative_input_error": "Введите дату в специальном формате или в формате \"{{format}}\"", + "time_input_error": "Введите время в формате \"{{format}}\"", + "time_placeholder": "ЧЧ:ММ", + "time_with_sec_placeholder": "ЧЧ:ММ:СС" +} diff --git a/src/i18n-keysets/components.common.YCSelect/context.json b/src/i18n-keysets/components.common.YCSelect/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/components.common.YCSelect/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/components.common.YCSelect/en.json b/src/i18n-keysets/components.common.YCSelect/en.json new file mode 100644 index 0000000..792ecba --- /dev/null +++ b/src/i18n-keysets/components.common.YCSelect/en.json @@ -0,0 +1,18 @@ +{ + "apply_button_text": "Apply", + "empty_string": "Empty string", + "error_details_button_text": "Details", + "item_except": "Except", + "item_only": "Only", + "items_add_new": "Add new entry", + "items_fetch_error": "Something went wrong", + "items_not_found": "No matches for the search string «{{inputValue}}»", + "items_prompt": "Enter search string...", + "items_show_more": "Show more", + "null": "null", + "refresh_button_text": "Retry", + "search_clear": "Clear", + "search_placeholder": "Search", + "search_select_all": "Select all", + "selected_popup_title": "Selected" +} diff --git a/src/i18n-keysets/components.common.YCSelect/keyset.json b/src/i18n-keysets/components.common.YCSelect/keyset.json new file mode 100644 index 0000000..cf19f4d --- /dev/null +++ b/src/i18n-keysets/components.common.YCSelect/keyset.json @@ -0,0 +1,76 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "apply_button_text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "empty_string": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_details_button_text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "item_except": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "item_only": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "items_add_new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "items_fetch_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "items_not_found": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "items_prompt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "items_show_more": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "null": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "refresh_button_text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "search_clear": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "search_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "search_select_all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "selected_popup_title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/components.common.YCSelect/ru.json b/src/i18n-keysets/components.common.YCSelect/ru.json new file mode 100644 index 0000000..aaee811 --- /dev/null +++ b/src/i18n-keysets/components.common.YCSelect/ru.json @@ -0,0 +1,18 @@ +{ + "apply_button_text": "Применить", + "empty_string": "Пустая строка", + "error_details_button_text": "Подробнее", + "item_except": "Кроме", + "item_only": "Только", + "items_add_new": "Добавить в список", + "items_fetch_error": "Что-то пошло не так", + "items_not_found": "По запросу «{{inputValue}}» ничего не найдено", + "items_prompt": "Начните вводить...", + "items_show_more": "Показать еще", + "null": "null", + "refresh_button_text": "Повторить", + "search_clear": "Очистить", + "search_placeholder": "Поиск", + "search_select_all": "Выбрать все", + "selected_popup_title": "Выбрано" +} diff --git a/src/i18n-keysets/connections.connectors-list.view/context.json b/src/i18n-keysets/connections.connectors-list.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/connections.connectors-list.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/connections.connectors-list.view/en.json b/src/i18n-keysets/connections.connectors-list.view/en.json new file mode 100644 index 0000000..d5224a4 --- /dev/null +++ b/src/i18n-keysets/connections.connectors-list.view/en.json @@ -0,0 +1,10 @@ +{ + "button_retry": "Retry", + "label_connector-ch_over_yt": "CHYT with authentication by OAuth token", + "label_connector-ch_over_yt_user_auth": "CHYT with authentication as user", + "label_connector-file": "Files", + "label_connector-gsheets": "Google Sheets", + "label_connector-gsheets_v2": "Google Sheets", + "label_error-500-schema": "Couldn't load form. Please reload the page.", + "label_error-500-title": "Failed to load list of connectors" +} diff --git a/src/i18n-keysets/connections.connectors-list.view/keyset.json b/src/i18n-keysets/connections.connectors-list.view/keyset.json new file mode 100644 index 0000000..e21e807 --- /dev/null +++ b/src/i18n-keysets/connections.connectors-list.view/keyset.json @@ -0,0 +1,44 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connector-ch_over_yt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connector-ch_over_yt_user_auth": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connector-file": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connector-gsheets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connector-gsheets_v2": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-500-schema": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-500-title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/connections.connectors-list.view/ru.json b/src/i18n-keysets/connections.connectors-list.view/ru.json new file mode 100644 index 0000000..0da7663 --- /dev/null +++ b/src/i18n-keysets/connections.connectors-list.view/ru.json @@ -0,0 +1,10 @@ +{ + "button_retry": "Повторить", + "label_connector-ch_over_yt": "CHYT с аутентификацией с помощью OAuth-токена", + "label_connector-ch_over_yt_user_auth": "CHYT с аутентификацией от имени пользователя", + "label_connector-file": "Файлы", + "label_connector-gsheets": "Google Sheets", + "label_connector-gsheets_v2": "Google Sheets", + "label_error-500-schema": "Не удалось загрузить форму. Пожалуйста, обновите страницу.", + "label_error-500-title": "Не удалось загрузить список коннекторов" +} diff --git a/src/i18n-keysets/connections.file.view/context.json b/src/i18n-keysets/connections.file.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/connections.file.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/connections.file.view/en.json b/src/i18n-keysets/connections.file.view/en.json new file mode 100644 index 0000000..d89b354 --- /dev/null +++ b/src/i18n-keysets/connections.file.view/en.json @@ -0,0 +1,32 @@ +{ + "button_add-file": "Upload files", + "button_add-file-hint": "No more than 10 files. The weight of each file should not exceed 200 MB. Supported formats: {{formats}}.", + "button_retry-download": "Retry upload", + "label_action-delete": "Delete", + "label_action-rename": "Rename", + "label_action-replace": "Replace", + "label_columns-header": "Columns header", + "label_delimiter": "Separator", + "label_delimiter-comma": "Comma", + "label_delimiter-semicolon": "Semicolon", + "label_delimiter-tab": "Tabulation character", + "label_downloaded-files": "Uploaded earlier", + "label_encoding": "Encoding", + "label_encoding-utf-8": "utf-8", + "label_encoding-utf-8-sig": "utf-8-sig", + "label_encoding-windows-1251": "windows-1251", + "label_file-download-failure": "File upload failed", + "label_file-promo": "To create a connection, upload files in the supported formats: {{formats}}.", + "label_file-size-error": "The size of the file must not exceed 200 MB", + "label_filter-by-column": "Search by column", + "label_header-disabled": "No", + "label_header-enabled": "Yes", + "label_list-item-downloading": "Downloading...", + "label_list-item-no-data": "No data", + "label_new-files": "New files", + "label_no-preview-data": "No preview data found", + "label_settings-update-failure": "Settings update failure", + "label_source-download-failure": "Source download failure", + "label_source-name": "Source name", + "label_source-replacing": "Replacing …" +} diff --git a/src/i18n-keysets/connections.file.view/keyset.json b/src/i18n-keysets/connections.file.view/keyset.json new file mode 100644 index 0000000..7ce56f7 --- /dev/null +++ b/src/i18n-keysets/connections.file.view/keyset.json @@ -0,0 +1,132 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-file": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_add-file-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_retry-download": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_action-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_action-rename": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_action-replace": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_columns-header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delimiter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delimiter-comma": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delimiter-semicolon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delimiter-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_downloaded-files": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_encoding": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_encoding-utf-8": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_encoding-utf-8-sig": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_encoding-windows-1251": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_file-download-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_file-promo": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_file-size-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-by-column": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_header-disabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_header-enabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_list-item-downloading": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_list-item-no-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-files": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-preview-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_settings-update-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-download-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-replacing": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/connections.file.view/ru.json b/src/i18n-keysets/connections.file.view/ru.json new file mode 100644 index 0000000..c2968a2 --- /dev/null +++ b/src/i18n-keysets/connections.file.view/ru.json @@ -0,0 +1,32 @@ +{ + "button_add-file": "Загрузить файлы", + "button_add-file-hint": "Не больше 10 файлов. Вес каждого из них не должен превышать 200 МБ. Поддерживаемые форматы: {{formats}}.", + "button_retry-download": "Повторить загрузку", + "label_action-delete": "Удалить", + "label_action-rename": "Переименовать", + "label_action-replace": "Заменить", + "label_columns-header": "Заголовок столбцов", + "label_delimiter": "Разделитель", + "label_delimiter-comma": "Запятая", + "label_delimiter-semicolon": "Точка с запятой", + "label_delimiter-tab": "Знак табуляции", + "label_downloaded-files": "Загруженные ранее", + "label_encoding": "Кодировка", + "label_encoding-utf-8": "utf-8", + "label_encoding-utf-8-sig": "utf-8-sig", + "label_encoding-windows-1251": "windows-1251", + "label_file-download-failure": "Не удалось загрузить файл", + "label_file-promo": "Чтобы создать подключение, загрузите файлы.

Поддерживаемые форматы: {{formats}}.", + "label_file-size-error": "Размер файла не должен превышать 200 МБ", + "label_filter-by-column": "Поиск по столбцу", + "label_header-disabled": "Нет", + "label_header-enabled": "Да", + "label_list-item-downloading": "Загрузка...", + "label_list-item-no-data": "Нет данных", + "label_new-files": "Новые файлы", + "label_no-preview-data": "Данные для предпросмотра отсутствуют", + "label_settings-update-failure": "Не удалось обновить настройки", + "label_source-download-failure": "Не удалось загрузить источник", + "label_source-name": "Название источника", + "label_source-replacing": "Замена ..." +} diff --git a/src/i18n-keysets/connections.form/context.json b/src/i18n-keysets/connections.form/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/connections.form/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/connections.form/en.json b/src/i18n-keysets/connections.form/en.json new file mode 100644 index 0000000..e40700f --- /dev/null +++ b/src/i18n-keysets/connections.form/en.json @@ -0,0 +1,183 @@ +{ + "button_add": "Add", + "button_add-connection": "Create connection", + "button_already-saved": "Saved", + "button_attach-file": "Attach file", + "button_cancel": "Cancel", + "button_connection-name": "Connector name", + "button_create": "Create", + "button_create-connection": "Create connection", + "button_create-dataset": "Create dataset", + "button_create-mdb-cluster": "Create new", + "button_create-new": "Create new", + "button_create-new-f": "Create new", + "button_create-sql-chart": "Create QL-chart", + "button_details": "Learn more", + "button_get-token-ya-podcast": "Get key", + "button_refresh-token": "Get token", + "button_refresh-token-help": "Enter \"ACCOUNT_NAME\", \"CLIENT_ID\", and \"CLIENT_SECRET\" fields first", + "button_remove": "Delete", + "button_reset": "Reset", + "button_retry": "Retry", + "button_save": "Save", + "button_save-conn-v2": "Save changes", + "button_save-connection": "Save", + "button_setting-permissions": "Configure permissions", + "button_specify-manually": "Specify manually", + "button_upload": "Upload", + "button_verify": "Check connection", + "column_created-at": "Creation date", + "column_owner": "Owner", + "column_status": "Status", + "column_title": "Name", + "column_type": "Type", + "context_where-can-it-be-taken-token-yt": "Get an OAuth token for YT", + "field_accuracy": "Accuracy", + "field_application": "App", + "field_auth-method": "Authentication

method ", + "field_cache-ttl-sec": "Cache TTL", + "field_cache-ttl-sec_unit": "s", + "field_click-house-port": "HTTP interface port", + "field_clika": "Clique", + "field_cloud-and-folder": "Cloud and folder", + "field_cluster": "Cluster", + "field_connection-title": "Connection name", + "field_counter-id": "Tag", + "field_database-id": "Database", + "field_default-cluster": "Default cluster", + "field_default-database": "Default database name", + "field_enforce-collate": "Specifying collate in the request", + "field_fill-mode": "Connection", + "field_get-token": "Get token", + "field_host-name": "Hostname", + "field_metrica-namespace": "Metric group", + "field_moysklad-token": "Access token", + "field_sa_key": "SA key", + "field_service-account": "Service account", + "field_settings": "Settings", + "field_token-metrika": "OAuth token", + "field_token-yt": "YT token", + "field_username": "Username", + "label_accuracy-auto": "Automatically", + "label_advanced-settings": "Advanced connection settings", + "label_auth-method-hint": "The authentication method can't be changed after the connection is created.", + "label_auth-showflake-copy": "Get snippet", + "label_authentication-method-choice": "Choose an authentication method", + "label_ch-frozen-description": "This is a special connection to ClickHouse (a part of the product in [Marketplace]({{link}})). You can't edit it.", + "label_ch-geo-filtered-description": "This is a service connection for interactions datasets with geolayers. You can set up a connection in [Marketplace]({{link}}))", + "label_chyt-token-auth-description": "Specify the token of the user for whom requests should be executed", + "label_chyt-user-auth-description": "Authentication by cookie, requests will be executed for the user viewing the dataset, chart, or dashboard", + "label_cloud-tree-fetching-failed": "Failed to load clouds tree", + "label_connection-csv-name": "Connection name", + "label_connection-info": "Connection info", + "label_connection-loading": "Upload", + "label_connections-empty-msg": "You don't have any connections", + "label_connections-list-failed": "Error: failed to load list of connections", + "label_connections-not-exist": "You don't have any connections", + "label_databases-empty-list": "No available databases", + "label_db-name": "Path to database", + "label_delimiter-comma": "Comma", + "label_delimiter-semicolon": "Semicolon", + "label_delimiter-tab": "Tabulation character", + "label_documentation": "documentation", + "label_entry-name-already-exists": "That name is already in use, try another one", + "label_error-400-no-connection-title": "No connection", + "label_error-400-title": "Incorrect request to connection", + "label_error-403-title": "You don't have access to this connection", + "label_error-404-title": "Connection not found", + "label_error-500-title": "Failed to load connection", + "label_error-duplicated-keys": "This key is already in use in another key-value pair", + "label_error-empty-field": "This is a required field", + "label_error-length-field": "Value has an incorrect number of characters", + "label_error-msg": "Failed to load list of connections", + "label_error-on-template-apply": "An error occurred while deploying the template", + "label_expiration-date": "Expires {{date}}", + "label_file-input-label": "File \"{{fileName}}\" successfully uploaded", + "label_file-upload-failure": "Couldn't upload file, try again later", + "label_get-refresh-token-failure": "Couldn't get refresh token", + "label_kp-interest-index-description": "The connection will allow you to view the index of interest in the series. By clicking the button below, you will receive a key (OAuth token) which grants you access to data in DataLens.", + "label_manual-title": "Instructions for connecting {{app}}", + "label_mdb-clusters-fetch-failed": "Failed to load list of clusters", + "label_mdb-databases-fetch-failed": "Failed to load list of databases", + "label_mdb-hosts-fetch-failed": "Failed to load list of hosts", + "label_mdb-no-clusters": "You don't have any clusters", + "label_mdb-users-fetch-failed": "Failed to load list of users", + "label_moysklad-token-hint": "Check developer's documentation on how to get token.", + "label_moysklad-token-hint-conn-v2": "Check [developer's documentation]({{link}}) on how to get token.", + "label_name-clickhouse": "ClickHouse", + "label_not-found-connections": "No data found", + "label_or": "or", + "label_placeholder-appmetrica-id": "Application ID", + "label_placeholder-metrica-id": "Tag ID", + "label_portal": "Portal", + "label_project-id": "Project ID", + "label_refresh-token-label": "Refresh token", + "label_secret-value": "Secret value", + "label_section-caption-counter-settings": "Tag settings", + "label_select-folder": "Select folder", + "label_service-acc-key-file": "Service account public key", + "label_service-account-ydb-hint": "Select an existing service account or create a new one", + "label_service-accounts-empty-list": "No available accounts", + "label_service-accounts-fetching-failed": "Failed to load service accounts list", + "label_showflake-oauth-integration-label": "OAuth integration", + "label_showflake-oauth-integration-placeholder": "Integration name", + "label_snowflake-doc-help": "Learn more in the [documentation]({{link}})", + "label_token": "Token", + "label_utyt-conn-description": "This connection will allow you to view detailed statistics for dataset queries and dashboard views (for which you have admin rights).", + "label_verify-connection-500": "Error: failed to check connection", + "section_connection": "Connection", + "section_connections": "Connections", + "section_creation-connection": "New connection", + "section_creation-dataset": "New dataset", + "section_db-name": "Database name", + "section_or-another-variant": "or enter YQL Public Link:", + "section_selection-connection": "Select connection", + "section_source-replacement": "Replace source", + "switch_access-settings": "Configure access", + "switch_create-dataset": "Create dataset", + "switch_delete-connection": "Delete", + "switch_edit-connection": "Edit", + "toast_create-connection-error": "Failed to create connection", + "toast_error-action-label": "Learn more", + "toast_modify-connection-error": "Failed to save connection", + "toast_unsaved": "There are unsaved changes on the page. Are you sure?", + "toast_verify-error": "Failed to check connection parameters", + "value_cluster-arnold": "Arnold", + "value_cluster-hahn": "Hahn", + "value_cluster-vanga": "Vanga", + "value_counter-source-advertising": "Clicks", + "value_counter-source-hits": "Views", + "value_counter-source-user-param": "User parameters", + "value_counter-source-visits": "Visits", + "value_customizable-cache-ttl": "Customized", + "value_db-connect-method-service-name": "Service name", + "value_db-connect-method-sid": "SID", + "value_default": "Default", + "value_enforce-collate-auto": "Auto", + "value_enforce-collate-off": "Off", + "value_enforce-collate-on": "On", + "value_fill-mode-cloud": "Select within the organization", + "value_fill-mode-manually": "Enter manually", + "value_metrica-counter-input-method-list": "From the list", + "value_metrica-counter-input-method-manually": "Manually", + "value_metrica-namespace-audience": "Audience", + "value_metrica-namespace-audience-socdem": "Audience + social demographic", + "value_metrica-namespace-clicks": "Tracking clicks", + "value_metrica-namespace-client-events": "Client events", + "value_metrica-namespace-crashes": "Crashes", + "value_metrica-namespace-deeplinks": "App openings via deeplink", + "value_metrica-namespace-errors": "Errors", + "value_metrica-namespace-events": "App events", + "value_metrica-namespace-installations": "App installations", + "value_metrica-namespace-installs": "Installations", + "value_metrica-namespace-postbacks": "Postbacks", + "value_metrica-namespace-profiles": "Profiles", + "value_metrica-namespace-push-events": "Push campaigns", + "value_metrica-namespace-push-tokens": "Push tokens", + "value_metrica-namespace-revenue-events": "In-app purchases", + "value_metrica-namespace-sessions-starts": "Session start", + "value_permission-explicit": "Available to the company", + "value_permission-owner-only": "Available only to me", + "value_token-input-mode-automatic": "Automatic", + "value_token-input-mode-manually": "Manually" +} diff --git a/src/i18n-keysets/connections.form/keyset.json b/src/i18n-keysets/connections.form/keyset.json new file mode 100644 index 0000000..10a8e59 --- /dev/null +++ b/src/i18n-keysets/connections.form/keyset.json @@ -0,0 +1,736 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_add-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_already-saved": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_attach-file": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_connection-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-mdb-cluster": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-new-f": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-sql-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_details": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_get-token-ya-podcast": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_refresh-token": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_refresh-token-help": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_remove": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_reset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-conn-v2": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_setting-permissions": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_specify-manually": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_upload": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_verify": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_created-at": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_owner": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_status": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_where-can-it-be-taken-token-yt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_accuracy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_application": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_auth-method": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_cache-ttl-sec": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_cache-ttl-sec_unit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_click-house-port": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_clika": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_cloud-and-folder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_cluster": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_connection-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_counter-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_database-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_default-cluster": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_default-database": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_enforce-collate": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_fill-mode": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_get-token": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_host-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_metrica-namespace": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_moysklad-token": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_sa_key": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_service-account": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_token-metrika": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_token-yt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_username": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_accuracy-auto": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_advanced-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_auth-method-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_auth-showflake-copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_authentication-method-choice": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_ch-frozen-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_ch-geo-filtered-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_chyt-token-auth-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_chyt-user-auth-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_cloud-tree-fetching-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connection-csv-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connection-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connection-loading": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connections-empty-msg": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connections-list-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connections-not-exist": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_databases-empty-list": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_db-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delimiter-comma": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delimiter-semicolon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delimiter-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_documentation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-name-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-400-no-connection-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-400-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-403-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-404-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-500-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-duplicated-keys": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-empty-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-length-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-msg": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-on-template-apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_expiration-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_file-input-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_file-upload-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_get-refresh-token-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_kp-interest-index-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_manual-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mdb-clusters-fetch-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mdb-databases-fetch-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mdb-hosts-fetch-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mdb-no-clusters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mdb-users-fetch-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_moysklad-token-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_moysklad-token-hint-conn-v2": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_name-clickhouse": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_not-found-connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_or": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-appmetrica-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-metrica-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_portal": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_project-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_refresh-token-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_secret-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_section-caption-counter-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-folder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_service-acc-key-file": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_service-account-ydb-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_service-accounts-empty-list": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_service-accounts-fetching-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_showflake-oauth-integration-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_showflake-oauth-integration-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_snowflake-doc-help": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_token": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_utyt-conn-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_verify-connection-500": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_creation-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_creation-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_db-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_or-another-variant": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_selection-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_source-replacement": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_access-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_create-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_delete-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_edit-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_create-connection-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_error-action-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_modify-connection-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_unsaved": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_verify-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_cluster-arnold": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_cluster-hahn": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_cluster-vanga": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_counter-source-advertising": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_counter-source-hits": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_counter-source-user-param": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_counter-source-visits": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_customizable-cache-ttl": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_db-connect-method-service-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_db-connect-method-sid": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_default": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_enforce-collate-auto": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_enforce-collate-off": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_enforce-collate-on": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_fill-mode-cloud": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_fill-mode-manually": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-counter-input-method-list": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-counter-input-method-manually": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-audience": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-audience-socdem": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-clicks": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-client-events": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-crashes": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-deeplinks": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-errors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-events": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-installations": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-installs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-postbacks": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-profiles": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-push-events": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-push-tokens": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-revenue-events": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_metrica-namespace-sessions-starts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_permission-explicit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_permission-owner-only": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_token-input-mode-automatic": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_token-input-mode-manually": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/connections.form/ru.json b/src/i18n-keysets/connections.form/ru.json new file mode 100644 index 0000000..7a4696a --- /dev/null +++ b/src/i18n-keysets/connections.form/ru.json @@ -0,0 +1,183 @@ +{ + "button_add": "Добавить", + "button_add-connection": "Создать подключение", + "button_already-saved": "Сохранено", + "button_attach-file": "Прикрепить файл", + "button_cancel": "Отменить", + "button_connection-name": "Имя коннектора", + "button_create": "Создать", + "button_create-connection": "Создать подключение", + "button_create-dataset": "Создать датасет", + "button_create-mdb-cluster": "Создать новый", + "button_create-new": "Создать новый", + "button_create-new-f": "Создать новую", + "button_create-sql-chart": "Создать QL-чарт", + "button_details": "Подробнее", + "button_get-token-ya-podcast": "Получить ключ", + "button_refresh-token": "Получить токен", + "button_refresh-token-help": "Сначала заполните поля \"ACCOUNT_NAME\", \"CLIENT_ID\" и \"CLIENT_SECRET\"", + "button_remove": "Удалить", + "button_reset": "Сбросить", + "button_retry": "Повторить", + "button_save": "Сохранить", + "button_save-conn-v2": "Сохранить изменения", + "button_save-connection": "Сохранить", + "button_setting-permissions": "Настроить права доступа", + "button_specify-manually": "Указать вручную", + "button_upload": "Загрузить", + "button_verify": "Проверить подключение", + "column_created-at": "Дата создания", + "column_owner": "Владелец", + "column_status": "Статус", + "column_title": "Имя", + "column_type": "Тип", + "context_where-can-it-be-taken-token-yt": "Получение OAuth-токена для YT", + "field_accuracy": "Точность", + "field_application": "Приложение", + "field_auth-method": "Метод

аутентификации ", + "field_cache-ttl-sec": "Время жизни кeша", + "field_cache-ttl-sec_unit": "с", + "field_click-house-port": "Порт HTTP-интерфейса", + "field_clika": "Клика", + "field_cloud-and-folder": "Облако и каталог", + "field_cluster": "Кластер", + "field_connection-title": "Название подключения", + "field_counter-id": "Счетчик", + "field_database-id": "База данных", + "field_default-cluster": "Кластер по умолчанию", + "field_default-database": "Имя базы данных по умолчанию", + "field_enforce-collate": "Указание collate в запросе", + "field_fill-mode": "Подключение", + "field_get-token": "Получить токен", + "field_host-name": "Имя хоста", + "field_metrica-namespace": "Группа метрик", + "field_moysklad-token": "Токен доступа", + "field_sa_key": "Ключ SA", + "field_service-account": "Сервисный аккаунт", + "field_settings": "Настройки", + "field_token-metrika": "OAuth-токен", + "field_token-yt": "Токен YT", + "field_username": "Имя пользователя", + "label_accuracy-auto": "Автоматически", + "label_advanced-settings": "Продвинутые настройки подключения", + "label_auth-method-hint": "Нельзя изменить метод аутентификации после создания подключения", + "label_auth-showflake-copy": "Получить сниппет", + "label_authentication-method-choice": "Выберите метод аутентификации", + "label_ch-frozen-description": "Это специальное подключение к ClickHouse (часть продукта в [Marletplace]({{link}})). Вы не можете его редактировать.", + "label_ch-geo-filtered-description": "Это служебное подключение для работы датасетов с геослоями. Соединение можно настроить в [Marletplace]({{link}})", + "label_chyt-token-auth-description": "Укажите токен пользователя, от имени которого должны выполняться запросы", + "label_chyt-user-auth-description": "Аутентификация по cookie, запросы будут выполняться от имени пользователя, просматривающего датасет, чарт или дашборд", + "label_cloud-tree-fetching-failed": "Не удалось загрузить список облаков", + "label_connection-csv-name": "Название подключения", + "label_connection-info": "Информация о подключении", + "label_connection-loading": "Загрузка", + "label_connections-empty-msg": "У вас пока нет ни одного подключения", + "label_connections-list-failed": "Ошибка: не удалось загрузить список подключений", + "label_connections-not-exist": "У вас пока нет ни одного подключения", + "label_databases-empty-list": "Нет доступных баз", + "label_db-name": "Путь к базе данных", + "label_delimiter-comma": "Запятая", + "label_delimiter-semicolon": "Точка с запятой", + "label_delimiter-tab": "Знак табуляции", + "label_documentation": "документации", + "label_entry-name-already-exists": "Название уже используется, придумайте другое", + "label_error-400-no-connection-title": "Отсутствует подключение", + "label_error-400-title": "Некорректный запрос к подключению", + "label_error-403-title": "У вас нет доступа к подключению", + "label_error-404-title": "Подключение не найдено", + "label_error-500-title": "Не удалось загрузить подключение", + "label_error-duplicated-keys": "Ключ уже используется в другой паре «ключ — значение»", + "label_error-empty-field": "Поле обязательно для заполнения", + "label_error-length-field": "Значение имеет некорректное количество символов", + "label_error-msg": "Не удалось загрузить список подключений", + "label_error-on-template-apply": "При разворачивании темплейта произошла ошибка", + "label_expiration-date": "Истекает {{date}}", + "label_file-input-label": "Файл \"{{fileName}}\" успешно загружен", + "label_file-upload-failure": "Не удалось загрузить файл, повторите попытку через некоторое время", + "label_get-refresh-token-failure": "Не удалось получить refresh-токен", + "label_kp-interest-index-description": "Подключение позволит вам просматривать индекс интереса к сериалам. По кнопке ниже вы получите ключ (OAuth-токен), с помощью которого вам будет предоставлен доступ к данным в DataLens.", + "label_manual-title": "Инструкция для подключения к {{app}}", + "label_mdb-clusters-fetch-failed": "Не удалось загрузить список кластеров", + "label_mdb-databases-fetch-failed": "Не удалось загрузить список баз", + "label_mdb-hosts-fetch-failed": "Не удалось загрузить список хостов", + "label_mdb-no-clusters": "У вас нет ни одного кластера", + "label_mdb-users-fetch-failed": "Не удалось загрузить список пользователей", + "label_moysklad-token-hint": "Как получить токен смотрите в документации разработчика.", + "label_moysklad-token-hint-conn-v2": "Как получить токен смотрите в [документации разработчика]({{link}}).", + "label_name-clickhouse": "ClickHouse", + "label_not-found-connections": "Ничего не нашлось", + "label_or": "или", + "label_placeholder-appmetrica-id": "ID приложения", + "label_placeholder-metrica-id": "ID счетчика", + "label_portal": "Портал", + "label_project-id": "ID проекта", + "label_refresh-token-label": "Refresh-токен", + "label_secret-value": "Секретное значение", + "label_section-caption-counter-settings": "Настройки счетчика", + "label_select-folder": "Выберите каталог", + "label_service-acc-key-file": "Публичный ключ сервисного аккаунта", + "label_service-account-ydb-hint": "Выберите существующий сервисный аккаунт или создайте новый", + "label_service-accounts-empty-list": "Нет доступных аккаунтов", + "label_service-accounts-fetching-failed": "Не удалось загрузить список сервисных аккаунтов", + "label_showflake-oauth-integration-label": "Интеграция OAuth", + "label_showflake-oauth-integration-placeholder": "Название интеграции", + "label_snowflake-doc-help": "Подробнее в [документации]({{link}})", + "label_token": "Токен", + "label_utyt-conn-description": "Это подключение позволит вам просматривать детальную статистику по запросам в датасеты и просмотрам дашбордов. Статистика доступна только по датасетам и дашбордам, для которых вы имеете право администрирования.", + "label_verify-connection-500": "Ошибка: не удалось проверить подключение", + "section_connection": "Подключение", + "section_connections": "Подключения", + "section_creation-connection": "Новое подключение", + "section_creation-dataset": "Новый датасет", + "section_db-name": "Имя базы данных", + "section_or-another-variant": "или введите YQL Public Link:", + "section_selection-connection": "Выбор подключения", + "section_source-replacement": "Замена источника", + "switch_access-settings": "Настроить доступ", + "switch_create-dataset": "Создать датасет", + "switch_delete-connection": "Удалить", + "switch_edit-connection": "Редактировать", + "toast_create-connection-error": "Не удалось создать подключение", + "toast_error-action-label": "Подробнее", + "toast_modify-connection-error": "Не удалось сохранить подключение", + "toast_unsaved": "На странице есть несохраненные изменения. Вы уверены?", + "toast_verify-error": "Не удалось проверить параметры подключения", + "value_cluster-arnold": "Arnold", + "value_cluster-hahn": "Hahn", + "value_cluster-vanga": "Vanga", + "value_counter-source-advertising": "Клики", + "value_counter-source-hits": "Просмотры", + "value_counter-source-user-param": "Параметры посетителей", + "value_counter-source-visits": "Визиты", + "value_customizable-cache-ttl": "Настраиваемое", + "value_db-connect-method-service-name": "Имя сервиса", + "value_db-connect-method-sid": "SID", + "value_default": "По умолчанию", + "value_enforce-collate-auto": "Авто", + "value_enforce-collate-off": "Выкл", + "value_enforce-collate-on": "Вкл", + "value_fill-mode-cloud": "Выбрать в организации", + "value_fill-mode-manually": "Указать вручную", + "value_metrica-counter-input-method-list": "Из списка", + "value_metrica-counter-input-method-manually": "Вручную", + "value_metrica-namespace-audience": "Аудитории", + "value_metrica-namespace-audience-socdem": "Аудитории + соц.дем", + "value_metrica-namespace-clicks": "Трекинговые клики", + "value_metrica-namespace-client-events": "Клиентские события", + "value_metrica-namespace-crashes": "Крэши", + "value_metrica-namespace-deeplinks": "Открытия приложения через deeplink", + "value_metrica-namespace-errors": "Ошибки", + "value_metrica-namespace-events": "События приложения", + "value_metrica-namespace-installations": "Установки приложения", + "value_metrica-namespace-installs": "Установки", + "value_metrica-namespace-postbacks": "Постбеки", + "value_metrica-namespace-profiles": "Профили", + "value_metrica-namespace-push-events": "Push-кампании", + "value_metrica-namespace-push-tokens": "Push-токены", + "value_metrica-namespace-revenue-events": "Покупки в приложении", + "value_metrica-namespace-sessions-starts": "Начало сессий", + "value_permission-explicit": "Доступно для компании", + "value_permission-owner-only": "Доступно только мне", + "value_token-input-mode-automatic": "Автоматически", + "value_token-input-mode-manually": "Вручную" +} diff --git a/src/i18n-keysets/connections.gsheet.view/context.json b/src/i18n-keysets/connections.gsheet.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/connections.gsheet.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/connections.gsheet.view/en.json b/src/i18n-keysets/connections.gsheet.view/en.json new file mode 100644 index 0000000..1fe749d --- /dev/null +++ b/src/i18n-keysets/connections.gsheet.view/en.json @@ -0,0 +1,42 @@ +{ + "button_add": "Add", + "button_add-table": "Add file", + "button_apply": "Done", + "button_cancel": "Cancel", + "button_google-login": "Log in to your Google account", + "button_google-logout": "Log out", + "button_update": "Update data", + "label_403-not-authotized-description": "Log in to your Google account or change your file access settings and try adding it again", + "label_403-title": "You don't have access to the file", + "label_add-table-placeholder": "Link to file", + "label_auth-failure": "Couldn't authenticate", + "label_auth-success": "Authentication passed", + "label_auto-update": "Update automatically", + "label_auto-update-help": "Data in the tables will be updated no more than once every 30 minutes", + "label_columns-header": "Show column headers", + "label_empty-file": "Empty document", + "label_file-limit-exceeded": "Maximum data size exceeded", + "label_file-not-found": "Document not found", + "label_google-auth-help": "To add files with restricted access, log in to your Google account", + "label_google-oauth-page-failure": "Couldn't open authentication page", + "label_gsheet-failed": "Couldn't load data from sheets", + "label_gsheet-ready": "Select sheets", + "label_gsheet-sources-dialog-title-batch": "Add sheets", + "label_gsheet-sources-dialog-title-single": "Replace sheet", + "label_gsheet-uploading-failure": "Couldn't upload file", + "label_invalid-link": "Invalid link format", + "label_lists": "Sheets", + "label_logout-dialog-description": "Sheets from files with restricted access will no longer be displayed", + "label_logout-dialog-title": "Are you sure you want to log out of your Goggle account?", + "label_no": "No", + "label_replace-name": "Rename", + "label_replace-source": "Replace", + "label_source-failed": "Couldn't load sheet", + "label_source-info-failed": "Couldn't load data from sheet", + "label_too-many-columns": "Maximum number of columns in the table exceeded", + "label_unsupported-document": "Document not supported", + "label_update-failure": "Couldn't update data", + "label_uploading": "Loading...", + "label_workspace-placeholder": "To create a connection, add links to Google Sheets files", + "label_yes": "Yes" +} diff --git a/src/i18n-keysets/connections.gsheet.view/keyset.json b/src/i18n-keysets/connections.gsheet.view/keyset.json new file mode 100644 index 0000000..c61bd18 --- /dev/null +++ b/src/i18n-keysets/connections.gsheet.view/keyset.json @@ -0,0 +1,172 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_add-table": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_google-login": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_google-logout": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_update": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_403-not-authotized-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_403-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-table-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_auth-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_auth-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_auto-update": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_auto-update-help": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_columns-header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-file": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_file-limit-exceeded": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_file-not-found": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_google-auth-help": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_google-oauth-page-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gsheet-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gsheet-ready": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gsheet-sources-dialog-title-batch": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gsheet-sources-dialog-title-single": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gsheet-uploading-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_lists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_logout-dialog-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_logout-dialog-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_replace-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_replace-source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-info-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_too-many-columns": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unsupported-document": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_update-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_uploading": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_workspace-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yes": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/connections.gsheet.view/ru.json b/src/i18n-keysets/connections.gsheet.view/ru.json new file mode 100644 index 0000000..f045b18 --- /dev/null +++ b/src/i18n-keysets/connections.gsheet.view/ru.json @@ -0,0 +1,42 @@ +{ + "button_add": "Добавить", + "button_add-table": "Добавить файл", + "button_apply": "Готово", + "button_cancel": "Отмена", + "button_google-login": "Войти в аккаунт Google", + "button_google-logout": "Выйти", + "button_update": "Обновить данные", + "label_403-not-authotized-description": "Войдите в аккаунт Google или измените настройки доступа к файлу и попробуйте добавить его снова", + "label_403-title": "У вас нет доступа к файлу", + "label_add-table-placeholder": "Ссылка на файл", + "label_auth-failure": "Не удалось пройти аутентификацию", + "label_auth-success": "Аутентификация пройдена", + "label_auto-update": "Обновлять автоматически", + "label_auto-update-help": "Данные в таблицах будут обновляться не чаще, чем один раз за 30 минут", + "label_columns-header": "Показывать заголовки столбцов", + "label_empty-file": "Пустой документ", + "label_file-limit-exceeded": "Превышен максимальный объём данных", + "label_file-not-found": "Документ не найден", + "label_google-auth-help": "Чтобы добавить файлы с ограниченным доступом, войдите в свой аккаунт Google", + "label_google-oauth-page-failure": "Не удалось открыть страницу аутентификации", + "label_gsheet-failed": "Не удалось загрузить данные из листов", + "label_gsheet-ready": "Выбрать листы", + "label_gsheet-sources-dialog-title-batch": "Добавить листы", + "label_gsheet-sources-dialog-title-single": "Заменить лист", + "label_gsheet-uploading-failure": "Не удалось загрузить файл", + "label_invalid-link": "Неверный формат ссылки", + "label_lists": "Листы", + "label_logout-dialog-description": "Листы из файлов с ограниченным доступом перестанут отображаться", + "label_logout-dialog-title": "Вы действительно хотите выйти из аккаунта Google?", + "label_no": "Нет", + "label_replace-name": "Переименовать", + "label_replace-source": "Заменить", + "label_source-failed": "Не удалось загрузить лист", + "label_source-info-failed": "Не удалось загрузить данные из листа", + "label_too-many-columns": "Превышено максимальное количество столбцов в таблице", + "label_unsupported-document": "Документ не поддерживается", + "label_update-failure": "Не удалось обновить данные", + "label_uploading": "Загрузка...", + "label_workspace-placeholder": "Чтобы создать подключение, добавьте ссылки на файлы Google Sheets", + "label_yes": "Да" +} diff --git a/src/i18n-keysets/connections.yadocs.view/context.json b/src/i18n-keysets/connections.yadocs.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/connections.yadocs.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/connections.yadocs.view/en.json b/src/i18n-keysets/connections.yadocs.view/en.json new file mode 100644 index 0000000..9b8273b --- /dev/null +++ b/src/i18n-keysets/connections.yadocs.view/en.json @@ -0,0 +1,29 @@ +{ + "button_add": "Add", + "button_apply": "Add", + "button_auth": "Log in to Yandex Documents", + "button_auth-dialog": "Log in", + "button_cancel": "Cancel", + "button_logout": "Revoke", + "button_update": "Update data", + "label_403-not-authotized-description": "Log in to your Yandex account or change the file access type and try adding it again", + "label_access-type": "Access to file", + "label_add-file": "Add file", + "label_add-input-private-help": "The path is specified relative to the root directory /disk. Read more about finding the path in the documentation.", + "label_add-input-private-note": "Example: /folder/path/table.xlsx", + "label_add-input-public-help": "Files with a password set or a download ban enabled are not supported. Please use private files available after authentication instead.", + "label_add-input-public-note": "Example: https://disk.yandex.ru/i/QLFNDx7c5*****", + "label_alert-authorized": "You are logged in to Yandex Documents and now can add private files", + "label_alert-not-authorized": "To add a private file, log in to Yandex Documents", + "label_auto-update": "Update automatically", + "label_auto-update-help": "Data in the tables will be updated no more than once every 30 minutes.", + "label_form-tile": "Yandex Documents", + "label_logout": "You are logged in to Yandex Documents", + "label_logout-dialog-description": "File contents with private access will no longer be displayed", + "label_logout-dialog-title": "Are you sure you want to revoke your token?", + "label_private-path": "Path to file in Yandex Documents", + "label_public-path": "Link to file in Yandex Documents", + "label_radio-value-private": "Private", + "label_radio-value-public": "Public", + "label_workspace-placeholder": "To create a connection, add files from Yandex Documents. Only the XLSX format is supported." +} diff --git a/src/i18n-keysets/connections.yadocs.view/keyset.json b/src/i18n-keysets/connections.yadocs.view/keyset.json new file mode 100644 index 0000000..9a01e6b --- /dev/null +++ b/src/i18n-keysets/connections.yadocs.view/keyset.json @@ -0,0 +1,120 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_auth": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_auth-dialog": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_logout": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_update": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_403-not-authotized-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_access-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-file": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-input-private-help": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-input-private-note": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-input-public-help": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-input-public-note": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alert-authorized": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alert-not-authorized": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_auto-update": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_auto-update-help": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_form-tile": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_logout": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_logout-dialog-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_logout-dialog-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_private-path": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_public-path": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_radio-value-private": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_radio-value-public": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_workspace-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/connections.yadocs.view/ru.json b/src/i18n-keysets/connections.yadocs.view/ru.json new file mode 100644 index 0000000..664209e --- /dev/null +++ b/src/i18n-keysets/connections.yadocs.view/ru.json @@ -0,0 +1,29 @@ +{ + "button_add": "Добавить", + "button_apply": "Добавить", + "button_auth": "Войти в Яндекс Документы", + "button_auth-dialog": "Войти", + "button_cancel": "Отмена", + "button_logout": "Отозвать", + "button_update": "Обновить данные", + "label_403-not-authotized-description": "Войдите в свой аккаунт на Яндексе или измените тип доступа к файлу и попробуйте добавить его снова", + "label_access-type": "Доступ к файлу", + "label_add-file": "Добавить файл", + "label_add-input-private-help": "Путь указывается относительно корневого каталога /disk. Подробнее о том, как узнать путь, в документации.", + "label_add-input-private-note": "Пример: /folder/path/table.xlsx", + "label_add-input-public-help": "Файлы, для которых установлен пароль или включен запрет на скачивание, не поддерживаются. Вместо них используйте приватные файлы, доступные после аутентификации.", + "label_add-input-public-note": "Пример: https://disk.yandex.ru/i/QLFNDx7c5*****", + "label_alert-authorized": "Вы авторизованы в Яндекс Документах и теперь можете добавлять приватные файлы", + "label_alert-not-authorized": "Чтобы добавить приватный файл, войдите в Яндекс Документы", + "label_auto-update": "Обновлять автоматически", + "label_auto-update-help": "Данные в таблицах будут обновляться не чаще, чем один раз за 30 минут.", + "label_form-tile": "Яндекс Документы", + "label_logout": "Вы авторизованы в Яндекс Документах", + "label_logout-dialog-description": "Содержимое файлов с приватным доступом перестанет отображаться", + "label_logout-dialog-title": "Вы действительно хотите отозвать свой токен?", + "label_private-path": "Путь до файла в Яндекс Документах", + "label_public-path": "Ссылка на файл в Яндекс Документах", + "label_radio-value-private": "Приватный", + "label_radio-value-public": "Публичный", + "label_workspace-placeholder": "Чтобы создать подключение, добавьте файлы из Яндекс Документов. Поддерживается только формат XLSX." +} diff --git a/src/i18n-keysets/dash.action-panel.view/context.json b/src/i18n-keysets/dash.action-panel.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.action-panel.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.action-panel.view/en.json b/src/i18n-keysets/dash.action-panel.view/en.json new file mode 100644 index 0000000..b7d6936 --- /dev/null +++ b/src/i18n-keysets/dash.action-panel.view/en.json @@ -0,0 +1,42 @@ +{ + "button_add-favorite": "Add to favorites", + "button_back": "Back", + "button_cancel": "Cancel", + "button_connections": "Links", + "button_continue": "Continue", + "button_edit": "Edit", + "button_mailing": "Mailing", + "button_menu": "Menu", + "button_ready": "Ready", + "button_remove-favorite": "Delete from favorites", + "button_request-rights": "Request permissions", + "button_save": "Save", + "button_save-as-draft": "Save as draft", + "button_settings": "Settings", + "button_tabs": "Tabs", + "dialog_deprecation-apply-label": "Update", + "dialog_deprecation-cancel-label": "Cancel", + "dialog_deprecation-confirm-header": "Update dashboard configuration", + "dialog_deprecation-confirm-text": "The dashboard may behave differently after the update. Are you sure you want to continue?", + "dialog_deprecation-copy-text": "The current version of the dashboard is deprecated. Its settings will be updated when it is copied.", + "label_as_draft": "Save as draft", + "label_dash-info": "Dashboard info", + "label_deprecation-warning": "The current version of the dashboard is deprecated.", + "label_mailing": "Go to the tab and choose selector values", + "label_save_and_publish": "Save and make actual", + "label_save_as_new": "Save as new dashboard", + "label_unsaved-changes-title": "There are unsaved changes on this page", + "label_unsaved-changes-warning": "You can return to the dashboard and save them. If you continue, these changes will be lost.", + "toast_error": "An error occurred", + "toast_failed-add-favorite": "Failed to add to favorites", + "toast_failed-remove-favorite": "Failed to delete from favorites", + "value_copy": "Copy", + "value_delete": "Delete", + "value_fullscreen": "Fullscreen", + "value_mailings": "Mailings", + "value_move": "Move", + "value_rename": "Rename", + "value_table-of-content": "Contents", + "value_widget-ql": "Chart (QL)", + "value_widget-wizard": "Chart (Wizard)" +} diff --git a/src/i18n-keysets/dash.action-panel.view/keyset.json b/src/i18n-keysets/dash.action-panel.view/keyset.json new file mode 100644 index 0000000..fe54582 --- /dev/null +++ b/src/i18n-keysets/dash.action-panel.view/keyset.json @@ -0,0 +1,172 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-favorite": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_back": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_continue": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_mailing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_menu": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_ready": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_remove-favorite": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_request-rights": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_tabs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog_deprecation-apply-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog_deprecation-cancel-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog_deprecation-confirm-header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog_deprecation-confirm-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dialog_deprecation-copy-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_as_draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dash-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_deprecation-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mailing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_save_and_publish": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_save_as_new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unsaved-changes-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unsaved-changes-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_failed-add-favorite": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_failed-remove-favorite": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_fullscreen": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_mailings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_move": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_rename": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_table-of-content": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_widget-ql": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_widget-wizard": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.action-panel.view/ru.json b/src/i18n-keysets/dash.action-panel.view/ru.json new file mode 100644 index 0000000..8d5534b --- /dev/null +++ b/src/i18n-keysets/dash.action-panel.view/ru.json @@ -0,0 +1,42 @@ +{ + "button_add-favorite": "Добавить в избранное", + "button_back": "Назад", + "button_cancel": "Отменить", + "button_connections": "Связи", + "button_continue": "Продолжить", + "button_edit": "Редактировать", + "button_mailing": "Рассылки", + "button_menu": "Меню", + "button_ready": "Готово", + "button_remove-favorite": "Удалить из избранного", + "button_request-rights": "Запросить права", + "button_save": "Сохранить", + "button_save-as-draft": "Сохранить как черновик", + "button_settings": "Настройки", + "button_tabs": "Вкладки", + "dialog_deprecation-apply-label": "Обновить", + "dialog_deprecation-cancel-label": "Отмена", + "dialog_deprecation-confirm-header": "Обновление конфигурации дашборда", + "dialog_deprecation-confirm-text": "После обновления поведение дашборда может измениться. Вы уверены, что хотите продолжить?", + "dialog_deprecation-copy-text": "Конфигурация дашборда устарела. При копировании конфигурация автоматически будет обновлена.", + "label_as_draft": "Сохранить как черновик", + "label_dash-info": "Информация о дашборде", + "label_deprecation-warning": "Конфигурация дашборда устарела", + "label_mailing": "Перейдите на вкладку и выберите значения селекторов", + "label_save_and_publish": "Сохранить и сделать актуальным", + "label_save_as_new": "Сохранить как новый дашборд", + "label_unsaved-changes-title": "На странице остались несохранённые изменения", + "label_unsaved-changes-warning": "Вы можете вернуться на дашборд и сохранить их. Если вы продолжите, эти изменения будут потеряны.", + "toast_error": "Произошла ошибка", + "toast_failed-add-favorite": "Не удалось добавить в избранное", + "toast_failed-remove-favorite": "Не удалось удалить из избранного", + "value_copy": "Создать копию", + "value_delete": "Удалить", + "value_fullscreen": "На весь экран", + "value_mailings": "Рассылки", + "value_move": "Переместить", + "value_rename": "Переименовать", + "value_table-of-content": "Оглавление", + "value_widget-ql": "Чарт (QL)", + "value_widget-wizard": "Чарт (Wizard)" +} diff --git a/src/i18n-keysets/dash.chartkit-menu.view/context.json b/src/i18n-keysets/dash.chartkit-menu.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.chartkit-menu.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.chartkit-menu.view/en.json b/src/i18n-keysets/dash.chartkit-menu.view/en.json new file mode 100644 index 0000000..f48403a --- /dev/null +++ b/src/i18n-keysets/dash.chartkit-menu.view/en.json @@ -0,0 +1,4 @@ +{ + "button_edit": "Edit", + "button_new-tab": "Open in a new tab" +} diff --git a/src/i18n-keysets/dash.chartkit-menu.view/keyset.json b/src/i18n-keysets/dash.chartkit-menu.view/keyset.json new file mode 100644 index 0000000..527a420 --- /dev/null +++ b/src/i18n-keysets/dash.chartkit-menu.view/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_new-tab": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.chartkit-menu.view/ru.json b/src/i18n-keysets/dash.chartkit-menu.view/ru.json new file mode 100644 index 0000000..937f424 --- /dev/null +++ b/src/i18n-keysets/dash.chartkit-menu.view/ru.json @@ -0,0 +1,4 @@ +{ + "button_edit": "Редактировать", + "button_new-tab": "Открыть в новой вкладке" +} diff --git a/src/i18n-keysets/dash.control-dialog.edit/context.json b/src/i18n-keysets/dash.control-dialog.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.control-dialog.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.control-dialog.edit/en.json b/src/i18n-keysets/dash.control-dialog.edit/en.json new file mode 100644 index 0000000..6971785 --- /dev/null +++ b/src/i18n-keysets/dash.control-dialog.edit/en.json @@ -0,0 +1,106 @@ +{ + "button_add": "Add", + "button_apply": "Apply", + "button_cancel": "Cancel", + "button_deselect-all": "Clear", + "button_edit-query": "Edit query", + "button_open": "Open", + "button_save": "Save", + "button_select-all": "Select all", + "button_setup": "Configure", + "context_add-value": "Add value", + "context_choose": "Select", + "context_date-days-ago": "Days ago", + "context_date-from-days-ago": "Start, days ago", + "context_date-to-days-ago": "End, days ago", + "context_title": "Title", + "error_unsupported-connection": "Connection not supported", + "field_acceptable-values": "Possible values", + "field_autoheight": "Auto height", + "field_color-accent": "Color accent", + "field_color-accent-note": "Use this option to highlight important selectors", + "field_connection": "Connection", + "field_control": "Selector", + "field_dataset": "Dataset", + "field_date-from": "Start", + "field_date-range": "Range", + "field_date-to": "End", + "field_date-with-time": "Time", + "field_default-value": "Default value", + "field_element-type": "Element type", + "field_end-time": "End time", + "field_field": "Field", + "field_field-name": "Field or parameter", + "field_field-name-note": "The name of the field or parameter, which can be used to link the selector with other widgets.", + "field_hint": "Hint", + "field_inner-title": "Inner title", + "field_inner-title-note": "The inner title allows you to specify a text inside the selector, e.g., to display an operation inside a selector.

Not available in the Checkbox selector type.", + "field_inner-title-note-connection-selector": "The inner title allows you to specify a text inside the selector.", + "field_label": "Label", + "field_multiselectable": "Multiple choice", + "field_operation": "Operation", + "field_operation-note": "The type of operation has no effect on QL Charts and parameters", + "field_parameter-name": "Field name or parameter", + "field_params": "Parameters", + "field_preset": "Type", + "field_query": "Query", + "field_query-type": "Query type", + "field_required-value": "Required field", + "field_selector-type": "Selector type", + "field_show-title": "Show", + "field_source": "Source", + "field_start-time": "Start time", + "field_title": "Title", + "field_type": "Type", + "hint_not-combined-with-group-setting": "You can select a value that does not exceed the value specified in the selector group settings", + "label_acceptable-values": "Possible values", + "label_autoheight-disable": "Disable", + "label_autoheight-enable": "Enable", + "label_common-settings": "Common settings", + "label_control": "Selector", + "label_default-value": "Default value", + "label_empty-list": "List is empty", + "label_no-operations": "Thee are no available operations for the settings you specified", + "label_section-appearance": "Appearance", + "label_selected-tabs-placeholder": "Select tabs", + "label_tabs-scope": "Show in tabs", + "placeholder_search": "Search", + "radio-button_all": "All", + "radio-button_selected": "Selected", + "text_empty-items": "Not found", + "validation_empty-impact-tabs-ids": "Select at least one tab", + "validation_empty-parameters-values": "Fields with parameters must be filled out", + "validation_field-name-in-parameters": "The parameter name must not be the same as the names of parameters in the section below", + "validation_field-name-unique": "Parameter names in the group should not match. These selectors have the same parameter names: {{selectorsNames}}", + "validation_field-not-found": "Field not found", + "validation_need-current-tab-impact": "There must be at least one selector visible on the current tab", + "validation_not-combined-with-group-setting": "The value in the selector settings should not exceed the selector group settings", + "validation_required": "Field must be filled", + "value_absolute-date": "Exact date", + "value_all-tabs": "All tabs", + "value_as-group": "As group settings", + "value_checkbox-default-value-false": "False", + "value_checkbox-default-value-true": "True", + "value_current-tab": "Current tab", + "value_date-accepted-all": "Entire permitted interval", + "value_date-accepted-end": "End of permitted interval", + "value_date-accepted-start": "Start of permitted interval", + "value_date-ago": "ago", + "value_date-days-ago-from-to": "Start: {{from}}. End: {{to}}", + "value_date-forward": "forward", + "value_date-manual": "Selecting a value", + "value_date-no-limits": "No limits", + "value_element-checkbox": "Checkbox", + "value_element-date": "Calendar", + "value_element-input": "Input field", + "value_element-select": "List", + "value_not-chosen": "Not selected", + "value_relative-date": "Offset from the current date", + "value_select-values": "Values: {{count}}", + "value_selected-tabs": "Selected tabs", + "value_source-connection": "Based on a connection", + "value_source-dataset": "Based on dataset", + "value_source-external": "External selector", + "value_source-manual": "Manual input", + "value_undefined": "Not defined" +} diff --git a/src/i18n-keysets/dash.control-dialog.edit/keyset.json b/src/i18n-keysets/dash.control-dialog.edit/keyset.json new file mode 100644 index 0000000..5aebd58 --- /dev/null +++ b/src/i18n-keysets/dash.control-dialog.edit/keyset.json @@ -0,0 +1,428 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_deselect-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-query": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_open": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_select-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_setup": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_add-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_choose": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_date-days-ago": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_date-from-days-ago": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_date-to-days-ago": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_unsupported-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_acceptable-values": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_autoheight": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_color-accent": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_color-accent-note": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_control": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_date-from": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_date-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_date-to": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_date-with-time": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_default-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_element-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_end-time": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_field-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_field-name-note": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_inner-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_inner-title-note": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_inner-title-note-connection-selector": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_multiselectable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_operation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_operation-note": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_parameter-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_params": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_preset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_query": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_query-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_required-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_selector-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_show-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_start-time": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "hint_not-combined-with-group-setting": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_acceptable-values": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_autoheight-disable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_autoheight-enable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_common-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_control": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-list": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-operations": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_section-appearance": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_selected-tabs-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tabs-scope": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_search": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "radio-button_all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "radio-button_selected": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_empty-items": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_empty-impact-tabs-ids": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_empty-parameters-values": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_field-name-in-parameters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_field-name-unique": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_field-not-found": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_need-current-tab-impact": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_not-combined-with-group-setting": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "validation_required": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_absolute-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_all-tabs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_as-group": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_checkbox-default-value-false": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_checkbox-default-value-true": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_current-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date-accepted-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date-accepted-end": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date-accepted-start": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date-ago": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date-days-ago-from-to": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date-forward": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date-manual": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date-no-limits": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_element-checkbox": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_element-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_element-input": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_element-select": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_not-chosen": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_relative-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_select-values": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_selected-tabs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_source-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_source-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_source-external": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_source-manual": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_undefined": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.control-dialog.edit/ru.json b/src/i18n-keysets/dash.control-dialog.edit/ru.json new file mode 100644 index 0000000..fb5ba58 --- /dev/null +++ b/src/i18n-keysets/dash.control-dialog.edit/ru.json @@ -0,0 +1,106 @@ +{ + "button_add": "Добавить", + "button_apply": "Применить", + "button_cancel": "Отменить", + "button_deselect-all": "Очистить", + "button_edit-query": "Редактировать запрос", + "button_open": "Открыть", + "button_save": "Сохранить", + "button_select-all": "Выбрать всё", + "button_setup": "Настроить", + "context_add-value": "Добавить значение", + "context_choose": "Выбрать", + "context_date-days-ago": "Дней назад", + "context_date-from-days-ago": "Начало, дней назад", + "context_date-to-days-ago": "Конец, дней назад", + "context_title": "Заголовок", + "error_unsupported-connection": "Подключение не поддерживается", + "field_acceptable-values": "Возможные значения", + "field_autoheight": "Автовысота", + "field_color-accent": "Цветовой акцент", + "field_color-accent-note": "Используйте опцию для выделения цветом важных селекторов", + "field_connection": "Подключение", + "field_control": "Селектор", + "field_dataset": "Датасет", + "field_date-from": "Начало", + "field_date-range": "Диапазон", + "field_date-to": "Конец", + "field_date-with-time": "Время", + "field_default-value": "Значение по умолчанию", + "field_element-type": "Тип элемента", + "field_end-time": "Конец", + "field_field": "Поле", + "field_field-name": "Поле или параметр", + "field_field-name-note": "Имя поля или параметра, по которому можно установить связь селектора с другими виджетами.", + "field_hint": "Подсказка", + "field_inner-title": "Внутренний заголовок", + "field_inner-title-note": "Внутренний заголовок позволяет указать текст внутри селектора. Например, его можно использовать для отображения операции внутри селектора.

Недоступен в селекторах типа «Чекбокс».", + "field_inner-title-note-connection-selector": "Внутренний заголовок позволяет указать текст внутри селектора.", + "field_label": "Лейбл", + "field_multiselectable": "Множественный выбор", + "field_operation": "Операция", + "field_operation-note": "Тип операции не влияет на QL-чарты и параметры", + "field_parameter-name": "Имя поля или параметр", + "field_params": "Параметры", + "field_preset": "Пресет", + "field_query": "Запрос", + "field_query-type": "Тип запроса", + "field_required-value": "Обязательное поле", + "field_selector-type": "Тип селектора", + "field_show-title": "Показывать", + "field_source": "Источник", + "field_start-time": "Начало", + "field_title": "Заголовок", + "field_type": "Тип", + "hint_not-combined-with-group-setting": "Можно выбрать значение, которое не превышает значения, указанного в настройках группы селекторов", + "label_acceptable-values": "Возможные значения", + "label_autoheight-disable": "Выключить", + "label_autoheight-enable": "Включить", + "label_common-settings": "Общие настройки", + "label_control": "Селектор", + "label_default-value": "Значение по умолчанию", + "label_empty-list": "Список пуст", + "label_no-operations": "Для заданных настроек нет подходящих операций", + "label_section-appearance": "Внешний вид", + "label_selected-tabs-placeholder": "Выберите вкладки", + "label_tabs-scope": "Показать на вкладках", + "placeholder_search": "Поиск", + "radio-button_all": "Все", + "radio-button_selected": "Выбранные", + "text_empty-items": "Нет значений", + "validation_empty-impact-tabs-ids": "Выберите хотя бы одну вкладку", + "validation_empty-parameters-values": "Поле с параметрами должно быть заполнено", + "validation_field-name-in-parameters": "Имя параметра не должно совпадать с именами параметров из раздела ниже", + "validation_field-name-unique": "Имена параметров в группе не должны совпадать. Селекторы с одинаковыми именами параметров: {{selectorsNames}}", + "validation_field-not-found": "Поле не найдено", + "validation_need-current-tab-impact": "Должен быть хотя бы один селектор, отображаемый на текущей вкладке", + "validation_not-combined-with-group-setting": "Значение в настройках селектора не должно превышать значения, указанного в настройках группы селекторов", + "validation_required": "Поле должно быть заполнено", + "value_absolute-date": "Точная дата", + "value_all-tabs": "Все вкладки", + "value_as-group": "Из настроек группы", + "value_checkbox-default-value-false": "False", + "value_checkbox-default-value-true": "True", + "value_current-tab": "Текущая вкладка", + "value_date-accepted-all": "Весь допустимый интервал", + "value_date-accepted-end": "Конец допустимого интервала", + "value_date-accepted-start": "Начало допустимого интервала", + "value_date-ago": "назад", + "value_date-days-ago-from-to": "Начало: {{from}}. Конец: {{to}}", + "value_date-forward": "вперёд", + "value_date-manual": "Выбор значения", + "value_date-no-limits": "Не ограничено", + "value_element-checkbox": "Чекбокс", + "value_element-date": "Календарь", + "value_element-input": "Поле ввода", + "value_element-select": "Список", + "value_not-chosen": "Не выбрано", + "value_relative-date": "Смещение от текущей даты", + "value_select-values": "Значений: {{count}}", + "value_selected-tabs": "Выбранные вкладки", + "value_source-connection": "На основе подключения", + "value_source-dataset": "На основе датасета", + "value_source-external": "Внешний селектор", + "value_source-manual": "Ручной ввод", + "value_undefined": "Не определено" +} diff --git a/src/i18n-keysets/dash.dashkit-control.error/context.json b/src/i18n-keysets/dash.dashkit-control.error/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-control.error/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.dashkit-control.error/en.json b/src/i18n-keysets/dash.dashkit-control.error/en.json new file mode 100644 index 0000000..ef92815 --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-control.error/en.json @@ -0,0 +1,7 @@ +{ + "button_retry": "Repeat", + "label_error": "Error", + "label_error-timeout": "The server response waiting time has been exceeded", + "label_field-error-text": "The specified field type for the selector is not supported. Please change the field type.", + "label_field-error-title": "Field type error" +} diff --git a/src/i18n-keysets/dash.dashkit-control.error/keyset.json b/src/i18n-keysets/dash.dashkit-control.error/keyset.json new file mode 100644 index 0000000..c5939d8 --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-control.error/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-timeout": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-error-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-error-title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.dashkit-control.error/ru.json b/src/i18n-keysets/dash.dashkit-control.error/ru.json new file mode 100644 index 0000000..6b1681d --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-control.error/ru.json @@ -0,0 +1,7 @@ +{ + "button_retry": "Повторить", + "label_error": "Ошибка", + "label_error-timeout": "Превышено время ожидания ответа от сервера", + "label_field-error-text": "Выбранный тип поля для селектора не поддерживается. Пожалуйста, измените тип поля.", + "label_field-error-title": "Ошибка типа поля" +} diff --git a/src/i18n-keysets/dash.dashkit-plugin-common.view/context.json b/src/i18n-keysets/dash.dashkit-plugin-common.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-plugin-common.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.dashkit-plugin-common.view/en.json b/src/i18n-keysets/dash.dashkit-plugin-common.view/en.json new file mode 100644 index 0000000..cf9c60b --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-plugin-common.view/en.json @@ -0,0 +1,7 @@ +{ + "label_autoheight-checkbox": "Autoheight", + "label_background-checkbox": "Background", + "label_border-radius": "Rounding", + "label_internal-margins": "Margins", + "label_like-dash": "Like the dashboard" +} diff --git a/src/i18n-keysets/dash.dashkit-plugin-common.view/keyset.json b/src/i18n-keysets/dash.dashkit-plugin-common.view/keyset.json new file mode 100644 index 0000000..1ea7d26 --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-plugin-common.view/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_autoheight-checkbox": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_background-checkbox": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_border-radius": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_internal-margins": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_like-dash": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.dashkit-plugin-common.view/ru.json b/src/i18n-keysets/dash.dashkit-plugin-common.view/ru.json new file mode 100644 index 0000000..8c0008e --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-plugin-common.view/ru.json @@ -0,0 +1,7 @@ +{ + "label_autoheight-checkbox": "Автовысота", + "label_background-checkbox": "Фон", + "label_border-radius": "Скругление", + "label_internal-margins": "Отступы", + "label_like-dash": "Как у дашборда" +} diff --git a/src/i18n-keysets/dash.dashkit-plugin-control.view/context.json b/src/i18n-keysets/dash.dashkit-plugin-control.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-plugin-control.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.dashkit-plugin-control.view/en.json b/src/i18n-keysets/dash.dashkit-plugin-control.view/en.json new file mode 100644 index 0000000..d21659a --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-plugin-control.view/en.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Apply", + "button_details": "Details", + "button_reset": "Reset", + "button_retry": "Retry", + "placeholder_empty": "No selected values", + "value_required": "This field is required", + "value_undefined": "Not defined" +} diff --git a/src/i18n-keysets/dash.dashkit-plugin-control.view/keyset.json b/src/i18n-keysets/dash.dashkit-plugin-control.view/keyset.json new file mode 100644 index 0000000..0183b0e --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-plugin-control.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_details": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_reset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "placeholder_empty": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_required": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_undefined": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.dashkit-plugin-control.view/ru.json b/src/i18n-keysets/dash.dashkit-plugin-control.view/ru.json new file mode 100644 index 0000000..e3a5f02 --- /dev/null +++ b/src/i18n-keysets/dash.dashkit-plugin-control.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Применить", + "button_details": "Подробнее", + "button_reset": "Сбросить", + "button_retry": "Повторить", + "placeholder_empty": "Нет выбранных значений", + "value_required": "Обязательно для заполнения", + "value_undefined": "Не определено" +} diff --git a/src/i18n-keysets/dash.dialogs-common.edit/context.json b/src/i18n-keysets/dash.dialogs-common.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.dialogs-common.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.dialogs-common.edit/en.json b/src/i18n-keysets/dash.dialogs-common.edit/en.json new file mode 100644 index 0000000..7e84ebf --- /dev/null +++ b/src/i18n-keysets/dash.dialogs-common.edit/en.json @@ -0,0 +1,4 @@ +{ + "button_back": "Back", + "title_widget-settings": "Widget settings" +} diff --git a/src/i18n-keysets/dash.dialogs-common.edit/keyset.json b/src/i18n-keysets/dash.dialogs-common.edit/keyset.json new file mode 100644 index 0000000..587ae32 --- /dev/null +++ b/src/i18n-keysets/dash.dialogs-common.edit/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_back": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_widget-settings": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.dialogs-common.edit/ru.json b/src/i18n-keysets/dash.dialogs-common.edit/ru.json new file mode 100644 index 0000000..824f2c6 --- /dev/null +++ b/src/i18n-keysets/dash.dialogs-common.edit/ru.json @@ -0,0 +1,4 @@ +{ + "button_back": "Назад", + "title_widget-settings": "Настройки виджета" +} diff --git a/src/i18n-keysets/dash.edit-query-dialog/context.json b/src/i18n-keysets/dash.edit-query-dialog/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.edit-query-dialog/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.edit-query-dialog/en.json b/src/i18n-keysets/dash.edit-query-dialog/en.json new file mode 100644 index 0000000..7903b10 --- /dev/null +++ b/src/i18n-keysets/dash.edit-query-dialog/en.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel", + "error_invalid-typed-query-response": "The selector does not support responses with 2 or more columns from the database.", + "title_database-response": "Database response", + "title_edit-query": "Edit query", + "title_invalid-sql-query": "Invalid SQL query to database", + "title_sent-query": "Sent query" +} diff --git a/src/i18n-keysets/dash.edit-query-dialog/keyset.json b/src/i18n-keysets/dash.edit-query-dialog/keyset.json new file mode 100644 index 0000000..f561a73 --- /dev/null +++ b/src/i18n-keysets/dash.edit-query-dialog/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_invalid-typed-query-response": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_database-response": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_edit-query": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_invalid-sql-query": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_sent-query": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.edit-query-dialog/ru.json b/src/i18n-keysets/dash.edit-query-dialog/ru.json new file mode 100644 index 0000000..55dadce --- /dev/null +++ b/src/i18n-keysets/dash.edit-query-dialog/ru.json @@ -0,0 +1,9 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отменить", + "error_invalid-typed-query-response": "Селектор не поддерживает ответ из базы данных, если в нём 2 и более колонок.", + "title_database-response": "Ответ базы данных", + "title_edit-query": "Редактирование запроса", + "title_invalid-sql-query": "Некорректный SQL-запрос к базе данных", + "title_sent-query": "Отправленный запрос" +} diff --git a/src/i18n-keysets/dash.empty-state.view/context.json b/src/i18n-keysets/dash.empty-state.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.empty-state.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.empty-state.view/en.json b/src/i18n-keysets/dash.empty-state.view/en.json new file mode 100644 index 0000000..23d93eb --- /dev/null +++ b/src/i18n-keysets/dash.empty-state.view/en.json @@ -0,0 +1,11 @@ +{ + "button_edit": "Edit", + "label_empty-dash": "This dashboard is empty for now", + "label_empty-fixed-content": "This area can be collapsed and expanded. To fill it, drag the necessary blocks from the panel at the bottom of the page.", + "label_empty-fixed-header": "This area is fixed when scrolling the page. To fill it, drag the necessary blocks from the panel at the bottom of the page.", + "label_empty-tab": "This tab is empty for now", + "label_mobile-dash": "You haven't added anything yet", + "label_mobile-tab": "You haven't added anything yet", + "label_with-edit-rights": "Drag and drop blocks from the panel below to add charts, selectors, and explanations", + "label_without-edit-rights": "The owner of the dashboard hasn't added anything yet" +} diff --git a/src/i18n-keysets/dash.empty-state.view/keyset.json b/src/i18n-keysets/dash.empty-state.view/keyset.json new file mode 100644 index 0000000..e306d59 --- /dev/null +++ b/src/i18n-keysets/dash.empty-state.view/keyset.json @@ -0,0 +1,48 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-dash": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-fixed-content": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-fixed-header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mobile-dash": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mobile-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_with-edit-rights": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_without-edit-rights": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.empty-state.view/ru.json b/src/i18n-keysets/dash.empty-state.view/ru.json new file mode 100644 index 0000000..b3091fb --- /dev/null +++ b/src/i18n-keysets/dash.empty-state.view/ru.json @@ -0,0 +1,11 @@ +{ + "button_edit": "Редактировать", + "label_empty-dash": "Дашборд пока пустой", + "label_empty-fixed-content": "Эту область можно сворачивать и разворачивать. Чтобы наполнить её, перетаскивайте нужные блоки с панели внизу страницы.", + "label_empty-fixed-header": "Эта область фиксируется при прокрутке страницы. Чтобы наполнить её, перетаскивайте нужные блоки с панели внизу страницы.", + "label_empty-tab": "На этой вкладке пока пусто", + "label_mobile-dash": "На него ещё ничего не добавили", + "label_mobile-tab": "На неё ещё ничего не добавили", + "label_with-edit-rights": "Перетаскивайте блоки с панели снизу, чтобы добавить на дашборд чарт, селектор или поясняющий текст", + "label_without-edit-rights": "Владелец дашборда ещё ничего сюда не добавил" +} diff --git a/src/i18n-keysets/dash.error.view/context.json b/src/i18n-keysets/dash.error.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.error.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.error.view/en.json b/src/i18n-keysets/dash.error.view/en.json new file mode 100644 index 0000000..bcd62eb --- /dev/null +++ b/src/i18n-keysets/dash.error.view/en.json @@ -0,0 +1,5 @@ +{ + "label_description-embed-dash-limit-reached": "For you to continue viewing the embedded object, the organization's administrator must increase the number of seats", + "label_error-404-message": "Object not found", + "label_title-embed-dash-limit-reached": "Embedded dashboard limit exceeded" +} diff --git a/src/i18n-keysets/dash.error.view/keyset.json b/src/i18n-keysets/dash.error.view/keyset.json new file mode 100644 index 0000000..3109ee2 --- /dev/null +++ b/src/i18n-keysets/dash.error.view/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_description-embed-dash-limit-reached": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-404-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-embed-dash-limit-reached": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.error.view/ru.json b/src/i18n-keysets/dash.error.view/ru.json new file mode 100644 index 0000000..5d2ed0f --- /dev/null +++ b/src/i18n-keysets/dash.error.view/ru.json @@ -0,0 +1,5 @@ +{ + "label_description-embed-dash-limit-reached": "Чтобы вы смогли продолжить просматривать встроенный объект, администратор организации должен увеличить число рабочих мест", + "label_error-404-message": "Объект не найден", + "label_title-embed-dash-limit-reached": "Превышено ограничение на число запросов" +} diff --git a/src/i18n-keysets/dash.extended-settings-dialog.edit/context.json b/src/i18n-keysets/dash.extended-settings-dialog.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.extended-settings-dialog.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.extended-settings-dialog.edit/en.json b/src/i18n-keysets/dash.extended-settings-dialog.edit/en.json new file mode 100644 index 0000000..2e1706f --- /dev/null +++ b/src/i18n-keysets/dash.extended-settings-dialog.edit/en.json @@ -0,0 +1,17 @@ +{ + "context_apply-button": "This option enables a button for applying selectors", + "context_reset-button": "This option enables a button for resetting selectors", + "context_update-controls-on-change": "Once you enable this option, the dependent selectors will have influence on each other before you click Apply.", + "label_apply-button-checkbox": "Apply button", + "label_autoheight-checkbox": "Autoheight", + "label_field-validation": "Invalid width value", + "label_group-display": "Display", + "label_group-name": "Name", + "label_note": "Selectors whose width does not fit completely inside the widget that unites a group of selectors will be moved to the next line", + "label_reset-button-checkbox": "Reset button", + "label_selectors-representation": "Selectors representation order and width", + "label_update-controls-on-change": "Auto-updating dependent selectors", + "value_auto": "Auto", + "value_percent": "%", + "value_pixels": "px" +} diff --git a/src/i18n-keysets/dash.extended-settings-dialog.edit/keyset.json b/src/i18n-keysets/dash.extended-settings-dialog.edit/keyset.json new file mode 100644 index 0000000..3ae6b05 --- /dev/null +++ b/src/i18n-keysets/dash.extended-settings-dialog.edit/keyset.json @@ -0,0 +1,72 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "context_apply-button": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_reset-button": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_update-controls-on-change": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_apply-button-checkbox": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_autoheight-checkbox": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-validation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_group-display": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_group-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_note": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_reset-button-checkbox": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_selectors-representation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_update-controls-on-change": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_auto": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_percent": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_pixels": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.extended-settings-dialog.edit/ru.json b/src/i18n-keysets/dash.extended-settings-dialog.edit/ru.json new file mode 100644 index 0000000..2998fb5 --- /dev/null +++ b/src/i18n-keysets/dash.extended-settings-dialog.edit/ru.json @@ -0,0 +1,17 @@ +{ + "context_apply-button": "Опция включает кнопку для применения селекторов", + "context_reset-button": "Опция включает кнопку для сброса селекторов", + "context_update-controls-on-change": "При включении опции зависимые селекторы будут влиять друг на друга до нажатия кнопки «Применить».", + "label_apply-button-checkbox": "Кнопка «‎Применить»‎", + "label_autoheight-checkbox": "Автовысота", + "label_field-validation": "Некорректное значение ширины", + "label_group-display": "Отображение", + "label_group-name": "Название", + "label_note": "Селекторы, которые по ширине не помещаются в виджет, объединяющий группу селекторов, будут перенесены на следующую строку", + "label_reset-button-checkbox": "Кнопка «‎Сбросить»", + "label_selectors-representation": "Порядок отображения и ширина селекторов", + "label_update-controls-on-change": "Автообновление зависимых селекторов", + "value_auto": "Авто", + "value_percent": "%", + "value_pixels": "px" +} diff --git a/src/i18n-keysets/dash.group-controls-dialog.edit/context.json b/src/i18n-keysets/dash.group-controls-dialog.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.group-controls-dialog.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.group-controls-dialog.edit/en.json b/src/i18n-keysets/dash.group-controls-dialog.edit/en.json new file mode 100644 index 0000000..2efbb93 --- /dev/null +++ b/src/i18n-keysets/dash.group-controls-dialog.edit/en.json @@ -0,0 +1,29 @@ +{ + "button_add-selector": "Add", + "button_cancel": "Cancel", + "button_join-all": "Join all", + "button_join-selectors": "Join", + "button_paste-selector": "Paste", + "button_save": "Save", + "hint_find-similiar-selectors": "Find selectors with the same field or parameter", + "label_copy-invalid-control": "Failed to copy selector: some fields did not pass validation", + "label_default-tab": "Selector {{index}}", + "label_filtration": "Filtration", + "label_find-no-similiar-selectors": "No similar selectors found. Selectors must have the same field or parameter.", + "label_finding-similiar-selectors-progress": "Finding similar selectors", + "label_general": "General", + "label_group-settings": "Group settings", + "label_joined-selectors": "Joined", + "label_no-display-on-current-tab": "Not displayed on the current tab", + "label_representation": "Representation", + "label_selector-group": "Selector group", + "label_selectors-list": "Selectors", + "label_similiar-selectors": "Similar selectors", + "label_similiar-selectors-tab": "Tab", + "label_similiar-selectors-title": "Title", + "label_source": "Source", + "label_title-placement": "Title placement", + "value_title-hidden": "Hidden", + "value_title-placement-left": "Left", + "value_title-placement-top": "Top" +} diff --git a/src/i18n-keysets/dash.group-controls-dialog.edit/keyset.json b/src/i18n-keysets/dash.group-controls-dialog.edit/keyset.json new file mode 100644 index 0000000..ef2a1be --- /dev/null +++ b/src/i18n-keysets/dash.group-controls-dialog.edit/keyset.json @@ -0,0 +1,120 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-selector": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_join-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_join-selectors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_paste-selector": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "hint_find-similiar-selectors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_copy-invalid-control": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filtration": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_find-no-similiar-selectors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_finding-similiar-selectors-progress": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_general": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_group-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_joined-selectors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-display-on-current-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_representation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_selector-group": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_selectors-list": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_similiar-selectors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_similiar-selectors-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_similiar-selectors-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-placement": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_title-hidden": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_title-placement-left": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_title-placement-top": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.group-controls-dialog.edit/ru.json b/src/i18n-keysets/dash.group-controls-dialog.edit/ru.json new file mode 100644 index 0000000..c450733 --- /dev/null +++ b/src/i18n-keysets/dash.group-controls-dialog.edit/ru.json @@ -0,0 +1,29 @@ +{ + "button_add-selector": "Добавить", + "button_cancel": "Отменить", + "button_join-all": "Объединить всё", + "button_join-selectors": "Объединить", + "button_paste-selector": "Вставить", + "button_save": "Сохранить", + "hint_find-similiar-selectors": "Найти селекторы с таким же полем или параметром", + "label_copy-invalid-control": "Не удалось скопировать селектор: некоторые его поля заполнены некорректно", + "label_default-tab": "Селектор {{index}}", + "label_filtration": "Фильтрация", + "label_find-no-similiar-selectors": "Нет похожих селекторов. У селекторов должны быть такие же поле или параметр.", + "label_finding-similiar-selectors-progress": "Поиск похожих селекторов", + "label_general": "Общее", + "label_group-settings": "Настройки группы", + "label_joined-selectors": "Объединены", + "label_no-display-on-current-tab": "Не отображается на текущей вкладке", + "label_representation": "Отображение", + "label_selector-group": "Группа селекторов", + "label_selectors-list": "Селекторы", + "label_similiar-selectors": "Похожие селекторы", + "label_similiar-selectors-tab": "Вкладка", + "label_similiar-selectors-title": "Заголовок", + "label_source": "Источник", + "label_title-placement": "Размещение заголовка", + "value_title-hidden": "Скрыт", + "value_title-placement-left": "Слева", + "value_title-placement-top": "Сверху" +} diff --git a/src/i18n-keysets/dash.image-dialog.edit/context.json b/src/i18n-keysets/dash.image-dialog.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.image-dialog.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.image-dialog.edit/en.json b/src/i18n-keysets/dash.image-dialog.edit/en.json new file mode 100644 index 0000000..36cdbad --- /dev/null +++ b/src/i18n-keysets/dash.image-dialog.edit/en.json @@ -0,0 +1,11 @@ +{ + "button_add": "Add", + "button_cancel": "Cancel", + "button_save": "Save", + "label_alt-text": "Alternative text", + "label_alt-text-description": "The alternative text is displayed on the page if the image can't be loaded for some reason, e.g., network errors, content blocking, or linkrot.", + "label_aspect-ratio": "Keep the aspect ratio", + "label_link": "Link", + "label_required-field": "This field cannot be empty", + "label_title": "Image" +} diff --git a/src/i18n-keysets/dash.image-dialog.edit/keyset.json b/src/i18n-keysets/dash.image-dialog.edit/keyset.json new file mode 100644 index 0000000..9cb5c64 --- /dev/null +++ b/src/i18n-keysets/dash.image-dialog.edit/keyset.json @@ -0,0 +1,48 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alt-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_alt-text-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_aspect-ratio": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_required-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.image-dialog.edit/ru.json b/src/i18n-keysets/dash.image-dialog.edit/ru.json new file mode 100644 index 0000000..3ee4b2c --- /dev/null +++ b/src/i18n-keysets/dash.image-dialog.edit/ru.json @@ -0,0 +1,11 @@ +{ + "button_add": "Добавить", + "button_cancel": "Отменить", + "button_save": "Сохранить", + "label_alt-text": "Альтернативный текст", + "label_alt-text-description": "Альтернативный текст отображается на странице, когда изображение не может быть загружено, например, из-за сетевых ошибок, блокировки контента или невалидной ссылки.", + "label_aspect-ratio": "Сохранять соотношение сторон", + "label_link": "Ссылка", + "label_required-field": "Поле должно быть заполнено", + "label_title": "Изображение" +} diff --git a/src/i18n-keysets/dash.main.view/context.json b/src/i18n-keysets/dash.main.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.main.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.main.view/en.json b/src/i18n-keysets/dash.main.view/en.json new file mode 100644 index 0000000..d109a0b --- /dev/null +++ b/src/i18n-keysets/dash.main.view/en.json @@ -0,0 +1,36 @@ +{ + "button_apply-remove-global-item": "Delete", + "button_cancel": "Cancel", + "button_close": "Close", + "button_create-new": "Copy", + "button_edit-panel-chart": "Chart", + "button_edit-panel-editor-selector": "Selector (JS)", + "button_edit-panel-image": "Image", + "button_edit-panel-paste": "Paste", + "button_edit-panel-selector": "Selector", + "button_edit-panel-text": "Text", + "button_edit-panel-title": "Header", + "button_link": "Change settings", + "button_links": "Links", + "label_details": "Details", + "label_failed-copy-global-item": "The copied selector settings make it impossible to display it on the current tab. Change the display settings in the original selector before trying to copy it.", + "label_fixed-collapsed-default": "Collapse by default", + "label_link-group-selector-to-tab": "A selector group with the same content already exists on another tab. You can change the display settings of the original group so that it displays on the current tab, or copy the selector group and create a new group independent from the original.", + "label_link-selector-to-tab": "This selector is already added to another tab. You can change the display settings of the original selector so that it displays on the current tab, or copy it to create a new independent selector.", + "label_loading": "Uploading", + "label_pin": "Pin", + "label_remove-global-selector": "The selector is displayed on multiple tabs. Are you sure you want to delete it from all tabs?", + "label_unpin": "Unpin", + "label_unpin-all": "Unpin all", + "label_updating": "Updating", + "title_failed-copy-global-item": "Failed to copy selector", + "title_link-selector-to-tab": "Change the selector display settings?", + "title_remove-global-selector": "Deleting a selector", + "toast_paste-invalid-workbook-entry": "Pasting widgets linked to objects from another workbook is forbidden", + "toast_unsaved": "There are unsaved changes on the page. Are you sure?", + "tooltip_collapse-fixed-group": "Collapse pinned group", + "tooltip_expand-fixed-group": "Expand pinned group", + "value_state-limit-error": "Dashboard state not saved", + "value_state-limit-error-message": "Too many parameters are configured in the charts. Try reducing the number of selectors or of the values in them.", + "warning_paste-invalid-workbook-entry": "To work with this object within another workbook, please migrate all linked objects from the source workbook. Migrating objects to workbooks" +} diff --git a/src/i18n-keysets/dash.main.view/keyset.json b/src/i18n-keysets/dash.main.view/keyset.json new file mode 100644 index 0000000..b3e5ba6 --- /dev/null +++ b/src/i18n-keysets/dash.main.view/keyset.json @@ -0,0 +1,148 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply-remove-global-item": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-panel-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-panel-editor-selector": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-panel-image": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-panel-paste": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-panel-selector": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-panel-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-panel-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_links": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_details": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_failed-copy-global-item": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_fixed-collapsed-default": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_link-group-selector-to-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_link-selector-to-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_loading": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_pin": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_remove-global-selector": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unpin": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unpin-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_updating": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_failed-copy-global-item": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_link-selector-to-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_remove-global-selector": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_paste-invalid-workbook-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_unsaved": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_collapse-fixed-group": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_expand-fixed-group": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_state-limit-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_state-limit-error-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "warning_paste-invalid-workbook-entry": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.main.view/ru.json b/src/i18n-keysets/dash.main.view/ru.json new file mode 100644 index 0000000..98ba436 --- /dev/null +++ b/src/i18n-keysets/dash.main.view/ru.json @@ -0,0 +1,36 @@ +{ + "button_apply-remove-global-item": "Удалить", + "button_cancel": "Отменить", + "button_close": "Закрыть", + "button_create-new": "Скопировать", + "button_edit-panel-chart": "Чарт", + "button_edit-panel-editor-selector": "Селектор (JS)", + "button_edit-panel-image": "Изображение", + "button_edit-panel-paste": "Вставка", + "button_edit-panel-selector": "Селектор", + "button_edit-panel-text": "Текст", + "button_edit-panel-title": "Заголовок", + "button_link": "Изменить настройки", + "button_links": "Связи", + "label_details": "Подробнее", + "label_failed-copy-global-item": "Из-за настроек скопированного селектора его нельзя показывать на текущей вкладке. Измените настройки показа на вкладках в исходном селекторе и повторите попытку его копирования.", + "label_fixed-collapsed-default": "Скрывать по умолчанию", + "label_link-group-selector-to-tab": "Такая группа селекторов уже добавлена на другой вкладке. Вы можете изменить настройки показа исходной группы селекторов, чтобы она отображалась также на текущей вкладке, или скопировать группу селекторов и создать новую группу, независимую от исходной.", + "label_link-selector-to-tab": "Этот селектор уже добавлен на другой вкладке. Вы можете изменить настройки показа исходного селектора, чтобы он отображался также на текущей вкладке, или скопировать его, чтобы создать новый независимый селектор.", + "label_loading": "Загрузка", + "label_pin": "Закрепить", + "label_remove-global-selector": "Этот селектор показывается на нескольких вкладках. Вы действительно хотите удалить его со всех вкладок?", + "label_unpin": "Открепить", + "label_unpin-all": "Открепить всё", + "label_updating": "Обновление", + "title_failed-copy-global-item": "Невозможно скопировать селектор", + "title_link-selector-to-tab": "Изменить настройки показа селекторов?", + "title_remove-global-selector": "Удаление селектора", + "toast_paste-invalid-workbook-entry": "Не допускается вставка виджетов, у которых есть связи с объектами из другого воркбука", + "toast_unsaved": "На странице есть несохраненные изменения. Вы уверены?", + "tooltip_collapse-fixed-group": "Свернуть закреплённую группу", + "tooltip_expand-fixed-group": "Развернуть закреплённую группу", + "value_state-limit-error": "Состояние дашборда не сохранено", + "value_state-limit-error-message": "В чартах настроено слишком много параметров. Попробуйте уменьшить количество селекторов или количество значений в них.", + "warning_paste-invalid-workbook-entry": "Чтобы работать с объектом в рамках другого воркбука, пожалуйста, перенесите в этот воркбук все связанные объекты из исходного воркбука. Миграция объектов в воркбук" +} diff --git a/src/i18n-keysets/dash.navigation-input.edit/context.json b/src/i18n-keysets/dash.navigation-input.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.navigation-input.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.navigation-input.edit/en.json b/src/i18n-keysets/dash.navigation-input.edit/en.json new file mode 100644 index 0000000..0b32d40 --- /dev/null +++ b/src/i18n-keysets/dash.navigation-input.edit/en.json @@ -0,0 +1,14 @@ +{ + "button_cancel": "Close", + "button_choose": "Select", + "button_ok": "OK", + "button_open": "Open", + "button_use-link": "Specify link", + "context_fill-link": "Link", + "toast_error": "Error", + "toast_incorrect-source": "Invalid source type", + "toast_incorrect-url": "Invalid URL", + "toast_not-found": "Source not found", + "toast_not-selector": "Source is not a selector", + "toast_wrong-workbook-id": "Select a source from the current workflow" +} diff --git a/src/i18n-keysets/dash.navigation-input.edit/keyset.json b/src/i18n-keysets/dash.navigation-input.edit/keyset.json new file mode 100644 index 0000000..f850bc7 --- /dev/null +++ b/src/i18n-keysets/dash.navigation-input.edit/keyset.json @@ -0,0 +1,60 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_choose": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_ok": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_open": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_use-link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_fill-link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_incorrect-source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_incorrect-url": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_not-found": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_not-selector": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_wrong-workbook-id": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.navigation-input.edit/ru.json b/src/i18n-keysets/dash.navigation-input.edit/ru.json new file mode 100644 index 0000000..131e467 --- /dev/null +++ b/src/i18n-keysets/dash.navigation-input.edit/ru.json @@ -0,0 +1,14 @@ +{ + "button_cancel": "Отмена", + "button_choose": "Выбрать", + "button_ok": "OK", + "button_open": "Открыть", + "button_use-link": "Указать ссылку", + "context_fill-link": "Ссылка", + "toast_error": "Ошибка", + "toast_incorrect-source": "Некорректный тип источника", + "toast_incorrect-url": "Некорректный URL", + "toast_not-found": "Источник не найден", + "toast_not-selector": "Не является селектором", + "toast_wrong-workbook-id": "Выберите источник из текущего воркбука" +} diff --git a/src/i18n-keysets/dash.palette-background/context.json b/src/i18n-keysets/dash.palette-background/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.palette-background/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.palette-background/en.json b/src/i18n-keysets/dash.palette-background/en.json new file mode 100644 index 0000000..06f7f5a --- /dev/null +++ b/src/i18n-keysets/dash.palette-background/en.json @@ -0,0 +1,10 @@ +{ + "aria_choose-color": "Select color", + "aria_enter-color": "HEX color code", + "aria_opacity": "Opacity", + "button_custom_value": "Enter value", + "tooltip_click-to-select": "Select value", + "value_base_gray": "Basic gray background", + "value_default": "Basic background for charts", + "value_transparent": "Transparent" +} diff --git a/src/i18n-keysets/dash.palette-background/keyset.json b/src/i18n-keysets/dash.palette-background/keyset.json new file mode 100644 index 0000000..452d9c0 --- /dev/null +++ b/src/i18n-keysets/dash.palette-background/keyset.json @@ -0,0 +1,44 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "aria_choose-color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "aria_enter-color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "aria_opacity": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_custom_value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_click-to-select": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_base_gray": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_default": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_transparent": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.palette-background/ru.json b/src/i18n-keysets/dash.palette-background/ru.json new file mode 100644 index 0000000..ebdd6b4 --- /dev/null +++ b/src/i18n-keysets/dash.palette-background/ru.json @@ -0,0 +1,10 @@ +{ + "aria_choose-color": "Выбрать цвет", + "aria_enter-color": "HEX-код цвета", + "aria_opacity": "Прозрачность", + "button_custom_value": "Указать значение", + "tooltip_click-to-select": "Выберите значение", + "value_base_gray": "Базовый серый фон", + "value_default": "Базовый фон чартов", + "value_transparent": "Прозрачный" +} diff --git a/src/i18n-keysets/dash.params-button-dialog.view/context.json b/src/i18n-keysets/dash.params-button-dialog.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.params-button-dialog.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.params-button-dialog.view/en.json b/src/i18n-keysets/dash.params-button-dialog.view/en.json new file mode 100644 index 0000000..08c3fea --- /dev/null +++ b/src/i18n-keysets/dash.params-button-dialog.view/en.json @@ -0,0 +1,4 @@ +{ + "context_invalid-format": "Incorrect format. Key won't be saved.", + "context_restricted-param": "Reserved name. Key won't be saved." +} diff --git a/src/i18n-keysets/dash.params-button-dialog.view/keyset.json b/src/i18n-keysets/dash.params-button-dialog.view/keyset.json new file mode 100644 index 0000000..0811ae8 --- /dev/null +++ b/src/i18n-keysets/dash.params-button-dialog.view/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "context_invalid-format": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_restricted-param": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.params-button-dialog.view/ru.json b/src/i18n-keysets/dash.params-button-dialog.view/ru.json new file mode 100644 index 0000000..f2b17fa --- /dev/null +++ b/src/i18n-keysets/dash.params-button-dialog.view/ru.json @@ -0,0 +1,4 @@ +{ + "context_invalid-format": "Некорректный формат. Ключ не будет сохранен.", + "context_restricted-param": "Зарезервированное имя. Ключ не будет сохранен." +} diff --git a/src/i18n-keysets/dash.params-setting.view/context.json b/src/i18n-keysets/dash.params-setting.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.params-setting.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.params-setting.view/en.json b/src/i18n-keysets/dash.params-setting.view/en.json new file mode 100644 index 0000000..c65f8f3 --- /dev/null +++ b/src/i18n-keysets/dash.params-setting.view/en.json @@ -0,0 +1,6 @@ +{ + "button_add-param": "Add parameter", + "button_remove-all": "Remove all", + "label_name": "Name", + "label_value": "Value" +} diff --git a/src/i18n-keysets/dash.params-setting.view/keyset.json b/src/i18n-keysets/dash.params-setting.view/keyset.json new file mode 100644 index 0000000..949e4e0 --- /dev/null +++ b/src/i18n-keysets/dash.params-setting.view/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-param": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_remove-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_value": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.params-setting.view/ru.json b/src/i18n-keysets/dash.params-setting.view/ru.json new file mode 100644 index 0000000..a8bcdf3 --- /dev/null +++ b/src/i18n-keysets/dash.params-setting.view/ru.json @@ -0,0 +1,6 @@ +{ + "button_add-param": "Добавить параметр", + "button_remove-all": "Удалить все", + "label_name": "Название", + "label_value": "Значение" +} diff --git a/src/i18n-keysets/dash.popup-widgets-order.view/context.json b/src/i18n-keysets/dash.popup-widgets-order.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.popup-widgets-order.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.popup-widgets-order.view/en.json b/src/i18n-keysets/dash.popup-widgets-order.view/en.json new file mode 100644 index 0000000..060ec91 --- /dev/null +++ b/src/i18n-keysets/dash.popup-widgets-order.view/en.json @@ -0,0 +1,13 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel", + "button_make-default": "By default", + "label_control": "Selector", + "label_group_control": "Group Selector", + "label_image": "Image", + "label_info": "Widgets order for mobile version", + "label_no-items": "No widgets added yet", + "label_text": "Text", + "label_title": "Title", + "label_widget": "Chart" +} diff --git a/src/i18n-keysets/dash.popup-widgets-order.view/keyset.json b/src/i18n-keysets/dash.popup-widgets-order.view/keyset.json new file mode 100644 index 0000000..b8c3a2c --- /dev/null +++ b/src/i18n-keysets/dash.popup-widgets-order.view/keyset.json @@ -0,0 +1,56 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_make-default": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_control": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_group_control": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_image": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-items": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_widget": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.popup-widgets-order.view/ru.json b/src/i18n-keysets/dash.popup-widgets-order.view/ru.json new file mode 100644 index 0000000..4900caa --- /dev/null +++ b/src/i18n-keysets/dash.popup-widgets-order.view/ru.json @@ -0,0 +1,13 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отменить", + "button_make-default": "По умолчанию", + "label_control": "Селектор", + "label_group_control": "Групповой селектор", + "label_image": "Изображение", + "label_info": "Порядок виджетов в мобильной версии", + "label_no-items": "Виджеты еще не добавлены", + "label_text": "Текст", + "label_title": "Заголовок", + "label_widget": "Чарт" +} diff --git a/src/i18n-keysets/dash.settings-dialog.edit/context.json b/src/i18n-keysets/dash.settings-dialog.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.settings-dialog.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.settings-dialog.edit/en.json b/src/i18n-keysets/dash.settings-dialog.edit/en.json new file mode 100644 index 0000000..b259aed --- /dev/null +++ b/src/i18n-keysets/dash.settings-dialog.edit/en.json @@ -0,0 +1,43 @@ +{ + "button_cancel": "Cancel", + "button_save": "Save", + "button_setup": "Settings", + "context_dependent-selectors": "\"Dependent selectors\" cannot be disabled. Continue?", + "context_max-concurrent-requests-hint": "This option limits maximum amount of simultaneously loaded widgets on a dashboard.

This can be useful when data source for the dashboard is not suitable for a big number of parallel queries. Leave empty to disable this limit.", + "field_seconds": "seconds", + "hint_load-only-visible-charts": "When this option is active, only the charts that are in the user's field of view load as the user scrolls through the dashboard page. When the option is inactive, all charts of the active dashboard tab load immediately as soon as the dashboard opens.", + "label_access-description": "Access error message", + "label_autoupdate": "Auto update", + "label_autoupdate-interval": "Interval", + "label_border-radius": "Rounding", + "label_dash-background": "Dashboard background", + "label_dependent-selectors": "Dependent selectors", + "label_display": "Appearance", + "label_display-info": "Select elements to display on the dashboard", + "label_expand-toc": "Expand contents", + "label_experimental": "Experimental", + "label_global-params": "Dashboard parameters", + "label_hide-tabs": "Hide tabs", + "label_hide-title": "Hide title", + "label_internal-margins": "Margins inside widgets", + "label_load-only-visible-charts": "Only load visible charts", + "label_load-priority": "Load priority", + "label_load-priority-hint": "Defines the order in which widgets are loaded on the dashboard. For example, if priority is given to charts, all the charts that appear on the screen will be loaded first and then selectors", + "label_loading": "Load indicator", + "label_margins": "Margins", + "label_max-concurrent-requests": "Number of simultaneously loaded widgets", + "label_max-concurrent-requests-placeholder": "Not limited", + "label_other-settings": "Other settings", + "label_settings": "Settings", + "label_show-tabs": "Show tabs", + "label_silent-loading": "Hide loading indicator", + "label_support-description": "Window before contacting support", + "label_tabs": "Tabs", + "label_title": "Title", + "label_toc": "Contents", + "label_widgets-background": "Widget background", + "value_load-priority-charts": "Charts", + "value_load-priority-selectors": "Selectors", + "value_theme-dark": "Dark theme", + "value_theme-light": "Light theme" +} diff --git a/src/i18n-keysets/dash.settings-dialog.edit/keyset.json b/src/i18n-keysets/dash.settings-dialog.edit/keyset.json new file mode 100644 index 0000000..bdecfef --- /dev/null +++ b/src/i18n-keysets/dash.settings-dialog.edit/keyset.json @@ -0,0 +1,176 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_setup": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_dependent-selectors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_max-concurrent-requests-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_seconds": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "hint_load-only-visible-charts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_access-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_autoupdate": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_autoupdate-interval": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_border-radius": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dash-background": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dependent-selectors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_display": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_display-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_expand-toc": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_experimental": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_global-params": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_hide-tabs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_hide-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_internal-margins": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_load-only-visible-charts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_load-priority": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_load-priority-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_loading": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_margins": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_max-concurrent-requests": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_max-concurrent-requests-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_other-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_show-tabs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_silent-loading": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_support-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tabs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_toc": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_widgets-background": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_load-priority-charts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_load-priority-selectors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_theme-dark": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_theme-light": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.settings-dialog.edit/ru.json b/src/i18n-keysets/dash.settings-dialog.edit/ru.json new file mode 100644 index 0000000..61c5614 --- /dev/null +++ b/src/i18n-keysets/dash.settings-dialog.edit/ru.json @@ -0,0 +1,43 @@ +{ + "button_cancel": "Отменить", + "button_save": "Сохранить", + "button_setup": "Настроить", + "context_dependent-selectors": "Опцию \"Зависимые селекторы\" нельзя будет отключить. Продолжить?", + "context_max-concurrent-requests-hint": "Опция ограничивает максимальное количество одновременно загружаемых виджетов на дашборде.

Это может быть полезно в случае, если источники данных дашборда не приспособлены к большой одновременной нагрузке. Укажите пустое значение, чтобы отключить это ограничение.", + "field_seconds": "секунды", + "hint_load-only-visible-charts": "Когда опция активна, загружаются только чарты, находящиеся в зоне видимости пользователя, по мере того, как он прокручивает страницу дашборда. Когда опция выключена — при открытии дашборда загружаются все чарты активной вкладки дашборда.", + "label_access-description": "Сообщение при ошибке доступа", + "label_autoupdate": "Автообновление", + "label_autoupdate-interval": "Интервал", + "label_border-radius": "Скругление", + "label_dash-background": "Фон дашборда", + "label_dependent-selectors": "Зависимые селекторы", + "label_display": "Внешний вид", + "label_display-info": "Выберите элементы, которые должны отображаться на дашборде", + "label_expand-toc": "Раскрыть оглавление", + "label_experimental": "Эксперимент", + "label_global-params": "Параметры дашборда", + "label_hide-tabs": "Скрыть вкладки", + "label_hide-title": "Скрыть заголовок", + "label_internal-margins": "Отступы внутри виджетов", + "label_load-only-visible-charts": "Загружать только видимые чарты", + "label_load-priority": "Приоритет загрузки", + "label_load-priority-hint": "Определяет порядок загрузки виджетов на дашборде. Например, если приоритет отдать чартам, сначала будут загружаться все попавшие на экран чарты, а затем селекторы", + "label_loading": "Индикатор загрузки", + "label_margins": "Отступы", + "label_max-concurrent-requests": "Количество одновременно загружаемых виджетов", + "label_max-concurrent-requests-placeholder": "Не ограничено", + "label_other-settings": "Прочие настройки", + "label_settings": "Настройки", + "label_show-tabs": "Показать вкладки", + "label_silent-loading": "Скрывать индикацию загрузки", + "label_support-description": "Окно перед обращением в поддержку", + "label_tabs": "Табы", + "label_title": "Заголовок", + "label_toc": "Оглавление", + "label_widgets-background": "Фон виджетов", + "value_load-priority-charts": "Чарты", + "value_load-priority-selectors": "Селекторы", + "value_theme-dark": "Тёмная тема", + "value_theme-light": "Светлая тема" +} diff --git a/src/i18n-keysets/dash.store.view/context.json b/src/i18n-keysets/dash.store.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.store.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.store.view/en.json b/src/i18n-keysets/dash.store.view/en.json new file mode 100644 index 0000000..5e22447 --- /dev/null +++ b/src/i18n-keysets/dash.store.view/en.json @@ -0,0 +1,12 @@ +{ + "button_cancel": "Cancel", + "button_edit-anyway": "Edit anyway", + "button_reload-page": "Reload page", + "label_copied-widget-postfix": "copy", + "label_dash-is-editing": "Dashboard is being edited", + "label_obsolete-dash": "Dashboard is obsolete", + "label_obsolete-version": "Dashboard's current version is obsolete. Reload the page to get a new version.", + "label_selector-dialog": "Selector 1", + "label_tab-default-name": "Tab 1", + "label_unexpected-error": "Error" +} diff --git a/src/i18n-keysets/dash.store.view/keyset.json b/src/i18n-keysets/dash.store.view/keyset.json new file mode 100644 index 0000000..933e8b1 --- /dev/null +++ b/src/i18n-keysets/dash.store.view/keyset.json @@ -0,0 +1,52 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-anyway": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_reload-page": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_copied-widget-postfix": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dash-is-editing": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_obsolete-dash": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_obsolete-version": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_selector-dialog": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tab-default-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_unexpected-error": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.store.view/ru.json b/src/i18n-keysets/dash.store.view/ru.json new file mode 100644 index 0000000..00b4b77 --- /dev/null +++ b/src/i18n-keysets/dash.store.view/ru.json @@ -0,0 +1,12 @@ +{ + "button_cancel": "Отмена", + "button_edit-anyway": "Все равно редактировать", + "button_reload-page": "Обновить страницу", + "label_copied-widget-postfix": "копия", + "label_dash-is-editing": "Дашборд уже редактируется", + "label_obsolete-dash": "Дашборд устарел", + "label_obsolete-version": "Текущая версия дашборда устарела. Обновите страницу, чтобы перейти к последней версии.", + "label_selector-dialog": "Селектор 1", + "label_tab-default-name": "Вкладка 1", + "label_unexpected-error": "Ошибка" +} diff --git a/src/i18n-keysets/dash.table-of-content.view/context.json b/src/i18n-keysets/dash.table-of-content.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.table-of-content.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.table-of-content.view/en.json b/src/i18n-keysets/dash.table-of-content.view/en.json new file mode 100644 index 0000000..8d95eaf --- /dev/null +++ b/src/i18n-keysets/dash.table-of-content.view/en.json @@ -0,0 +1,4 @@ +{ + "context_table-of-content": "Contents", + "label_table-of-content": "Contents" +} diff --git a/src/i18n-keysets/dash.table-of-content.view/keyset.json b/src/i18n-keysets/dash.table-of-content.view/keyset.json new file mode 100644 index 0000000..9b7c7aa --- /dev/null +++ b/src/i18n-keysets/dash.table-of-content.view/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "context_table-of-content": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_table-of-content": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.table-of-content.view/ru.json b/src/i18n-keysets/dash.table-of-content.view/ru.json new file mode 100644 index 0000000..af8acf7 --- /dev/null +++ b/src/i18n-keysets/dash.table-of-content.view/ru.json @@ -0,0 +1,4 @@ +{ + "context_table-of-content": "Оглавление", + "label_table-of-content": "Оглавление" +} diff --git a/src/i18n-keysets/dash.tabs-dialog.edit/context.json b/src/i18n-keysets/dash.tabs-dialog.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.tabs-dialog.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.tabs-dialog.edit/en.json b/src/i18n-keysets/dash.tabs-dialog.edit/en.json new file mode 100644 index 0000000..28dc6ba --- /dev/null +++ b/src/i18n-keysets/dash.tabs-dialog.edit/en.json @@ -0,0 +1,13 @@ +{ + "button_add-tab": "Add", + "button_cancel": "Cancel", + "button_save": "Save", + "label_tabs": "Tabs", + "menu_delete": "Delete", + "menu_delete_other": "Delete all but this", + "menu_duplicate": "Duplicate", + "menu_order": "Widget order", + "menu_rename": "Rename", + "value_default": "Tab {{index}}", + "value_default-duplicated": "{{title}} (copy)" +} diff --git a/src/i18n-keysets/dash.tabs-dialog.edit/keyset.json b/src/i18n-keysets/dash.tabs-dialog.edit/keyset.json new file mode 100644 index 0000000..beba6d1 --- /dev/null +++ b/src/i18n-keysets/dash.tabs-dialog.edit/keyset.json @@ -0,0 +1,56 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tabs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_delete_other": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_duplicate": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_order": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_rename": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_default": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_default-duplicated": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.tabs-dialog.edit/ru.json b/src/i18n-keysets/dash.tabs-dialog.edit/ru.json new file mode 100644 index 0000000..4f7adbb --- /dev/null +++ b/src/i18n-keysets/dash.tabs-dialog.edit/ru.json @@ -0,0 +1,13 @@ +{ + "button_add-tab": "Добавить", + "button_cancel": "Отменить", + "button_save": "Сохранить", + "label_tabs": "Вкладки", + "menu_delete": "Удалить", + "menu_delete_other": "Удалить все, кроме этой", + "menu_duplicate": "Дублировать", + "menu_order": "Порядок виджетов", + "menu_rename": "Переименовать", + "value_default": "Вкладка {{index}}", + "value_default-duplicated": "{{title}} (копия)" +} diff --git a/src/i18n-keysets/dash.text-dialog.edit/context.json b/src/i18n-keysets/dash.text-dialog.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.text-dialog.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.text-dialog.edit/en.json b/src/i18n-keysets/dash.text-dialog.edit/en.json new file mode 100644 index 0000000..1d6896a --- /dev/null +++ b/src/i18n-keysets/dash.text-dialog.edit/en.json @@ -0,0 +1,10 @@ +{ + "button_add": "Add", + "button_cancel": "Cancel", + "button_save": "Save", + "context_fill-text": "Enter text", + "label_domain": "Domain", + "label_image-hint": "The link to the image must be located in the {{domain}} domain, read more", + "label_syntax": "Markdown supported", + "label_text": "Text" +} diff --git a/src/i18n-keysets/dash.text-dialog.edit/keyset.json b/src/i18n-keysets/dash.text-dialog.edit/keyset.json new file mode 100644 index 0000000..f76f69f --- /dev/null +++ b/src/i18n-keysets/dash.text-dialog.edit/keyset.json @@ -0,0 +1,44 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_fill-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_domain": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_image-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_syntax": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_text": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.text-dialog.edit/ru.json b/src/i18n-keysets/dash.text-dialog.edit/ru.json new file mode 100644 index 0000000..4d6cea3 --- /dev/null +++ b/src/i18n-keysets/dash.text-dialog.edit/ru.json @@ -0,0 +1,10 @@ +{ + "button_add": "Добавить", + "button_cancel": "Отменить", + "button_save": "Сохранить", + "context_fill-text": "Введите текст", + "label_domain": "Domain", + "label_image-hint": "Ссылка на изображение должна находиться в домене {{domain}}, подробнее", + "label_syntax": "Поддерживается Markdown", + "label_text": "Текст" +} diff --git a/src/i18n-keysets/dash.title-dialog.edit/context.json b/src/i18n-keysets/dash.title-dialog.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.title-dialog.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.title-dialog.edit/en.json b/src/i18n-keysets/dash.title-dialog.edit/en.json new file mode 100644 index 0000000..ceb6f1e --- /dev/null +++ b/src/i18n-keysets/dash.title-dialog.edit/en.json @@ -0,0 +1,12 @@ +{ + "button_add": "Add", + "button_cancel": "Cancel", + "button_save": "Save", + "context_fill-title": "Enter title", + "field_show-in-toc": "Display in contents", + "label_size": "Size", + "label_text-color": "Text color", + "label_title": "Title", + "toast_required-field": "The field cannot be empty", + "value_default": "Title" +} diff --git a/src/i18n-keysets/dash.title-dialog.edit/keyset.json b/src/i18n-keysets/dash.title-dialog.edit/keyset.json new file mode 100644 index 0000000..e14cb46 --- /dev/null +++ b/src/i18n-keysets/dash.title-dialog.edit/keyset.json @@ -0,0 +1,52 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_fill-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_show-in-toc": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_size": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_text-color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_required-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_default": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.title-dialog.edit/ru.json b/src/i18n-keysets/dash.title-dialog.edit/ru.json new file mode 100644 index 0000000..a879c0a --- /dev/null +++ b/src/i18n-keysets/dash.title-dialog.edit/ru.json @@ -0,0 +1,12 @@ +{ + "button_add": "Добавить", + "button_cancel": "Отменить", + "button_save": "Сохранить", + "context_fill-title": "Введите заголовок", + "field_show-in-toc": "Отображать в оглавлении", + "label_size": "Размер", + "label_text-color": "Цвет текста", + "label_title": "Заголовок", + "toast_required-field": "Поле должно быть заполнено", + "value_default": "Заголовок" +} diff --git a/src/i18n-keysets/dash.widget-dialog.edit/context.json b/src/i18n-keysets/dash.widget-dialog.edit/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dash.widget-dialog.edit/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dash.widget-dialog.edit/en.json b/src/i18n-keysets/dash.widget-dialog.edit/en.json new file mode 100644 index 0000000..d68d5b7 --- /dev/null +++ b/src/i18n-keysets/dash.widget-dialog.edit/en.json @@ -0,0 +1,37 @@ +{ + "button_add": "Add", + "button_add-param": "Add parameter", + "button_cancel": "Cancel", + "button_params-setup": "Configure", + "button_save": "Save", + "context_autoheight-availability-hint": "Available only for charts with \"Table\" and \"Markdown\" types", + "context_fill-title": "Enter title", + "context_filtering-other-charts": "This option enables you to apply filtering from this chart to other charts on the dashboard", + "context_filtering-usage-limitations": "More about filtration", + "context_hint-display-info": "The hint appears in the widget title if the title is enabled", + "field_autoheight": "Auto height", + "field_background": "Background", + "field_background-enable": "Enable", + "field_description": "Description", + "field_enable-filtering-other-charts": "Enable", + "field_hint": "Hint", + "field_param-name": "Name", + "field_param-value": "Value", + "field_params": "Parameters", + "field_show-title": "Show", + "field_title": "Title", + "field_widget": "Chart", + "label_autoheight-enable": "Enable", + "label_default-tab": "Default tab", + "label_empty-list": "List is empty", + "label_filtering-other-charts": "Filtering", + "label_new-param": "New parameter", + "label_settings": "Settings", + "label_widget": "Chart", + "section_appearance": "Appearance", + "section_common": "Common", + "tab_settings": "Settings", + "tab_tabs": "Tabs", + "toast_required-field": "The field cannot be empty", + "value_title-default": "Title {{index}}" +} diff --git a/src/i18n-keysets/dash.widget-dialog.edit/keyset.json b/src/i18n-keysets/dash.widget-dialog.edit/keyset.json new file mode 100644 index 0000000..c6c0246 --- /dev/null +++ b/src/i18n-keysets/dash.widget-dialog.edit/keyset.json @@ -0,0 +1,152 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_add-param": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_params-setup": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_autoheight-availability-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_fill-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_filtering-other-charts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_filtering-usage-limitations": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "context_hint-display-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_autoheight": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_background": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_background-enable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_enable-filtering-other-charts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_param-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_param-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_params": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_show-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_autoheight-enable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-list": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filtering-other-charts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-param": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_appearance": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_common": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab_tabs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_required-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_title-default": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dash.widget-dialog.edit/ru.json b/src/i18n-keysets/dash.widget-dialog.edit/ru.json new file mode 100644 index 0000000..26c1c6d --- /dev/null +++ b/src/i18n-keysets/dash.widget-dialog.edit/ru.json @@ -0,0 +1,37 @@ +{ + "button_add": "Добавить", + "button_add-param": "Добавить параметр", + "button_cancel": "Отменить", + "button_params-setup": "Настроить", + "button_save": "Сохранить", + "context_autoheight-availability-hint": "Доступно для чартов типа \"Таблица\" и \"Markdown\"", + "context_fill-title": "Введите заголовок", + "context_filtering-other-charts": "Опция позволяет применить фильтрацию из этого чарта к другим чартам на дашборде", + "context_filtering-usage-limitations": "Подробнее о фильтрации", + "context_hint-display-info": "Подсказка показывается в заголовке виджета, если он включён", + "field_autoheight": "Автовысота", + "field_background": "Фон", + "field_background-enable": "Включить", + "field_description": "Описание", + "field_enable-filtering-other-charts": "Включить", + "field_hint": "Подсказка", + "field_param-name": "Имя", + "field_param-value": "Значение", + "field_params": "Параметры", + "field_show-title": "Показывать", + "field_title": "Заголовок", + "field_widget": "Чарт", + "label_autoheight-enable": "Включить", + "label_default-tab": "Вкладка по умолчанию", + "label_empty-list": "Список пуст", + "label_filtering-other-charts": "Фильтрация", + "label_new-param": "Новый параметр", + "label_settings": "Настройки", + "label_widget": "Чарт", + "section_appearance": "Внешний вид", + "section_common": "Общие", + "tab_settings": "Настройки", + "tab_tabs": "Вкладки", + "toast_required-field": "Поле должно быть заполнено", + "value_title-default": "Заголовок {{index}}" +} diff --git a/src/i18n-keysets/datalens.landing.error/context.json b/src/i18n-keysets/datalens.landing.error/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/datalens.landing.error/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/datalens.landing.error/en.json b/src/i18n-keysets/datalens.landing.error/en.json new file mode 100644 index 0000000..2d89ed1 --- /dev/null +++ b/src/i18n-keysets/datalens.landing.error/en.json @@ -0,0 +1,19 @@ +{ + "label_description-auth-denied": "Please contact technical support", + "label_description-auth-failed": "Please contact technical support", + "label_description-fail": "Please contact technical support", + "label_page-title-auth-denied": "Auth denied", + "label_page-title-auth-failed": "Auth error", + "label_page-title-cloud-folder-access-denied": "Access error", + "label_page-title-fail": "Error", + "label_page-title-forbidden-entry": "Forbidden", + "label_page-title-inaccessible-entry-folder": "Access error", + "label_page-title-missing-entry": "Not found", + "label_title-auth-denied": "You don't have access", + "label_title-auth-failed": "Something went wrong", + "label_title-cloud-folder-access-denied": "You don't have access to DataLens objects in this folder", + "label_title-fail": "Something went wrong", + "label_title-forbidden-entry": "Access restricted", + "label_title-missing-current-cloud-folder": "Select a folder to work with DataLens", + "label_title-missing-entry": "Not found" +} diff --git a/src/i18n-keysets/datalens.landing.error/keyset.json b/src/i18n-keysets/datalens.landing.error/keyset.json new file mode 100644 index 0000000..d89395c --- /dev/null +++ b/src/i18n-keysets/datalens.landing.error/keyset.json @@ -0,0 +1,80 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_description-auth-denied": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_description-auth-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_description-fail": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_page-title-auth-denied": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_page-title-auth-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_page-title-cloud-folder-access-denied": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_page-title-fail": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_page-title-forbidden-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_page-title-inaccessible-entry-folder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_page-title-missing-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-auth-denied": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-auth-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-cloud-folder-access-denied": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-fail": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-forbidden-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-missing-current-cloud-folder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-missing-entry": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/datalens.landing.error/ru.json b/src/i18n-keysets/datalens.landing.error/ru.json new file mode 100644 index 0000000..3736ca3 --- /dev/null +++ b/src/i18n-keysets/datalens.landing.error/ru.json @@ -0,0 +1,19 @@ +{ + "label_description-auth-denied": "Пожалуйста, обратитесь в тех. поддержку", + "label_description-auth-failed": "Пожалуйста, обратитесь в тех. поддержку", + "label_description-fail": "Пожалуйста, обратитесь в тех. поддержку", + "label_page-title-auth-denied": "Ошибка доступа", + "label_page-title-auth-failed": "Ошибка авторизации", + "label_page-title-cloud-folder-access-denied": "Ошибка доступа", + "label_page-title-fail": "Ошибка", + "label_page-title-forbidden-entry": "Нет прав", + "label_page-title-inaccessible-entry-folder": "Ошибка доступа", + "label_page-title-missing-entry": "Не найдено", + "label_title-auth-denied": "У вас нет доступа", + "label_title-auth-failed": "Что-то пошло не так", + "label_title-cloud-folder-access-denied": "У вас нет доступа к объектам DataLens в этом каталоге", + "label_title-fail": "Что-то пошло не так", + "label_title-forbidden-entry": "Доступ ограничен", + "label_title-missing-current-cloud-folder": "Выберите каталог для работы с DataLens", + "label_title-missing-entry": "Не найдено" +} diff --git a/src/i18n-keysets/dataset.dataset-creation.create/context.json b/src/i18n-keysets/dataset.dataset-creation.create/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dataset.dataset-creation.create/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dataset.dataset-creation.create/en.json b/src/i18n-keysets/dataset.dataset-creation.create/en.json new file mode 100644 index 0000000..f3531f4 --- /dev/null +++ b/src/i18n-keysets/dataset.dataset-creation.create/en.json @@ -0,0 +1,36 @@ +{ + "button_apply": "Create", + "button_cancel": "Cancel", + "button_change-connection": "Edit connection", + "button_change-source": "Save", + "button_create-dataset": "Create dataset", + "button_select-connection": "Select connection", + "field_dataset-title": "Dataset name", + "field_db-name": "Database", + "field_table-name": "Table", + "field_yt-table-path": "Path to table in YT", + "field_yt-table-path-error": "Invalid table path or URL", + "field_yt-table-path-error-cluster": "The connection uses {{cluster}} cluster", + "label_database-list-connection-error": "Error: unable to connect to connection", + "label_database-list-forbidden-error": "You don't have permission to access this connection", + "label_database-list-general-error": "Error: failed to load list of available databases", + "label_entry-name-already-exists": "That name is already in use, try another one", + "label_error": "Failed to create dataset", + "label_error-400-no-connection-title": "Error: no connection", + "label_error-400-title": "Error: incorrect request to connection", + "label_error-403-title": "You don't have access to this connection", + "label_error-404-title": "Error: connection not found", + "label_error-500-title": "Error: failed to load connection", + "label_error-empty-field": "This is a required field", + "label_name-default": "New dataset", + "label_name-placeholder": "Dataset name", + "label_table-list-connection-error": "Error: unable to connect to connection", + "label_table-list-forbidden-error": "You don't have access to this connection", + "label_table-list-general-error": "Error: failed to load list of available tables", + "section_change-source": "Edit connection", + "section_select-db-and-table": "Select a database and table:", + "section_title": "Create dataset", + "toast_create-dataset-error": "Error: failed to create dataset", + "toast_default-error": "Error", + "toast_error-action-label": "Learn more" +} diff --git a/src/i18n-keysets/dataset.dataset-creation.create/keyset.json b/src/i18n-keysets/dataset.dataset-creation.create/keyset.json new file mode 100644 index 0000000..dab024c --- /dev/null +++ b/src/i18n-keysets/dataset.dataset-creation.create/keyset.json @@ -0,0 +1,148 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_change-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_change-source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_select-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_dataset-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_db-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_table-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_yt-table-path": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_yt-table-path-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_yt-table-path-error-cluster": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_database-list-connection-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_database-list-forbidden-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_database-list-general-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-name-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-400-no-connection-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-400-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-403-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-404-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-500-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-empty-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_name-default": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_name-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_table-list-connection-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_table-list-forbidden-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_table-list-general-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_change-source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_select-db-and-table": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_create-dataset-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_default-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_error-action-label": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dataset.dataset-creation.create/ru.json b/src/i18n-keysets/dataset.dataset-creation.create/ru.json new file mode 100644 index 0000000..9f02779 --- /dev/null +++ b/src/i18n-keysets/dataset.dataset-creation.create/ru.json @@ -0,0 +1,36 @@ +{ + "button_apply": "Создать", + "button_cancel": "Отменить", + "button_change-connection": "Изменить подключение", + "button_change-source": "Сохранить", + "button_create-dataset": "Создать датасет", + "button_select-connection": "Выбрать подключение", + "field_dataset-title": "Имя датасета", + "field_db-name": "База данных", + "field_table-name": "Таблица", + "field_yt-table-path": "Путь к таблице в YT", + "field_yt-table-path-error": "Указан неправильный путь или URL к таблице", + "field_yt-table-path-error-cluster": "В подключении используется {{cluster}} кластер", + "label_database-list-connection-error": "Ошибка: не удалось установить соединение к подключению", + "label_database-list-forbidden-error": "У вас нет прав доступа к подключению", + "label_database-list-general-error": "Ошибка: не удалось загрузить список доступных баз данных", + "label_entry-name-already-exists": "Название уже используется, придумайте другое", + "label_error": "Не удалось создать датасет", + "label_error-400-no-connection-title": "Ошибка: отсутствует подключение", + "label_error-400-title": "Ошибка: некорректный запрос к подключению", + "label_error-403-title": "У вас нет доступа к подключению", + "label_error-404-title": "Ошибка: подключение не найдено", + "label_error-500-title": "Ошибка: не удалось загрузить подключение", + "label_error-empty-field": "Поле обязательно для заполнения", + "label_name-default": "Новый датасет", + "label_name-placeholder": "Введите имя датасета", + "label_table-list-connection-error": "Ошибка: не удалось установить соединение к подключению", + "label_table-list-forbidden-error": "У вас нет доступа к подключению", + "label_table-list-general-error": "Ошибка: не удалось загрузить список доступных таблиц", + "section_change-source": "Изменение подключения", + "section_select-db-and-table": "Выберите базу данных и таблицу:", + "section_title": "Создать датасет", + "toast_create-dataset-error": "Ошибка: не удалось создать датасет", + "toast_default-error": "Ошибка", + "toast_error-action-label": "Подробнее" +} diff --git a/src/i18n-keysets/dataset.dataset-editor.modify/context.json b/src/i18n-keysets/dataset.dataset-editor.modify/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dataset.dataset-editor.modify/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dataset.dataset-editor.modify/en.json b/src/i18n-keysets/dataset.dataset-editor.modify/en.json new file mode 100644 index 0000000..2ec2f86 --- /dev/null +++ b/src/i18n-keysets/dataset.dataset-editor.modify/en.json @@ -0,0 +1,111 @@ +{ + "button_add-field": "Add field", + "button_apply": "Apply", + "button_ask-access-rights": "Request permissions", + "button_batch-aggregation": "Change aggregation", + "button_batch-apply": "Apply", + "button_batch-cancel": "Cancel", + "button_batch-hide": "Hide", + "button_batch-remove": "Delete", + "button_batch-show": "Show", + "button_batch-type": "Change type", + "button_copy-id": "Copy ID", + "button_create": "Create", + "button_create-dashboard": "Create dashboard", + "button_create-widget": "Create chart", + "button_display-field": "Show field", + "button_duplicate": "Duplicate", + "button_edit": "Edit", + "button_field-settings": "Display settings", + "button_hide-field": "Hide field", + "button_inspect": "Inspect", + "button_open-field-editor": "Edit", + "button_preview": "Preview", + "button_preview-bottom": "Expand at bottom", + "button_preview-close": "Close", + "button_preview-full": "Full screen", + "button_preview-right": "Expand from the right", + "button_remove": "Delete", + "button_row-level-security": "Permissions", + "button_save": "Save", + "button_update-fields": "Update fields", + "column_aggregation": "Aggregation", + "column_cast": "Type", + "column_description": "Description", + "column_field-aggregation": "Aggregation", + "column_field-cast": "Type", + "column_field-name": "Name", + "column_filed-description": "Description", + "column_source": "Field source", + "column_title": "Name", + "field_display-rows": "Number of rows:", + "field_find-field": "Field name or description", + "label_batch-aggregation": "New field aggregation", + "label_batch-selected-fields-count": "Fields selected — {{value}}", + "label_batch-type": "New field type", + "label_column-field-id": "Field ID", + "label_data-preparation-preview": "Loading data for preview", + "label_dataset-auto-creation-process": "Creating dataset", + "label_dataset-info": "Dataset info", + "label_display-fields-id": "Show field IDs", + "label_display-hidden-fields": "Show hidden fields", + "label_enable-templating": "Enable parameterization", + "label_enable-templating-disabled-hint": "Dataset parameterization is prohibited in the connection settings", + "label_enable-templating-hint": "Parameterization allows you to use dynamic parameters when configuring the source", + "label_error-400-no-connection-title": "Error: no connection", + "label_error-400-title": "Error: invalid request to dataset", + "label_error-403-title": "You don't have access to the connection", + "label_error-404-title": "Error: dataset not found", + "label_error-500-title": "Error: failed to upload dataset", + "label_field": "Field", + "label_load_preview_by_default": "Show preview by default", + "label_loading-dataset": "Loading dataset", + "label_loading-dataset-preview": "Loading data for preview", + "label_max-amount-rows": "maximum {{count}}", + "label_new-dataset": "New dataset", + "label_no-data": "No data", + "label_preview-not-supported": "Preview is not available for this type of source", + "label_request-dataset-preview-error": "Error: failed to upload data for preview", + "label_templating-disabled": "Forbidden", + "label_templating-enabled": "Permitted", + "section_preview": "Preview", + "text_batch-aggregation-alert": "The aggregations available depend on the selected field type", + "text_batch-aggregation-header": "Changing the field aggregation", + "text_batch-hide-header": "Hide fields", + "text_batch-hide-message": "Are you sure want to hide the selected fields?", + "text_batch-remove-header": "Delete fields", + "text_batch-remove-message": "Are you sure want to delete the selected fields?", + "text_batch-show-header": "Show fields", + "text_batch-show-message": "Are you sure want to show the selected fields?", + "text_batch-type-alert": "Some conversion types are not available", + "text_batch-type-header": "Changing the field type", + "text_no-field-settings": "No display settings", + "value_array_float": "Array of fractional numbers", + "value_array_int": "Array of integers", + "value_array_str": "Array of strings", + "value_auto": "Auto", + "value_avg": "Average", + "value_boolean": "Logical", + "value_count": "Count", + "value_countunique": "Count (Distinct)", + "value_data": "Sources", + "value_dataset": "Fields", + "value_date": "Date", + "value_filter": "Filtration", + "value_float": "Fractional number", + "value_genericdatetime": "Date and time", + "value_geopoint": "Geopoint", + "value_geopolygon": "Geopolygon", + "value_integer": "Integer", + "value_markup": "Markup", + "value_max": "Maximum", + "value_min": "Minimum", + "value_none": "No", + "value_parameter": "Parameters", + "value_string": "String", + "value_sum": "Sum", + "value_tree_float": "Tree of fractional numbers", + "value_tree_int": "Tree of integers", + "value_tree_str": "Tree of strings", + "value_unsupported": "Unsupported type" +} diff --git a/src/i18n-keysets/dataset.dataset-editor.modify/keyset.json b/src/i18n-keysets/dataset.dataset-editor.modify/keyset.json new file mode 100644 index 0000000..1f84e75 --- /dev/null +++ b/src/i18n-keysets/dataset.dataset-editor.modify/keyset.json @@ -0,0 +1,448 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_ask-access-rights": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_batch-aggregation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_batch-apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_batch-cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_batch-hide": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_batch-remove": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_batch-show": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_batch-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_copy-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_display-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_duplicate": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_field-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_hide-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_inspect": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_open-field-editor": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_preview": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_preview-bottom": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_preview-close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_preview-full": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_preview-right": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_remove": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_row-level-security": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_update-fields": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_aggregation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_cast": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_field-aggregation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_field-cast": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_field-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_filed-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "column_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_display-rows": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_find-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_batch-aggregation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_batch-selected-fields-count": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_batch-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_column-field-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_data-preparation-preview": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dataset-auto-creation-process": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dataset-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_display-fields-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_display-hidden-fields": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_enable-templating": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_enable-templating-disabled-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_enable-templating-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-400-no-connection-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-400-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-403-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-404-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-500-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_load_preview_by_default": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_loading-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_loading-dataset-preview": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_max-amount-rows": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preview-not-supported": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_request-dataset-preview-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_templating-disabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_templating-enabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_preview": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_batch-aggregation-alert": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_batch-aggregation-header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_batch-hide-header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_batch-hide-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_batch-remove-header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_batch-remove-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_batch-show-header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_batch-show-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_batch-type-alert": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_batch-type-header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_no-field-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_array_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_array_int": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_array_str": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_auto": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_avg": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_boolean": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_count": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_countunique": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_filter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_genericdatetime": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geopoint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geopolygon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_integer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_markup": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_min": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_none": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_parameter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_string": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_sum": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_tree_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_tree_int": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_tree_str": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_unsupported": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dataset.dataset-editor.modify/ru.json b/src/i18n-keysets/dataset.dataset-editor.modify/ru.json new file mode 100644 index 0000000..bc0e043 --- /dev/null +++ b/src/i18n-keysets/dataset.dataset-editor.modify/ru.json @@ -0,0 +1,111 @@ +{ + "button_add-field": "Добавить поле", + "button_apply": "Применить", + "button_ask-access-rights": "Запросить права", + "button_batch-aggregation": "Изменить агрегацию", + "button_batch-apply": "Изменить", + "button_batch-cancel": "Отменить", + "button_batch-hide": "Скрыть", + "button_batch-remove": "Удалить", + "button_batch-show": "Показать", + "button_batch-type": "Изменить тип", + "button_copy-id": "Скопировать ID", + "button_create": "Создать", + "button_create-dashboard": "Создать дашборд", + "button_create-widget": "Создать чарт", + "button_display-field": "Показать поле", + "button_duplicate": "Дублировать", + "button_edit": "Редактировать", + "button_field-settings": "Настройки отображения", + "button_hide-field": "Скрыть поле", + "button_inspect": "Inspect", + "button_open-field-editor": "Редактировать", + "button_preview": "Предпросмотр", + "button_preview-bottom": "Развернуть снизу", + "button_preview-close": "Закрыть", + "button_preview-full": "На весь экран", + "button_preview-right": "Развернуть справа", + "button_remove": "Удалить", + "button_row-level-security": "Права доступа", + "button_save": "Сохранить", + "button_update-fields": "Обновить поля", + "column_aggregation": "Агрегация", + "column_cast": "Тип", + "column_description": "Описание", + "column_field-aggregation": "Агрегация", + "column_field-cast": "Тип", + "column_field-name": "Имя", + "column_filed-description": "Описание", + "column_source": "Источник поля", + "column_title": "Имя", + "field_display-rows": "Количество строк:", + "field_find-field": "Имя или описание поля", + "label_batch-aggregation": "Новая агрегация полей", + "label_batch-selected-fields-count": "Выбрано полей — {{value}}", + "label_batch-type": "Новый тип полей", + "label_column-field-id": "ID поля", + "label_data-preparation-preview": "Загрузка данных для предпросмотра", + "label_dataset-auto-creation-process": "Создаём датасет", + "label_dataset-info": "Информация о датасете", + "label_display-fields-id": "Отображать ID полей", + "label_display-hidden-fields": "Отображать скрытые поля", + "label_enable-templating": "Включить параметризацию", + "label_enable-templating-disabled-hint": "Параметризация датасетов запрещена в настройках подключения", + "label_enable-templating-hint": "Параметризация позволяет использовать динамические параметры при настройке источника", + "label_error-400-no-connection-title": "Ошибка: отсутствует подключение", + "label_error-400-title": "Ошибка: некорректный запрос к датасету", + "label_error-403-title": "У вас нет доступа к подключению", + "label_error-404-title": "Ошибка: датасет не найден", + "label_error-500-title": "Ошибка: не удалось загрузить датасет", + "label_field": "Поле", + "label_load_preview_by_default": "Включить предпросмотр по умолчанию", + "label_loading-dataset": "Загрузка датасета", + "label_loading-dataset-preview": "Загрузка данных для предпросмотра", + "label_max-amount-rows": "не больше {{count}}", + "label_new-dataset": "Новый датасет", + "label_no-data": "Нет данных", + "label_preview-not-supported": "Для данного типа источника предпросмотр не доступен", + "label_request-dataset-preview-error": "Ошибка: не удалось загрузить данные для предпросмотра", + "label_templating-disabled": "Запрещено", + "label_templating-enabled": "Разрешено", + "section_preview": "Предпросмотр", + "text_batch-aggregation-alert": "Доступные агрегации зависят от типа выбранных полей", + "text_batch-aggregation-header": "Изменение агрегации полей", + "text_batch-hide-header": "Скрыть поля датасета", + "text_batch-hide-message": "Вы уверены, что хотите скрыть выбранные поля датасета?", + "text_batch-remove-header": "Удалить поля датасета", + "text_batch-remove-message": "Вы уверены, что хотите удалить выбранные поля датасета?", + "text_batch-show-header": "Показать поля датасета", + "text_batch-show-message": "Вы уверены, что хотите показать выбранные поля датасета?", + "text_batch-type-alert": "Некоторые преобразования типов недоступны", + "text_batch-type-header": "Изменение типа полей", + "text_no-field-settings": "Нет настроек отображения", + "value_array_float": "Массив дробных чисел", + "value_array_int": "Массив целых чисел", + "value_array_str": "Массив строк", + "value_auto": "Авто", + "value_avg": "Среднее", + "value_boolean": "Логический", + "value_count": "Количество", + "value_countunique": "Количество уникальных", + "value_data": "Источники", + "value_dataset": "Поля", + "value_date": "Дата", + "value_filter": "Фильтрация", + "value_float": "Дробное число", + "value_genericdatetime": "Дата и время", + "value_geopoint": "Геоточка", + "value_geopolygon": "Геополигон", + "value_integer": "Целое число", + "value_markup": "Разметка", + "value_max": "Максимум", + "value_min": "Минимум", + "value_none": "Нет", + "value_parameter": "Параметры", + "value_string": "Строка", + "value_sum": "Сумма", + "value_tree_float": "Дерево дробных чисел", + "value_tree_int": "Дерево целых чисел", + "value_tree_str": "Дерево строк", + "value_unsupported": "Неподдерживаемый тип" +} diff --git a/src/i18n-keysets/dataset.field-settings-dialog.modify/context.json b/src/i18n-keysets/dataset.field-settings-dialog.modify/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dataset.field-settings-dialog.modify/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dataset.field-settings-dialog.modify/en.json b/src/i18n-keysets/dataset.field-settings-dialog.modify/en.json new file mode 100644 index 0000000..b3c7973 --- /dev/null +++ b/src/i18n-keysets/dataset.field-settings-dialog.modify/en.json @@ -0,0 +1,16 @@ +{ + "button_cancel": "Cancel", + "button_colors": "Set colors", + "button_reset": "Reset", + "button_save": "Save", + "label_colors": "Colors", + "label_field-other-colors": [ + "{{count}} more color", + "{{count}} more colors", + "{{count}} more colors", + "", + "", + "" + ], + "label_title": "Display settings" +} diff --git a/src/i18n-keysets/dataset.field-settings-dialog.modify/keyset.json b/src/i18n-keysets/dataset.field-settings-dialog.modify/keyset.json new file mode 100644 index 0000000..59a37d9 --- /dev/null +++ b/src/i18n-keysets/dataset.field-settings-dialog.modify/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_colors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_reset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_colors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-other-colors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dataset.field-settings-dialog.modify/ru.json b/src/i18n-keysets/dataset.field-settings-dialog.modify/ru.json new file mode 100644 index 0000000..9108dac --- /dev/null +++ b/src/i18n-keysets/dataset.field-settings-dialog.modify/ru.json @@ -0,0 +1,16 @@ +{ + "button_cancel": "Отменить", + "button_colors": "Настроить цвета", + "button_reset": "Сбросить", + "button_save": "Сохранить", + "label_colors": "Цвета", + "label_field-other-colors": [ + "Ещё {{count}} цвет", + "Ещё {{count}} цвета", + "Ещё {{count}} цветов", + "", + "", + "" + ], + "label_title": "Настройки отображения" +} diff --git a/src/i18n-keysets/dataset.filters-tab.modify/context.json b/src/i18n-keysets/dataset.filters-tab.modify/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dataset.filters-tab.modify/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dataset.filters-tab.modify/en.json b/src/i18n-keysets/dataset.filters-tab.modify/en.json new file mode 100644 index 0000000..56e39c9 --- /dev/null +++ b/src/i18n-keysets/dataset.filters-tab.modify/en.json @@ -0,0 +1,10 @@ +{ + "empty-filters-readonly-alert-message": "To add filters, go to the original dataset", + "filters-readonly-alert-message": "To edit or add filters, go to the original dataset", + "filters-readonly-alert-title": "No filters", + "label_filter-list-field-th": "Field", + "label_filter-list-value-th": "Value", + "label_obligatory-filter-add": "Add", + "label_obligatory-filter-description": "The values will be applied to all new charts created using the dataset. This will reduce the chart rendering time when working with large amounts of data.

The default filters for new charts do not affect the display in the dataset preview.", + "label_obligatory-filter-title": "Default filters for new charts" +} diff --git a/src/i18n-keysets/dataset.filters-tab.modify/keyset.json b/src/i18n-keysets/dataset.filters-tab.modify/keyset.json new file mode 100644 index 0000000..f0927a4 --- /dev/null +++ b/src/i18n-keysets/dataset.filters-tab.modify/keyset.json @@ -0,0 +1,44 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "empty-filters-readonly-alert-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "filters-readonly-alert-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "filters-readonly-alert-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-list-field-th": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-list-value-th": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_obligatory-filter-add": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_obligatory-filter-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_obligatory-filter-title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dataset.filters-tab.modify/ru.json b/src/i18n-keysets/dataset.filters-tab.modify/ru.json new file mode 100644 index 0000000..dd9dd6d --- /dev/null +++ b/src/i18n-keysets/dataset.filters-tab.modify/ru.json @@ -0,0 +1,10 @@ +{ + "empty-filters-readonly-alert-message": "Для добавления фильтров перейдите к оригиналу датасета", + "filters-readonly-alert-message": "Для редактирования и добавления фильтров перейдите к оригиналу датасета", + "filters-readonly-alert-title": "Фильтры отсутствуют", + "label_filter-list-field-th": "Поле", + "label_filter-list-value-th": "Значение", + "label_obligatory-filter-add": "Добавить", + "label_obligatory-filter-description": "Эти значения будут применены для всех новых чартов, созданных над данным датасетом, это позволит уменьшить время отрисовки чарта при работе с большими объёмами данных.

Фильтры по умолчанию для чартов не влияют на отображение в предпросмотре датасета.", + "label_obligatory-filter-title": "Фильтры по умолчанию для новых чартов" +} diff --git a/src/i18n-keysets/dataset.notifications.view/context.json b/src/i18n-keysets/dataset.notifications.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dataset.notifications.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dataset.notifications.view/en.json b/src/i18n-keysets/dataset.notifications.view/en.json new file mode 100644 index 0000000..93a643a --- /dev/null +++ b/src/i18n-keysets/dataset.notifications.view/en.json @@ -0,0 +1,16 @@ +{ + "toast_create-dataset-msgs-failure": "Error: failed to create dataset", + "toast_create-dataset-msgs-success": "Dataset created", + "toast_dataset-fetch-preview-msgs-failure": "Error: failed to upload data for preview", + "toast_dataset-save-msgs-failure": "Error: failed to save dataset", + "toast_dataset-save-msgs-success": "Dataset saved", + "toast_dataset-validation-msgs-failure": "Error: failed to validate dataset", + "toast_default-error": "Error", + "toast_error-action-label": "Learn more", + "toast_fetch-types-msgs-failure": "Error: failed to load data types", + "toast_field-duplicated-msgs-success": "Field duplicated", + "toast_field-remove-msgs-success": "Field deleted", + "toast_preview-not-supported": "Preview is not available for this type of source", + "toast_replace-connection-failure": "Error: failed to replace connection", + "toast_select-connection-failure": "Error: failed to add connection" +} diff --git a/src/i18n-keysets/dataset.notifications.view/keyset.json b/src/i18n-keysets/dataset.notifications.view/keyset.json new file mode 100644 index 0000000..f4ed321 --- /dev/null +++ b/src/i18n-keysets/dataset.notifications.view/keyset.json @@ -0,0 +1,68 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "toast_create-dataset-msgs-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_create-dataset-msgs-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_dataset-fetch-preview-msgs-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_dataset-save-msgs-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_dataset-save-msgs-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_dataset-validation-msgs-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_default-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_error-action-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_fetch-types-msgs-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_field-duplicated-msgs-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_field-remove-msgs-success": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_preview-not-supported": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_replace-connection-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_select-connection-failure": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dataset.notifications.view/ru.json b/src/i18n-keysets/dataset.notifications.view/ru.json new file mode 100644 index 0000000..6c7d0cf --- /dev/null +++ b/src/i18n-keysets/dataset.notifications.view/ru.json @@ -0,0 +1,16 @@ +{ + "toast_create-dataset-msgs-failure": "Ошибка: не удалось создать датасет", + "toast_create-dataset-msgs-success": "Датасет создан", + "toast_dataset-fetch-preview-msgs-failure": "Ошибка: не удалось загрузить данные для предпросмотра", + "toast_dataset-save-msgs-failure": "Ошибка: не удалось сохранить датасет", + "toast_dataset-save-msgs-success": "Датасет сохранен", + "toast_dataset-validation-msgs-failure": "Ошибка: датасет не прошел валидацию", + "toast_default-error": "Ошибка", + "toast_error-action-label": "Подробнее", + "toast_fetch-types-msgs-failure": "Ошибка: не удалось загрузить типы данных", + "toast_field-duplicated-msgs-success": "Поле продублировано", + "toast_field-remove-msgs-success": "Поле удалено", + "toast_preview-not-supported": "Для данного типа источника предпросмотр не доступен", + "toast_replace-connection-failure": "Ошибка: не удалось заменить подключение", + "toast_select-connection-failure": "Ошибка: не удалось добавить подключение" +} diff --git a/src/i18n-keysets/dataset.parameters-tab.modify/context.json b/src/i18n-keysets/dataset.parameters-tab.modify/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dataset.parameters-tab.modify/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dataset.parameters-tab.modify/en.json b/src/i18n-keysets/dataset.parameters-tab.modify/en.json new file mode 100644 index 0000000..3dd3cf3 --- /dev/null +++ b/src/i18n-keysets/dataset.parameters-tab.modify/en.json @@ -0,0 +1,14 @@ +{ + "empty-parameters-readonly-alert-message": "To add parameters, go to the original dataset", + "label_parameter-name-column": "Name", + "label_parameter-name-column-description": "Parameter name restrictions: \n* Lowercase Latin letters, digits, hyphens, and underscores are acceptable. The first character can't be an underscore: `_name`. \n* Parameter name is case-sensitive: `Test` and `test` are different parameters. \n* Parameter name length should not exceed 36 characters. \n* Reserved parameter names that cannot be used: `tab`, `state`, `mode`, `focus`, `grid`, `tz`, `from`, `to`.", + "label_parameter-type-column": "Type", + "label_parameter-validation-column": "Use in source setup", + "label_parameter-value-column": "Default Value", + "label_parameter-value-column-description": "The default value is required. It is used in cases when the parameter value is not defined on the dashboard, in the chart URL, or in the chart settings.", + "parameters-readonly-alert-message": "To edit or add parameters, go to the original dataset", + "parameters-readonly-alert-title": "No parameters", + "tab-parameters_add-button": "Add", + "tab-parameters_description": "A parameter is a dataset or chart variable that can replace constant values in calculated fields. The parameter can be a number, string, date, or boolean. Dataset parameters are available in all charts based on this dataset. A default parameter value can be overridden at the chart level.", + "tab-parameters_title": "Parameters" +} diff --git a/src/i18n-keysets/dataset.parameters-tab.modify/keyset.json b/src/i18n-keysets/dataset.parameters-tab.modify/keyset.json new file mode 100644 index 0000000..49d211d --- /dev/null +++ b/src/i18n-keysets/dataset.parameters-tab.modify/keyset.json @@ -0,0 +1,60 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "empty-parameters-readonly-alert-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_parameter-name-column": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_parameter-name-column-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_parameter-type-column": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_parameter-validation-column": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_parameter-value-column": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_parameter-value-column-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "parameters-readonly-alert-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "parameters-readonly-alert-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab-parameters_add-button": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab-parameters_description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tab-parameters_title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dataset.parameters-tab.modify/ru.json b/src/i18n-keysets/dataset.parameters-tab.modify/ru.json new file mode 100644 index 0000000..337d67b --- /dev/null +++ b/src/i18n-keysets/dataset.parameters-tab.modify/ru.json @@ -0,0 +1,14 @@ +{ + "empty-parameters-readonly-alert-message": "Для добавления параметров перейдите к оригиналу датасета", + "label_parameter-name-column": "Имя параметра", + "label_parameter-name-column-description": "Ограничения на имя параметра: \n* Допускаются только латинские буквы (прописные и строчные), цифры, знак тире `-` и нижнее подчеркивание `_`. \n* Имя параметра регистрозависимое: `Test` и `test` — различные параметры. \n* Длина имени не должна превышать 36 символов. \n* Не допускается наименование параметра, начинающегося с символа нижнего подчеркивания: `_name`. \n* Зарезервированные имена параметров, которые нельзя использовать: `tab`, `state`, `mode`, `focus`, `grid`, `tz`, `from`, `to`.", + "label_parameter-type-column": "Тип", + "label_parameter-validation-column": "Использование в настройке источника", + "label_parameter-value-column": "Значение по умолчанию", + "label_parameter-value-column-description": "Значение по умолчанию обязательно. Оно используется в случаях, когда значение параметра не определено на дашборде, в URL чарта, или в настройках самого чарта.", + "parameters-readonly-alert-message": "Для редактирования и добавления параметров перейдите к оригиналу датасета", + "parameters-readonly-alert-title": "Параметры отсутствуют", + "tab-parameters_add-button": "Добавить", + "tab-parameters_description": "Параметр — это переменная датасета или чарта, которая может заменять константные значения в вычисляемых полях. Параметр может быть числом, строкой, датой или логическим значением. Параметры датасета доступны во всех чартах, созданных на основе этого датасета. Значение по умолчанию для параметра может быть переопределено на уровне чарта.", + "tab-parameters_title": "Параметры" +} diff --git a/src/i18n-keysets/dataset.rls.modify/context.json b/src/i18n-keysets/dataset.rls.modify/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dataset.rls.modify/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dataset.rls.modify/en.json b/src/i18n-keysets/dataset.rls.modify/en.json new file mode 100644 index 0000000..301cda7 --- /dev/null +++ b/src/i18n-keysets/dataset.rls.modify/en.json @@ -0,0 +1,6 @@ +{ + "button_cancel": "Cancel", + "button_save": "Save", + "field_placeholder-row-level-security": "Entry format: 'Field value': login1, login2, etc.", + "label_row-level-security": "Field permission settings" +} diff --git a/src/i18n-keysets/dataset.rls.modify/keyset.json b/src/i18n-keysets/dataset.rls.modify/keyset.json new file mode 100644 index 0000000..4ca8661 --- /dev/null +++ b/src/i18n-keysets/dataset.rls.modify/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_placeholder-row-level-security": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_row-level-security": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dataset.rls.modify/ru.json b/src/i18n-keysets/dataset.rls.modify/ru.json new file mode 100644 index 0000000..555af17 --- /dev/null +++ b/src/i18n-keysets/dataset.rls.modify/ru.json @@ -0,0 +1,6 @@ +{ + "button_cancel": "Отменить", + "button_save": "Сохранить", + "field_placeholder-row-level-security": "Формат записи: 'Значение поля': логин1, логин2, etc.", + "label_row-level-security": "Настройка прав доступа для поля" +} diff --git a/src/i18n-keysets/dataset.sources-tab.modify/context.json b/src/i18n-keysets/dataset.sources-tab.modify/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dataset.sources-tab.modify/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dataset.sources-tab.modify/en.json b/src/i18n-keysets/dataset.sources-tab.modify/en.json new file mode 100644 index 0000000..89f7e7d --- /dev/null +++ b/src/i18n-keysets/dataset.sources-tab.modify/en.json @@ -0,0 +1,64 @@ +{ + "button_add-condition": "Add link", + "button_add-connection": "Add", + "button_add-source": "Add", + "button_apply": "Apply", + "button_cancel": "Cancel", + "button_create": "Create", + "button_create-param": "Create parameter", + "button_details": "Learn more", + "button_insert-param": "Paste parameter", + "button_retry": "Retry", + "field_placeholder-title": "Name", + "hint_optimize-join": "Disabling optimization makes the link mandatory and may increase query execution time.", + "label_chydb-database-example": "/ru/home/user/mydb", + "label_chydb-table-example": "/ru/home/user/mydb/test_table", + "label_chydb-table-path": "Path to table in YDB", + "label_cluster": "Cluster", + "label_data-source-error-db-request": "Sent query:", + "label_data-source-error-db-response": "Database response:", + "label_db-name": "Database name", + "label_deleted-connection": "Deleted connection", + "label_menu-popup-delete-connection": "Delete connection", + "label_menu-popup-delete-source": "Delete table", + "label_menu-popup-modify-source": "Modify settings", + "label_menu-popup-open-connection": "Open connection", + "label_menu-popup-replace-connection": "Replace connection", + "label_optimize-join": "Optimize link", + "label_param-select-empty-placeholder-description": "Use parameters to make dynamic queries to a data source", + "label_param-select-empty-placeholder-title": "Create your first parameter", + "label_param-select-option-disabled": "The use of the parameter is prohibited in its settings. If necessary, activate the \"Allow use in source configuration\" option.", + "label_param-select-option-edit": "Edit", + "label_source-editor-header": "Source", + "label_source-relation": "Link", + "label_sources": "Connections", + "label_sources-loading-error": "Failed to load tables list", + "label_sources-map-placeholder": "Drag and drop tables to create links", + "label_subselect-form-comment": "-- Write your request", + "label_subselect-form-tooltip-chyt": "• When copying from YQL, don't forget to remove use ... at the beginning and ; at the end.", + "label_subselect-form-tooltip-common": "• The entered query works in the select *from () as source construction.", + "label_subselect-form-tooltip-mssql": "• Microsoft SQL can define date/datetime types as a string in a subquery schema. In this case, explicitly specify the type in the dataset fields.", + "label_subselect-form-tooltip-pg": "• PostgreSQL can define some constants as null (example: select 'a'). In this case, explicitly specify the CAST (example: select 'a'::text).", + "label_tables": "Tables", + "label_title": "Source name", + "label_tooltip-choveryt-path-restriction": "You can enter the full URL or path to the table. CHYT connections only support schematized static tables.", + "label_yc-chyt-subselect-subsql": "When copying from queries, don't forget to remove use ... at the beginning and ; at the end.", + "label_yc-chyt-table-list-title-body": "The table concatenation function for multiple tables is used:", + "label_yc-chyt-table-name-body": "Enter the path to the table. CHYT connections only support schematized tables.", + "label_yc-chyt-table-range-title-body": "The table concatenation function for all tables along the path is used:", + "label_yt-function-tables": "The table concatenation function for multiple tables is used:", + "label_yt-function-tables-example": "concatYtTables(table1, [table2, [...]])", + "label_yt-function-tables-range": "The table concatenation function for all tables along the path is used:", + "label_yt-function-tables-range-example": "concatYtTablesRange(cypressPath, [from, [to]])", + "label_yt-subsql-example": "select * from \"//home/arivkin/choyt/demo/concat_test/1\"", + "label_yt-table-example": "Example:", + "label_yt-table-example-directory-path": "//home/arivkin/choyt/demo/concat_test", + "label_yt-table-example-path": "//home/arivkin/choyt/demo/concat_test/1", + "label_yt-table-example-paths": "//home/arivkin/choyt/demo/concat_test/1 //home/arivkin/choyt/demo/concat_test/2", + "value_binary-join-operator-eq": "=", + "value_binary-join-operator-gt": ">", + "value_binary-join-operator-gte": ">=", + "value_binary-join-operator-lt": "<", + "value_binary-join-operator-lte": "<=", + "value_binary-join-operator-ne": "<>" +} diff --git a/src/i18n-keysets/dataset.sources-tab.modify/keyset.json b/src/i18n-keysets/dataset.sources-tab.modify/keyset.json new file mode 100644 index 0000000..195eef8 --- /dev/null +++ b/src/i18n-keysets/dataset.sources-tab.modify/keyset.json @@ -0,0 +1,260 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-condition": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_add-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_add-source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create-param": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_details": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_insert-param": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_placeholder-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "hint_optimize-join": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_chydb-database-example": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_chydb-table-example": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_chydb-table-path": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_cluster": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_data-source-error-db-request": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_data-source-error-db-response": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_db-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_deleted-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_menu-popup-delete-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_menu-popup-delete-source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_menu-popup-modify-source": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_menu-popup-open-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_menu-popup-replace-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_optimize-join": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_param-select-empty-placeholder-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_param-select-empty-placeholder-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_param-select-option-disabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_param-select-option-edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-editor-header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_source-relation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sources": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sources-loading-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sources-map-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_subselect-form-comment": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_subselect-form-tooltip-chyt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_subselect-form-tooltip-common": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_subselect-form-tooltip-mssql": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_subselect-form-tooltip-pg": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tables": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tooltip-choveryt-path-restriction": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yc-chyt-subselect-subsql": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yc-chyt-table-list-title-body": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yc-chyt-table-name-body": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yc-chyt-table-range-title-body": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yt-function-tables": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yt-function-tables-example": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yt-function-tables-range": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yt-function-tables-range-example": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yt-subsql-example": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yt-table-example": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yt-table-example-directory-path": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yt-table-example-path": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yt-table-example-paths": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_binary-join-operator-eq": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_binary-join-operator-gt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_binary-join-operator-gte": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_binary-join-operator-lt": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_binary-join-operator-lte": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_binary-join-operator-ne": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dataset.sources-tab.modify/ru.json b/src/i18n-keysets/dataset.sources-tab.modify/ru.json new file mode 100644 index 0000000..9f63424 --- /dev/null +++ b/src/i18n-keysets/dataset.sources-tab.modify/ru.json @@ -0,0 +1,64 @@ +{ + "button_add-condition": "Добавить связь", + "button_add-connection": "Добавить", + "button_add-source": "Добавить", + "button_apply": "Применить", + "button_cancel": "Отменить", + "button_create": "Создать", + "button_create-param": "Создать параметр", + "button_details": "Подробнее", + "button_insert-param": "Вставить параметр", + "button_retry": "Повторить", + "field_placeholder-title": "Название", + "hint_optimize-join": "Отключение оптимизации делает связь обязательной и может увеличить время выполнения запроса.", + "label_chydb-database-example": "/ru/home/user/mydb", + "label_chydb-table-example": "/ru/home/user/mydb/test_table", + "label_chydb-table-path": "Путь к таблице в YDB", + "label_cluster": "Кластер", + "label_data-source-error-db-request": "Отправленный запрос:", + "label_data-source-error-db-response": "Ответ БД:", + "label_db-name": "Имя базы данных", + "label_deleted-connection": "Удаленное соединение", + "label_menu-popup-delete-connection": "Удалить подключение", + "label_menu-popup-delete-source": "Удалить таблицу", + "label_menu-popup-modify-source": "Изменить настройки", + "label_menu-popup-open-connection": "Открыть подключение", + "label_menu-popup-replace-connection": "Заменить подключение", + "label_optimize-join": "Оптимизировать связь", + "label_param-select-empty-placeholder-description": "Используйте параметры, чтобы выполнять динамические запросы к источнику данных", + "label_param-select-empty-placeholder-title": "Создайте ваш первый параметр", + "label_param-select-option-disabled": "Использование параметра запрещено в его настройках. При необходимости активируйте опцию «Разрешить использовать в настройке источника».", + "label_param-select-option-edit": "Редактировать", + "label_source-editor-header": "Источник", + "label_source-relation": "Связь", + "label_sources": "Подключения", + "label_sources-loading-error": "Не удалось загрузить список таблиц", + "label_sources-map-placeholder": "Перетащите нужные таблицы для формирования связей", + "label_subselect-form-comment": "-- Напишите свой запрос", + "label_subselect-form-tooltip-chyt": "• При копировании из YQL следует не забыть убрать use ... в начале и ; в конце.", + "label_subselect-form-tooltip-common": "• Введенный запрос работает в конструкции вида select * from (<ввод>) as source.", + "label_subselect-form-tooltip-mssql": "• Microsoft SQL может определять как строку типы date/datetime в схеме подзапроса. В этом случае явно укажите тип в полях датасета.", + "label_subselect-form-tooltip-pg": "• PostgreSQL может определять некоторые константы как null (пример: select 'a' ). В этом случае явно указывайте CAST (пример: select 'a'::text ).", + "label_tables": "Таблицы", + "label_title": "Название источника", + "label_tooltip-choveryt-path-restriction": "Вы можете ввести полный URL или путь к таблице. CHYT-подключение поддерживает только схематизированные статические таблицы.", + "label_yc-chyt-subselect-subsql": "При копировании из запросов не забудьте убрать use ... в начале и ; в конце.", + "label_yc-chyt-table-list-title-body": "Используется табличная функция объединения данных из нескольких таблиц:", + "label_yc-chyt-table-name-body": "Укажите путь до таблицы. Подключение к CHYT поддерживает только схематизированные таблицы.", + "label_yc-chyt-table-range-title-body": "Используется табличная функция объединения данных из всех таблиц по пути:", + "label_yt-function-tables": "Используется табличная функция конкатенации нескольких таблиц YT:", + "label_yt-function-tables-example": "concatYtTables(table1, [table2, [...]])", + "label_yt-function-tables-range": "Используется табличная функция конкатенации всех таблиц по пути:", + "label_yt-function-tables-range-example": "concatYtTablesRange(cypressPath, [from, [to]])", + "label_yt-subsql-example": "select * from \"//home/arivkin/choyt/demo/concat_test/1\"", + "label_yt-table-example": "Пример:", + "label_yt-table-example-directory-path": "//home/arivkin/choyt/demo/concat_test", + "label_yt-table-example-path": "//home/arivkin/choyt/demo/concat_test/1", + "label_yt-table-example-paths": "//home/arivkin/choyt/demo/concat_test/1 //home/arivkin/choyt/demo/concat_test/2", + "value_binary-join-operator-eq": "=", + "value_binary-join-operator-gt": ">", + "value_binary-join-operator-gte": ">=", + "value_binary-join-operator-lt": "<", + "value_binary-join-operator-lte": "<=", + "value_binary-join-operator-ne": "<>" +} diff --git a/src/i18n-keysets/dataset.validation/context.json b/src/i18n-keysets/dataset.validation/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dataset.validation/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dataset.validation/en.json b/src/i18n-keysets/dataset.validation/en.json new file mode 100644 index 0000000..4219e4f --- /dev/null +++ b/src/i18n-keysets/dataset.validation/en.json @@ -0,0 +1,5 @@ +{ + "label_duplicated_paths_error": "There are the same paths in list", + "label_duplicated_titles_error": "There is already a source with this name on the list. To avoid repetition, change the name of the added source.", + "label_required-field-error": "This is a required field" +} diff --git a/src/i18n-keysets/dataset.validation/keyset.json b/src/i18n-keysets/dataset.validation/keyset.json new file mode 100644 index 0000000..2bcb0ee --- /dev/null +++ b/src/i18n-keysets/dataset.validation/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_duplicated_paths_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_duplicated_titles_error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_required-field-error": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dataset.validation/ru.json b/src/i18n-keysets/dataset.validation/ru.json new file mode 100644 index 0000000..a4442c4 --- /dev/null +++ b/src/i18n-keysets/dataset.validation/ru.json @@ -0,0 +1,5 @@ +{ + "label_duplicated_paths_error": "В списке есть одинаковые пути", + "label_duplicated_titles_error": "В списке уже есть источник с таким же названием. Чтобы избежать повторения, пожалуйста, измените название добавляемого источника.", + "label_required-field-error": "Поле обязательно для заполнения" +} diff --git a/src/i18n-keysets/dl-dialog-settings/context.json b/src/i18n-keysets/dl-dialog-settings/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/dl-dialog-settings/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/dl-dialog-settings/en.json b/src/i18n-keysets/dl-dialog-settings/en.json new file mode 100644 index 0000000..830d413 --- /dev/null +++ b/src/i18n-keysets/dl-dialog-settings/en.json @@ -0,0 +1,8 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel", + "label_chart-settings": "Chart settings", + "label_header": "Header", + "label_hide": "Hide", + "label_show": "Show" +} diff --git a/src/i18n-keysets/dl-dialog-settings/keyset.json b/src/i18n-keysets/dl-dialog-settings/keyset.json new file mode 100644 index 0000000..9effc6d --- /dev/null +++ b/src/i18n-keysets/dl-dialog-settings/keyset.json @@ -0,0 +1,36 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_chart-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_hide": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_show": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/dl-dialog-settings/ru.json b/src/i18n-keysets/dl-dialog-settings/ru.json new file mode 100644 index 0000000..309e51c --- /dev/null +++ b/src/i18n-keysets/dl-dialog-settings/ru.json @@ -0,0 +1,8 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отменить", + "label_chart-settings": "Настройки чарта", + "label_header": "Заголовок", + "label_hide": "Скрыть", + "label_show": "Показать" +} diff --git a/src/i18n-keysets/editor.button-save.view/context.json b/src/i18n-keysets/editor.button-save.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/editor.button-save.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/editor.button-save.view/en.json b/src/i18n-keysets/editor.button-save.view/en.json new file mode 100644 index 0000000..a98b191 --- /dev/null +++ b/src/i18n-keysets/editor.button-save.view/en.json @@ -0,0 +1,9 @@ +{ + "button_save": "Save", + "button_save-as": "Save as copy", + "button_save-as-draft": "Save as draft", + "button_save-as-published": "Save and make actual", + "button_saved": "Saved", + "label_widget-name-copy": "{{name}} — copy", + "toast_warning-not-latest-version": "You are not using the latest chart version. Apply changes?" +} diff --git a/src/i18n-keysets/editor.button-save.view/keyset.json b/src/i18n-keysets/editor.button-save.view/keyset.json new file mode 100644 index 0000000..836ad71 --- /dev/null +++ b/src/i18n-keysets/editor.button-save.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-published": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_saved": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_widget-name-copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_warning-not-latest-version": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/editor.button-save.view/ru.json b/src/i18n-keysets/editor.button-save.view/ru.json new file mode 100644 index 0000000..d9982b2 --- /dev/null +++ b/src/i18n-keysets/editor.button-save.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_save": "Сохранить", + "button_save-as": "Сохранить как копию", + "button_save-as-draft": "Сохранить как черновик", + "button_save-as-published": "Сохранить и сделать актуальной", + "button_saved": "Сохранено", + "label_widget-name-copy": "{{name}} — копия", + "toast_warning-not-latest-version": "Вы работаете не с самой последней версией чарта. Хотите применить изменения?" +} diff --git a/src/i18n-keysets/editor.common.view/context.json b/src/i18n-keysets/editor.common.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/editor.common.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/editor.common.view/en.json b/src/i18n-keysets/editor.common.view/en.json new file mode 100644 index 0000000..b3491cc --- /dev/null +++ b/src/i18n-keysets/editor.common.view/en.json @@ -0,0 +1,9 @@ +{ + "button_retry": "Repeat", + "button_secrets": "Add secret", + "button_show-preview": "Execute", + "label_load-failed": "Failed to load", + "label_new-chart": "New chart", + "section_choose-template": "Select template", + "toast_unsaved": "There are unsaved changes on the page. Are you sure?" +} diff --git a/src/i18n-keysets/editor.common.view/keyset.json b/src/i18n-keysets/editor.common.view/keyset.json new file mode 100644 index 0000000..639e864 --- /dev/null +++ b/src/i18n-keysets/editor.common.view/keyset.json @@ -0,0 +1,40 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_secrets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_show-preview": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_load-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_choose-template": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_unsaved": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/editor.common.view/ru.json b/src/i18n-keysets/editor.common.view/ru.json new file mode 100644 index 0000000..52621f5 --- /dev/null +++ b/src/i18n-keysets/editor.common.view/ru.json @@ -0,0 +1,9 @@ +{ + "button_retry": "Повторить", + "button_secrets": "Добавить секрет", + "button_show-preview": "Выполнить", + "label_load-failed": "Не удалось загрузить", + "label_new-chart": "Новый чарт", + "section_choose-template": "Выберите шаблон", + "toast_unsaved": "На странице есть несохраненные изменения. Вы уверены?" +} diff --git a/src/i18n-keysets/editor.dialog-confirm-warning.view/context.json b/src/i18n-keysets/editor.dialog-confirm-warning.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/editor.dialog-confirm-warning.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/editor.dialog-confirm-warning.view/en.json b/src/i18n-keysets/editor.dialog-confirm-warning.view/en.json new file mode 100644 index 0000000..f0d0849 --- /dev/null +++ b/src/i18n-keysets/editor.dialog-confirm-warning.view/en.json @@ -0,0 +1,4 @@ +{ + "button_apply": "Apply", + "button_cancel": "Cancel" +} diff --git a/src/i18n-keysets/editor.dialog-confirm-warning.view/keyset.json b/src/i18n-keysets/editor.dialog-confirm-warning.view/keyset.json new file mode 100644 index 0000000..e6d16db --- /dev/null +++ b/src/i18n-keysets/editor.dialog-confirm-warning.view/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/editor.dialog-confirm-warning.view/ru.json b/src/i18n-keysets/editor.dialog-confirm-warning.view/ru.json new file mode 100644 index 0000000..5a41894 --- /dev/null +++ b/src/i18n-keysets/editor.dialog-confirm-warning.view/ru.json @@ -0,0 +1,4 @@ +{ + "button_apply": "Применить", + "button_cancel": "Отменить" +} diff --git a/src/i18n-keysets/editor.dialog-no-edit-rights.view/context.json b/src/i18n-keysets/editor.dialog-no-edit-rights.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/editor.dialog-no-edit-rights.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/editor.dialog-no-edit-rights.view/en.json b/src/i18n-keysets/editor.dialog-no-edit-rights.view/en.json new file mode 100644 index 0000000..6ec35be --- /dev/null +++ b/src/i18n-keysets/editor.dialog-no-edit-rights.view/en.json @@ -0,0 +1,6 @@ +{ + "button_access-rights": "Request permissions", + "button_save-as": "Save as copy", + "label_no-rights-text": "This chart is read-only. Request write permissions or save changes as a new chart", + "section_no-rights-title": "No write permissions" +} diff --git a/src/i18n-keysets/editor.dialog-no-edit-rights.view/keyset.json b/src/i18n-keysets/editor.dialog-no-edit-rights.view/keyset.json new file mode 100644 index 0000000..4f502d6 --- /dev/null +++ b/src/i18n-keysets/editor.dialog-no-edit-rights.view/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_access-rights": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-rights-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_no-rights-title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/editor.dialog-no-edit-rights.view/ru.json b/src/i18n-keysets/editor.dialog-no-edit-rights.view/ru.json new file mode 100644 index 0000000..193cceb --- /dev/null +++ b/src/i18n-keysets/editor.dialog-no-edit-rights.view/ru.json @@ -0,0 +1,6 @@ +{ + "button_access-rights": "Запросить права", + "button_save-as": "Сохранить как копию", + "label_no-rights-text": "Чарт доступен в режиме «Только для чтения». Запросите права на редактирование или сохраните изменения новым чартом", + "section_no-rights-title": "Нет прав для редактирования" +} diff --git a/src/i18n-keysets/editor.docs.view/context.json b/src/i18n-keysets/editor.docs.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/editor.docs.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/editor.docs.view/en.json b/src/i18n-keysets/editor.docs.view/en.json new file mode 100644 index 0000000..691a432 --- /dev/null +++ b/src/i18n-keysets/editor.docs.view/en.json @@ -0,0 +1,7 @@ +{ + "label_any-questions": "Any more questions?", + "label_full-docs": "Full documentation is available here.", + "label_no-module-docs": "Documentation for this module is missing", + "section_common-information": "General information", + "section_modules": "Modules" +} diff --git a/src/i18n-keysets/editor.docs.view/keyset.json b/src/i18n-keysets/editor.docs.view/keyset.json new file mode 100644 index 0000000..25cf4b9 --- /dev/null +++ b/src/i18n-keysets/editor.docs.view/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_any-questions": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_full-docs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-module-docs": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_common-information": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_modules": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/editor.docs.view/ru.json b/src/i18n-keysets/editor.docs.view/ru.json new file mode 100644 index 0000000..3a85876 --- /dev/null +++ b/src/i18n-keysets/editor.docs.view/ru.json @@ -0,0 +1,7 @@ +{ + "label_any-questions": "Остались вопросы?", + "label_full-docs": "Полная документация доступна по ссылке.", + "label_no-module-docs": "Документация для данного модуля отсутствует", + "section_common-information": "Общая информация", + "section_modules": "Модули" +} diff --git a/src/i18n-keysets/editor.editor-page-error.view/context.json b/src/i18n-keysets/editor.editor-page-error.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/editor.editor-page-error.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/editor.editor-page-error.view/en.json b/src/i18n-keysets/editor.editor-page-error.view/en.json new file mode 100644 index 0000000..4285a02 --- /dev/null +++ b/src/i18n-keysets/editor.editor-page-error.view/en.json @@ -0,0 +1,5 @@ +{ + "button_retry": "Repeat", + "label_error-404-title": "Chart not found", + "label_error-500-title": "Failed to load chart" +} diff --git a/src/i18n-keysets/editor.editor-page-error.view/keyset.json b/src/i18n-keysets/editor.editor-page-error.view/keyset.json new file mode 100644 index 0000000..9c1cacc --- /dev/null +++ b/src/i18n-keysets/editor.editor-page-error.view/keyset.json @@ -0,0 +1,24 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-404-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-500-title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/editor.editor-page-error.view/ru.json b/src/i18n-keysets/editor.editor-page-error.view/ru.json new file mode 100644 index 0000000..d1a4021 --- /dev/null +++ b/src/i18n-keysets/editor.editor-page-error.view/ru.json @@ -0,0 +1,5 @@ +{ + "button_retry": "Повторить", + "label_error-404-title": "Чарт не найден", + "label_error-500-title": "Не удалось загрузить чарт" +} diff --git a/src/i18n-keysets/editor.fetch.view/context.json b/src/i18n-keysets/editor.fetch.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/editor.fetch.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/editor.fetch.view/en.json b/src/i18n-keysets/editor.fetch.view/en.json new file mode 100644 index 0000000..9b2564e --- /dev/null +++ b/src/i18n-keysets/editor.fetch.view/en.json @@ -0,0 +1,4 @@ +{ + "button_repeat": "Repeat", + "label_loading-error": "Failed to load" +} diff --git a/src/i18n-keysets/editor.fetch.view/keyset.json b/src/i18n-keysets/editor.fetch.view/keyset.json new file mode 100644 index 0000000..8bb5570 --- /dev/null +++ b/src/i18n-keysets/editor.fetch.view/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_repeat": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_loading-error": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/editor.fetch.view/ru.json b/src/i18n-keysets/editor.fetch.view/ru.json new file mode 100644 index 0000000..65f4d56 --- /dev/null +++ b/src/i18n-keysets/editor.fetch.view/ru.json @@ -0,0 +1,4 @@ +{ + "button_repeat": "Повторить", + "label_loading-error": "Не удалось загрузить" +} diff --git a/src/i18n-keysets/editor.grid-scheme-select.view/context.json b/src/i18n-keysets/editor.grid-scheme-select.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/editor.grid-scheme-select.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/editor.grid-scheme-select.view/en.json b/src/i18n-keysets/editor.grid-scheme-select.view/en.json new file mode 100644 index 0000000..522d43d --- /dev/null +++ b/src/i18n-keysets/editor.grid-scheme-select.view/en.json @@ -0,0 +1,10 @@ +{ + "value_count-panes": [ + "{{count}} Pane", + "{{count}} Panes", + "{{count}} Panes", + "Zero panes", + "", + "" + ] +} diff --git a/src/i18n-keysets/editor.grid-scheme-select.view/keyset.json b/src/i18n-keysets/editor.grid-scheme-select.view/keyset.json new file mode 100644 index 0000000..de2533e --- /dev/null +++ b/src/i18n-keysets/editor.grid-scheme-select.view/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "value_count-panes": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/editor.grid-scheme-select.view/ru.json b/src/i18n-keysets/editor.grid-scheme-select.view/ru.json new file mode 100644 index 0000000..b978c57 --- /dev/null +++ b/src/i18n-keysets/editor.grid-scheme-select.view/ru.json @@ -0,0 +1,10 @@ +{ + "value_count-panes": [ + "{{count}} Панель", + "{{count}} Панели", + "{{count}} Панелей", + "Ноль Панелей", + "", + "" + ] +} diff --git a/src/i18n-keysets/exports.enable-data-export-settings/context.json b/src/i18n-keysets/exports.enable-data-export-settings/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/exports.enable-data-export-settings/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/exports.enable-data-export-settings/en.json b/src/i18n-keysets/exports.enable-data-export-settings/en.json new file mode 100644 index 0000000..daa63b3 --- /dev/null +++ b/src/i18n-keysets/exports.enable-data-export-settings/en.json @@ -0,0 +1,4 @@ +{ + "label_data-export-info": "When the option is disabled, the export data button will be hidden from charts. At the same time, it will be possible to extract data from charts, as well as take screenshots.", + "label_enable-data-export": "Enable data export" +} diff --git a/src/i18n-keysets/exports.enable-data-export-settings/keyset.json b/src/i18n-keysets/exports.enable-data-export-settings/keyset.json new file mode 100644 index 0000000..aba64fa --- /dev/null +++ b/src/i18n-keysets/exports.enable-data-export-settings/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_data-export-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_enable-data-export": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/exports.enable-data-export-settings/ru.json b/src/i18n-keysets/exports.enable-data-export-settings/ru.json new file mode 100644 index 0000000..4e5690d --- /dev/null +++ b/src/i18n-keysets/exports.enable-data-export-settings/ru.json @@ -0,0 +1,4 @@ +{ + "label_data-export-info": "При отключении опции в чартах будет скрыта кнопка экспорта данных. При этом останется возможность извлекать данные из чартов, а также снимать скриншоты.", + "label_enable-data-export": "Разрешить экспорт данных" +} diff --git a/src/i18n-keysets/main.service-settings.view/context.json b/src/i18n-keysets/main.service-settings.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/main.service-settings.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/main.service-settings.view/en.json b/src/i18n-keysets/main.service-settings.view/en.json new file mode 100644 index 0000000..59afcb8 --- /dev/null +++ b/src/i18n-keysets/main.service-settings.view/en.json @@ -0,0 +1,4 @@ +{ + "label_header": "Service settings", + "section_color-palettes": "Palettes" +} diff --git a/src/i18n-keysets/main.service-settings.view/keyset.json b/src/i18n-keysets/main.service-settings.view/keyset.json new file mode 100644 index 0000000..bbb7ebf --- /dev/null +++ b/src/i18n-keysets/main.service-settings.view/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_color-palettes": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/main.service-settings.view/ru.json b/src/i18n-keysets/main.service-settings.view/ru.json new file mode 100644 index 0000000..70cb6ac --- /dev/null +++ b/src/i18n-keysets/main.service-settings.view/ru.json @@ -0,0 +1,4 @@ +{ + "label_header": "Настройки сервиса", + "section_color-palettes": "Палитры" +} diff --git a/src/i18n-keysets/new-workbooks.empty-table/context.json b/src/i18n-keysets/new-workbooks.empty-table/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/new-workbooks.empty-table/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/new-workbooks.empty-table/en.json b/src/i18n-keysets/new-workbooks.empty-table/en.json new file mode 100644 index 0000000..587cec6 --- /dev/null +++ b/src/i18n-keysets/new-workbooks.empty-table/en.json @@ -0,0 +1,12 @@ +{ + "button_clear-filters": "Reset filters", + "label_empty-scope-all-description": "Start by creating a connection to a data source, then describe the dataset, create a chart, and assemble a dashboard!", + "label_not-found-description": "Try adding other conditions to filters", + "section_empty-scope-all": "There are no objects in this workbook yet", + "section_empty-scope-connection": "There are no connections in this workbook yet", + "section_empty-scope-dashboard": "There are no dashboards in this workbook yet", + "section_empty-scope-dataset": "There are no datasets in this workbook yet", + "section_empty-scope-report": "There are no reports in this workbook yet", + "section_empty-scope-widget": "There are no charts in this workbook yet", + "section_not-found": "Nothing found" +} diff --git a/src/i18n-keysets/new-workbooks.empty-table/keyset.json b/src/i18n-keysets/new-workbooks.empty-table/keyset.json new file mode 100644 index 0000000..550b1fa --- /dev/null +++ b/src/i18n-keysets/new-workbooks.empty-table/keyset.json @@ -0,0 +1,52 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_clear-filters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-scope-all-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_not-found-description": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_empty-scope-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_empty-scope-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_empty-scope-dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_empty-scope-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_empty-scope-report": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_empty-scope-widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_not-found": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/new-workbooks.empty-table/ru.json b/src/i18n-keysets/new-workbooks.empty-table/ru.json new file mode 100644 index 0000000..444f971 --- /dev/null +++ b/src/i18n-keysets/new-workbooks.empty-table/ru.json @@ -0,0 +1,12 @@ +{ + "button_clear-filters": "Очистить фильтры", + "label_empty-scope-all-description": "Начните с создания подключения к источнику данных, затем опишите датасет, создайте чарт и соберите дашборд!", + "label_not-found-description": "Попробуйте задать другие условия в фильтрах", + "section_empty-scope-all": "В этом воркбуке пока нет объектов", + "section_empty-scope-connection": "В этом воркбуке пока нет подключений", + "section_empty-scope-dashboard": "В этом воркбуке пока нет дашбордов", + "section_empty-scope-dataset": "В этом воркбуке пока нет датасетов", + "section_empty-scope-report": "В этом воркбуке пока нет отчётов", + "section_empty-scope-widget": "В этом воркбуке пока нет чартов", + "section_not-found": "Ничего не найдено" +} diff --git a/src/i18n-keysets/new-workbooks.table-filters/context.json b/src/i18n-keysets/new-workbooks.table-filters/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/new-workbooks.table-filters/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/new-workbooks.table-filters/en.json b/src/i18n-keysets/new-workbooks.table-filters/en.json new file mode 100644 index 0000000..59a2e2e --- /dev/null +++ b/src/i18n-keysets/new-workbooks.table-filters/en.json @@ -0,0 +1,12 @@ +{ + "label_filter-string-placeholder": "Search by name", + "label_sort-first-alphabet-asc": "Sort A to Z", + "label_sort-first-alphabet-desc": "Sort Z to A", + "label_sort-first-new": "Newest first", + "label_sort-first-old": "Oldest first", + "switch_filter-by-scope-all": "All objects", + "switch_filter-by-scope-connection": "Connections", + "switch_filter-by-scope-dash": "Dashboards", + "switch_filter-by-scope-dataset": "Datasets", + "switch_filter-by-scope-widget": "Charts" +} diff --git a/src/i18n-keysets/new-workbooks.table-filters/keyset.json b/src/i18n-keysets/new-workbooks.table-filters/keyset.json new file mode 100644 index 0000000..b263bf1 --- /dev/null +++ b/src/i18n-keysets/new-workbooks.table-filters/keyset.json @@ -0,0 +1,52 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_filter-string-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sort-first-alphabet-asc": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sort-first-alphabet-desc": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sort-first-new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sort-first-old": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_filter-by-scope-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_filter-by-scope-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_filter-by-scope-dash": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_filter-by-scope-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "switch_filter-by-scope-widget": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/new-workbooks.table-filters/ru.json b/src/i18n-keysets/new-workbooks.table-filters/ru.json new file mode 100644 index 0000000..6313e87 --- /dev/null +++ b/src/i18n-keysets/new-workbooks.table-filters/ru.json @@ -0,0 +1,12 @@ +{ + "label_filter-string-placeholder": "Искать по названию", + "label_sort-first-alphabet-asc": "Сортировка от А до Я", + "label_sort-first-alphabet-desc": "Сортировка от Я до А", + "label_sort-first-new": "Сначала новые", + "label_sort-first-old": "Сначала старые", + "switch_filter-by-scope-all": "Все объекты", + "switch_filter-by-scope-connection": "Подключения", + "switch_filter-by-scope-dash": "Дашборды", + "switch_filter-by-scope-dataset": "Датасеты", + "switch_filter-by-scope-widget": "Чарты" +} diff --git a/src/i18n-keysets/new-workbooks/context.json b/src/i18n-keysets/new-workbooks/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/new-workbooks/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/new-workbooks/en.json b/src/i18n-keysets/new-workbooks/en.json new file mode 100644 index 0000000..1e84967 --- /dev/null +++ b/src/i18n-keysets/new-workbooks/en.json @@ -0,0 +1,53 @@ +{ + "action_access": "Access", + "action_cancel": "Cancel", + "action_copy": "Copy", + "action_create": "Create", + "action_create-chart": "Create chart", + "action_create-connection": "Create connection", + "action_create-dashboard": "Create dashboard", + "action_create-dataset": "Create dataset", + "action_create-report": "Create report", + "action_delete": "Delete", + "action_duplicate": "Duplicate", + "action_edit": "Edit", + "action_export": "Export", + "action_move": "Move", + "action_rename": "Rename", + "action_retry": "Retry", + "action_show-more": "Show more", + "button_apply": "Apply", + "button_cancel": "Cancel", + "button_import-alert-close": "OK", + "button_import-alert-documentation": "Documentation", + "button_workbook-table-load-more": "Load more", + "button_workbook-table-retry": "Reload page", + "label_author": "Author", + "label_copy": "copy", + "label_delete-entry": "Delete", + "label_empty-mobile-workbook": "There are no viewable objects in this workbook yet", + "label_entry-name-already-exists": "That name is already in use, try another one", + "label_error-load-entities": "Unable to load entities", + "label_import-alert-title": "The workbook is almost ready", + "label_last-modified": "Last modified", + "label_no-data": "Empty", + "label_placeholder": "Name", + "label_rename-entry": "Rename", + "label_title": "Name", + "menu_connection": "Connection", + "menu_dashboard": "Dashboard", + "menu_dataset": "Dataset", + "menu_editor-chart": "Editor chart", + "menu_ql-chart": "QL Chart", + "menu_report": "Report", + "menu_wizard-chart": "Wizard chart", + "no_objects": "Empty", + "section_delete-entry": "Are you sure you want to delete \"{{entryName}}\"?", + "shared-entry-workbook-table-title": "Bound objects", + "title_charts": "Charts", + "title_connections": "Connections", + "title_dashboards": "Dashboards", + "title_datasets": "Datasets", + "title_reports": "Reports", + "toast_duplicate-failed": "An error occurred while duplicating" +} diff --git a/src/i18n-keysets/new-workbooks/keyset.json b/src/i18n-keysets/new-workbooks/keyset.json new file mode 100644 index 0000000..cad96f9 --- /dev/null +++ b/src/i18n-keysets/new-workbooks/keyset.json @@ -0,0 +1,216 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "action_access": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_create-report": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_duplicate": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_export": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_move": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_rename": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "action_show-more": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_import-alert-close": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_import-alert-documentation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_workbook-table-load-more": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_workbook-table-retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_author": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-mobile-workbook": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_entry-name-already-exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-load-entities": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_import-alert-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_last-modified": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-data": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_rename-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_editor-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_ql-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_report": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "menu_wizard-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "no_objects": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_delete-entry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "shared-entry-workbook-table-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_charts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_connections": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_dashboards": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_datasets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_reports": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_duplicate-failed": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/new-workbooks/ru.json b/src/i18n-keysets/new-workbooks/ru.json new file mode 100644 index 0000000..96ec8af --- /dev/null +++ b/src/i18n-keysets/new-workbooks/ru.json @@ -0,0 +1,53 @@ +{ + "action_access": "Доступ", + "action_cancel": "Отменить", + "action_copy": "Копировать", + "action_create": "Создать", + "action_create-chart": "Создать чарт", + "action_create-connection": "Создать подключение", + "action_create-dashboard": "Создать дашборд", + "action_create-dataset": "Создать датасет", + "action_create-report": "Создать отчёт", + "action_delete": "Удалить", + "action_duplicate": "Дублировать", + "action_edit": "Редактировать", + "action_export": "Экспортировать", + "action_move": "Переместить", + "action_rename": "Переименовать", + "action_retry": "Обновить", + "action_show-more": "Показать еще", + "button_apply": "Применить", + "button_cancel": "Отменить", + "button_import-alert-close": "Понятно", + "button_import-alert-documentation": "Документация", + "button_workbook-table-load-more": "Загрузить ещё", + "button_workbook-table-retry": "Обновить страницу", + "label_author": "Автор", + "label_copy": "копия", + "label_delete-entry": "Удаление", + "label_empty-mobile-workbook": "В этом воркбуке пока нет доступных для просмотра объектов", + "label_entry-name-already-exists": "Название уже используется, придумайте другое", + "label_error-load-entities": "Не удалось загрузить сущности", + "label_import-alert-title": "Воркбук практически готов к работе", + "label_last-modified": "Последнее изменение", + "label_no-data": "Пусто", + "label_placeholder": "Введите имя", + "label_rename-entry": "Переименование", + "label_title": "Название", + "menu_connection": "Подключение", + "menu_dashboard": "Дашборд", + "menu_dataset": "Датасет", + "menu_editor-chart": "Чарт в Editor", + "menu_ql-chart": "QL-чарт", + "menu_report": "Отчёт", + "menu_wizard-chart": "Чарт в Wizard", + "no_objects": "Пока нет объектов", + "section_delete-entry": "Вы действительно хотите удалить \"{{entryName}}\"?", + "shared-entry-workbook-table-title": "Привязанные объекты", + "title_charts": "Чарты", + "title_connections": "Подключения", + "title_dashboards": "Дашборды", + "title_datasets": "Датасеты", + "title_reports": "Отчёты", + "toast_duplicate-failed": "При дублировании произошла ошибка" +} diff --git a/src/i18n-keysets/preview/context.json b/src/i18n-keysets/preview/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/preview/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/preview/en.json b/src/i18n-keysets/preview/en.json new file mode 100644 index 0000000..97d6d4e --- /dev/null +++ b/src/i18n-keysets/preview/en.json @@ -0,0 +1,3 @@ +{ + "label_not-found": "Not found" +} diff --git a/src/i18n-keysets/preview/keyset.json b/src/i18n-keysets/preview/keyset.json new file mode 100644 index 0000000..e1eec6e --- /dev/null +++ b/src/i18n-keysets/preview/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_not-found": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/preview/ru.json b/src/i18n-keysets/preview/ru.json new file mode 100644 index 0000000..34a79eb --- /dev/null +++ b/src/i18n-keysets/preview/ru.json @@ -0,0 +1,3 @@ +{ + "label_not-found": "Не найдено" +} diff --git a/src/i18n-keysets/public.dash.error/context.json b/src/i18n-keysets/public.dash.error/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/public.dash.error/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/public.dash.error/en.json b/src/i18n-keysets/public.dash.error/en.json new file mode 100644 index 0000000..9a1ae1e --- /dev/null +++ b/src/i18n-keysets/public.dash.error/en.json @@ -0,0 +1,3 @@ +{ + "button_retry": "Retry" +} diff --git a/src/i18n-keysets/public.dash.error/keyset.json b/src/i18n-keysets/public.dash.error/keyset.json new file mode 100644 index 0000000..ed836e5 --- /dev/null +++ b/src/i18n-keysets/public.dash.error/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/public.dash.error/ru.json b/src/i18n-keysets/public.dash.error/ru.json new file mode 100644 index 0000000..d3674b9 --- /dev/null +++ b/src/i18n-keysets/public.dash.error/ru.json @@ -0,0 +1,3 @@ +{ + "button_retry": "Повторить" +} diff --git a/src/i18n-keysets/public.dash.footer/context.json b/src/i18n-keysets/public.dash.footer/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/public.dash.footer/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/public.dash.footer/en.json b/src/i18n-keysets/public.dash.footer/en.json new file mode 100644 index 0000000..eb782c8 --- /dev/null +++ b/src/i18n-keysets/public.dash.footer/en.json @@ -0,0 +1,3 @@ +{ + "label_info": "Constructed in {{productName}}" +} diff --git a/src/i18n-keysets/public.dash.footer/keyset.json b/src/i18n-keysets/public.dash.footer/keyset.json new file mode 100644 index 0000000..8b1b5ef --- /dev/null +++ b/src/i18n-keysets/public.dash.footer/keyset.json @@ -0,0 +1,16 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_info": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/public.dash.footer/ru.json b/src/i18n-keysets/public.dash.footer/ru.json new file mode 100644 index 0000000..4084bae --- /dev/null +++ b/src/i18n-keysets/public.dash.footer/ru.json @@ -0,0 +1,3 @@ +{ + "label_info": "Построено в {{productName}}" +} diff --git a/src/i18n-keysets/revisions.dialog-confirm/context.json b/src/i18n-keysets/revisions.dialog-confirm/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/revisions.dialog-confirm/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/revisions.dialog-confirm/en.json b/src/i18n-keysets/revisions.dialog-confirm/en.json new file mode 100644 index 0000000..c80d77e --- /dev/null +++ b/src/i18n-keysets/revisions.dialog-confirm/en.json @@ -0,0 +1,7 @@ +{ + "button_cancel": "Cancel", + "button_confirm": "Yes, make actual", + "label_text": "A copy of the version you are currently viewing will be created. But you can always return to previous versions.", + "label_text-unsaved-lost": "All unsaved changed will be lost.", + "label_title": "Make actual?" +} diff --git a/src/i18n-keysets/revisions.dialog-confirm/keyset.json b/src/i18n-keysets/revisions.dialog-confirm/keyset.json new file mode 100644 index 0000000..9db5fa9 --- /dev/null +++ b/src/i18n-keysets/revisions.dialog-confirm/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_confirm": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_text-unsaved-lost": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/revisions.dialog-confirm/ru.json b/src/i18n-keysets/revisions.dialog-confirm/ru.json new file mode 100644 index 0000000..f6776c8 --- /dev/null +++ b/src/i18n-keysets/revisions.dialog-confirm/ru.json @@ -0,0 +1,7 @@ +{ + "button_cancel": "Отмена", + "button_confirm": "Да, сделать", + "label_text": "Будет создана копия версии, которую вы просматриваете сейчас. При этом вы всегда можете вернуться к предыдущим версиям.", + "label_text-unsaved-lost": "Все несохраненные изменения будут потеряны.", + "label_title": "Сделать актуальной?" +} diff --git a/src/i18n-keysets/service-settings.create-user.view/context.json b/src/i18n-keysets/service-settings.create-user.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/service-settings.create-user.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/service-settings.create-user.view/en.json b/src/i18n-keysets/service-settings.create-user.view/en.json new file mode 100644 index 0000000..79c2397 --- /dev/null +++ b/src/i18n-keysets/service-settings.create-user.view/en.json @@ -0,0 +1,6 @@ +{ + "button_cancel": "Cancel", + "button_create": "Add", + "label_success-user-creation": "User added successfully", + "title_create-user": "Creating a user" +} diff --git a/src/i18n-keysets/service-settings.create-user.view/keyset.json b/src/i18n-keysets/service-settings.create-user.view/keyset.json new file mode 100644 index 0000000..f8351a1 --- /dev/null +++ b/src/i18n-keysets/service-settings.create-user.view/keyset.json @@ -0,0 +1,28 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_success-user-creation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title_create-user": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/service-settings.create-user.view/ru.json b/src/i18n-keysets/service-settings.create-user.view/ru.json new file mode 100644 index 0000000..15bcfc2 --- /dev/null +++ b/src/i18n-keysets/service-settings.create-user.view/ru.json @@ -0,0 +1,6 @@ +{ + "button_cancel": "Отменить", + "button_create": "Добавить", + "label_success-user-creation": "Пользователь успешно добавлен", + "title_create-user": "Добавление пользователя" +} diff --git a/src/i18n-keysets/service-settings.main.view/context.json b/src/i18n-keysets/service-settings.main.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/service-settings.main.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/service-settings.main.view/en.json b/src/i18n-keysets/service-settings.main.view/en.json new file mode 100644 index 0000000..cfbc0e3 --- /dev/null +++ b/src/i18n-keysets/service-settings.main.view/en.json @@ -0,0 +1,7 @@ +{ + "label_header": "Service settings", + "section_appearance": "Appearance", + "section_color-palettes": "Palettes", + "section_general": "General", + "section_users": "Users" +} diff --git a/src/i18n-keysets/service-settings.main.view/keyset.json b/src/i18n-keysets/service-settings.main.view/keyset.json new file mode 100644 index 0000000..205a0d8 --- /dev/null +++ b/src/i18n-keysets/service-settings.main.view/keyset.json @@ -0,0 +1,32 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_appearance": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_color-palettes": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_general": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_users": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/service-settings.main.view/ru.json b/src/i18n-keysets/service-settings.main.view/ru.json new file mode 100644 index 0000000..63bf5a3 --- /dev/null +++ b/src/i18n-keysets/service-settings.main.view/ru.json @@ -0,0 +1,7 @@ +{ + "label_header": "Настройки сервиса", + "section_appearance": "Оформление", + "section_color-palettes": "Палитры", + "section_general": "Общие", + "section_users": "Пользователи" +} diff --git a/src/i18n-keysets/service-settings.users-list.view/context.json b/src/i18n-keysets/service-settings.users-list.view/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/service-settings.users-list.view/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/service-settings.users-list.view/en.json b/src/i18n-keysets/service-settings.users-list.view/en.json new file mode 100644 index 0000000..2771a00 --- /dev/null +++ b/src/i18n-keysets/service-settings.users-list.view/en.json @@ -0,0 +1,17 @@ +{ + "button_add-user": "Add user", + "button_load-more": "Load more", + "label_field-email": "Email", + "label_field-first-name": "Name", + "label_field-id": "ID", + "label_field-last-name": "Surname", + "label_field-login": "Username", + "label_field-roles": "Role", + "label_menu-change-password": "Change password", + "label_menu-change-role": "Assign role", + "label_menu-delete": "Delete user", + "label_menu-edit-profile": "Edit profile", + "label_search-placeholder": "Search", + "label_user": "User", + "label_users-empty-message": "Users not found" +} diff --git a/src/i18n-keysets/service-settings.users-list.view/keyset.json b/src/i18n-keysets/service-settings.users-list.view/keyset.json new file mode 100644 index 0000000..68e6ae8 --- /dev/null +++ b/src/i18n-keysets/service-settings.users-list.view/keyset.json @@ -0,0 +1,72 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-user": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_load-more": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-email": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-first-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-id": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-last-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-login": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-roles": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_menu-change-password": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_menu-change-role": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_menu-delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_menu-edit-profile": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_search-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_user": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_users-empty-message": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/service-settings.users-list.view/ru.json b/src/i18n-keysets/service-settings.users-list.view/ru.json new file mode 100644 index 0000000..25be0ed --- /dev/null +++ b/src/i18n-keysets/service-settings.users-list.view/ru.json @@ -0,0 +1,17 @@ +{ + "button_add-user": "Добавить пользователя", + "button_load-more": "Загрузить ещё", + "label_field-email": "Почта", + "label_field-first-name": "Имя", + "label_field-id": "Идентификатор", + "label_field-last-name": "Фамилия", + "label_field-login": "Логин", + "label_field-roles": "Роль", + "label_menu-change-password": "Сменить пароль", + "label_menu-change-role": "Назначить роль", + "label_menu-delete": "Удалить пользователя", + "label_menu-edit-profile": "Редактировать профиль", + "label_search-placeholder": "Поиск", + "label_user": "Пользователь", + "label_users-empty-message": "Пользователи не найдены" +} diff --git a/src/i18n-keysets/shared-entry/context.json b/src/i18n-keysets/shared-entry/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/shared-entry/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/shared-entry/en.json b/src/i18n-keysets/shared-entry/en.json new file mode 100644 index 0000000..dea0d29 --- /dev/null +++ b/src/i18n-keysets/shared-entry/en.json @@ -0,0 +1,11 @@ +{ + "add-entry-workbook-toast-message": "Name: {{name}}", + "add-entry-workbook-toast-title-connection": "Connection bound to workbook", + "add-entry-workbook-toast-title-dataset": "Dataset bound to workbook", + "label-shared-connection": "Connection", + "label-shared-dataset": "Dataset", + "shared-entry-bindings-dropdown-menu-title": "Manage bindings", + "title-select-shared-entry-dialog-connection": "Select connection", + "title-select-shared-entry-dialog-dataset": "Select dataset", + "workbook-shared-entry-original-link": "Open original" +} diff --git a/src/i18n-keysets/shared-entry/keyset.json b/src/i18n-keysets/shared-entry/keyset.json new file mode 100644 index 0000000..49c7b53 --- /dev/null +++ b/src/i18n-keysets/shared-entry/keyset.json @@ -0,0 +1,48 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "add-entry-workbook-toast-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "add-entry-workbook-toast-title-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "add-entry-workbook-toast-title-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-shared-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label-shared-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "shared-entry-bindings-dropdown-menu-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title-select-shared-entry-dialog-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "title-select-shared-entry-dialog-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "workbook-shared-entry-original-link": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/shared-entry/ru.json b/src/i18n-keysets/shared-entry/ru.json new file mode 100644 index 0000000..678c325 --- /dev/null +++ b/src/i18n-keysets/shared-entry/ru.json @@ -0,0 +1,11 @@ +{ + "add-entry-workbook-toast-message": "Название: {{name}}", + "add-entry-workbook-toast-title-connection": "Подключение привязано к воркбуку", + "add-entry-workbook-toast-title-dataset": "Датасет привязан к воркбуку", + "label-shared-connection": "Подключение", + "label-shared-dataset": "Датасет", + "shared-entry-bindings-dropdown-menu-title": "Управление привязками", + "title-select-shared-entry-dialog-connection": "Выберите подключение", + "title-select-shared-entry-dialog-dataset": "Выберите датасет", + "workbook-shared-entry-original-link": "Открыть оригинал" +} diff --git a/src/i18n-keysets/sql/context.json b/src/i18n-keysets/sql/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/sql/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/sql/en.json b/src/i18n-keysets/sql/en.json new file mode 100644 index 0000000..ebf7c61 --- /dev/null +++ b/src/i18n-keysets/sql/en.json @@ -0,0 +1,58 @@ +{ + "button_add-param": "Add param", + "button_add-param-value": "Add param value", + "button_change-connection": "Change connection", + "button_chart-settings": "Chart settings", + "button_open-in-monitoring": "Open in Monitoring", + "button_save": "Save", + "button_save-as": "Save as", + "button_save-as-draft": "Save as draft", + "button_save-as-new": "Save as new chart", + "button_save-as-published": "Save and make actual", + "button_to-connection": "Go to connection", + "error_failed-to-load-chart": "Failed to load chart", + "error_failed-to-load-connection": "Failed to load connection", + "error_failed-to-load-default-connection": "Failed to load default connection for Monitoring", + "error_failed-to-parse-loaded-chart": "Failed to process loaded chart: shared data is invalid", + "error_failed-to-save-chart": "Failed to save chart", + "error_select-connection": "Connection must be selected", + "hint_available-warning": "Unused columns may distort estimated visualization", + "label_add-query": "Add query", + "label_applied": "Applied:", + "label_chart-type": "Chart type", + "label_connection": "Connection", + "label_copy-as-text": "Copy as text", + "label_create": "Create", + "label_create-ql-chart": "Create QL chart", + "label_delete": "Delete", + "label_delete-param": "Delete param", + "label_delete-param-value": "Delete param value", + "label_duplicate": "Duplicate", + "label_empty-chart": "The visualization will appear here as soon as you write the SQL query and press Run in the bottom-left corner", + "label_failed-connection-text": "Failed to load connection", + "label_new-chart-type-text": "Select chart type to start:", + "label_new-connection-text": "To start, select a connection", + "label_params": "Params", + "label_placeholder-default-value": "Default value", + "label_placeholder-name": "Name", + "label_ql-auto-execution-chart": "Automatically apply changes", + "label_ql-info": "Chart info", + "label_query": "Query", + "label_run": "Run", + "label_select-connection": "Select", + "label_select-visualization": "Select visualization", + "label_visualization-area": "Area chart", + "label_visualization-area-100p": "Normalized stacked area chart", + "label_visualization-bar": "Bar chart", + "label_visualization-bar-100p": "Normalized bar chart", + "label_visualization-column": "Column chart", + "label_visualization-column-100p": "Normalized column chart", + "label_visualization-donut": "Donut chart", + "label_visualization-flat-table": "Table", + "label_visualization-line": "Line chart", + "label_visualization-metric": "Indicator", + "label_visualization-pie": "Pie chart", + "label_visualization-scatter": "Scatter chart", + "label_visualization-treemap": "Tree chart", + "text_default-name": "New QL-сhart" +} diff --git a/src/i18n-keysets/sql/keyset.json b/src/i18n-keysets/sql/keyset.json new file mode 100644 index 0000000..ef755b0 --- /dev/null +++ b/src/i18n-keysets/sql/keyset.json @@ -0,0 +1,236 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_add-param": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_add-param-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_change-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_chart-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_open-in-monitoring": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-published": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_to-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_failed-to-load-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_failed-to-load-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_failed-to-load-default-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_failed-to-parse-loaded-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_failed-to-save-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "error_select-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "hint_available-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-query": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_applied": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_chart-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_copy-as-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_create": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_create-ql-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-param": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-param-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_duplicate": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_empty-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_failed-connection-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-chart-type-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-connection-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_params": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-default-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_placeholder-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_ql-auto-execution-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_ql-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_query": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_run": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-connection": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_select-visualization": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-area": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-area-100p": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-bar": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-bar-100p": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-column": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-column-100p": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-donut": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-flat-table": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-line": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-metric": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-pie": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-scatter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-treemap": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "text_default-name": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/sql/ru.json b/src/i18n-keysets/sql/ru.json new file mode 100644 index 0000000..832fe3f --- /dev/null +++ b/src/i18n-keysets/sql/ru.json @@ -0,0 +1,58 @@ +{ + "button_add-param": "Добавить параметр", + "button_add-param-value": "Добавить значение", + "button_change-connection": "Заменить подключение", + "button_chart-settings": "Настройки чарта", + "button_open-in-monitoring": "Открыть в Мониторинге", + "button_save": "Сохранить", + "button_save-as": "Сохранить как", + "button_save-as-draft": "Сохранить как черновик", + "button_save-as-new": "Сохранить как новый чарт", + "button_save-as-published": "Сохранить и сделать актуальной", + "button_to-connection": "Перейти к подключению", + "error_failed-to-load-chart": "Не удалось загрузить чарт", + "error_failed-to-load-connection": "Не удалось загрузить подключение", + "error_failed-to-load-default-connection": "Не удалось загрузить стандартное подключение для Monitoring", + "error_failed-to-parse-loaded-chart": "Не удалось обработать чарт: ошибка в данных", + "error_failed-to-save-chart": "Не удалось сохранить чарт", + "error_select-connection": "Необходимо выбрать подключение", + "hint_available-warning": "Неиспользованные колонки могут искажать ожидаемую визуализацию", + "label_add-query": "Добавить запрос", + "label_applied": "Применены:", + "label_chart-type": "Тип чарта", + "label_connection": "Подключение", + "label_copy-as-text": "Скопировать как текст", + "label_create": "Создать", + "label_create-ql-chart": "Создать QL-чарт", + "label_delete": "Удалить", + "label_delete-param": "Удалить параметр", + "label_delete-param-value": "Удалить значение параметра", + "label_duplicate": "Дублировать", + "label_empty-chart": "Визуализация появится, как только вы напишете SQL-запрос и нажмете кнопку «Запустить» в левом нижнем углу", + "label_failed-connection-text": "Не удалось загрузить подключение", + "label_new-chart-type-text": "Выберите тип чарта, чтобы начать:", + "label_new-connection-text": "Выберите подключение, чтобы начать", + "label_params": "Параметры", + "label_placeholder-default-value": "Значение по умолчанию", + "label_placeholder-name": "Имя", + "label_ql-auto-execution-chart": "Автоматически применять изменения", + "label_ql-info": "Информация о чарте", + "label_query": "Запрос", + "label_run": "Запустить", + "label_select-connection": "Выбрать", + "label_select-visualization": "Выбрать визуализацию", + "label_visualization-area": "Диаграмма с областями", + "label_visualization-area-100p": "Нормированная диаграмма с областями", + "label_visualization-bar": "Линейчатая диаграмма", + "label_visualization-bar-100p": "Нормированная линейчатая диаграмма", + "label_visualization-column": "Столбчатая диаграмма", + "label_visualization-column-100p": "Нормированная cтолбчатая диаграмма", + "label_visualization-donut": "Бубликовая диаграмма", + "label_visualization-flat-table": "Таблица", + "label_visualization-line": "Линейная диаграмма", + "label_visualization-metric": "Индикатор", + "label_visualization-pie": "Круговая диаграмма", + "label_visualization-scatter": "Точечная диаграмма", + "label_visualization-treemap": "Древовидная диаграмма", + "text_default-name": "Новый QL-чарт" +} diff --git a/src/i18n-keysets/validation/context.json b/src/i18n-keysets/validation/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/validation/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/validation/en.json b/src/i18n-keysets/validation/en.json new file mode 100644 index 0000000..cab23ca --- /dev/null +++ b/src/i18n-keysets/validation/en.json @@ -0,0 +1,4 @@ +{ + "label_unknown-error": "Unknown error", + "label_validation-slash_error": "Name cannot contain a slash “/”" +} diff --git a/src/i18n-keysets/validation/keyset.json b/src/i18n-keysets/validation/keyset.json new file mode 100644 index 0000000..62c227d --- /dev/null +++ b/src/i18n-keysets/validation/keyset.json @@ -0,0 +1,20 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_unknown-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_validation-slash_error": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/validation/ru.json b/src/i18n-keysets/validation/ru.json new file mode 100644 index 0000000..4262a65 --- /dev/null +++ b/src/i18n-keysets/validation/ru.json @@ -0,0 +1,4 @@ +{ + "label_unknown-error": "Неизвестная ошибка", + "label_validation-slash_error": "Название не должно содержать символ «/»" +} diff --git a/src/i18n-keysets/wizard.hierarchy-editor/context.json b/src/i18n-keysets/wizard.hierarchy-editor/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/wizard.hierarchy-editor/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/wizard.hierarchy-editor/en.json b/src/i18n-keysets/wizard.hierarchy-editor/en.json new file mode 100644 index 0000000..05efb4b --- /dev/null +++ b/src/i18n-keysets/wizard.hierarchy-editor/en.json @@ -0,0 +1,10 @@ +{ + "button_cancel": "Cancel", + "button_save": "Save", + "caption": "Hierarchy settings", + "field_already_exists": "A field with the specified name already exists.", + "help_tooltip": "Editing the hierarchy. The order of the fields determines fields drill down.", + "name-placeholder": "Name", + "new_hierarchy": "New hierarchy", + "selector_title": "Available" +} diff --git a/src/i18n-keysets/wizard.hierarchy-editor/keyset.json b/src/i18n-keysets/wizard.hierarchy-editor/keyset.json new file mode 100644 index 0000000..78fe34c --- /dev/null +++ b/src/i18n-keysets/wizard.hierarchy-editor/keyset.json @@ -0,0 +1,44 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "caption": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_already_exists": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "help_tooltip": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "name-placeholder": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "new_hierarchy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "selector_title": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/wizard.hierarchy-editor/ru.json b/src/i18n-keysets/wizard.hierarchy-editor/ru.json new file mode 100644 index 0000000..610ccb3 --- /dev/null +++ b/src/i18n-keysets/wizard.hierarchy-editor/ru.json @@ -0,0 +1,10 @@ +{ + "button_cancel": "Отменить", + "button_save": "Сохранить", + "caption": "Настройка иерархии", + "field_already_exists": "Поле с таким именем уже существует", + "help_tooltip": "Редактирование иерархии. Порядок полей определяет порядок их развертывания", + "name-placeholder": "Название", + "new_hierarchy": "Новая иерархия", + "selector_title": "Доступно" +} diff --git a/src/i18n-keysets/wizard.palette/context.json b/src/i18n-keysets/wizard.palette/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/wizard.palette/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/wizard.palette/en.json b/src/i18n-keysets/wizard.palette/en.json new file mode 100644 index 0000000..eff52a8 --- /dev/null +++ b/src/i18n-keysets/wizard.palette/en.json @@ -0,0 +1,10 @@ +{ + "label_classic20": "Classic 20", + "label_datalens-neo-20": "Neo 20", + "label_default20": "Default 20", + "label_emerald20": "Emerald 20", + "label_golden20": "Golden 20", + "label_neutral20": "Neutral 20", + "label_oceanic20": "Oceanic 20", + "label_traffic-light9": "Traffic Light 9" +} diff --git a/src/i18n-keysets/wizard.palette/keyset.json b/src/i18n-keysets/wizard.palette/keyset.json new file mode 100644 index 0000000..75f7876 --- /dev/null +++ b/src/i18n-keysets/wizard.palette/keyset.json @@ -0,0 +1,44 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_classic20": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_datalens-neo-20": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default20": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_emerald20": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_golden20": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_neutral20": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_oceanic20": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_traffic-light9": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/wizard.palette/ru.json b/src/i18n-keysets/wizard.palette/ru.json new file mode 100644 index 0000000..eff52a8 --- /dev/null +++ b/src/i18n-keysets/wizard.palette/ru.json @@ -0,0 +1,10 @@ +{ + "label_classic20": "Classic 20", + "label_datalens-neo-20": "Neo 20", + "label_default20": "Default 20", + "label_emerald20": "Emerald 20", + "label_golden20": "Golden 20", + "label_neutral20": "Neutral 20", + "label_oceanic20": "Oceanic 20", + "label_traffic-light9": "Traffic Light 9" +} diff --git a/src/i18n-keysets/wizard.prepares/context.json b/src/i18n-keysets/wizard.prepares/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/wizard.prepares/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/wizard.prepares/en.json b/src/i18n-keysets/wizard.prepares/en.json new file mode 100644 index 0000000..a7ae4fb --- /dev/null +++ b/src/i18n-keysets/wizard.prepares/en.json @@ -0,0 +1,11 @@ +{ + "label_color": "Color: {{value}}", + "label_line": "Line: {{value}}", + "label_new-layer": "Layer 1", + "label_null-sorting-disabled-info": "Sorting for null values is not supported", + "label_order": "Order: {{value}}", + "label_point": "Point: {{value}}", + "label_segment": "Segment: {{value}}", + "label_total": "Total", + "label_total-value": "Total: {{value}}" +} diff --git a/src/i18n-keysets/wizard.prepares/keyset.json b/src/i18n-keysets/wizard.prepares/keyset.json new file mode 100644 index 0000000..33034c0 --- /dev/null +++ b/src/i18n-keysets/wizard.prepares/keyset.json @@ -0,0 +1,48 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "label_color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_line": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-layer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_null-sorting-disabled-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_order": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_point": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_segment": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_total": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_total-value": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/wizard.prepares/ru.json b/src/i18n-keysets/wizard.prepares/ru.json new file mode 100644 index 0000000..5b65c5a --- /dev/null +++ b/src/i18n-keysets/wizard.prepares/ru.json @@ -0,0 +1,11 @@ +{ + "label_color": "Цвет: {{value}}", + "label_line": "Линия: {{value}}", + "label_new-layer": "Слой 1", + "label_null-sorting-disabled-info": "Сортировка по значениям NULL не поддерживается", + "label_order": "Порядок: {{value}}", + "label_point": "Точка: {{value}}", + "label_segment": "Отрезок: {{value}}", + "label_total": "Итого", + "label_total-value": "Итого: {{value}}" +} diff --git a/src/i18n-keysets/wizard/context.json b/src/i18n-keysets/wizard/context.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n-keysets/wizard/context.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n-keysets/wizard/en.json b/src/i18n-keysets/wizard/en.json new file mode 100644 index 0000000..878236f --- /dev/null +++ b/src/i18n-keysets/wizard/en.json @@ -0,0 +1,402 @@ +{ + "add_field_item": "Field", + "add_hierarchy_item": "Hierarchy", + "add_parameter_item": "Parameter", + "button_access-rights": "Request permissions", + "button_add-dataset": "Add dataset", + "button_apply": "Apply", + "button_apply-filter": "Apply filter", + "button_cancel": "Cancel", + "button_choose-dataset": "Select dataset", + "button_choose-visualization": "Select chart type", + "button_clear": "Clear", + "button_copy-link": "Copy link", + "button_datasets-settings": "Dataset settings", + "button_discard": "Discard", + "button_duplicate": "Duplicate", + "button_edit": "Edit", + "button_edit-built-in": "Edit", + "button_remove": "Delete", + "button_remove-dataset": "Delete dataset", + "button_replace-dataset": "Replace dataset", + "button_reset": "Reset", + "button_retry": "Repeat", + "button_save": "Save", + "button_save-and-publish": "Save and make actual", + "button_save-as": "Save as", + "button_save-as-draft": "Save as draft", + "button_save-as-editor-script": "Save as Editor script", + "button_save-as-new": "Save as new chart", + "button_saved": "Saved", + "button_select": "Select", + "button_select-all": "Select all", + "button_to-dataset": "Go to dataset", + "button_toggle-fullscreen": "Full screen", + "button_turn-into-formula": "Convert to formula", + "confirm_chart-save_header": "Save chart before export", + "confirm_chart-save_message": "To export data, save the changes to the chart.", + "confirm_chart-save_message_alerts": "To set up alerts, first save the changes to the chart.", + "dataset-selector_label": "Dataset:", + "field_search": "Search by name or description", + "label_2-point": "2 point", + "label_3-point": "3 point", + "label_absolute": "Absolute", + "label_absolute-date": "Choose from calendar", + "label_add-link": "Add link", + "label_add-value": "Add value", + "label_aggregation": "Aggregation", + "label_all-lines": "All lines", + "label_angle": "Angle", + "label_array_float": "Array of fractional numbers", + "label_array_int": "Array of integers", + "label_array_str": "Array of strings", + "label_as-0": "Display as 0", + "label_auto": "Auto", + "label_autoscale": "Auto scale", + "label_avg": "Average", + "label_axis-format-by-field": "By the first field on the {{axisName}} axis", + "label_axis-format-settings": "Axis format", + "label_axis-hide-labels": "Labels", + "label_axis-mode": "Display mode", + "label_axis-mode-unavailable-forbidden": "Continuous mode is unavailable if the query contains ORDER BY", + "label_axis-mode-unavailable-sort-measures": "Continuous mode is unavailable if the sort section contains measures", + "label_axis-mode-unavailable-type": "Continuous mode is only available for \"Floating-point number\", \"Integer\", \"Date\", and \"Date and time\" types", + "label_axis-title": "Axis title", + "label_axis-title-manual-warning": "You are using a split, so the axis title can't be edited", + "label_axis-type": "Axis type", + "label_axis-visibility": "Axis on the chart", + "label_background-color-field-selector": "By field", + "label_background-settings": "Column fill color", + "label_bars-align": "Align", + "label_bars-align-info": "Align works only when all values in the column are positive or negative", + "label_bars-align-left": "Left", + "label_bars-align-right": "Right", + "label_bars-color": "Color", + "label_bars-custom-color-error": "Use HEX format", + "label_bars-in-totals": "Show in totals", + "label_bars-labels": "Show labels", + "label_bars-negative-color": "Negative values", + "label_bars-positive-color": "Positive values", + "label_bars-scale": "Scale", + "label_bars-switcher": "Linear indicator", + "label_blue": "Blue (shades)", + "label_blue-gray-red": "Blue-Gray-Red", + "label_blue-yellow-red": "Blue-Yellow-Red", + "label_boolean": "Logical", + "label_borders": "Borders", + "label_center": "Center", + "label_chart-settings": "Chart settings", + "label_chartkit-menu-open-in-new-tab": "Open in new tab", + "label_choose-from-list": "Select a value from the list", + "label_choose-lines": "Choose lines", + "label_choose-visualization-type": "Chart type", + "label_color": "Color", + "label_color-mode": "Fill type", + "label_color-settings": "Color settings", + "label_colors-overflow": "The maximum number of colors is set", + "label_colors-settings": "Color settings", + "label_column-freeze": "Pinning", + "label_column-freeze-tooltip": "Specify the number of pinned columns when scrolling horizontally", + "label_column-width": "Width", + "label_combined-chart-axis-format-by-field": "By the first field within layers", + "label_common-line-settings": "Common settings", + "label_connect": "Connect", + "label_continuous": "Continuous", + "label_count": "Count", + "label_countunique": "Count distinct", + "label_cyan": "Cyan (shades)", + "label_d3-fallback": "Highcharts", + "label_dataset-blank": "To get started, select a dataset", + "label_datasets": "Datasets", + "label_date": "Date", + "label_date-format": "Format", + "label_date-group": "Grouping", + "label_date-group-preaggregated": "Grouping (before aggregation)", + "label_date-mode": "Date mode", + "label_datetimetz": "Date and time with timezone", + "label_day": "Day", + "label_dayofweek": "Day of week", + "label_default": "Default", + "label_default-period": "Default period", + "label_delete-all-filters-action": "Delete all filters in section", + "label_dialog-column-info-text": "The total width of the table always occupies 100% of the available space, regardless of the specified widths of the individual columns.", + "label_dialog-radius": "Point size", + "label_dialog-radius-max": "Maximal point size", + "label_dialog-radius-min": "Minimal point size", + "label_direction": "Direction", + "label_disabled": "Disabled", + "label_discrete": "Discrete", + "label_enabled": "Enabled", + "label_enter-manualy": "Enter value manually", + "label_enter-number": "Number required", + "label_error-connection-no-access-rights": "You don't have access to the connection", + "label_error-dataset-no-access-rights": "You don't have access to the dataset or to it's connection", + "label_error-dataset-not-found": "Dataset not found", + "label_error-dataset-server-error": "Error: failed to fetch dataset", + "label_error-dataset-unknown-error": "Error: failed to fetch dataset", + "label_error-loading-filter-values": "Error: failed to load filter values", + "label_error-remove-field": "Error: you can't delete a field used in the formula", + "label_error-widget-no-access": "No access to chart", + "label_error-widget-not-found": "Chart not found", + "label_error-widget-save": "Error: failed to save chart", + "label_error-widget-unknown-error": "An error occurred", + "label_feed": "Comments feed", + "label_field-has-wrong-type": "Invalid field type", + "label_field-invalid": "Incorrect field", + "label_field-more-than-one-hierarchy": "You can't apply more than one hierarchy", + "label_field-name": "Field name", + "label_field-not-exist": "Field not found in dataset", + "label_field-not-exist-in-query": "Field not found in query", + "label_field-title": "Field name", + "label_fields-overflow": "The maximum number of fields is set", + "label_fill-color-type": "Fill type", + "label_filter-all-values": "All", + "label_filter-from-dashboard": "filter from dashboard", + "label_float": "Fractional number", + "label_font-size": "Size", + "label_font-size-disabled-hint": "The size of a field with the Markup type cannot be set", + "label_genericdatetime": "Date and time", + "label_geopoint": "Geopoint", + "label_geopolygon": "Geopolygon", + "label_golden": "Golden", + "label_gradient": "Gradient", + "label_gradient-2": "Gradient 2", + "label_gradient-3": "Gradient 3", + "label_gradient-type": "Gradient type", + "label_gray": "Gray (shades)", + "label_green": "Green (shades)", + "label_green-blue": "Green-Blue", + "label_grid": "Grid", + "label_grid-step": "Grid step", + "label_groupping": "Grouping", + "label_header": "Header", + "label_hide": "Hide", + "label_hint": "Hint", + "label_holidays": "Holidays on the chart", + "label_horizontal": "Horizontal", + "label_hour": "Hour", + "label_html": "HTML", + "label_ignore": "Ignore", + "label_integer": "Integer", + "label_invalid-name": "The chart with the specified name already exists", + "label_items-available": "Available", + "label_items-selected": "Selected", + "label_label-hide-mode": "Label", + "label_label-mode": "Label value", + "label_labels-overflow": "The maximum number of labels is set", + "label_labels-position": "Label inside", + "label_labels-view": "Labels view", + "label_layer-remove-confirmation": "Remove layer?", + "label_layer-settings": "Layer settings", + "label_layers-overflow": "The maximum number of layers is set", + "label_legend": "Legend", + "label_limit": "Limit", + "label_limit-input-error": "Enter a value from {{min}} to {{max}}", + "label_limit-message": "Only first 1000 values shown", + "label_line-width": "Line width", + "label_linear": "Linear", + "label_linecap": "Line cap", + "label_linejoin": "Line join", + "label_link-failed": "This dimension is not part of dataset links", + "label_links": "Links", + "label_links-between-sources": "Links between datasets", + "label_logarithmic": "Logarithmic", + "label_manual": "Manual", + "label_markdown": "Markdown", + "label_markup": "Markup", + "label_markup-type": "Markup type", + "label_markup-type-tooltip": "The field value will be formatted according to the selected markup language", + "label_max": "Maximum", + "label_message-link-copied": "Link copied", + "label_metric-colors-add-field-disabled": "Uses the field from the Measure section", + "label_metric-colors-edit-field-disabled": "The color of a field with the Markup type cannot be set", + "label_min": "Minimum", + "label_minute": "Minute", + "label_month": "Month", + "label_mounted-line-settings": "Lines", + "label_navigator": "Navigator", + "label_new-layer": "Layer", + "label_new-widget": "New chart", + "label_no": "No", + "label_no-colors-setup-for-hierarchy": "Colors setup for the hierarchy is not available", + "label_no-colors-setup-for-string-measures": "Colors cannot be chosen for measures with string-type data", + "label_no-links": "There are no links between datasets", + "label_no-rights-text": "This chart is read-only. Request write permissions or save changes as a new chart", + "label_no-rights-title": "No write permissions", + "label_no-shapes-setup-for-hierarchy": "Shapes setup for the hierarchy is not available", + "label_none": "No", + "label_nulls": "Empty values (null)", + "label_oceanic": "Oceanic", + "label_off": "Off", + "label_on": "On", + "label_one-color": "One color", + "label_orange-blue-green": "Orange-Blue-Green", + "label_orange-gray-blue": "Orange-Gray-Blue", + "label_orange-violet-blue": "Orange-Violet-Blue", + "label_orange-yellow": "Orange-Yellow", + "label_order-overflow": "The maximum number of orders is set", + "label_original-title": "Dataset field name", + "label_overlap": "Labels overlap", + "label_overriden-from-dashboard": "overriden from dashboard", + "label_pagination": "Pagination", + "label_painting-as-0": "Paint as 0", + "label_painting-ignore": "Do not paint", + "label_palette": "Palette", + "label_part": "Date part", + "label_percent": "Percentage", + "label_pink-gray-green": "Lilac-Gray-Green", + "label_pivot-fallback": "Old pivot tables", + "label_pivot-inline-sort": "Sort by lines", + "label_pivot-measure-sort": "Sorting by indicator affects only the query to the source, but not the pivot table.", + "label_pivot-table-title-dialog-column-settings": "Column and row settings", + "label_pixels-shortname": "px", + "label_polyline-points": "Polyline points", + "label_preserve-whitespace": "Preserve spaces and line breaks", + "label_preview-dataset-error": "Unable to display chart", + "label_preview-dataset-stub": "Drag the dimensions and measures to the appropriate sections to build the visualization", + "label_quarter": "Quarter", + "label_red": "Red (shades)", + "label_red-blue": "Red-Blue", + "label_red-orange-green": "Red-Yellow-Green", + "label_relative-date": "Set relative date", + "label_relative-from": "Start, days ago", + "label_relative-to": "End, days ago", + "label_rename": "Rename", + "label_reversed": "Reversed", + "label_save-widget": "Save chart", + "label_scale": "Scaling", + "label_scale-0-max": "from 0 to max", + "label_scale-min-max": "from min to max", + "label_second": "Second", + "label_segments-overflow": "The maximum number of splits is set", + "label_settings": "Settings", + "label_shapes-overflow": "The maximum number of shapes is set", + "label_shapes-settings": "Shapes settings", + "label_short-max": "max", + "label_short-min": "min", + "label_should-be-less-than-middle": "This value should be less than average", + "label_should-be-less-than-right": "This value should be less than max", + "label_show": "Show", + "label_sort-overflow": "The maximum number of sorts is set", + "label_stacking": "Stacking", + "label_string": "String", + "label_sub-totals-switcher": "Sub-totals", + "label_sum": "Sum", + "label_thresholds": "Set threshold values", + "label_time-interval": "Time interval", + "label_title": "Name", + "label_title-dialog-column-settings": "Column settings", + "label_tooltip": "Tooltip", + "label_tooltip-sum": "Sum in tooltips", + "label_tooltip-thresholds": "You can manually set threshold values, which will each correspond to a color.", + "label_totals": "Totals", + "label_traffic-light": "Traffic Light", + "label_tree_float": "Tree of fractional numbers", + "label_tree_int": "Tree of integers", + "label_tree_str": "Tree of strings", + "label_trunc": "Rounding", + "label_two-color": "Two colors", + "label_type": "Type", + "label_type-preaggregated": "Type (before aggregation)", + "label_use-previous": "Use previous", + "label_value": "Value", + "label_vertical": "Vertical", + "label_violet": "Violet (shades)", + "label_violet-blue-green": "Violet-Blue-Green", + "label_violet-orange": "Lilac-Red", + "label_visualization-area": "Area chart", + "label_visualization-area-100p": "Normalized stacked area chart", + "label_visualization-bar": "Bar chart", + "label_visualization-bar-100p": "Normalized bar chart", + "label_visualization-column": "Column chart", + "label_visualization-column-100p": "Normalized column chart", + "label_visualization-combined-chart": "Combined chart", + "label_visualization-donut": "Donut chart", + "label_visualization-flat-table": "Table", + "label_visualization-geolayer": "Map", + "label_visualization-geopoint": "Points", + "label_visualization-geopoint-with-cluster": "Points with cluster", + "label_visualization-geopolygon": "Polygons", + "label_visualization-heatmap": "Heatmap", + "label_visualization-line": "Line chart", + "label_visualization-metric": "Indicator", + "label_visualization-pie": "Pie chart", + "label_visualization-pivot-table": "Pivot table", + "label_visualization-polyline": "Polylines", + "label_visualization-scatter": "Scatter chart", + "label_visualization-treemap": "Tree chart", + "label_visualization-types-all": "All", + "label_visualization-types-column": "Bar", + "label_visualization-types-line": "Charts", + "label_visualization-types-pie": "Pie", + "label_visualization-types-table": "Tables", + "label_week": "Week", + "label_widget-name-copy": "{{name}} - copy", + "label_widget-size": "Size", + "label_without-group": "Without grouping", + "label_wizard-info": "Chart info", + "label_year": "Year", + "label_yellow": "Yellow (shades)", + "label_yellow-green-blue": "Yellow-Green-Blue", + "label_yes": "Yes", + "label_zoom": "Zoom", + "section_available": "Available", + "section_categories": "Categories", + "section_color": "Color", + "section_colors": "Colors", + "section_columns": "Columns", + "section_common-filters": "Common filters", + "section_dimensions": "Dimensions", + "section_filters": "Chart filters", + "section_filters_from_dashboard": "Dashboard filters", + "section_geodata": "Geodata", + "section_geopoint": "Geopoints", + "section_geopolygon": "Geopolygons", + "section_grouping": "Grouping", + "section_hierarchy": "Hierarchy", + "section_labels": "Labels", + "section_layer-filters": "Layer filters", + "section_measure": "Measure", + "section_measures": "Measures", + "section_obligatory-filters": "Obligatory filters", + "section_order": "Order", + "section_parameters": "Parameters", + "section_parameters_from_dashboard": "Dashboard parameters", + "section_points": "Points", + "section_points_size": "Points size", + "section_polyline": "Polyline", + "section_rows": "Rows", + "section_scatter": "Points", + "section_segments": "Split", + "section_shapes": "Shapes", + "section_size": "Size", + "section_sort": "Sorting", + "section_tooltips": "Tooltips", + "section_x": "X", + "section_y": "Y", + "section_y2": "Y2", + "toast_confirm-remove-item": "Deletion cannot be undone. Are you sure?", + "toast_error-action-details-label": "Learn more", + "toast_get-publication-status-failed_error-action-details-label": "Learn more", + "toast_unsaved": "There are unsaved changes on the page. Are you sure?", + "toast_wizard-dataset-validation-failure": "Error: failed to validate dataset", + "tooltip_backend-pivot_unavailable": "New pivot tables are not available for multidataset charts", + "tooltip_dataset-fetch-failed": "Couldn't load dataset", + "tooltip_dataset-fetch-failed-details-label": "Learn more", + "tooltip_filter_item": "To apply a filter, remove the same field from the Filters from dashboard section.", + "tooltip_filters_from_dashboard": "Filters from the dashboard applied to the chart. These filters will not be saved in the chart.", + "tooltip_logarithmic-axis": "A scale with negative values or 0 cannot be logarithmic", + "tooltip_no-available-fields": "No suitable fields to add", + "tooltip_pagination_unavailable": "Pagination is not available for multidataset charts", + "tooltip_parameter-changed-icon": "Parameter value changed", + "tooltip_sort_unavailable": "Sorting is not available for multi-dataset charts", + "tooltip_table-preview": "Data preview", + "tooltip_tree-pagination_unavailable": "Pagination is not available in a tree table", + "tooltip_tree-total_unavailable": "Total is not available in a tree table", + "tooltip_zero-to-max-scale": "A logarithmic scale cannot contain values of 0 or negative values", + "value_geotype-select-geopoint": "Points (Geopoints)", + "value_geotype-select-geopoint-with-cluster": "Points with clusters (Geopoints)", + "value_geotype-select-geopolygon": "Polygons (Geopolygons)", + "value_geotype-select-heatmap": "Heatmap (Geopoints)", + "value_geotype-select-polyline": "Polylines (Geopoints)" +} diff --git a/src/i18n-keysets/wizard/keyset.json b/src/i18n-keysets/wizard/keyset.json new file mode 100644 index 0000000..9582436 --- /dev/null +++ b/src/i18n-keysets/wizard/keyset.json @@ -0,0 +1,1612 @@ +{ + "context": "", + "allowedStatuses": [ + "APPROVED", + "EXPIRED", + "GENERATED", + "REQUIRES_TRANSLATION", + "TRANSLATED" + ], + "status": { + "add_field_item": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "add_hierarchy_item": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "add_parameter_item": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_access-rights": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_add-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_apply-filter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_cancel": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_choose-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_choose-visualization": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_clear": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_copy-link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_datasets-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_discard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_duplicate": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_edit-built-in": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_remove": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_remove-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_replace-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_reset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_retry": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-and-publish": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-draft": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-editor-script": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_save-as-new": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_saved": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_select": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_select-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_to-dataset": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_toggle-fullscreen": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "button_turn-into-formula": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "confirm_chart-save_header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "confirm_chart-save_message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "confirm_chart-save_message_alerts": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "dataset-selector_label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "field_search": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_2-point": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_3-point": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_absolute": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_absolute-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-link": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_add-value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_aggregation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_all-lines": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_angle": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_array_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_array_int": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_array_str": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_as-0": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_auto": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_autoscale": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_avg": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-format-by-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-format-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-hide-labels": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-mode": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-mode-unavailable-forbidden": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-mode-unavailable-sort-measures": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-mode-unavailable-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-title-manual-warning": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_axis-visibility": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_background-color-field-selector": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_background-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-align": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-align-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-align-left": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-align-right": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-custom-color-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-in-totals": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-labels": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-negative-color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-positive-color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-scale": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_bars-switcher": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_blue": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_blue-gray-red": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_blue-yellow-red": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_boolean": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_borders": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_center": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_chart-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_chartkit-menu-open-in-new-tab": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_choose-from-list": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_choose-lines": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_choose-visualization-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_color-mode": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_color-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_colors-overflow": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_colors-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_column-freeze": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_column-freeze-tooltip": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_column-width": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_combined-chart-axis-format-by-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_common-line-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_connect": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_continuous": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_count": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_countunique": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_cyan": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_d3-fallback": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dataset-blank": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_datasets": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_date-format": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_date-group": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_date-group-preaggregated": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_date-mode": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_datetimetz": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_day": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dayofweek": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_default-period": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_delete-all-filters-action": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dialog-column-info-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dialog-radius": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dialog-radius-max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_dialog-radius-min": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_direction": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_disabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_discrete": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_enabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_enter-manualy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_enter-number": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-connection-no-access-rights": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-dataset-no-access-rights": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-dataset-not-found": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-dataset-server-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-dataset-unknown-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-loading-filter-values": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-remove-field": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-widget-no-access": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-widget-not-found": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-widget-save": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_error-widget-unknown-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_feed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-has-wrong-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-invalid": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-more-than-one-hierarchy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-not-exist": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-not-exist-in-query": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_field-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_fields-overflow": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_fill-color-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-all-values": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_filter-from-dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_font-size": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_font-size-disabled-hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_genericdatetime": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_geopoint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_geopolygon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_golden": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gradient": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gradient-2": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gradient-3": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gradient-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_gray": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_green": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_green-blue": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_grid": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_grid-step": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_groupping": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_header": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_hide": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_hint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_holidays": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_horizontal": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_hour": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_html": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_ignore": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_integer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_invalid-name": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_items-available": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_items-selected": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_label-hide-mode": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_label-mode": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_labels-overflow": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_labels-position": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_labels-view": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_layer-remove-confirmation": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_layer-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_layers-overflow": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_legend": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_limit": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_limit-input-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_limit-message": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_line-width": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_linear": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_linecap": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_linejoin": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_link-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_links": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_links-between-sources": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_logarithmic": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_manual": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_markdown": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_markup": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_markup-type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_markup-type-tooltip": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_message-link-copied": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_metric-colors-add-field-disabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_metric-colors-edit-field-disabled": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_min": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_minute": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_month": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_mounted-line-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_navigator": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-layer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_new-widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-colors-setup-for-hierarchy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-colors-setup-for-string-measures": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-links": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-rights-text": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-rights-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_no-shapes-setup-for-hierarchy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_none": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_nulls": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_oceanic": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_off": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_on": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_one-color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_orange-blue-green": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_orange-gray-blue": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_orange-violet-blue": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_orange-yellow": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_order-overflow": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_original-title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_overlap": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_overriden-from-dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_pagination": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_painting-as-0": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_painting-ignore": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_palette": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_part": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_percent": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_pink-gray-green": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_pivot-fallback": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_pivot-inline-sort": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_pivot-measure-sort": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_pivot-table-title-dialog-column-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_pixels-shortname": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_polyline-points": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preserve-whitespace": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preview-dataset-error": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_preview-dataset-stub": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_quarter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_red": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_red-blue": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_red-orange-green": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_relative-date": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_relative-from": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_relative-to": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_rename": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_reversed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_save-widget": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-0-max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_scale-min-max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_second": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_segments-overflow": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_shapes-overflow": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_shapes-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_short-max": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_short-min": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_should-be-less-than-middle": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_should-be-less-than-right": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_show": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sort-overflow": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_stacking": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_string": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sub-totals-switcher": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_sum": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_thresholds": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_time-interval": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_title-dialog-column-settings": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tooltip": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tooltip-sum": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tooltip-thresholds": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_totals": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_traffic-light": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tree_float": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tree_int": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_tree_str": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_trunc": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_two-color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_type": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_type-preaggregated": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_use-previous": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_value": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_vertical": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_violet": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_violet-blue-green": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_violet-orange": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-area": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-area-100p": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-bar": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-bar-100p": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-column": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-column-100p": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-combined-chart": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-donut": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-flat-table": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-geolayer": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-geopoint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-geopoint-with-cluster": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-geopolygon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-heatmap": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-line": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-metric": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-pie": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-pivot-table": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-polyline": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-scatter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-treemap": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-types-all": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-types-column": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-types-line": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-types-pie": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_visualization-types-table": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_week": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_widget-name-copy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_widget-size": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_without-group": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_wizard-info": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_year": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yellow": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yellow-green-blue": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_yes": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "label_zoom": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_available": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_categories": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_color": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_colors": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_columns": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_common-filters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_dimensions": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_filters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_filters_from_dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_geodata": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_geopoint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_geopolygon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_grouping": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_hierarchy": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_labels": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_layer-filters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_measure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_measures": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_obligatory-filters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_order": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_parameters": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_parameters_from_dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_points": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_points_size": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_polyline": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_rows": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_scatter": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_segments": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_shapes": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_size": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_sort": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_tooltips": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_x": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_y": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "section_y2": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_confirm-remove-item": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_error-action-details-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_get-publication-status-failed_error-action-details-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_unsaved": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "toast_wizard-dataset-validation-failure": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_backend-pivot_unavailable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_dataset-fetch-failed": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_dataset-fetch-failed-details-label": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_filter_item": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_filters_from_dashboard": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_logarithmic-axis": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_no-available-fields": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_pagination_unavailable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_parameter-changed-icon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_sort_unavailable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_table-preview": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_tree-pagination_unavailable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_tree-total_unavailable": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "tooltip_zero-to-max-scale": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geotype-select-geopoint": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geotype-select-geopoint-with-cluster": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geotype-select-geopolygon": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geotype-select-heatmap": { + "en": "APPROVED", + "ru": "APPROVED" + }, + "value_geotype-select-polyline": { + "en": "APPROVED", + "ru": "APPROVED" + } + } +} diff --git a/src/i18n-keysets/wizard/ru.json b/src/i18n-keysets/wizard/ru.json new file mode 100644 index 0000000..2035b0d --- /dev/null +++ b/src/i18n-keysets/wizard/ru.json @@ -0,0 +1,402 @@ +{ + "add_field_item": "Поле", + "add_hierarchy_item": "Иерархия", + "add_parameter_item": "Параметр", + "button_access-rights": "Запросить права", + "button_add-dataset": "Добавить датасет", + "button_apply": "Применить", + "button_apply-filter": "Применить фильтр", + "button_cancel": "Отменить", + "button_choose-dataset": "Выберите датасет", + "button_choose-visualization": "Выберите тип чарта", + "button_clear": "Очистить", + "button_copy-link": "Копировать ссылку", + "button_datasets-settings": "Настройки датасетов", + "button_discard": "Сбросить", + "button_duplicate": "Дублировать", + "button_edit": "Редактировать", + "button_edit-built-in": "Редактировать", + "button_remove": "Удалить", + "button_remove-dataset": "Удалить датасет", + "button_replace-dataset": "Заменить датасет", + "button_reset": "Сбросить", + "button_retry": "Повторить", + "button_save": "Сохранить", + "button_save-and-publish": "Сохранить и сделать актуальной", + "button_save-as": "Сохранить как", + "button_save-as-draft": "Сохранить как черновик", + "button_save-as-editor-script": "Сохранить как скрипт в Editor", + "button_save-as-new": "Сохранить как новый чарт", + "button_saved": "Сохранено", + "button_select": "Выбрать", + "button_select-all": "Выбрать все", + "button_to-dataset": "Перейти к датасету", + "button_toggle-fullscreen": "На весь экран", + "button_turn-into-formula": "Преобразовать в формулу", + "confirm_chart-save_header": "Сохраните чарт перед экспортом", + "confirm_chart-save_message": "Чтобы экспортировать данные, сохраните изменения в чарте.", + "confirm_chart-save_message_alerts": "Чтобы настроить алерты, сначала сохраните изменения в чарте.", + "dataset-selector_label": "Датасет:", + "field_search": "Поиск по имени и описанию", + "label_2-point": "Двухцветный", + "label_3-point": "Трехцветный", + "label_absolute": "Абсолютное", + "label_absolute-date": "Выбрать в календаре", + "label_add-link": "Добавить связь", + "label_add-value": "Добавить значение", + "label_aggregation": "Агрегация", + "label_all-lines": "Все линии", + "label_angle": "Под углом", + "label_array_float": "Массив дробных чисел", + "label_array_int": "Массив целых чисел", + "label_array_str": "Массив строк", + "label_as-0": "Отображать как 0", + "label_auto": "Авто", + "label_autoscale": "Автомасштаб", + "label_avg": "Среднее", + "label_axis-format-by-field": "По первому полю на оси {{axisName}}", + "label_axis-format-settings": "Форматирование оси", + "label_axis-hide-labels": "Подписи", + "label_axis-mode": "Режим отображения", + "label_axis-mode-unavailable-forbidden": "Непрерывный режим недоступен, когда в запросе используется ORDER BY", + "label_axis-mode-unavailable-sort-measures": "Непрерывный режим недоступен, когда в секции сортировки есть показатели", + "label_axis-mode-unavailable-type": "Непрерывный режим доступен только для типов данных «Целое число», «Дробное число», «Дата» и «Дата и время»", + "label_axis-title": "Название оси", + "label_axis-title-manual-warning": "Вы используете сплит, поэтому название оси недоступно для редактирования", + "label_axis-type": "Тип оси", + "label_axis-visibility": "Ось на графике", + "label_background-color-field-selector": "По полю", + "label_background-settings": "Заливка столбца", + "label_bars-align": "Выравнивание", + "label_bars-align-info": "Выравнивание слева или справа применяется только в случаях, когда все числа в столбце положительные или отрицательные", + "label_bars-align-left": "Слева", + "label_bars-align-right": "Справа", + "label_bars-color": "Цвет", + "label_bars-custom-color-error": "Используйте формат HEX", + "label_bars-in-totals": "Отображать в итогах", + "label_bars-labels": "Отображать значения", + "label_bars-negative-color": "Отрицательные значения", + "label_bars-positive-color": "Положительные значения", + "label_bars-scale": "Масштаб", + "label_bars-switcher": "Линейный индикатор", + "label_blue": "Синий (оттенки)", + "label_blue-gray-red": "Синий-Серый-Красный", + "label_blue-yellow-red": "Синий-Желтый-Красный", + "label_boolean": "Логический", + "label_borders": "Границы", + "label_center": "Центр", + "label_chart-settings": "Настройки чарта", + "label_chartkit-menu-open-in-new-tab": "Открыть в новой вкладке", + "label_choose-from-list": "Выбрать значение из списка", + "label_choose-lines": "Выбрать линии", + "label_choose-visualization-type": "Тип чарта", + "label_color": "Цвет", + "label_color-mode": "Тип заливки", + "label_color-settings": "Настройки цветов", + "label_colors-overflow": "Установлено максимально доступное количество цветов", + "label_colors-settings": "Настройки цветов", + "label_column-freeze": "Закрепление", + "label_column-freeze-tooltip": "Укажите число столбцов для закрепления при горизонтальном скролле", + "label_column-width": "Ширина", + "label_combined-chart-axis-format-by-field": "По первому полю в верхнем слое", + "label_common-line-settings": "Общие настройки", + "label_connect": "Соединять", + "label_continuous": "Непрерывный", + "label_count": "Количество", + "label_countunique": "Количество уникальных", + "label_cyan": "Бирюзовый (оттенки)", + "label_d3-fallback": "Highcharts", + "label_dataset-blank": "Для начала работы выберите датасет", + "label_datasets": "Датасеты", + "label_date": "Дата", + "label_date-format": "Формат", + "label_date-group": "Группировка", + "label_date-group-preaggregated": "Группировка (до агрегации)", + "label_date-mode": "Режим даты", + "label_datetimetz": "Дата и время с часовым поясом", + "label_day": "День", + "label_dayofweek": "День недели", + "label_default": "По умолчанию", + "label_default-period": "Период по умолчанию", + "label_delete-all-filters-action": "Удалить все фильтры в секции", + "label_dialog-column-info-text": "Общая ширина таблицы всегда занимает 100% доступного пространства вне зависимости от указанной ширины отдельных столбцов.", + "label_dialog-radius": "Размер точек", + "label_dialog-radius-max": "Максимальный размер точек", + "label_dialog-radius-min": "Минимальный размер точек", + "label_direction": "Направление", + "label_disabled": "Выключена", + "label_discrete": "Дискретный", + "label_enabled": "Включена", + "label_enter-manualy": "Ввести значение вручную", + "label_enter-number": "Введите число", + "label_error-connection-no-access-rights": "У вас нет доступа к подключению", + "label_error-dataset-no-access-rights": "У вас нет доступа к датасету или к его подключению", + "label_error-dataset-not-found": "Датасет не найден", + "label_error-dataset-server-error": "Ошибка: не удалось загрузить датасет", + "label_error-dataset-unknown-error": "Ошибка: не удалось загрузить датасет", + "label_error-loading-filter-values": "Ошибка: не удалось загрузить значения для фильтра", + "label_error-remove-field": "Ошибка: нельзя удалить поле, используемое в формуле", + "label_error-widget-no-access": "Нет доступа к чарту", + "label_error-widget-not-found": "Чарт не найден", + "label_error-widget-save": "Ошибка: не удалось сохранить чарт", + "label_error-widget-unknown-error": "Произошла ошибка", + "label_feed": "Фид комментариев", + "label_field-has-wrong-type": "Поле имеет недопустимый тип", + "label_field-invalid": "Поле содержит ошибки", + "label_field-more-than-one-hierarchy": "Нельзя применить больше одной иерархии", + "label_field-name": "Название поля", + "label_field-not-exist": "Поле отсутствует в датасете", + "label_field-not-exist-in-query": "Поле отсутствует в запросе", + "label_field-title": "Название поля", + "label_fields-overflow": "Установлено максимально доступное количество полей", + "label_fill-color-type": "Тип заливки", + "label_filter-all-values": "Все", + "label_filter-from-dashboard": "фильтр с дашборда", + "label_float": "Дробное число", + "label_font-size": "Размер", + "label_font-size-disabled-hint": "Нельзя задать размер для поля с типом «Markup»", + "label_genericdatetime": "Дата и время", + "label_geopoint": "Геоточка", + "label_geopolygon": "Геополигон", + "label_golden": "Голден", + "label_gradient": "Градиент", + "label_gradient-2": "Градиент-2", + "label_gradient-3": "Градиент-3", + "label_gradient-type": "Тип градиента", + "label_gray": "Серый (оттенки)", + "label_green": "Зеленый (оттенки)", + "label_green-blue": "Зеленый-Синий", + "label_grid": "Сетка", + "label_grid-step": "Шаг сетки", + "label_groupping": "Группировка", + "label_header": "Заголовок", + "label_hide": "Скрыть", + "label_hint": "Подсказка", + "label_holidays": "Выходные на графике", + "label_horizontal": "Горизонтальные", + "label_hour": "Час", + "label_html": "HTML", + "label_ignore": "Не отображать", + "label_integer": "Целое число", + "label_invalid-name": "Чарт с таким именем уже сущестует", + "label_items-available": "Доступны", + "label_items-selected": "Выбраны", + "label_label-hide-mode": "Подпись", + "label_label-mode": "Значение подписи", + "label_labels-overflow": "Установлено максимально доступное количество подписей", + "label_labels-position": "Подпись внутри", + "label_labels-view": "Вид подписей", + "label_layer-remove-confirmation": "Удалить слой?", + "label_layer-settings": "Настройки слоя", + "label_layers-overflow": "Установлено максимально доступное количество слоев", + "label_legend": "Легенда", + "label_limit": "Лимит", + "label_limit-input-error": "Введите значение от {{min}} до {{max}}", + "label_limit-message": "Выводится только первая тысяча значений", + "label_line-width": "Толщина линии", + "label_linear": "Линейная", + "label_linecap": "Конец линии", + "label_linejoin": "Соединение линий", + "label_link-failed": "Измерение не участвует в связи датасетов", + "label_links": "Связи", + "label_links-between-sources": "Связи между датасетами", + "label_logarithmic": "Логарифмическая", + "label_manual": "Вручную", + "label_markdown": "Markdown", + "label_markup": "Markup", + "label_markup-type": "Разметка", + "label_markup-type-tooltip": "Значение поля будет отформатировано в соответствии с выбранным языком разметки", + "label_max": "Максимум", + "label_message-link-copied": "Ссылка скопирована", + "label_metric-colors-add-field-disabled": "Используется поле из секции «Показатель»", + "label_metric-colors-edit-field-disabled": "Нельзя задать цвет для поля с типом «Markup»", + "label_min": "Минимум", + "label_minute": "Минута", + "label_month": "Месяц", + "label_mounted-line-settings": "Линии", + "label_navigator": "Навигатор", + "label_new-layer": "Слой", + "label_new-widget": "Новый чарт", + "label_no": "Нет", + "label_no-colors-setup-for-hierarchy": "Определение цветов для иерархии не доступно", + "label_no-colors-setup-for-string-measures": "Определение цветов для строковых показателей недоступно", + "label_no-links": "Связи между датасетами пока отсутствуют", + "label_no-rights-text": "Чарт доступен в режиме «Только для чтения». Запросите права на редактирование или сохраните изменения новым чартом", + "label_no-rights-title": "Нет прав для редактирования", + "label_no-shapes-setup-for-hierarchy": "Определение форм для иерархии не доступно", + "label_none": "Нет", + "label_nulls": "Пустые значения (null)", + "label_oceanic": "Океаник", + "label_off": "Выкл", + "label_on": "Вкл", + "label_one-color": "Один цвет", + "label_orange-blue-green": "Оранжевый-Голубой-Салатовый", + "label_orange-gray-blue": "Оранжевый-Серый-Голубой", + "label_orange-violet-blue": "Оранжевый-Фиолетовый-Голубой", + "label_orange-yellow": "Оранжевый-Желтый", + "label_order-overflow": "Установлено максимально доступное количество порядков для точек", + "label_original-title": "Название в датасете", + "label_overlap": "Перекрытие подписей", + "label_overriden-from-dashboard": "перезаписан с дашборда", + "label_pagination": "Пагинация", + "label_painting-as-0": "Окрашивать как 0", + "label_painting-ignore": "Не окрашивать", + "label_palette": "Палитра", + "label_part": "Часть даты", + "label_percent": "В процентах", + "label_pink-gray-green": "Сиреневый-Серый-Салатовый", + "label_pivot-fallback": "Cтарые сводные таблицы", + "label_pivot-inline-sort": "Сортировка по строкам", + "label_pivot-measure-sort": "Сортировка по показателю влияет только на запрос в источник, но не на сводную таблицу.", + "label_pivot-table-title-dialog-column-settings": "Настройка столбцов и строк", + "label_pixels-shortname": "px", + "label_polyline-points": "Точки полилинии", + "label_preserve-whitespace": "Сохранять пробелы и переносы", + "label_preview-dataset-error": "Невозможно отобразить график", + "label_preview-dataset-stub": "Перетащите измерения и показатели в соответствующие секции, чтобы построить визуализацию", + "label_quarter": "Квартал", + "label_red": "Красный (оттенки)", + "label_red-blue": "Красный-Синий", + "label_red-orange-green": "Красный-Желтый-Зеленый", + "label_relative-date": "Указать относительно сегодняшнего дня", + "label_relative-from": "Начало, дней назад", + "label_relative-to": "Конец, дней назад", + "label_rename": "Переименовать", + "label_reversed": "В обратном направлении", + "label_save-widget": "Сохранить чарт", + "label_scale": "Масштабирование", + "label_scale-0-max": "от 0 до max", + "label_scale-min-max": "от min до max", + "label_second": "Секунда", + "label_segments-overflow": "Установлено максимально доступное количество сплитов", + "label_settings": "Настройки", + "label_shapes-overflow": "Установлено максимально доступное количество форм", + "label_shapes-settings": "Настройки форм", + "label_short-max": "макс", + "label_short-min": "мин", + "label_should-be-less-than-middle": "Значение должно быть меньше среднего", + "label_should-be-less-than-right": "Значение должно быть меньше максимального", + "label_show": "Показать", + "label_sort-overflow": "Установлено максимально доступное количество сортировок", + "label_stacking": "Накопление", + "label_string": "Строка", + "label_sub-totals-switcher": "Подытоги", + "label_sum": "Сумма", + "label_thresholds": "Задать пороговые значения", + "label_time-interval": "Временной интервал", + "label_title": "Название", + "label_title-dialog-column-settings": "Настройка столбцов", + "label_tooltip": "Тултип", + "label_tooltip-sum": "Сумма в тултипах", + "label_tooltip-thresholds": "Вы можете вручную установить пороговые числовые значения, которые будут соответствовать каждому цвету", + "label_totals": "Итоги", + "label_traffic-light": "Траффик Лайт", + "label_tree_float": "Дерево дробных чисел", + "label_tree_int": "Дерево целых чисел", + "label_tree_str": "Дерево строк", + "label_trunc": "Округление", + "label_two-color": "Два цвета", + "label_type": "Тип", + "label_type-preaggregated": "Тип (до агрегации)", + "label_use-previous": "Использовать предыдущее", + "label_value": "Значение", + "label_vertical": "Вертикальные", + "label_violet": "Фиолетовый (оттенки)", + "label_violet-blue-green": "Фиолетовый-Синий-Салатовый", + "label_violet-orange": "Сиреневый-Красный", + "label_visualization-area": "Диаграмма с областями", + "label_visualization-area-100p": "Нормированная диаграмма с областями", + "label_visualization-bar": "Линейчатая диаграмма", + "label_visualization-bar-100p": "Нормированная линейчатая диаграмма", + "label_visualization-column": "Столбчатая диаграмма", + "label_visualization-column-100p": "Нормированная cтолбчатая диаграмма", + "label_visualization-combined-chart": "Комбинированная диаграмма", + "label_visualization-donut": "Кольцевая диаграмма", + "label_visualization-flat-table": "Таблица", + "label_visualization-geolayer": "Карта", + "label_visualization-geopoint": "Точки", + "label_visualization-geopoint-with-cluster": "Точки с кластеризацией", + "label_visualization-geopolygon": "Полигоны", + "label_visualization-heatmap": "Теплокарта", + "label_visualization-line": "Линейная диаграмма", + "label_visualization-metric": "Индикатор", + "label_visualization-pie": "Круговая диаграмма", + "label_visualization-pivot-table": "Сводная таблица", + "label_visualization-polyline": "Полилинии", + "label_visualization-scatter": "Точечная диаграмма", + "label_visualization-treemap": "Древовидная диаграмма", + "label_visualization-types-all": "Все", + "label_visualization-types-column": "Столбчатые", + "label_visualization-types-line": "Графики", + "label_visualization-types-pie": "Круговые", + "label_visualization-types-table": "Таблицы", + "label_week": "Неделя", + "label_widget-name-copy": "{{name}} - копия", + "label_widget-size": "Размер", + "label_without-group": "Без группировки", + "label_wizard-info": "Информация о чарте", + "label_year": "Год", + "label_yellow": "Желтый (оттенки)", + "label_yellow-green-blue": "Желтый-Салатовый-Голубой", + "label_yes": "Да", + "label_zoom": "Масштаб", + "section_available": "Доступные", + "section_categories": "Категории", + "section_color": "Цвет", + "section_colors": "Цвета", + "section_columns": "Столбцы", + "section_common-filters": "Общие фильтры", + "section_dimensions": "Измерения", + "section_filters": "Фильтры", + "section_filters_from_dashboard": "Фильтры с дашборда", + "section_geodata": "Геоданные", + "section_geopoint": "Геоточки", + "section_geopolygon": "Геополигоны", + "section_grouping": "Группировка", + "section_hierarchy": "Иерархии", + "section_labels": "Подписи", + "section_layer-filters": "Фильтры слоя", + "section_measure": "Показатель", + "section_measures": "Показатели", + "section_obligatory-filters": "Обязательные фильтры", + "section_order": "Порядок", + "section_parameters": "Параметры", + "section_parameters_from_dashboard": "Параметры с дашборда", + "section_points": "Точки", + "section_points_size": "Размер точек", + "section_polyline": "Полилиния", + "section_rows": "Строки", + "section_scatter": "Точки", + "section_segments": "Сплит", + "section_shapes": "Формы", + "section_size": "Размер", + "section_sort": "Сортировка", + "section_tooltips": "Тултипы", + "section_x": "X", + "section_y": "Y", + "section_y2": "Y2", + "toast_confirm-remove-item": "Удаление нельзя будет отменить. Вы уверены?", + "toast_error-action-details-label": "Подробнее", + "toast_get-publication-status-failed_error-action-details-label": "Подробнее", + "toast_unsaved": "На странице есть несохраненные изменения. Вы уверены?", + "toast_wizard-dataset-validation-failure": "Ошибка: датасет не прошел валидацию", + "tooltip_backend-pivot_unavailable": "Новые сводные таблицы недоступны для мультидатасетных чартов", + "tooltip_dataset-fetch-failed": "Не удалось загрузить датасет", + "tooltip_dataset-fetch-failed-details-label": "Подробнее", + "tooltip_filter_item": "Чтобы применить фильтр, удалите такое же поле из секции Фильтры с дашборда.", + "tooltip_filters_from_dashboard": "К чарту применены фильтры с дашборда. Эти фильтры не будут сохранены в чарте.", + "tooltip_logarithmic-axis": "Шкала с 0 или отрицательными значениями не может быть логарифмической", + "tooltip_no-available-fields": "Нет подходящих полей для добавления", + "tooltip_pagination_unavailable": "Для мультидатасетных чартов пагинация недоступна", + "tooltip_parameter-changed-icon": "Значение параметра изменено", + "tooltip_sort_unavailable": "Для мультидатасетных чартов сортировка недоступна", + "tooltip_table-preview": "Предпросмотр данных", + "tooltip_tree-pagination_unavailable": "Пагинация недоступна в таблице с деревьями", + "tooltip_tree-total_unavailable": "Итоги недоступны в таблице с деревьями", + "tooltip_zero-to-max-scale": "Логарифмическая шкала не может содержать 0 или отрицательные значения", + "value_geotype-select-geopoint": "Точки (Геоточки)", + "value_geotype-select-geopoint-with-cluster": "Точки с кластеризацией (Геоточки)", + "value_geotype-select-geopolygon": "Полигоны (Геoполигоны)", + "value_geotype-select-heatmap": "Теплокарта (Геоточки)", + "value_geotype-select-polyline": "Полилинии (Геоточки)" +} diff --git a/src/i18n/constants.ts b/src/i18n/constants.ts new file mode 100644 index 0000000..e17ac73 --- /dev/null +++ b/src/i18n/constants.ts @@ -0,0 +1,4 @@ +export const GLOBAL_I18N_VAR = 'CLOUD_I18N'; +export const I18N_DEST_PATH = 'dist/public/build/i18n'; +export const I18N_TYPES_PATH = 'src/i18n/keysets'; +export const I18N_TYPES_FILE = 'data.json'; diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..d9435b8 --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,22 @@ +import {GLOBAL_I18N_VAR} from './constants'; +import {initI18n} from './utils'; + +const isBrowserEnv = typeof window === 'object'; +const isJestEnv = typeof jest !== 'undefined'; + +export const lang = + // @ts-ignore avoid `window.d.ts` import because of too many ui types relations + isBrowserEnv ? (window.DL.user.lang as string | undefined) || 'en' : 'en'; + +let i18nPrepared: ReturnType; +if (isBrowserEnv && !isJestEnv) { + // @ts-ignore ignore `window` string index + i18nPrepared = initI18n({lang, content: window[GLOBAL_I18N_VAR] || {}}); +} else { + // empty keysets for jest (mock i18n as needed) + i18nPrepared = initI18n([{lang: 'ru'}, {lang: 'en'}]); +} + +export const I18N = i18nPrepared.I18N; +export const I18n = i18nPrepared.I18n; +export const i18n = i18nPrepared.i18n; diff --git a/src/i18n/prepare-keysets/constants.ts b/src/i18n/prepare-keysets/constants.ts new file mode 100644 index 0000000..528d9a2 --- /dev/null +++ b/src/i18n/prepare-keysets/constants.ts @@ -0,0 +1,2 @@ +export const KEYSET_LANGUAGES = ['ru', 'en']; +export const LANG_FOR_TYPES = 'ru'; diff --git a/src/i18n/prepare-keysets/index.ts b/src/i18n/prepare-keysets/index.ts new file mode 100644 index 0000000..803c102 --- /dev/null +++ b/src/i18n/prepare-keysets/index.ts @@ -0,0 +1,22 @@ +import fs from 'fs'; +import path from 'path'; + +import {I18N_DEST_PATH, I18N_TYPES_FILE, I18N_TYPES_PATH} from '../constants'; + +import {prepareKeysets} from './prepare-keysets'; + +const prepare = async () => { + await prepareKeysets('src/i18n-keysets'); + + await fs.promises.mkdir(I18N_TYPES_PATH, {recursive: true}); + await fs.promises.copyFile( + path.join(I18N_DEST_PATH, I18N_TYPES_FILE), + path.join(I18N_TYPES_PATH, I18N_TYPES_FILE), + ); +}; + +prepare().catch((err) => { + // eslint-disable-next-line no-console + console.error(err.stack || err); + process.exit(1); +}); diff --git a/src/i18n/prepare-keysets/prepare-keysets.ts b/src/i18n/prepare-keysets/prepare-keysets.ts new file mode 100644 index 0000000..53a4130 --- /dev/null +++ b/src/i18n/prepare-keysets/prepare-keysets.ts @@ -0,0 +1,48 @@ +import fs from 'fs'; +import path from 'path'; + +import {GLOBAL_I18N_VAR, I18N_DEST_PATH, I18N_TYPES_FILE} from '../constants'; + +import {KEYSET_LANGUAGES, LANG_FOR_TYPES} from './constants'; +import {clearFiles, getJSFileName, getKeysets, prepareKeysetData, umdTemplate} from './utils'; + +export const prepareKeysets = async (keysetsDir: string) => { + await clearFiles(I18N_DEST_PATH); + + const keysets = await getKeysets(keysetsDir); + const writePromises = []; + + for (const lang of KEYSET_LANGUAGES) { + const typeFileName = lang === LANG_FOR_TYPES ? I18N_TYPES_FILE : undefined; + const keysetData = prepareKeysetData({lang, keysets, typeFileName}); + + for (const dataItem of keysetData) { + const JSONContent = JSON.stringify(dataItem.keyset, null, 4); + + if (dataItem.filename === I18N_TYPES_FILE) { + writePromises.push({ + filename: dataItem.filename, + promise: fs.promises.writeFile( + path.resolve(I18N_DEST_PATH, dataItem.filename), + JSONContent, + {encoding: 'utf-8'}, + ), + }); + } else { + const content = umdTemplate(JSONContent, GLOBAL_I18N_VAR); + const JSFileName = getJSFileName(dataItem.filename, JSONContent); + + writePromises.push({ + filename: JSFileName, + promise: fs.promises.writeFile( + path.resolve(I18N_DEST_PATH, JSFileName), + content, + {encoding: 'utf-8'}, + ), + }); + } + } + } + + await Promise.all(writePromises.map(({promise}) => promise)); +}; diff --git a/src/i18n/prepare-keysets/utils.ts b/src/i18n/prepare-keysets/utils.ts new file mode 100644 index 0000000..8e687e5 --- /dev/null +++ b/src/i18n/prepare-keysets/utils.ts @@ -0,0 +1,143 @@ +import crypto from 'crypto'; +import fs from 'fs'; +import path from 'path'; + +import {KEYSET_LANGUAGES} from './constants'; + +function getContentHash(content: string) { + const hash = crypto.createHash('md5'); + hash.update(content); + return hash.digest('hex'); +} + +export function getJSFileName(name: string, fileContent: string) { + const hash = `.${getContentHash(fileContent).slice(0, 8)}`; + return `${name}${hash}.js`; +} + +export async function clearFiles(destPath: string) { + await fs.promises.rm(destPath, {recursive: true, force: true}); + await fs.promises.mkdir(destPath, {recursive: true}); +} + +export function umdTemplate(content: string, globalVar: string) { + return ` +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define([], factory); + } else if (typeof exports === 'object') { + module.exports = factory(); + } else { + root.${globalVar} = factory(); + } +}(typeof self !== 'undefined' ? self : this, function () { + return ${content}; +})); +`.trim(); +} + +export async function listKeysetDirNames(keysetsDir: string) { + const result: string[] = []; + const directories = await fs.promises.readdir(keysetsDir, {withFileTypes: true}); + + for (const dir of directories) { + if (!dir.isDirectory() || dir.name.startsWith('.')) { + continue; + } + result.push(dir.name); + } + return result; +} + +export async function loadJson(dirname: string, filename: string) { + const fullname = path.resolve(dirname, filename); + const content = await fs.promises.readFile(fullname, { + encoding: 'utf-8', + }); + try { + return JSON.parse(content) as Record; + } catch (error) { + throw new Error('Failed to load json file'); + } +} + +export type KeysetsWithLangs = { + [lang in string]: { + [name in string]: { + content: Record; + }; + }; +}; + +export async function getKeysets(keysetsDir: string) { + const keysets = KEYSET_LANGUAGES.reduce((acc, lang) => { + acc[lang] = {}; + return acc; + }, {}); + + for (const keysetDirName of await listKeysetDirNames(keysetsDir)) { + const keysetDir = path.resolve(keysetsDir, keysetDirName); + const keysetDirFiles = await fs.promises.readdir(keysetDir, {withFileTypes: true}); + + for (const file of keysetDirFiles) { + if (!file.isFile()) { + continue; + } + + for (const lang of KEYSET_LANGUAGES) { + if (`${lang}.json` === file.name) { + const content = await loadJson(keysetDir, file.name); + keysets[lang][keysetDirName] = { + content, + }; + } + } + } + } + return keysets; +} + +export type KeysetData = { + filename: string; + keyset: Record>; +}; + +export function prepareKeysetData({ + lang, + keysets, + typeFileName, +}: { + lang: string; + keysets: KeysetsWithLangs; + typeFileName?: string; +}): KeysetData[] { + const keyset: KeysetData['keyset'] = {}; + const langKeysets = keysets[lang]; + Object.keys(langKeysets).forEach((key) => { + keyset[key] = langKeysets[key].content; + }); + const result: KeysetData[] = [ + { + filename: lang, + keyset, + }, + ]; + + if (typeFileName) { + const typeFileContent: KeysetData['keyset'] = {}; + Object.keys(keyset).forEach((keysetName) => { + typeFileContent[keysetName] = Object.keys(keyset[keysetName]).reduce< + Record + >((acc, key) => { + acc[key] = 'str'; + return acc; + }, {}); + }); + result.push({ + filename: typeFileName, + keyset: typeFileContent, + }); + } + + return result; +} diff --git a/src/i18n/read-keysets.ts b/src/i18n/read-keysets.ts new file mode 100644 index 0000000..ebaddaf --- /dev/null +++ b/src/i18n/read-keysets.ts @@ -0,0 +1,26 @@ +import fs from 'fs'; + +interface KeysetFile { + filename: string; +} + +export function readKeysets(destination: string) { + const files = fs.readdirSync(destination); + + return files.reduce( + (acc: Record, filename) => { + const match = filename.match(/(\.(\d|\w)+)?\.js$/); // trying to find {hash}?.js + + if (!match) { + return acc; + } + + acc[filename.slice(0, match.index)] = { + filename, + }; + + return acc; + }, + {} as Record, + ); +} diff --git a/src/i18n/tsconfig.json b/src/i18n/tsconfig.json new file mode 100644 index 0000000..5c07eda --- /dev/null +++ b/src/i18n/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig", + "include": ["**/*"], + "compilerOptions": { + "checkJs": false, + "sourceMap": false, + "outDir": "../../dist/i18n", + "resolveJsonModule": true + } +} diff --git a/src/i18n/types.ts b/src/i18n/types.ts new file mode 100644 index 0000000..a80616c --- /dev/null +++ b/src/i18n/types.ts @@ -0,0 +1,13 @@ +import type {I18NFn} from '@gravity-ui/i18n'; + +import type KeysetsData from './keysets/data.json'; + +export type Keysets = typeof KeysetsData; + +export type TypedI18n = I18NFn & {lang?: string}; + +export type ServerI18n = { + lang: string; + keyset: TypedI18n['keyset']; + getI18nServer: () => TypedI18n; +}; diff --git a/src/i18n/utils.ts b/src/i18n/utils.ts new file mode 100644 index 0000000..ea6220e --- /dev/null +++ b/src/i18n/utils.ts @@ -0,0 +1,39 @@ +import {I18N} from '@gravity-ui/i18n'; + +import type {Keysets, TypedI18n} from './types'; + +type LocaleI18n = {lang: string; content?: Keysets}; + +// *** +// I18N - used to install a new language for an instance +// I18n - factory for creating curried functions based on a keyset +// i18n - a function with a full set of parameters +// *** +export function initI18n(locale: LocaleI18n | LocaleI18n[]) { + const i18nInstance = new I18N(); + + if (Array.isArray(locale)) { + locale.forEach(({lang, content}) => { + if (content) { + i18nInstance.registerKeysets(lang, content as Keysets); + } else { + i18nInstance.registerKeyset(lang, 'empty', {}); + } + }); + if (locale.length > 0) { + i18nInstance.setLang(locale[0].lang); + } + } else { + if (locale.content) { + i18nInstance.registerKeysets(locale.lang, locale.content as Keysets); + } else { + i18nInstance.registerKeyset(locale.lang, 'empty', {}); + } + i18nInstance.setLang(locale.lang); + } + + const i18n = i18nInstance.i18n.bind(i18nInstance) as TypedI18n; + const i18nFactoryTyped = i18nInstance as unknown as TypedI18n; + + return {I18N: i18nInstance, I18n: i18nFactoryTyped, i18n}; +} diff --git a/src/server/.eslintrc b/src/server/.eslintrc new file mode 100644 index 0000000..0694580 --- /dev/null +++ b/src/server/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "@gravity-ui/eslint-config/server" +} diff --git a/src/server/app-env.ts b/src/server/app-env.ts new file mode 100644 index 0000000..bd7cd29 --- /dev/null +++ b/src/server/app-env.ts @@ -0,0 +1,15 @@ +import {AppInstallation, AppMode} from '../shared'; + +const mode = process.env.APP_MODE; +export const appInstallation = process.env.APP_INSTALLATION; +export const appEnv = process.env.APP_ENV; +export const releaseVersion = process.env.RELEASE_VERSION; +export const docsUrl = process.env.DOCS_URL; + +export const isFullMode = mode === AppMode.Full; +export const isDatalensMode = mode === AppMode.Datalens; +export const isChartsMode = mode === AppMode.Charts; +export const isApiMode = mode === AppMode.Api; +export const isPublicApiMode = mode === AppMode.PublicApi; + +export const isOpensourceInstallation = appInstallation === AppInstallation.Opensource; diff --git a/src/server/callbacks/index.ts b/src/server/callbacks/index.ts new file mode 100644 index 0000000..4082627 --- /dev/null +++ b/src/server/callbacks/index.ts @@ -0,0 +1,17 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import {getLandingLayout} from '../components'; + +import {getError} from './page-error'; + +export async function onFail(req: Request, res: Response) { + res.status(500).send(await getLandingLayout(req, res, getError(req).onFail)); +} + +export async function onMissingEntry(req: Request, res: Response) { + res.status(404).send(await getLandingLayout(req, res, getError(req).onMissingEntry)); +} + +export function defaultOnFail(_: Request, res: Response) { + res.status(500).send({message: 'Internal server error'}); +} diff --git a/src/server/callbacks/page-error.ts b/src/server/callbacks/page-error.ts new file mode 100644 index 0000000..e442b07 --- /dev/null +++ b/src/server/callbacks/page-error.ts @@ -0,0 +1,28 @@ +import type {Request} from '@gravity-ui/expresskit'; + +import type {LandingLayoutPageError} from '../../shared'; +import {ErrorContentTypes} from '../../shared'; + +export const getError = (req: Request): Record => { + const {ctx} = req; + + const serviceName = ctx.config.serviceName; + + const I18n = req.ctx.get('i18n'); + + const i18n = I18n.keyset('datalens.landing.error'); + + return { + onFail: { + errorType: ErrorContentTypes.ERROR, + title: i18n('label_title-fail'), + description: i18n('label_description-fail'), + pageTitle: `${i18n('label_page-title-fail')} – ${serviceName}`, + }, + onMissingEntry: { + errorType: ErrorContentTypes.NOT_FOUND, + title: i18n('label_title-missing-entry'), + pageTitle: `${i18n('label_page-title-missing-entry')} – ${serviceName}`, + }, + }; +}; diff --git a/src/server/components/api-docs/constants.ts b/src/server/components/api-docs/constants.ts new file mode 100644 index 0000000..6b1a01c --- /dev/null +++ b/src/server/components/api-docs/constants.ts @@ -0,0 +1 @@ +export const CONTENT_TYPE_JSON = 'application/json'; diff --git a/src/server/components/api-docs/index.ts b/src/server/components/api-docs/index.ts new file mode 100644 index 0000000..a50caf9 --- /dev/null +++ b/src/server/components/api-docs/index.ts @@ -0,0 +1 @@ +export type {SecuritySchemeObject} from './types'; diff --git a/src/server/components/api-docs/types.ts b/src/server/components/api-docs/types.ts new file mode 100644 index 0000000..1fb28b4 --- /dev/null +++ b/src/server/components/api-docs/types.ts @@ -0,0 +1,14 @@ +// Copied from @asteasolutions/zod-to-openapi +export type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options' | 'trace'; + +export type SecuritySchemeType = 'apiKey' | 'http' | 'oauth2' | 'openIdConnect'; + +export type SecuritySchemeObject = { + type: SecuritySchemeType; + description?: string; + name?: string; + in?: string; + scheme?: string; + bearerFormat?: string; + openIdConnectUrl?: string; +}; diff --git a/src/server/components/app-layout/app-layout-settings.ts b/src/server/components/app-layout/app-layout-settings.ts new file mode 100644 index 0000000..448cb33 --- /dev/null +++ b/src/server/components/app-layout/app-layout-settings.ts @@ -0,0 +1,65 @@ +import type {Meta} from '@gravity-ui/app-layout'; +import type {Request, Response} from '@gravity-ui/expresskit'; + +import type {AppLayoutSettings, AppLayoutSettingsName} from '../../types/app-layout'; +export const getAppLayoutSettings = ( + req: Request, + _res: Response, + settingsId?: string, +): AppLayoutSettings => { + const config = req.ctx.config; + switch (settingsId as AppLayoutSettingsName) { + case 'dl-main': { + return { + renderConfig: {title: config.serviceName}, + DL: {}, + bundleName: 'dl-main', + }; + } + case 'navigation': { + return { + renderConfig: { + title: `Navigation - ${config.serviceName}`, + }, + DL: {}, + bundleName: 'dl-main', + }; + } + case 'landing-layout': { + const pageSettings = req.ctx.get('landingPageSettings'); + + if (!pageSettings) { + throw new Error('Page settings are required'); + } + + const meta: Meta[] | undefined = pageSettings.pageMeta; + + return { + renderConfig: { + title: pageSettings.title || '', + meta, + links: pageSettings.pageLinks, + }, + DL: { + landingPageSettings: pageSettings, + isLanding: true, + }, + bundleName: 'dl-main', + }; + } + case 'auth-layout': { + return { + renderConfig: {title: config.serviceName}, + DL: {}, + bundleName: 'dl-main', + }; + } + default: { + return { + renderConfig: {title: 'default'}, + DL: {}, + bundleName: 'default', + }; + } + } +}; diff --git a/src/server/components/app-layout/plugins/layout/index.ts b/src/server/components/app-layout/plugins/layout/index.ts new file mode 100644 index 0000000..5e40d3b --- /dev/null +++ b/src/server/components/app-layout/plugins/layout/index.ts @@ -0,0 +1,104 @@ +import path from 'path'; + +import type {RenderHelpers} from '@gravity-ui/app-layout'; +import {createLayoutPlugin as createLayoutPluginBase} from '@gravity-ui/app-layout'; + +const filename = 'assets-manifest.json'; + +const manifest = path.resolve(process.cwd(), 'dist/public/build', filename); +const publicPath = '/build/'; + +export function createLayoutPlugin(): ReturnType { + const layoutPlugin = createLayoutPluginBase({publicPath, manifest}); + return { + name: layoutPlugin.name, + apply({options, commonOptions, renderContent, utils}) { + layoutPlugin.apply({options, commonOptions, renderContent, utils}); + if (!renderContent.bodyContent.root) { + renderContent.bodyContent.root = getSpinner(utils); + } + }, + }; +} + +function getSpinner(utils: RenderHelpers) { + const styles = utils.renderInlineStyle(` + @keyframes yc-pulse { + 50% { + background: rgb(255, 190, 92); + } + } + .g-loader { + position: relative; + background: rgba(255, 190, 92, 0.14); + animation: yc-pulse ease 800ms infinite; + animation-delay: 400ms; + } + .g-loader:before, .g-loader:after { + content: ""; + position: absolute; + display: block; + background: rgba(255, 190, 92, 0.14); + top: 50%; + transform: translateY(-50%); + animation: yc-pulse ease 800ms infinite; + } + .g-loader:before { + animation-delay: 200ms; + } + .g-loader:after { + animation-delay: 600ms; + } + .g-loader_size_l { + width: 9px; + height: 36px; + } + .g-loader_size_l:before { + height: 24px; + width: 9px; + left: -18px; + } + .g-loader_size_l:after { + height: 24px; + width: 9px; + left: 18px; + } + + .local_wrapper { + justify-content: center; + align-items: center; + height: 100%; + } + + .g-root_theme_dark .local_wrapper { + background-color: var(--g-color-base-background, rgb(45, 44, 51)); + } + .g-root_theme_dark-hc .local_wrapper { + background-color: var(--g-color-base-background, rgb(34, 35, 38)); + } + + html, body, #root { + width: 100%; + height: 100%; + margin: 0; + } + `); + + const loader = ` + + `; + + const template = ` + ${styles} + ${loader} + ${utils.renderInlineScript(` + setTimeout(function() { + const element = document.getElementById("local_wrapper"); + element && element.setAttribute('style', 'display: flex'); + }, 100); + `)} +`; + return template; +} diff --git a/src/server/components/auth/callbacks/auth-layout.ts b/src/server/components/auth/callbacks/auth-layout.ts new file mode 100644 index 0000000..9f977e3 --- /dev/null +++ b/src/server/components/auth/callbacks/auth-layout.ts @@ -0,0 +1,23 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import type {AuthLayoutSettings} from '../../../../shared/components/auth/types/layout'; +import {registry} from '../../../registry'; + +export const getAuthLayout = async ( + req: Request, + res: Response, + pageSettings: AuthLayoutSettings, +) => { + const layoutConfig = await registry.useGetLayoutConfig({ + req, + res, + settingsId: 'auth-layout', + }); + + layoutConfig.data!.DL.authPageSettings = { + ...pageSettings, + isAuthPage: true, + }; + + return res.renderDatalensLayout(layoutConfig); +}; diff --git a/src/server/components/auth/callbacks/index.ts b/src/server/components/auth/callbacks/index.ts new file mode 100644 index 0000000..10d187a --- /dev/null +++ b/src/server/components/auth/callbacks/index.ts @@ -0,0 +1,15 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import {getAuthLayout} from './auth-layout'; + +export async function onAuthReload(req: Request, res: Response) { + res.status(409).send(await getAuthLayout(req, res, {page: 'reload'})); +} + +export async function onAuthSignin(req: Request, res: Response) { + res.status(200).send(await getAuthLayout(req, res, {page: 'signin'})); +} + +export async function onAuthLogout(req: Request, res: Response) { + res.status(401).send(await getAuthLayout(req, res, {page: 'logout'})); +} diff --git a/src/server/components/auth/middlewares/auth/api-auth.ts b/src/server/components/auth/middlewares/auth/api-auth.ts new file mode 100644 index 0000000..3fd712d --- /dev/null +++ b/src/server/components/auth/middlewares/auth/api-auth.ts @@ -0,0 +1,50 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; +import jwt from 'jsonwebtoken'; + +import {getAuthCookieName} from '../../../../../shared/components/auth/utils'; +import type {AccessTokenPayload} from '../../types/token'; + +import {ALGORITHMS} from './constants'; + +export const apiAuth = async (req: Request, res: Response, next: NextFunction) => { + req.ctx.log('API_AUTH'); + const authCookieName = getAuthCookieName(req.ctx.config.authCookieName); + + const authCookie = req.cookies[authCookieName]; + + if (!authCookie) { + req.ctx.logError('API_AUTH_NO_COOKIE'); + res.status(401).send('Unauthorized access'); + return; + } + + try { + req.ctx.log('CHECK_ACCESS_TOKEN'); + + const {accessToken} = JSON.parse(authCookie); + const {userId, sessionId, roles} = jwt.verify( + accessToken, + req.ctx.config.authTokenPublicKey || '', + { + algorithms: ALGORITHMS, + }, + ) as AccessTokenPayload; + + req.originalContext.set('userId', userId); + req.originalContext.set('user', { + userId, + sessionId, + accessToken, + roles, + }); + + req.ctx.log('CHECK_ACCESS_TOKEN_SUCCESS'); + + next(); + return; + } catch (err) { + req.ctx.logError('CHECK_ACCESS_TOKEN_ERROR', err); + res.status(401).send('Unauthorized access'); + return; + } +}; diff --git a/src/server/components/auth/middlewares/auth/constants.ts b/src/server/components/auth/middlewares/auth/constants.ts new file mode 100644 index 0000000..7a58bbc --- /dev/null +++ b/src/server/components/auth/middlewares/auth/constants.ts @@ -0,0 +1,3 @@ +import type {Algorithm} from 'jsonwebtoken'; + +export const ALGORITHMS: Algorithm[] = ['PS256']; diff --git a/src/server/components/auth/middlewares/auth/index.ts b/src/server/components/auth/middlewares/auth/index.ts new file mode 100644 index 0000000..acf329d --- /dev/null +++ b/src/server/components/auth/middlewares/auth/index.ts @@ -0,0 +1,12 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; + +import {apiAuth} from './api-auth'; +import {uiAuth} from './ui-auth'; + +export async function appAuth(req: Request, res: Response, next: NextFunction) { + if (req.routeInfo.ui) { + uiAuth(req, res, next); + } else { + apiAuth(req, res, next); + } +} diff --git a/src/server/components/auth/middlewares/auth/ui-auth.ts b/src/server/components/auth/middlewares/auth/ui-auth.ts new file mode 100644 index 0000000..bb77780 --- /dev/null +++ b/src/server/components/auth/middlewares/auth/ui-auth.ts @@ -0,0 +1,166 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; +import jwt from 'jsonwebtoken'; +import requestIp from 'request-ip'; +import setCookieParser from 'set-cookie-parser'; + +import {ErrorCode} from '../../../../../shared'; +import {ACCESS_TOKEN_TIME_RESERVE} from '../../../../../shared/components/auth/constants/token'; +import {RELOADED_URL_QUERY} from '../../../../../shared/components/auth/constants/url'; +import {getAuthCookieName, getAuthExpCookieName} from '../../../../../shared/components/auth/utils'; +import { + AuthHeader, + FORWARDED_FOR_HEADER, + SET_COOKIE_HEADER, +} from '../../../../../shared/constants/header'; +import {onFail} from '../../../../callbacks'; +import {registry} from '../../../../registry'; +import type {DatalensGatewaySchemas} from '../../../../types/gateway'; +import Utils from '../../../../utils'; +import {isGatewayError} from '../../../../utils/gateway'; +import {onAuthLogout, onAuthReload, onAuthSignin} from '../../callbacks'; +import type {AccessTokenPayload} from '../../types/token'; + +import {ALGORITHMS} from './constants'; + +export const uiAuth = async (req: Request, res: Response, next: NextFunction) => { + req.ctx.log('UI_AUTH'); + const authCookieName = getAuthCookieName(req.ctx.config.authCookieName); + const authExpCookieName = getAuthExpCookieName(req.ctx.config.authCookieName); + + let authCookie = req.cookies[authCookieName]; + let authExpCookie = req.cookies[authExpCookieName]; + + if (!authCookie || !authExpCookie) { + req.ctx.log('SIGNIN'); + onAuthSignin(req, res); + return; + } + + const {gatewayApi} = registry.getGatewayApi(); + + const now = Math.floor(new Date().getTime() / 1000); + const exp = Number(authExpCookie); + + if (now + ACCESS_TOKEN_TIME_RESERVE > exp) { + req.ctx.log('START_REFRESH_TOKEN', { + now, + exp, + }); + + try { + await gatewayApi.auth.auth + .refreshTokens({ + ctx: req.ctx, + headers: { + [AuthHeader.Cookie]: req.headers.cookie, + [FORWARDED_FOR_HEADER]: requestIp.getClientIp(req) ?? undefined, + }, + authArgs: {}, + requestId: req.ctx.get('requestId') ?? '', + args: undefined, + }) + .then((result) => { + const {responseHeaders} = result; + + if (responseHeaders) { + Object.keys(responseHeaders).forEach((header) => { + if (header.toLowerCase() === SET_COOKIE_HEADER.toLowerCase()) { + res.header(header, responseHeaders[header]); + + const settedCookies = setCookieParser.parse( + responseHeaders[header], + ); + + settedCookies.forEach((cookie) => { + if (cookie.name === authCookieName) { + authCookie = cookie.value; + } else if (cookie.name === authExpCookieName) { + authExpCookie = cookie.value; + } + }); + } + }); + } + + req.ctx.log('FINISH_REFRESH_TOKEN', { + newExp: Number(authExpCookie), + }); + }); + } catch (err) { + req.ctx.logError('REFRESH_TOKEN_ERROR', isGatewayError(err) ? err.error : err); + if (req.query[RELOADED_URL_QUERY]) { + onAuthLogout(req, res); + } else { + onAuthReload(req, res); + } + return; + } + } + + let accessToken, userId, sessionId, roles; + + try { + req.ctx.log('VERIFY_ACCESS_TOKEN'); + ({accessToken} = JSON.parse(authCookie)); + + ({userId, sessionId, roles} = jwt.verify( + accessToken, + req.ctx.config.authTokenPublicKey || '', + { + algorithms: ALGORITHMS, + }, + ) as AccessTokenPayload); + } catch (err) { + req.ctx.logError('VERIFY_ACCESS_TOKEN_ERROR', err); + onAuthLogout(req, res); + return; + } + + try { + req.ctx.log('SET_USER_CTX'); + + const { + responseData: {profile}, + } = await gatewayApi.root.auth.getMyUserProfile({ + ctx: req.ctx, + headers: { + [AuthHeader.Authorization]: 'Bearer ' + accessToken, + }, + requestId: req.id, + authArgs: {}, + args: undefined, + }); + + req.originalContext.set('userId', userId); + req.originalContext.set('user', { + userId, + sessionId, + accessToken, + roles, + profile: { + login: profile.login!, + email: profile.email ?? undefined, + formattedLogin: profile.login!, + displayName: + `${profile.firstName || ''} ${profile.lastName || ''}`.trim() || + profile.login || + profile.email || + userId, + idpType: profile.idpType, + }, + }); + } catch (err) { + const errorCode = Utils.getErrorCode(err); + + if (errorCode === ErrorCode.AuthUserNotExists) { + onAuthLogout(req, res); + return; + } + + req.ctx.logError('SET_USER_CTX_ERROR', isGatewayError(err) ? err.error : err); + onFail(req, res); + return; + } + + next(); +}; diff --git a/src/server/components/auth/routes.ts b/src/server/components/auth/routes.ts new file mode 100644 index 0000000..f276d46 --- /dev/null +++ b/src/server/components/auth/routes.ts @@ -0,0 +1,20 @@ +import {AuthPolicy} from '@gravity-ui/expresskit'; + +import type {ExtendedAppRouteDescription} from '../../types/controllers'; +import {getConfiguredRoute} from '../../utils/routes'; + +export function getAuthRoutes({ + routeParams, +}: { + routeParams: Omit; +}) { + const routes: Record = { + postAuthGateway: getConfiguredRoute('schematic-gateway', { + authPolicy: AuthPolicy.disabled, + route: 'POST /gateway/:scope(auth)/:service/:action?', + ...routeParams, + }), + }; + + return routes; +} diff --git a/src/server/components/auth/types/token.ts b/src/server/components/auth/types/token.ts new file mode 100644 index 0000000..f942a3b --- /dev/null +++ b/src/server/components/auth/types/token.ts @@ -0,0 +1,12 @@ +import type {UserRole} from '../../../../shared/components/auth/constants/role'; + +export interface ExpirableTokenPayload { + iat: number; + exp: number; +} + +export interface AccessTokenPayload extends ExpirableTokenPayload { + userId: string; + sessionId: string; + roles: `${UserRole}`[]; +} diff --git a/src/server/components/auth/types/user.ts b/src/server/components/auth/types/user.ts new file mode 100644 index 0000000..8e6f1e3 --- /dev/null +++ b/src/server/components/auth/types/user.ts @@ -0,0 +1,15 @@ +import type {UserRole} from '../../../../shared/components/auth/constants/role'; + +export interface CtxUser { + userId: string; + sessionId: string; + accessToken: string; + roles: `${UserRole}`[]; + profile?: { + login: string; + email?: string; + formattedLogin: string; + displayName: string; + idpType: string | null; + }; +} diff --git a/src/server/components/axios.ts b/src/server/components/axios.ts new file mode 100644 index 0000000..606934c --- /dev/null +++ b/src/server/components/axios.ts @@ -0,0 +1,31 @@ +import type {AppConfig} from '@gravity-ui/nodekit'; +import type {AxiosInstance} from 'axios'; +import axios from 'axios'; +import axiosRetry from 'axios-retry'; + +import {DEFAULT_TIMEOUT} from '../../shared'; +import {IPV6_AXIOS_OPTIONS} from '../constants/axios'; + +let axiosInstance: AxiosInstance | undefined; +export const getAxios = (config: AppConfig): AxiosInstance => { + if (!axiosInstance) { + axiosInstance = axios.create({ + ...(config.useIPV6 ? IPV6_AXIOS_OPTIONS : {}), + timeout: DEFAULT_TIMEOUT, + }); + + axiosRetry(axiosInstance, { + retries: 0, + retryDelay: (retryCount) => retryCount * 3000, + retryCondition: (error) => { + if (!error.config) { + return false; + } + + return axiosRetry.isNetworkError(error) || axiosRetry.isRetryableError(error); + }, + }); + } + + return axiosInstance; +}; diff --git a/src/server/components/cache-client.ts b/src/server/components/cache-client.ts new file mode 100644 index 0000000..545ded9 --- /dev/null +++ b/src/server/components/cache-client.ts @@ -0,0 +1,123 @@ +import type {AppConfig} from '@gravity-ui/nodekit'; +import Redis from 'ioredis'; + +export type CacheStatus = + | typeof CacheClient.OK + | typeof CacheClient.KEY_NOT_FOUND + | typeof CacheClient.NOT_OK; +export type CacheClientResponse = { + message?: string; + status: CacheStatus; + data?: any; +}; + +const REDIS_TIMEOUT = 2000; + +const timeout = (prom: Promise, time: number): Promise => { + let timer: NodeJS.Timeout; + return Promise.race([ + prom, + new Promise((_r, rej) => { + timer = setTimeout(rej, time, Error('TimeoutError')); + }), + ]).finally(() => clearTimeout(timer)); +}; + +export class CacheClient { + static OK = Symbol('CACHE_STATUS_OK'); + static NOT_OK = Symbol('CACHE_STATUS_NOT_OK'); + static KEY_NOT_FOUND = Symbol('CACHE_STATUS_KEY_NOT_FOUND'); + + _debug = false; + client: Redis.Redis | null = null; + + constructor({config}: {config: AppConfig}) { + this._debug = config.appInstallation === 'development'; + + if (config.redis && config.redis.password) { + this.client = new Redis(config.redis); + } + } + + async get({key}: {key: string}): Promise { + if (this.client) { + try { + const data = await timeout(this.client.get(key), REDIS_TIMEOUT); + if (data === null) { + return { + status: CacheClient.KEY_NOT_FOUND, + }; + } + return { + status: CacheClient.OK, + data: JSON.parse(data), + }; + } catch (error: any) { + return { + status: CacheClient.NOT_OK, + message: error.message || 'Service unavailable', + }; + } + } else { + return { + status: CacheClient.NOT_OK, + message: 'Redis client is null', + }; + } + } + + async set({ + key, + value, + ttl, + }: { + key: string; + value: any; + ttl?: number; + }): Promise { + if (this.client) { + try { + await timeout( + this.client.set(key, JSON.stringify(value), 'EX', ttl), + REDIS_TIMEOUT, + ); + return { + status: CacheClient.OK, + }; + } catch (error: any) { + return { + status: CacheClient.NOT_OK, + message: error.message || 'Service unavailable', + }; + } + } else { + return { + status: CacheClient.NOT_OK, + message: 'Redis client is null', + }; + } + } + + async del({key}: {key: string}): Promise { + if (this.client) { + try { + await timeout(this.client.del(key), REDIS_TIMEOUT); + return { + status: CacheClient.OK, + }; + } catch (error: any) { + return { + status: CacheClient.NOT_OK, + message: error.message || 'Service unavailable', + }; + } + } else { + return { + status: CacheClient.NOT_OK, + message: 'Redis client is null', + }; + } + } +} + +export default CacheClient; diff --git a/src/server/components/chart-editor-developer-mode-access-check.ts b/src/server/components/chart-editor-developer-mode-access-check.ts new file mode 100644 index 0000000..ab740a3 --- /dev/null +++ b/src/server/components/chart-editor-developer-mode-access-check.ts @@ -0,0 +1,4 @@ +import {DeveloperModeCheckStatus} from '../../shared/types'; +export const checkRequestForDeveloperModeAccess = async () => { + return DeveloperModeCheckStatus.Allowed; +}; diff --git a/src/server/components/charts-engine/components/__tests__/chart-validator.test.ts b/src/server/components/charts-engine/components/__tests__/chart-validator.test.ts new file mode 100644 index 0000000..fb5a4c2 --- /dev/null +++ b/src/server/components/charts-engine/components/__tests__/chart-validator.test.ts @@ -0,0 +1,9 @@ +import {chartValidator} from '../chart-validator'; + +import {validDataChunks} from './mocks/chart-validator'; + +describe('Charts Engine chartValidator', () => { + test.each([...validDataChunks])('should return true %j', (data) => { + expect(chartValidator.validate(data)).toBeTruthy(); + }); +}); diff --git a/src/server/components/charts-engine/components/__tests__/mocks/chart-validator.ts b/src/server/components/charts-engine/components/__tests__/mocks/chart-validator.ts new file mode 100644 index 0000000..3c39662 --- /dev/null +++ b/src/server/components/charts-engine/components/__tests__/mocks/chart-validator.ts @@ -0,0 +1,442 @@ +import {EDITOR_TYPE} from '../../../../../../shared/constants'; + +export const validDataChunks: {data: Record; type: string}[] = [ + // GRAPH_NODE + { + data: { + graph: {}, + js: {}, + params: {}, + shared: {}, + statface_graph: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.GRAPH_NODE, + }, + { + data: { + graph: {}, + js: {}, + meta: {}, + params: {}, + shared: {}, + statface_graph: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.GRAPH_NODE, + }, + { + data: { + graph: {}, + js: {}, + params: {}, + secrets: {}, + shared: {}, + statface_graph: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.GRAPH_NODE, + }, + { + data: { + graph: {}, + js: {}, + meta: {}, + params: {}, + secrets: {}, + shared: {}, + statface_graph: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.GRAPH_NODE, + }, + // METRIC_NODE + { + data: { + js: {}, + params: {}, + shared: {}, + statface_metric: {}, + url: {}, + }, + type: EDITOR_TYPE.METRIC_NODE, + }, + { + data: { + js: {}, + meta: {}, + params: {}, + shared: {}, + statface_metric: {}, + url: {}, + }, + type: EDITOR_TYPE.METRIC_NODE, + }, + { + data: { + js: {}, + params: {}, + secrets: {}, + shared: {}, + statface_metric: {}, + url: {}, + }, + type: EDITOR_TYPE.METRIC_NODE, + }, + { + data: { + js: {}, + meta: {}, + params: {}, + secrets: {}, + shared: {}, + statface_metric: {}, + url: {}, + }, + type: EDITOR_TYPE.METRIC_NODE, + }, + // TABLE_NODE + { + data: { + js: {}, + params: {}, + shared: {}, + table: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.TABLE_NODE, + }, + { + data: { + js: {}, + meta: {}, + params: {}, + shared: {}, + table: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.TABLE_NODE, + }, + { + data: { + js: {}, + params: {}, + secrets: {}, + shared: {}, + table: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.TABLE_NODE, + }, + { + data: { + js: {}, + meta: {}, + params: {}, + secrets: {}, + shared: {}, + table: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.TABLE_NODE, + }, + // YMAP_NODE + { + data: { + js: {}, + params: {}, + shared: {}, + url: {}, + ymap: {}, + }, + type: EDITOR_TYPE.YMAP_NODE, + }, + { + data: { + js: {}, + meta: {}, + params: {}, + shared: {}, + url: {}, + ymap: {}, + }, + type: EDITOR_TYPE.YMAP_NODE, + }, + { + data: { + js: {}, + params: {}, + secrets: {}, + shared: {}, + url: {}, + ymap: {}, + }, + type: EDITOR_TYPE.YMAP_NODE, + }, + { + data: { + js: {}, + meta: {}, + params: {}, + secrets: {}, + shared: {}, + url: {}, + ymap: {}, + }, + type: EDITOR_TYPE.YMAP_NODE, + }, + // CONTROL_NODE + { + data: { + js: {}, + params: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.CONTROL_NODE, + }, + { + data: { + js: {}, + meta: {}, + params: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.CONTROL_NODE, + }, + { + data: { + js: {}, + params: {}, + secrets: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.CONTROL_NODE, + }, + { + data: { + js: {}, + meta: {}, + params: {}, + secrets: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.CONTROL_NODE, + }, + // MARKDOWN_NODE + { + data: { + js: {}, + params: {}, + shared: {}, + url: {}, + }, + type: EDITOR_TYPE.MARKDOWN_NODE, + }, + { + data: { + js: {}, + meta: {}, + params: {}, + shared: {}, + url: {}, + }, + type: EDITOR_TYPE.MARKDOWN_NODE, + }, + { + data: { + js: {}, + params: {}, + secrets: {}, + shared: {}, + url: {}, + }, + type: EDITOR_TYPE.MARKDOWN_NODE, + }, + { + data: { + js: {}, + meta: {}, + params: {}, + secrets: {}, + shared: {}, + url: {}, + }, + type: EDITOR_TYPE.MARKDOWN_NODE, + }, + // MARKUP_NODE + { + data: { + config: {}, + js: {}, + params: {}, + shared: {}, + url: {}, + }, + type: EDITOR_TYPE.MARKUP_NODE, + }, + { + data: { + config: {}, + js: {}, + meta: {}, + params: {}, + shared: {}, + url: {}, + }, + type: EDITOR_TYPE.MARKUP_NODE, + }, + { + data: { + config: {}, + js: {}, + params: {}, + secrets: {}, + shared: {}, + url: {}, + }, + type: EDITOR_TYPE.MARKUP_NODE, + }, + { + data: { + config: {}, + js: {}, + meta: {}, + params: {}, + secrets: {}, + shared: {}, + url: {}, + }, + type: EDITOR_TYPE.MARKUP_NODE, + }, + // MODULE + { + data: {documentation_en: {}, documentation_ru: {}, js: {}}, + type: EDITOR_TYPE.MODULE, + }, + { + data: {documentation_en: {}, js: {}}, + type: EDITOR_TYPE.MODULE, + }, + { + data: {documentation_ru: {}, js: {}}, + type: EDITOR_TYPE.MODULE, + }, + { + data: {js: {}}, + type: EDITOR_TYPE.MODULE, + }, + // GRAVITY_CHARTS_NODE + { + data: { + config: {}, + js: {}, + params: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.GRAVITY_CHARTS_NODE, + }, + { + data: { + config: {}, + js: {}, + meta: {}, + params: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.GRAVITY_CHARTS_NODE, + }, + { + data: { + config: {}, + js: {}, + params: {}, + secrets: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.GRAVITY_CHARTS_NODE, + }, + { + data: { + config: {}, + js: {}, + meta: {}, + params: {}, + secrets: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.GRAVITY_CHARTS_NODE, + }, + // BLANK_CHART_NODE + { + data: { + config: {}, + js: {}, + params: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.ADVANCED_CHART_NODE, + }, + { + data: { + config: {}, + js: {}, + meta: {}, + params: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.ADVANCED_CHART_NODE, + }, + { + data: { + config: {}, + js: {}, + params: {}, + secrets: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.ADVANCED_CHART_NODE, + }, + { + data: { + config: {}, + js: {}, + meta: {}, + params: {}, + secrets: {}, + shared: {}, + ui: {}, + url: {}, + }, + type: EDITOR_TYPE.ADVANCED_CHART_NODE, + }, +]; diff --git a/src/server/components/charts-engine/components/__tests__/source-alias.test.ts b/src/server/components/charts-engine/components/__tests__/source-alias.test.ts new file mode 100644 index 0000000..fb63b73 --- /dev/null +++ b/src/server/components/charts-engine/components/__tests__/source-alias.test.ts @@ -0,0 +1,115 @@ +import type {Source, SourceConfig, SourceWithAPIConnector} from '../../types'; +import {convertToAPIConnectorSource, shouldUseAlias} from '../processor/source-alias'; +import { + getApiConnectorParamsFromSource, + isAPIConnectorSource, + prepareSource, +} from '../processor/sources'; + +describe('source-alias: legacy source to API Connector conversion', () => { + const aliasConfig: SourceConfig = { + dataEndpoint: 'https://api.example.com/v2', + passedCredentials: {cookie: true, oauth: true}, + allowedMethods: ['GET'], + aliasTo: {apiConnectionId: 'test-connection-id'}, + }; + + describe('convertToAPIConnectorSource', () => { + test('converts legacy URL to API Connector format', () => { + const result = convertToAPIConnectorSource( + {url: '/_example/issues?filter=open&page=1'}, + aliasConfig, + ); + + expect(result.apiConnectionId).toBe('test-connection-id'); + expect(result.method).toBe('GET'); + expect(result.path).toBe('/issues?filter=open&page=1'); + expect(result.url).toBeUndefined(); + expect(isAPIConnectorSource(result)).toBe(true); + }); + + test('uses source method when specified', () => { + const result = convertToAPIConnectorSource( + {url: '/_example/issues', method: 'POST'}, + {...aliasConfig, allowedMethods: ['GET', 'POST']}, + ); + + expect(result.method).toBe('POST'); + }); + + test('uses first allowedMethod from config as default', () => { + const result = convertToAPIConnectorSource( + {url: '/_api/data'}, + {...aliasConfig, allowedMethods: ['POST', 'GET']}, + ); + + expect(result.method).toBe('POST'); + }); + + test('falls back to GET when allowedMethods is undefined', () => { + const result = convertToAPIConnectorSource( + {url: '/_api/data'}, + {...aliasConfig, allowedMethods: undefined}, + ); + + expect(result.method).toBe('GET'); + }); + + test('works with prepareSource pipeline', () => { + const result = convertToAPIConnectorSource({url: '/_example/issues'}, aliasConfig); + + const prepared = prepareSource(result); + expect(prepared.url).toBe('/_bi_connections/test-connection-id/typed_query_raw'); + + // Double prepareSource must preserve _original + const prepared2 = prepareSource(prepared) as SourceWithAPIConnector; + expect((prepared2._original as Source).method).toBe('GET'); + expect(getApiConnectorParamsFromSource(prepared2).method).toBe('GET'); + }); + }); + + describe('shouldUseAlias', () => { + const enabledFlag = () => true; + + test('returns true when aliasTo exists and no OAuth header', () => { + expect(shouldUseAlias({url: '/_example/issues'}, aliasConfig, enabledFlag)).toBe(true); + }); + + test('skips alias for explicit OAuth header', () => { + const source: Source = { + url: '/_example/issues', + headers: {authorization: 'OAuth my-secret-token'}, + }; + + expect(shouldUseAlias(source, aliasConfig, enabledFlag)).toBe(false); + }); + + test('enable alias for non-OAuth Authorization headers', () => { + const source: Source = { + url: '/_example/issues', + headers: {authorization: 'Bearer some-token'}, + }; + + expect(shouldUseAlias(source, aliasConfig, enabledFlag)).toBe(true); + }); + + test('skips alias for OAuth header with capital Authorization', () => { + const source: Source = { + url: '/_example/issues', + headers: {Authorization: 'OAuth my-secret-token'}, + }; + + expect(shouldUseAlias(source, aliasConfig, enabledFlag)).toBe(false); + }); + + test('returns false when aliasTo is not defined', () => { + expect( + shouldUseAlias( + {url: '/_example/issues'}, + {dataEndpoint: 'https://api.example.com/v2'}, + enabledFlag, + ), + ).toBe(false); + }); + }); +}); diff --git a/src/server/components/charts-engine/components/__tests__/utils.test.ts b/src/server/components/charts-engine/components/__tests__/utils.test.ts new file mode 100644 index 0000000..0aebd10 --- /dev/null +++ b/src/server/components/charts-engine/components/__tests__/utils.test.ts @@ -0,0 +1,242 @@ +import {URL_ACTION_PARAMS_PREFIX} from '../../../../../shared'; +import {normalizeParams} from '../utils'; + +const cloneDeep = require('lodash/cloneDeep'); +const MockDate = require('mockdate'); + +const {resolveParams, resolveNormalizedParams} = require('../utils'); + +MockDate.set('2020-02-14T22:34:55.359Z'); + +const normalizeParamsTestData = { + test1: { + in: { + param1: '1', + param2: ['2'], + }, + out: { + params: { + param1: ['1'], + param2: ['2'], + }, + actionParams: {}, + }, + }, + test2: { + in: { + param1: '1', + param2: ['2'], + [`${URL_ACTION_PARAMS_PREFIX}param3`]: '3', + [`${URL_ACTION_PARAMS_PREFIX}param4`]: ['4'], + }, + out: { + params: { + param1: ['1'], + param2: ['2'], + }, + actionParams: { + param3: ['3'], + param4: ['4'], + }, + }, + }, + test3: { + in: { + [`${URL_ACTION_PARAMS_PREFIX}param3`]: '3', + [`${URL_ACTION_PARAMS_PREFIX}param4`]: ['4'], + }, + out: { + params: {}, + actionParams: { + param3: ['3'], + param4: ['4'], + }, + }, + }, +}; + +describe('Charts Engine utils', () => { + test('do not change incorrect formats', () => { + const params = { + a: [''], + b: ['2020-01-01T00:00:00.000Z'], + c: ['__relative_*0d'], + d: ['__relative_-0T'], + f: ['__interval___relative_-0d_'], + e: ['__interval___relative_-14d__relative_-7d'], + g: ['__interval_2020-01-01_'], + h: ['__interval_2020-01-01T23:59_01.01.2010'], + i: ['__interval_2020-01-01 23:59_2010-01-01'], + }; + + const resolvedNormalizedParams = resolveNormalizedParams(params); + + const resolvedParams = cloneDeep(params); + resolveParams(resolvedParams); + + expect(resolvedParams).toStrictEqual(params); + expect(resolvedNormalizedParams).toStrictEqual(params); + }); + + test('resolve relative', () => { + const params = { + a: ['__relative_-0d'], + b: ['__relative_+1w'], + c: ['__relative_-2M'], + d: ['__relative_+3Q'], + e: ['__relative_-4y'], + f: ['__relative_-24h'], + g: ['__relative_+60m'], + h: ['__relative_-300s'], + i: ['__relative_+1000ms'], + j: ['2021-01-01T00:00:00.000Z'], + }; + + const resolvedNormalizedParams = resolveNormalizedParams(params); + + const resolvedParams = cloneDeep(params); + resolveParams(resolvedParams); + + const expectation = { + a: ['2020-02-14T00:00:00.000Z'], + b: ['2020-02-21T00:00:00.000Z'], + c: ['2019-12-14T00:00:00.000Z'], + d: ['2020-11-14T00:00:00.000Z'], + e: ['2016-02-14T00:00:00.000Z'], + f: ['2020-02-13T22:34:55.359Z'], + g: ['2020-02-14T23:34:55.359Z'], + h: ['2020-02-14T22:29:55.359Z'], + i: ['2020-02-14T22:34:56.359Z'], + j: ['2021-01-01T00:00:00.000Z'], + }; + + expect(resolvedParams).toStrictEqual(expectation); + expect(resolvedNormalizedParams).toStrictEqual(expectation); + }); + + test('resolve relative with cast', () => { + const params = { + a: ['__relative_-0d_sw'], + b: ['__relative_+1w_eM'], + c: ['__relative_-2M_sQ'], + d: ['__relative_+3Q_ey'], + e: ['__relative_-4y_sw'], + f: ['__relative_-24h_em'], + g: ['__relative_+60m_ss'], + h: ['__relative_-300s_eh'], + i: ['__relative_+1000ms_sd'], + j: ['2021-01-01T00:00:00.000Z'], + }; + + const resolvedNormalizedParams = resolveNormalizedParams(params); + + const resolvedParams = cloneDeep(params); + resolveParams(resolvedParams); + + const expectation = { + a: ['2020-02-10T00:00:00.000Z'], + b: ['2020-02-29T23:59:59.999Z'], + c: ['2019-10-01T00:00:00.000Z'], + d: ['2020-12-31T23:59:59.999Z'], + e: ['2016-02-08T00:00:00.000Z'], + f: ['2020-02-13T22:34:59.999Z'], + g: ['2020-02-14T23:34:55.000Z'], + h: ['2020-02-14T22:59:59.999Z'], + i: ['2020-02-14T00:00:00.000Z'], + j: ['2021-01-01T00:00:00.000Z'], + }; + + expect(resolvedParams).toStrictEqual(expectation); + expect(resolvedNormalizedParams).toStrictEqual(expectation); + }); + + test('resolve interval', () => { + const params = { + a: ['__interval_01_2021'], + b: ['__interval_2020-01-01_2021-01-01'], + c: ['__interval_2020-01-01_2021-01-01'], + d: ['__interval_2020-01-01_2010-01-01'], + e: ['__interval_2020___relative_-0d'], + f: ['__interval___relative_+0d_2099-12-31'], + g: ['__interval_2020-01-01___relative_-15m'], + h: ['__interval___relative_-1d___relative_+1h'], + i: ['__interval___relative_+1h___relative_-1d'], + j: ['2021-01-01T00:00:00.000Z'], + }; + + const resolvedNormalizedParams = resolveNormalizedParams(params); + + const resolvedParams = cloneDeep(params); + resolveParams(resolvedParams); + + const expectation = { + a: ['__interval_01_2021'], + b: ['__interval_2020-01-01_2021-01-01'], + c: ['__interval_2020-01-01_2021-01-01'], + d: ['__interval_2020-01-01_2010-01-01'], + e: ['__interval_2020_2020-02-14T23:59:59.999Z'], + f: ['__interval_2020-02-14T00:00:00.000Z_2099-12-31'], + g: ['__interval_2020-01-01_2020-02-14T22:19:55.359Z'], + h: ['__interval_2020-02-13T00:00:00.000Z_2020-02-14T23:34:55.359Z'], + i: ['__interval_2020-02-14T23:34:55.359Z_2020-02-13T23:59:59.999Z'], + j: ['2021-01-01T00:00:00.000Z'], + }; + + expect(resolvedParams).toStrictEqual(expectation); + expect(resolvedNormalizedParams).toStrictEqual(expectation); + }); + + test('resolve interval with cast', () => { + const params = { + a: ['__interval_01_2021'], + b: ['__interval_2020-01-01_2021-01-01'], + c: ['__interval_2020-01-01_2021-01-01'], + d: ['__interval_2020-01-01_2010-01-01'], + e: ['__interval_2020___relative_-0d_sw'], + f: ['__interval___relative_+0d_eM_2099-12-31'], + g: ['__interval_2020-01-01___relative_-15m_sQ'], + h: ['__interval___relative_-1d_ey___relative_+1h_sd'], + i: ['__interval___relative_+1h_ed___relative_-1d_sM'], + j: ['2021-01-01T00:00:00.000Z'], + }; + + const resolvedNormalizedParams = resolveNormalizedParams(params); + + const resolvedParams = cloneDeep(params); + resolveParams(resolvedParams); + + const expectation = { + a: ['__interval_01_2021'], + b: ['__interval_2020-01-01_2021-01-01'], + c: ['__interval_2020-01-01_2021-01-01'], + d: ['__interval_2020-01-01_2010-01-01'], + e: ['__interval_2020_2020-02-10T00:00:00.000Z'], + f: ['__interval_2020-02-29T23:59:59.999Z_2099-12-31'], + g: ['__interval_2020-01-01_2020-01-01T00:00:00.000Z'], + h: ['__interval_2020-12-31T23:59:59.999Z_2020-02-14T00:00:00.000Z'], + i: ['__interval_2020-02-14T23:59:59.999Z_2020-02-01T00:00:00.000Z'], + j: ['2021-01-01T00:00:00.000Z'], + }; + + expect(resolvedParams).toStrictEqual(expectation); + expect(resolvedNormalizedParams).toStrictEqual(expectation); + }); + + test('normalizeParams different cases', () => { + const res = normalizeParams(); + expect(res.params).toEqual({}); + expect(res.actionParams).toEqual({}); + + const res1 = normalizeParams(normalizeParamsTestData.test1.in); + expect(res1.params).toEqual(normalizeParamsTestData.test1.out.params); + expect(res1.actionParams).toEqual(normalizeParamsTestData.test1.out.actionParams); + + const res2 = normalizeParams(normalizeParamsTestData.test2.in); + expect(res2.params).toEqual(normalizeParamsTestData.test2.out.params); + expect(res2.actionParams).toEqual(normalizeParamsTestData.test2.out.actionParams); + + const res3 = normalizeParams(normalizeParamsTestData.test3.in); + expect(res3.params).toEqual(normalizeParamsTestData.test3.out.params); + expect(res3.actionParams).toEqual(normalizeParamsTestData.test3.out.actionParams); + }); +}); diff --git a/src/server/components/charts-engine/components/chart-generator.ts b/src/server/components/charts-engine/components/chart-generator.ts new file mode 100644 index 0000000..33bf778 --- /dev/null +++ b/src/server/components/charts-engine/components/chart-generator.ts @@ -0,0 +1,337 @@ +import type {Request} from '@gravity-ui/expresskit'; +import type {AppContext} from '@gravity-ui/nodekit'; + +import {type FeatureConfig, type StringParams, type ValueOf, WizardType} from '../../../../shared'; + +const commonTemplateGraph = ` +const {buildHighchartsConfig, buildLibraryConfig} = require('#module'); + +const buildFn = typeof buildLibraryConfig === 'function' ? buildLibraryConfig : buildHighchartsConfig; + +const result = buildFn({ + shared: Editor.getSharedData(), + params: Editor.getParams(), + actionParams: Editor.getActionParams(), + widgetConfig: Editor.getWidgetConfig(), + Editor +}); + +// your code here + +module.exports = result; +`; + +const commonTemplate = { + prepare: ` +const {buildGraph} = require('#module'); + +const result = buildGraph({ + apiVersion: '#apiVersion', + data: Editor.getLoadedData(), + shared: Editor.getSharedData(), + params: Editor.getParams(), + actionParams: Editor.getActionParams(), + widgetConfig: Editor.getWidgetConfig(), + Editor, +}); + +// your code here + +module.exports = result; +`, + params: ` +const {buildParams} = require('#module'); + +if (buildParams) { + const result = buildParams({ + shared: Editor.getSharedData(), + Editor + }); + + // your code here + + module.exports = result; +} else { + // your code here + + module.exports = #params; +} +`, + shared: null, + config: ` +const {buildChartsConfig} = require('#module'); + +const result = buildChartsConfig({ + shared: Editor.getSharedData(), + params: Editor.getParams(), + actionParams: Editor.getActionParams(), + widgetConfig: Editor.getWidgetConfig(), + Editor +}); + +// your code here + +module.exports = result; +`, + controls: ` +const {buildUI} = require('#module'); + +if (buildUI) { + const result = buildUI({ + shared: Editor.getSharedData(), + params: Editor.getParams(), + actionParams: Editor.getActionParams(), + widgetConfig: Editor.getWidgetConfig(), + Editor + }); + + // your code here + + module.exports = result; +} +`, + sources: ` +const {buildSources} = require('#module'); + +const result = buildSources({ + apiVersion: '#apiVersion', + shared: Editor.getSharedData(), + params: Editor.getParams(), + Editor +}); + +// your code here + +module.exports = result; +`, +}; + +const getGravityChartEditorTemplate = ({module, params}: {module: string; params: string}) => ({ + params: `module.exports = ${params}; + `, + sources: `const {buildSources} = require('${module}'); + +const result = buildSources({ + apiVersion: '2', + shared: Editor.getSharedData(), + params: Editor.getParams(), + Editor +}); + +// your code here + +module.exports = result; +`, + config: `const {buildChartsConfig} = require('${module}'); + +const result = buildChartsConfig({ + shared: Editor.getSharedData(), + params: Editor.getParams(), + widgetConfig: Editor.getWidgetConfig(), +}); + +// your code here + +module.exports = result; +`, + prepare: `const {buildGravityChartsConfig} = require('${module}'); + +const result = buildGravityChartsConfig({ + data: Editor.getLoadedData(), + shared: Editor.getSharedData(), + Editor, +}); + +// your code here + +module.exports = result; +`, +}); + +export type ChartTemplates = { + datalens: ChartTemplate; + sql: ChartTemplate; + ql: ChartTemplate; + control_dash: ChartTemplate; + solomon: ChartTemplate; + legacy_wizard: ChartTemplate; +}; + +type ChartTemplate = { + module: string; + identifyParams: (data: unknown, req: Request) => StringParams; + identifyChartType: (data: unknown, req: Request, features?: FeatureConfig) => string; + identifyLinks: (data: unknown, req: Request) => Record; +}; + +type Chart = { + prepare?: string; + params: string; + shared: null | string; + config: string; + controls?: string; + sources?: string; + table?: string; + statface_metric?: string; + graph?: string; + statface_graph?: string; + meta?: string; +}; + +function createChartManifest(args: {links?: Record}) { + const manifest = { + links: args.links ?? {}, + }; + + return JSON.stringify(manifest, null, 4); +} + +function getChartTemplate(ctx: AppContext, chartOldType?: string, template?: keyof ChartTemplates) { + const config = ctx.config; + const chartTemplates = config.chartTemplates as ChartTemplates; + + if (!template && chartOldType && chartOldType in chartTemplates) { + template = chartOldType as keyof ChartTemplates; + } + + const chartTemplate = template && chartTemplates[template]; + + if (!chartTemplate) { + throw new Error('Unknown chart template'); + } + + return chartTemplate; +} + +export const chartGenerator = { + gatherChartLinks: (options: { + req: Request; + shared: {type?: string}; + chartTemplate: ValueOf; + }) => { + const {req, shared, chartTemplate} = options; + + let links; + + if (chartTemplate.identifyLinks) { + links = chartTemplate.identifyLinks(shared, req); + } + + return links; + }, + serializeShared: (options: { + ctx: AppContext; + shared: {type?: string}; + links: Record | undefined; + }) => { + const {ctx, shared, links} = options; + const output: { + shared: string; + meta?: string; + } = { + shared: '', + }; + + try { + output.shared = JSON.stringify(shared, null, 4); + } catch (e) { + throw new Error('Invalid chart data'); + } + + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + if (isEnabledServerFeature('EnableChartEditorMetaTab')) { + output.meta = createChartManifest({links}); + } + + return output; + }, + identifyChartTemplate: (options: { + ctx: AppContext; + shared: {type?: string}; + template?: keyof ChartTemplates; + }) => { + const {shared, ctx, template} = options; + + const chartTemplate = getChartTemplate(ctx, shared.type, template); + + if (!chartTemplate) { + throw new Error('Invalid chart data type'); + } + + return { + type: shared.type as string, + chartTemplate, + }; + }, + generateChart: ({ + data, + template, + req, + ctx, + }: { + data: {type?: string}; + template?: keyof ChartTemplates; + req: Request; + ctx: AppContext; + }) => { + const {chartTemplate} = chartGenerator.identifyChartTemplate({ctx, shared: data, template}); + const params = chartTemplate.identifyParams(data, req); + const type = chartTemplate.identifyChartType(data, req); + const links = chartGenerator.gatherChartLinks({req, shared: data, chartTemplate}); + const serializedData = chartGenerator.serializeShared({ctx, shared: data, links}); + + switch (type) { + case WizardType.GravityChartsWizardNode: { + const chart: Chart = { + ...getGravityChartEditorTemplate({ + module: chartTemplate.module, + params: JSON.stringify(params), + }), + shared: serializedData.shared, + meta: serializedData.meta, + }; + + return {chart, links, type}; + } + default: { + const chart: Chart = {...commonTemplate}; + + chart.shared = serializedData.shared; + + if (serializedData.meta) { + chart.meta = serializedData.meta; + } + + chart.params = chart.params.replace('#params', JSON.stringify(params)); + if (chart.params.indexOf('#module') > -1) { + chart.params = chart.params.replace('#module', chartTemplate.module); + } + + const isTable = type.indexOf('table') > -1; + if (type.indexOf('metric') > -1) { + chart.statface_metric = chart.config.replace('#module', chartTemplate.module); + } else if (type.indexOf('markup') > -1 || isTable) { + chart.config = chart.config.replace('#module', chartTemplate.module); + } else { + chart.graph = commonTemplateGraph.replace('#module', chartTemplate.module); + + chart.statface_graph = chart.config.replace('#module', chartTemplate.module); + } + + chart.prepare = chart.prepare?.replace('#module', chartTemplate.module) ?? ''; + chart.sources = chart.sources?.replace('#module', chartTemplate.module) ?? ''; + chart.controls = chart.controls?.replace('#module', chartTemplate.module); + + const apiVersion = '2'; + + chart.prepare = chart.prepare.replace('#apiVersion', apiVersion); + chart.sources = chart.sources.replace('#apiVersion', apiVersion); + + const chartsWithConfig = isTable; + const {config: _, ...chartWithoutConfig} = chart; + + return {chart: chartsWithConfig ? chart : chartWithoutConfig, links, type}; + } + } + }, +}; diff --git a/src/server/components/charts-engine/components/chart-validator.ts b/src/server/components/charts-engine/components/chart-validator.ts new file mode 100644 index 0000000..c702107 --- /dev/null +++ b/src/server/components/charts-engine/components/chart-validator.ts @@ -0,0 +1,116 @@ +import {isEqual} from 'lodash'; + +import {EDITOR_TYPE} from '../../../../shared/constants'; + +// Parts in Set keys should be separeted by comma and defined in alphabetic order +const MODEL_TABS: Record> = { + [EDITOR_TYPE.GRAPH_NODE]: new Set([ + 'graph,js,params,shared,statface_graph,ui,url', + 'graph,js,meta,params,shared,statface_graph,ui,url', + 'graph,js,params,secrets,shared,statface_graph,ui,url', + 'graph,js,meta,params,secrets,shared,statface_graph,ui,url', + ]), + + [EDITOR_TYPE.METRIC_NODE]: new Set([ + 'js,params,shared,statface_metric,url', + 'js,meta,params,shared,statface_metric,url', + 'js,params,secrets,shared,statface_metric,url', + 'js,meta,params,secrets,shared,statface_metric,url', + ]), + + [EDITOR_TYPE.TABLE_NODE]: new Set([ + 'js,params,shared,table,ui,url', + 'js,meta,params,shared,table,ui,url', + 'js,params,secrets,shared,table,ui,url', + 'js,meta,params,secrets,shared,table,ui,url', + ]), + + [EDITOR_TYPE.YMAP_NODE]: new Set([ + 'js,params,shared,url,ymap', + 'js,meta,params,shared,url,ymap', + 'js,params,secrets,shared,url,ymap', + 'js,meta,params,secrets,shared,url,ymap', + ]), + + [EDITOR_TYPE.CONTROL_NODE]: new Set([ + 'js,params,shared,ui,url', + 'js,meta,params,shared,ui,url', + 'js,params,secrets,shared,ui,url', + 'js,meta,params,secrets,shared,ui,url', + ]), + + [EDITOR_TYPE.MARKDOWN_NODE]: new Set([ + 'js,params,shared,url', + 'js,params,shared,url,ui', + 'js,meta,params,shared,url', + 'js,params,secrets,shared,url', + 'js,params,secrets,shared,url,ui', + 'js,meta,params,secrets,shared,url', + ]), + + [EDITOR_TYPE.MARKUP_NODE]: new Set([ + 'config,js,params,shared,url', + 'config,js,meta,params,shared,url', + 'config,js,params,secrets,shared,url', + 'config,js,meta,params,secrets,shared,url', + ]), + + [EDITOR_TYPE.MODULE]: new Set([ + 'documentation_en,documentation_ru,js', + 'documentation_en,documentation_ru,js,meta', + 'documentation_ru,js', + 'documentation_ru,js,meta', + 'documentation_en,js', + 'documentation_en,js,meta', + 'js', + 'js,meta', + ]), + + [EDITOR_TYPE.GRAVITY_CHARTS_NODE]: new Set([ + 'config,js,params,shared,ui,url', + 'config,js,meta,params,shared,ui,url', + 'config,js,params,secrets,shared,ui,url', + 'config,js,meta,params,secrets,shared,ui,url', + ]), + + [EDITOR_TYPE.ADVANCED_CHART_NODE]: new Set([ + 'config,js,params,shared,ui,url', + 'config,js,meta,params,shared,ui,url', + 'config,js,params,secrets,shared,ui,url', + 'config,js,meta,params,secrets,shared,ui,url', + ]), +}; + +MODEL_TABS[EDITOR_TYPE.BLANK_CHART_NODE] = MODEL_TABS[EDITOR_TYPE.ADVANCED_CHART_NODE]; + +export const chartValidator = { + validate: ({data, type}: {data: Record; type: string}) => { + let dataTabs = Object.keys(data).sort(); + const modelTabs = MODEL_TABS[type as keyof typeof MODEL_TABS]; + + if (modelTabs) { + dataTabs.forEach((key) => { + if (typeof data[key] !== 'string' && typeof data[key] !== 'object') { + throw new Error(`Each tab must have content (failed at tab "${key}")`); + } + }); + + // "activities" is not a required tab + dataTabs = dataTabs.filter((tab) => tab !== 'activities'); + + return Array.from(modelTabs).some((modelTab) => { + const oldTabs = modelTab.split(',').sort(); + const newTabs = modelTab + .replace('js', 'prepare') + .replace('ui', 'controls') + .replace('url', 'sources') + .replace('table', 'config') + .split(',') + .sort(); + return isEqual(oldTabs, dataTabs) || isEqual(newTabs, dataTabs); + }); + } else { + throw new Error(`Unknown chart type "${type}"`); + } + }, +}; diff --git a/src/server/components/charts-engine/components/error-handler.ts b/src/server/components/charts-engine/components/error-handler.ts new file mode 100644 index 0000000..325ddef --- /dev/null +++ b/src/server/components/charts-engine/components/error-handler.ts @@ -0,0 +1,49 @@ +export class ExtendedError extends Error { + handled?: boolean; + code?: string; + meta?: Record; +} + +// This error-handler working only with axios, because only axios has error.code +// eslint-disable-next-line no-unused-vars +export function createErrorHandler({meta = {}, rethrow = true}) { + const defaultRetrhow = rethrow; + const defaultMeta = meta; + return function handleError({ + code = '', + message: inputMessage = '', + error: inputError, + meta = {}, + rethrow = defaultRetrhow, // eslint-disable-line no-shadow + }: { + code?: string; + message?: string; + error?: ExtendedError; + meta: Record; + rethrow?: boolean; + }) { + const message = inputMessage || code || 'EMPTY_ERROR_MESSAGE'; + const error = inputError || new ExtendedError(message); + + if (!error.handled) { + error.handled = true; + if (code) { + if (error.code) { + meta.originalCode = error.code; + } + error.code = code; + } + } + + if (!error.meta) { + error.meta = {}; + } + error.meta = {...error.meta, ...meta, ...defaultMeta}; + + if (rethrow) { + throw error; + } + + return error; + }; +} diff --git a/src/server/components/charts-engine/components/markdown.ts b/src/server/components/charts-engine/components/markdown.ts new file mode 100644 index 0000000..9717efe --- /dev/null +++ b/src/server/components/charts-engine/components/markdown.ts @@ -0,0 +1,16 @@ +import type {MarkdownItPluginCb} from '@diplodoc/transform/lib/typings'; + +import {renderHTML as renderHtmlFromMarkdown} from '../../../../shared/modules/markdown/markdown'; +import {registry} from '../../../registry'; + +export function renderHTML( + args: {text: string; lang: string}, + additionalPlugins: MarkdownItPluginCb[] = [], +) { + const plugins = registry.getYfmPlugins() ?? []; + + return renderHtmlFromMarkdown({ + ...args, + plugins: [...plugins, ...additionalPlugins], + }); +} diff --git a/src/server/components/charts-engine/components/processor/chart-api-context.ts b/src/server/components/charts-engine/components/processor/chart-api-context.ts new file mode 100644 index 0000000..e23f480 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/chart-api-context.ts @@ -0,0 +1,168 @@ +import type {DashWidgetConfig, IChartEditor} from '../../../../../shared'; +import {WRAPPED_FN_KEY} from '../../../../../shared/constants/ui-sandbox'; +import {wrapHtml} from '../../../../../shared/utils/ui-sandbox'; +import {resolveIntervalDate, resolveOperation, resolveRelativeDate} from '../utils'; + +import {getCurrentPage, getParam, getSortParams} from './paramsUtils'; +import type {ChartApiContext} from './types'; +import {isWrapFnArgsValid} from './utils'; + +function getOrphanedObject() { + return Object.create(null); +} + +export type GetChartApiContextArgs = { + name: string; + params: Record; + actionParams: Record; + widgetConfig?: DashWidgetConfig['widgetConfig']; + data?: Record; + dataStats?: any; + shared: unknown; + hooks?: Record; + userLang: string | null; +}; + +export const getChartApiContext = (args: GetChartApiContextArgs): ChartApiContext => { + const { + name, + params, + actionParams, + widgetConfig, + data, + dataStats, + shared = {}, + hooks, + userLang, + } = args; + + const api: IChartEditor = { + getSharedData: () => shared, + getLang: () => userLang, + ...hooks?.getSandboxApiMethods(), + }; + + api.resolveRelative = resolveRelativeDate; + + api.resolveInterval = resolveIntervalDate; + + api.resolveOperation = resolveOperation; + + const context = { + ChartEditor: api, + __runtimeMetadata: getOrphanedObject(), + }; + + context.__runtimeMetadata.userConfigOverride = getOrphanedObject(); + context.__runtimeMetadata.libraryConfigOverride = getOrphanedObject(); + context.__runtimeMetadata.extra = getOrphanedObject(); + context.__runtimeMetadata.dataSourcesInfos = getOrphanedObject(); + + api.setError = (value) => { + context.__runtimeMetadata.error = value; + }; + + api.setChartsInsights = (value) => { + context.__runtimeMetadata.chartsInsights = value; + }; + + /** We need for backward compatibility with ≤0.19.2 */ + api._setError = api.setError; + + api.getWidgetConfig = () => widgetConfig || {}; + + api.getActionParams = () => actionParams || {}; + + api.wrapFn = (value) => { + if (!isWrapFnArgsValid(value)) { + // There is no way to reach this code, just satisfy ts + throw new Error('You should pass a valid arguments to Editor.wrapFn method'); + } + + const fnArgs = Array.isArray(value.args) + ? (value.args as unknown[]).map((arg: unknown) => + typeof arg === 'function' ? arg.toString() : arg, + ) + : value.args; + + return { + [WRAPPED_FN_KEY]: { + fn: value.fn.toString(), + args: fnArgs, + libs: value.libs, + }, + }; + }; + + api.generateHtml = wrapHtml; + + if (params) { + api.getParams = () => params; + api.getParam = (paramName: string) => getParam(paramName, params); + } + + if (name === 'Sources') { + api.getSortParams = () => getSortParams(params); + } + + if (name === 'Sources' || name === 'Prepare') { + api.getCurrentPage = () => getCurrentPage(params); + } + + if (name === 'Params' || name === 'Prepare' || name === 'Controls' || name === 'Sources') { + api.updateParams = (updatedParams) => { + context.__runtimeMetadata.userParamsOverride = Object.assign( + {}, + context.__runtimeMetadata.userParamsOverride, + updatedParams, + ); + }; + api.updateActionParams = (updatedActionParams) => { + context.__runtimeMetadata.userActionParamsOverride = Object.assign( + {}, + context.__runtimeMetadata.userActionParamsOverride, + updatedActionParams, + ); + }; + } + + if (name === 'Controls' || name === 'Prepare') { + api.getLoadedData = () => data || {}; + api.getLoadedDataStats = () => dataStats || {}; + api.setDataSourceInfo = (dataSourceKey, info) => { + context.__runtimeMetadata.dataSourcesInfos[dataSourceKey] = {info}; + }; + + if (name === 'Prepare') { + api.updateConfig = (updatedFragment) => { + context.__runtimeMetadata.userConfigOverride = Object.assign( + {}, + context.__runtimeMetadata.userConfigOverride, + updatedFragment, + ); + }; + api.updateHighchartsConfig = (updatedFragment) => { + context.__runtimeMetadata.libraryConfigOverride = Object.assign( + {}, + context.__runtimeMetadata.libraryConfigOverride, + updatedFragment, + ); + }; + api.updateLibraryConfig = api.updateHighchartsConfig; + api.setSideHtml = (html) => { + context.__runtimeMetadata.sideMarkdown = html; + }; + api.setSideMarkdown = (markdown: string) => { + context.__runtimeMetadata.sideMarkdown = markdown; + }; + api.setExtra = (key, value) => { + context.__runtimeMetadata.extra[key] = value; + }; + api.setExportFilename = (filename: string) => { + context.__runtimeMetadata.exportFilename = filename; + }; + } + } + + return context; +}; diff --git a/src/server/components/charts-engine/components/processor/comments-fetcher.ts b/src/server/components/charts-engine/components/processor/comments-fetcher.ts new file mode 100644 index 0000000..b240925 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/comments-fetcher.ts @@ -0,0 +1,283 @@ +import {stringify} from 'querystring'; + +import type {ChartData} from '@gravity-ui/chartkit/gravity-charts'; +import type {StringParams} from '@gravity-ui/chartkit/highcharts'; +import type {AppContext} from '@gravity-ui/nodekit'; +import type {AxiosRequestConfig} from 'axios'; +import axios from 'axios'; +import get from 'lodash/get'; +import moment from 'moment'; + +import type {ChartComment} from '../../../../../shared'; + +const TEN_SECONDS = 10000; + +type UsComment = ChartComment; + +export type Graph = { + id?: string; + fname?: string; + title?: string; + name?: string; + data: (number | number[] | {x: number; t?: string; v?: Date})[]; + type?: string; +}; + +type CommentsConfig = { + feeds?: [ + { + feed: string; + matchedParams: string[]; + }, + ]; + matchType?: 'full' | 'contains'; + matchedParams?: string[]; + path?: string; +}; + +type CommentsFetcherFetchParams = { + feeds: {feed: string; params: UsComment['params']}[]; + statFeed: {path: string; field_name: string} | null; + meta: {matchType?: 'full' | 'contains'; dateFrom: string; dateTo: string}; +}; + +export type CommentsFetcherPrepareCommentsParams = { + chartName: string; + config?: CommentsConfig; + data: + | { + graphs: Graph[]; + categories_ms?: number[]; + } + | Graph[]; + params: Record; +}; + +export type CommentsFetcherFetchResult = { + comments: UsComment[]; + logs: {path?: string; feed?: string; error: Error['message']}[]; +}; + +export class CommentsFetcher { + static async fetch( + {feeds, meta: {matchType = 'full', dateFrom, dateTo}}: CommentsFetcherFetchParams, + headers: AxiosRequestConfig['headers'], + ctx: AppContext, + ): Promise { + const comments: UsComment[] = []; + const logs: {path?: string; feed?: string; error: Error['message']}[] = []; + + const usEndpoint = ctx.config.endpoints.api.us; + + // Filtering feeds to cancel loading same feeds + const knownFeeds = new Set(); + const filteredFeeds = feeds.filter((feedData) => { + if (knownFeeds.has(feedData.feed)) { + return false; + } else { + knownFeeds.add(feedData.feed); + return true; + } + }); + + const requests = filteredFeeds.map(({feed, params = {}}) => { + const paramsArray = Object.keys(params) as Array; + const processedParams = paramsArray.reduce< + Record + >((result, key) => { + result[`params[${key}]`] = params[key]; + return result; + }, {}); + + const query = stringify({ + feed, + dateFrom, + dateTo, + ...processedParams, + matchType, + }); + + return axios({ + method: 'get', + url: `${usEndpoint}/v1/comments?${query}`, + headers, + timeout: TEN_SECONDS, + }).then( + (response) => { + return comments.push(...response.data); + }, + (error) => { + logs.push({feed, error: error.message}); + ctx.logError('FETCH_COMMENTS_UNITED_STORAGE_ERROR', error); + }, + ); + }); + + await Promise.all(requests); + + return { + comments: comments.sort((a, b) => moment(b.date).valueOf() - moment(a.date).valueOf()), + logs, + }; + } + + static prepareComments( + {chartName, config = {}, data, params}: CommentsFetcherPrepareCommentsParams, + headers: AxiosRequestConfig['headers'], + ctx: AppContext, + ) { + // Possible data structure: + // * [{...},...] + // * {graphs: [{...},...]} + // * {graphs: [{...},...], categories_ms: [...]} + const series = Array.isArray(data) ? data : data.graphs; + + if (!Array.isArray(series)) { + return undefined; + } + + const seriesIds = series.map( + (graph) => graph.id || graph.fname || graph.title || graph.name || '', + ); + + let dateFromMs: number | undefined; + let dateToMs: number | undefined; + + if (!Array.isArray(data) && data.categories_ms?.length) { + dateFromMs = data.categories_ms[0]; + dateToMs = data.categories_ms[data.categories_ms.length - 1]; + } else { + series.forEach(({data}) => { + if (Array.isArray(data)) { + data.forEach((value) => { + let x; + + if (Array.isArray(value)) { + x = value[0]; + } else if (value !== null && typeof value === 'object') { + x = value.x; + } + + if (typeof x === 'number') { + dateFromMs = dateFromMs === undefined ? x : Math.min(dateFromMs, x); + dateToMs = dateToMs === undefined ? x : Math.max(dateToMs, x); + } + }); + } + }); + } + + if (!dateFromMs || !dateToMs) { + return undefined; + } + + const fetchCommentsArgs = getFetchCommentsArgs({ + config, + chartName, + params, + seriesIds, + dateFromMs, + dateToMs, + }); + return CommentsFetcher.fetch( + { + ...fetchCommentsArgs, + }, + headers, + ctx, + ); + } + + static prepareGravityChartsComments( + { + chartName, + config = {}, + data, + params, + }: { + chartName: string; + config?: CommentsConfig; + data: Partial; + params: StringParams; + }, + headers: AxiosRequestConfig['headers'], + ctx: AppContext, + ) { + const series = data.series?.data ?? []; + + const seriesIds = series.map((s) => get(s, 'name', '')); + + let dateFromMs: number | undefined; + let dateToMs: number | undefined; + + series.forEach((s) => { + s.data.forEach((value) => { + const field = s.type === 'bar-y' ? 'y' : 'x'; + const val = get(value, field); + + if (typeof val === 'number') { + dateFromMs = dateFromMs === undefined ? val : Math.min(dateFromMs, val); + dateToMs = dateToMs === undefined ? val : Math.max(dateToMs, val); + } + }); + }); + + if (!dateFromMs || !dateToMs) { + return undefined; + } + + const fetchCommentsArgs = getFetchCommentsArgs({ + config, + chartName, + params, + seriesIds, + dateFromMs, + dateToMs, + }); + return CommentsFetcher.fetch( + { + ...fetchCommentsArgs, + }, + headers, + ctx, + ); + } +} + +function getFetchCommentsArgs({ + config, + chartName, + params, + seriesIds, + dateFromMs, + dateToMs, +}: { + config: CommentsConfig; + chartName: string; + params: StringParams; + seriesIds: string[]; + dateFromMs: number; + dateToMs: number; +}) { + const {path, matchedParams = [], matchType, feeds = []} = config; + return { + feeds: [{feed: chartName, matchedParams}].concat(feeds).map(({feed, matchedParams = []}) => + matchedParams.reduce<{ + feed: string; + params: Record; + }>( + (result, name) => { + result.params[name] = params[name]; + return result; + }, + {feed, params: {}}, + ), + ), + statFeed: path ? {path, field_name: ['none'].concat(seriesIds).join(',')} : null, + meta: { + matchType, + dateFrom: moment.utc(dateFromMs).format(), + dateTo: moment.utc(dateToMs).format(), + }, + }; +} diff --git a/src/server/components/charts-engine/components/processor/console.ts b/src/server/components/charts-engine/components/processor/console.ts new file mode 100644 index 0000000..ec9a431 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/console.ts @@ -0,0 +1,87 @@ +import util from 'util'; + +import {cloneDeepWith} from 'lodash'; + +const MAX_LOGS_ROWS = 1000; + +export type LogItem = { + type: string; + value: string; +}; + +export class Console { + private logs: LogItem[][]; + private isScreenshoter: boolean; + constructor(settings: {isScreenshoter?: boolean} = {}) { + this.logs = []; + this.isScreenshoter = Boolean(settings.isScreenshoter); + } + + log(...args: unknown[]) { + if (this.isScreenshoter) { + return; + } + if (this.logs.length >= MAX_LOGS_ROWS) { + return; + } + const rowLogs: LogItem[] = []; + + args.forEach((input) => { + const linkSet = new Set(); + + function customCloneAnalyze(value: unknown) { + const complex = value !== null && typeof value === 'object'; + + if (complex && linkSet.has(value)) { + return '[Circular]'; + } + + if (complex) { + linkSet.add(value); + } + + if (value instanceof Set || value instanceof Map) { + return util.inspect(value); + } + + if (typeof value === 'undefined') { + return '[undefined]'; + } + + return undefined; + } + + rowLogs.push({ + type: typeof input, + value: cloneDeepWith(input, customCloneAnalyze), + }); + }); + + this.logs.push(rowLogs); + } + + getLogs() { + if (this.logs.length >= MAX_LOGS_ROWS) { + this.logs.push([ + { + type: 'string', + value: 'Too much logs', + }, + ]); + } + + try { + JSON.stringify(this.logs); + return this.logs; + } catch (e) { + return [ + [ + { + type: 'string', + value: (e as Error).message, + }, + ], + ]; + } + } +} diff --git a/src/server/components/charts-engine/components/processor/control-builder.ts b/src/server/components/charts-engine/components/processor/control-builder.ts new file mode 100644 index 0000000..52ad238 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/control-builder.ts @@ -0,0 +1,151 @@ +import type {StringParams} from '../../../../../shared'; +import {buildUI} from '../../../../modes/charts/plugins/control/controls'; +import {buildGraph} from '../../../../modes/charts/plugins/control/js'; +import type {SourceResponseData} from '../../../../modes/charts/plugins/control/js/types'; +import type {ControlShared} from '../../../../modes/charts/plugins/control/types'; +import {buildSources} from '../../../../modes/charts/plugins/control/url'; + +import {getChartApiContext} from './chart-api-context'; +import type {ChartBuilder} from './types'; + +type ChartBuilderArgs = { + config: { + data: { + shared: string; + }; + }; +}; + +export const getControlBuilder = async (args: ChartBuilderArgs): Promise => { + const {config} = args; + let shared: ControlShared; + + // Nothing happens here - just for compatibility with the editor + const emptyStep = (name: string) => async (options: {params: StringParams}) => { + const {params} = options; + const timeStart = process.hrtime(); + const context = getChartApiContext({ + name, + shared, + params, + actionParams: {} as Record, + userLang: null, + }); + + return { + exports: {}, + executionTiming: process.hrtime(timeStart), + name, + runtimeMetadata: context.__runtimeMetadata, + }; + }; + + const ChartBuilder: ChartBuilder = { + type: 'CONTROL', + buildShared: async () => { + if (typeof config.data.shared === 'string') { + shared = JSON.parse(config.data.shared); + } else { + shared = config.data.shared; + } + }, + buildModules: async () => { + return {}; + }, + buildParams: async (options) => { + const {params} = options; + const timeStart = process.hrtime(); + const context = getChartApiContext({ + name: 'Params', + shared, + params, + actionParams: {} as Record, + userLang: null, + }); + return { + exports: {}, + executionTiming: process.hrtime(timeStart), + name: 'Params', + runtimeMetadata: context.__runtimeMetadata, + }; + }, + buildUrls: async (options) => { + const {params} = options; + const timeStart = process.hrtime(); + + const context = getChartApiContext({ + name: 'Sources', + shared, + params, + actionParams: {} as Record, + userLang: null, + }); + + return { + exports: buildSources({ + shared, + params, + }), + executionTiming: process.hrtime(timeStart), + name: 'Sources', + runtimeMetadata: context.__runtimeMetadata, + }; + }, + + buildChartLibraryConfig: emptyStep('Highcharts'), + + buildChartConfig: emptyStep('Config'), + + buildChart: async (options) => { + const {data, params} = options; + const timeStart = process.hrtime(); + + const context = getChartApiContext({ + name: 'Prepare', + shared, + params, + actionParams: {} as Record, + userLang: null, + }); + + buildGraph({ + data: data as unknown as SourceResponseData, + shared, + params, + ChartEditor: context.ChartEditor, + }); + + return { + exports: {}, + executionTiming: process.hrtime(timeStart), + name: 'Prepare', + runtimeMetadata: context.__runtimeMetadata, + }; + }, + + buildUI: async (options) => { + const {params} = options; + const timeStart = process.hrtime(); + + const context = getChartApiContext({ + name: 'Controls', + shared: shared, + params: params, + actionParams: {} as Record, + userLang: null, + }); + + return { + exports: buildUI({ + shared: shared, + }), + executionTiming: process.hrtime(timeStart), + name: 'Controls', + runtimeMetadata: context.__runtimeMetadata, + }; + }, + dispose: () => {}, + }; + + return ChartBuilder; +}; diff --git a/src/server/components/charts-engine/components/processor/data-fetcher.ts b/src/server/components/charts-engine/components/processor/data-fetcher.ts new file mode 100644 index 0000000..d76a347 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/data-fetcher.ts @@ -0,0 +1,1248 @@ +/* eslint-disable complexity */ +import type {IncomingHttpHeaders, OutgoingHttpHeaders} from 'http'; +import querystring from 'node:querystring'; +import url from 'node:url'; + +import type {AppContext} from '@gravity-ui/nodekit'; +import {REQUEST_ID_PARAM_NAME} from '@gravity-ui/nodekit'; +import {isObject, isString} from 'lodash'; +import sizeof from 'object-sizeof'; +import PQueue from 'p-queue'; + +import type {ChartsEngine} from '../..'; +import type {WorkbookId} from '../../../../../shared'; +import { + DL_CONTEXT_HEADER, + DL_EMBED_TOKEN_HEADER, + Feature, + SuperuserHeader, + WORKBOOK_ID_HEADER, +} from '../../../../../shared'; +import type {PartialDatasetField} from '../../../../../shared/schema/types'; +import {registry} from '../../../../registry'; +import type {CacheClient} from '../../../cache-client'; +import {config} from '../../constants'; +import type {AdapterContext, Source, SourceConfig, TelemetryCallbacks} from '../../types'; +import {Request as RequestPromise} from '../request'; +import {hideSensitiveData} from '../utils'; + +import {convertToAPIConnectorSource, shouldUseAlias} from './source-alias'; +import {getApiConnectorParamsFromSource, isAPIConnectorSource, prepareSource} from './sources'; +import {getMessageFromUnknownError} from './utils'; + +/** + * Unwraps API Connector response format {data: {body: {result: X}}} -> X + */ +function unwrapAPIConnectorResponse(body: unknown): unknown { + const result = (body as any)?.data?.body?.result; + return result === undefined ? body : result; +} + +const { + ALL_REQUESTS_SIZE_LIMIT_EXCEEDED, + ALL_REQUESTS_TIMEOUT_EXCEEDED, + CONCURRENT_REQUESTS_LIMIT, + DEFAULT_FETCHING_TIMEOUT, + DEFAULT_SINGLE_FETCHING_TIMEOUT, + EMPTY_RESPONSE, + INVALID_SOURCE_FORMAT, + INVALID_SOURCES_FORMAT, + REDACTED_DATA_PLACEHOLDER, + REDIRECT, + REQUEST_CANCELLED, + REQUEST_SIZE_LIMIT_EXCEEDED, + SOURCE_IS_CIRCULAR, + UNHANDLED_INTERNAL_SERVER_ERROR, + UNKNOWN_SOURCE, +} = config; + +// https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 +const ALLOWED_HTTP_METHODS = ['GET', 'POST']; +const ALLOWED_REQUEST_FORMATS = ['json', 'form']; + +type ChartkitSourceDecription = {title?: string}; +type ChartkitSource = { + description?: ChartkitSourceDecription; + uiEndpoint?: string; + dataEndpoint?: string; +}; +type PromiseWithAbortController = [Promise, AbortController]; + +type DataFetcherOptions = { + sources: Record; + ctx: AppContext; + postprocess?: + | (( + data: Record, + options?: Record, + ) => Record) + | null; + subrequestHeaders: Record; + userId?: string | null; + userLogin?: string | null; + iamToken?: string | null; + workbookId?: WorkbookId; + isEmbed?: boolean; + authParams?: AuthParams | undefined; + originalReqHeaders: DataFetcherOriginalReqHeaders; + adapterContext: AdapterContext; + telemetryCallbacks: TelemetryCallbacks; + cacheClient: CacheClient; + sourcesConfig: ChartsEngine['sources']; +}; + +export type DataFetcherOriginalReqHeaders = { + xRealIP: IncomingHttpHeaders['x-real-ip']; + xForwardedFor: IncomingHttpHeaders['x-forwarded-for']; + xChartsFetcherVia: IncomingHttpHeaders['x-charts-fetcher-via']; + referer: IncomingHttpHeaders['referer']; +}; + +type DataFetcherRequestOptions = { + headers: OutgoingHttpHeaders; + method: string; + uri: string; + timeout: number; + spStatFormat: string | string[] | null; + maxRedirects?: number; + form?: string | Record>; + body?: string | Record>; + json?: boolean; +}; + +type SourceWithMiddlewareUrl = Source & Required>; + +function isSourceWithMiddlewareUrl(source: Source): source is SourceWithMiddlewareUrl { + return isObject(source.middlewareUrl) && isObject(source.sourceArgs); +} + +function getDatasetId(publicTargetUri?: string | Record) { + if (!publicTargetUri || typeof publicTargetUri !== 'string') { + return null; + } + + // [backend-api]/api/data/v1/datasets/[id]/versions/draft/result + return publicTargetUri.split('/')[7]; +} + +function cancelRequestsPromises( + requestsPromises: PromiseWithAbortController[], + cancelCode: string, + current?: Promise, +) { + requestsPromises.forEach((requestPromise: PromiseWithAbortController) => { + if (requestPromise[0] === current) { + return; + } + + requestPromise[1].abort(cancelCode); + }); +} + +function filterObjectWhitelist(source: Record, whitelist?: string[]) { + return whitelist + ? Object.keys(source).reduce>((acc, key) => { + if (whitelist.includes(key)) { + acc[key] = source[key]; + } + return acc; + }, {}) + : source; +} + +const isFetchLimitError = (errorMessage: string) => + errorMessage === `Error: ${REQUEST_SIZE_LIMIT_EXCEEDED}` || + errorMessage === `Error: ${ALL_REQUESTS_SIZE_LIMIT_EXCEEDED}`; + +export type DataFetcherResult = { + sourceId: string; + sourceType: string; + source?: Source; + body: unknown; + responseHeaders: IncomingHttpHeaders; + status: number; + latency: number; + size: number; + uiUrl?: string; + dataUrl?: string; + datasetId: string; + datasetFields?: PartialDatasetField[]; + hideInInspector?: boolean; + url: string; + message?: string; + code?: string; + data?: any; + details?: string; +}; + +type FetchSourceResult = { + sourceId: string; + sourceType: string; + body?: unknown; + responseHeaders?: IncomingHttpHeaders | null; + status?: number | null; + latency?: number; + size?: number; + uiUrl?: string | null; + dataUrl?: string | null; + datasetId?: string | null; + hideInInspector?: boolean; + url?: string | null; + message?: string; + code?: string | null; + data?: unknown; + details?: string; + source?: Source; +}; + +export type AuthParams = { + accessToken?: string; +}; + +export function addAuthHeaders({ + headers, + authParams, +}: { + headers: OutgoingHttpHeaders; + authParams: AuthParams; +}) { + if (authParams?.accessToken) { + Object.assign(headers, {authorization: `Bearer ${authParams.accessToken}`}); + } +} + +export class DataFetcher { + static async fetch(options: DataFetcherOptions) { + const result = await this.fetchSources(options); + return result.result; + } + + static fetchSources({ + sources, + ctx, + postprocess = null, + subrequestHeaders, + userId, + userLogin, + iamToken, + workbookId, + isEmbed = false, + authParams, + originalReqHeaders, + adapterContext, + telemetryCallbacks, + cacheClient, + sourcesConfig, + }: DataFetcherOptions): Promise<{ + result: Record; + sources?: Record; + }> { + const fetchingTimeout = ctx.config.fetchingTimeout || DEFAULT_FETCHING_TIMEOUT; + + const fetchingStartTime = Date.now(); + + const processingRequests: PromiseWithAbortController[] = []; + + const overallTimeout = setTimeout(() => { + cancelRequestsPromises(processingRequests, ALL_REQUESTS_TIMEOUT_EXCEEDED); + }, fetchingTimeout); + + return new Promise((resolve, reject) => { + if (typeof sources !== 'object' && sources !== null) { + return reject({ + code: INVALID_SOURCES_FORMAT, + }); + } + + const queue = new PQueue({concurrency: CONCURRENT_REQUESTS_LIMIT}); + const fetchPromisesList: (() => unknown)[] = []; + + if (!originalReqHeaders || !adapterContext) { + throw new Error('Missing original request headers or adapter context'); + } + + Object.keys(sources).forEach((sourceName) => { + const source = sources[sourceName]; + + fetchPromisesList.push(() => + source + ? DataFetcher.fetchSource({ + ctx, + sourceName, + source: isString(source) ? {url: source} : source, + fetchingStartTime, + subrequestHeaders, + processingRequests, + rejectFetchingSource: reject, + userId, + userLogin, + iamToken, + workbookId, + isEmbed, + authParams, + originalReqHeaders: + originalReqHeaders as DataFetcherOriginalReqHeaders, + adapterContext: adapterContext as AdapterContext, + telemetryCallbacks, + cacheClient, + sourcesConfig, + }) + : { + sourceId: sourceName, + sourceType: 'Invalid', + code: INVALID_SOURCE_FORMAT, + }, + ); + }); + + queue + .addAll(fetchPromisesList) + .then((results) => { + const failed: Record> = {}; + const fetched: Record = {}; + const fetchedSources: Record = {}; + + clearTimeout(overallTimeout); + + (results as FetchSourceResult[]).forEach((result) => { + Object.keys(result).forEach((key) => { + if ((result as Record)[key] === null) { + delete (result as Record)[key]; + } + }); + if (result.message || result.code) { + const entry: Record = { + sourceType: result.sourceType, + status: result.status, + message: result.message, + code: result.code, + responseHeaders: result.responseHeaders, + uiUrl: result.uiUrl, + dataUrl: result.dataUrl, + data: result.data, + hideInInspector: result.hideInInspector, + /** @deprecated use uiUrl and dataUrl */ + url: result.url, + details: result.details, + }; + + if (result.body) { + entry.body = result.body; + } + + failed[result.sourceId] = filterObjectWhitelist( + entry, + ctx.config.runResponseWhitelist, + ); + } else { + fetched[result.sourceId] = filterObjectWhitelist( + result, + ctx.config.runResponseWhitelist, + ) as DataFetcherResult; + + if (result.source) { + fetchedSources[result.sourceId] = result.source; + } + } + }); + + if (Object.keys(failed).length) { + reject(failed); + } else if (postprocess) { + resolve({result: postprocess(fetched), sources: fetchedSources}); + } else { + resolve({result: fetched, sources: fetchedSources}); + } + }) + .catch((error) => { + reject(error); + }); + }); + } + + static getSourceName(sourcePath: string) { + return sourcePath.replace(/\/([^?/]+)(.|\n)*/g, '$1').slice(1); + } + + /** + * @param {String} chartsEngine + * @param {String} sourcePath requested source + * + * @returns {Object} source configuration + */ + static getSourceConfig({ + sourcesConfig, + sourcePath, + isEmbed, + }: { + sourcesConfig: ChartsEngine['sources']; + sourcePath: string; + isEmbed?: boolean; + }) { + let sourceName = DataFetcher.getSourceName(sourcePath); + + // Temporary hack for embed endpoints + if (isEmbed && sourceName === 'bi_datasets') { + sourceName = 'bi_datasets_embed'; + } + if (isEmbed && sourceName === 'bi_connections') { + sourceName = 'bi_connections_embed'; + } + + const resultSourceType = Object.keys(sourcesConfig).find((sourceType) => { + if (sourceName === sourceType) { + return true; + } + + const aliases = sourcesConfig[sourceType].aliases; + if (aliases) { + return aliases.has(sourceName); + } + + return false; + }); + + if (resultSourceType) { + const sourceConfig = sourcesConfig[resultSourceType]; + + sourceConfig.sourceType = resultSourceType; + + // Temporary hack for embed endpoints + if (isEmbed && resultSourceType === 'bi_datasets_embed') { + sourceConfig.sourceType = 'bi_datasets'; + } + + if (isEmbed && resultSourceType === 'bi_connections_embed') { + sourceConfig.sourceType = 'bi_connections'; + } + + return sourceConfig; + } else { + return null; + } + } + + /** + * @param {String} sourcesConfig + * @param {String} lang target lang + * + * @returns {Object} config for all sources + */ + static getChartKitSources({ + sourcesConfig, + lang = 'en', + }: { + sourcesConfig: ChartsEngine['sources']; + lang: 'en' | 'ru'; + }) { + const sources = sourcesConfig; + + const chartkitSources: Record = {}; + + Object.keys(sources).forEach((sourceType) => { + const chartkitSource: ChartkitSource = {}; + const source = sources[sourceType]; + + if (source.description) { + const chartkitSourceDescription: ChartkitSourceDecription = {}; + const description = source.description; + + if (description.title) { + chartkitSourceDescription.title = description.title[lang]; + } + + chartkitSource.description = chartkitSourceDescription; + } + + if (source.uiEndpoint) { + chartkitSource.uiEndpoint = source.uiEndpoint; + } + + if (source.dataEndpoint) { + chartkitSource.dataEndpoint = source.dataEndpoint; + } + + chartkitSources[sourceType] = chartkitSource; + + if (source.aliases) { + [...source.aliases].forEach((alias) => { + chartkitSources[alias] = chartkitSource; + }); + } + }); + + return chartkitSources; + } + + /** + * @param {String} sourcePath requested source + * + * @returns {Boolean} check is source stat or not + */ + static isStat({ + sourcesConfig, + sourcePath, + }: { + sourcesConfig: ChartsEngine['sources']; + sourcePath: string; + }) { + return DataFetcher.getSourceConfig({sourcesConfig, sourcePath}) === null; + } + + private static removeFromProcessingRequests( + promise: Promise, + processingRequests: PromiseWithAbortController[], + ) { + const index = processingRequests.findIndex((elem) => elem[0] === promise); + + processingRequests.splice(index, 1); + } + + private static async fetchSource({ + sourceName, + source, + ctx, + fetchingStartTime, + subrequestHeaders, + processingRequests, + rejectFetchingSource, + userId, + userLogin, + iamToken, + workbookId, + isEmbed, + authParams, + originalReqHeaders, + adapterContext, + telemetryCallbacks, + cacheClient, + sourcesConfig, + }: { + sourceName: string; + source: Source; + ctx: AppContext; + telemetryCallbacks: TelemetryCallbacks; + fetchingStartTime: number; + subrequestHeaders: Record; + processingRequests: PromiseWithAbortController[]; + rejectFetchingSource: () => void; + userId?: string | null; + userLogin?: string | null; + iamToken?: string | null; + workbookId?: WorkbookId; + isEmbed: boolean; + authParams: AuthParams | undefined; + originalReqHeaders: DataFetcherOriginalReqHeaders; + adapterContext: AdapterContext; + cacheClient: CacheClient; + sourcesConfig: ChartsEngine['sources']; + }): Promise { + const singleFetchingTimeout = + ctx.config.singleFetchingTimeout || DEFAULT_SINGLE_FETCHING_TIMEOUT; + + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + const useChartsEngineLogin = Boolean(isEnabledServerFeature(Feature.UseChartsEngineLogin)); + + try { + source = prepareSource(source); + } catch (e) { + return { + sourceId: sourceName, + sourceType: 'Unresolved', + code: 'INVALID_SOURCE_CONFIG', + details: getMessageFromUnknownError(e), + }; + } + + const onDataFetched = telemetryCallbacks.onDataFetched || (() => {}); + const onDataFetchingFailed = telemetryCallbacks.onDataFetchingFailed || (() => {}); + + const requestControl = { + allBuffersLength: 0, + }; + + const hideInInspector = source.hideInInspector; + + let targetUri = source.url; + + const loggedSource = Object.assign({}, source, { + data: REDACTED_DATA_PLACEHOLDER, + sourceArgs: REDACTED_DATA_PLACEHOLDER, + headers: REDACTED_DATA_PLACEHOLDER, + }); + const loggedInfo: {sourceName: string; source: Source; login?: string} = { + sourceName, + source: loggedSource, + }; + + if (useChartsEngineLogin && userLogin) { + loggedInfo.login = userLogin; + } + + ctx.log('FETCHER_REQUEST', loggedInfo); + + if (typeof targetUri !== 'string' || !targetUri) { + ctx.logError('FETCHER_UNKNOWN_SOURCE', null, {targetUri}); + + return { + sourceId: sourceName, + sourceType: 'Unresolved', + code: UNKNOWN_SOURCE, + }; + } + + targetUri = targetUri.replace(/^\/_node/, '/_charts/_node'); + + targetUri = targetUri.replace(/^\/api\/special\/traf/, '/_traf'); + + targetUri = targetUri.replace(/^\/api\/wizard\/v1\/run/, '/_charts/api/wizard/v1/run'); + targetUri = targetUri.replace(/^\/api\/editor\/v1\/run/, '/_charts/api/editor/v1/run'); + targetUri = targetUri.replace(/^\/api\/run/, '/_charts/api/run'); + + if (DataFetcher.isStat({sourcesConfig, sourcePath: targetUri})) { + targetUri = '/_stat' + targetUri; + } + + const parsedUrl = url.parse(targetUri); + const parsedQuery = querystring.parse(parsedUrl.query || ''); + + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 + const sourceMethod = (source.method && source.method.toUpperCase()) || 'GET'; + const customHeaders = source.headers || {}; + + if (!ALLOWED_HTTP_METHODS.includes(sourceMethod)) { + const message = `This HTTP method is not allowed: ${sourceMethod}`; + + ctx.logError(message); + + return { + sourceId: sourceName, + sourceType: sourceName, + message, + }; + } + + const sourceCache = source.cache || parsedQuery['_sp_cache_duration'] || null; + const sourceStatFormat = source.statFormat || parsedQuery['_sp_stat_format'] || null; + + let sourceFormat = source.format; + if (sourceFormat === 'text') { + sourceFormat = 'form'; + } + + if (!sourceFormat || !ALLOWED_REQUEST_FORMATS.includes(sourceFormat)) { + sourceFormat = 'json'; + } + + const dataSourceName = DataFetcher.getSourceName(targetUri); + + const sourceConfig = DataFetcher.getSourceConfig({ + sourcesConfig, + sourcePath: targetUri, + isEmbed, + }); + + if (!sourceConfig) { + ctx.logError(`Invalid source: ${targetUri}`); + + return { + sourceId: sourceName, + sourceType: 'Unresolved', + code: INVALID_SOURCE_FORMAT, + }; + } + + if (shouldUseAlias(source, sourceConfig, isEnabledServerFeature)) { + const aliasedSource = convertToAPIConnectorSource(source, sourceConfig); + const preparedAliasedSource = prepareSource(aliasedSource); + + ctx.log('FETCHER_ALIAS_TO_API_CONNECTOR', { + sourceName: dataSourceName, + apiConnectionId: sourceConfig.aliasTo!.apiConnectionId, + }); + + // Re-enter fetchSource with the converted source + const result = (await DataFetcher.fetchSource({ + sourceName, + source: preparedAliasedSource, + ctx, + fetchingStartTime, + subrequestHeaders, + processingRequests, + rejectFetchingSource, + userId, + userLogin, + iamToken, + workbookId, + isEmbed, + authParams, + originalReqHeaders, + adapterContext, + telemetryCallbacks, + cacheClient, + sourcesConfig, + })) as FetchSourceResult; + + if (sourceConfig.aliasTo!.unwrapResponse && result.body) { + result.body = unwrapAPIConnectorResponse(result.body); + } + + return result; + } + + const {passedCredentials, extraHeaders, sourceType: sourceTypeFromConfig} = sourceConfig; + if (!sourceTypeFromConfig) { + ctx.logError(`Invalid sourceType from config: ${sourceTypeFromConfig}`); + + return { + sourceId: sourceName, + sourceType: 'Unresolved', + code: INVALID_SOURCE_FORMAT, + }; + } + const sourceType = sourceTypeFromConfig; + + if ( + sourceConfig.allowedMethods && + !sourceConfig.allowedMethods.includes( + sourceMethod as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', + ) + ) { + const message = `This HTTP method (${sourceMethod}) is not allowed for this source: ${sourceName}`; + + ctx.logError(message); + + return { + sourceId: sourceName, + sourceType, + message, + }; + } + + if (sourceConfig.check) { + const result = await sourceConfigCheck({ + ctx, + targetUri, + sourceConfig, + sourceName, + sourceType, + }); + if (result.valid === false && result.meta) { + return result.meta; + } + } + + const useCaching = Boolean(sourceConfig.useCaching && sourceCache); + + const croppedTargetUri = targetUri.replace('/_' + sourceType, ''); + + let userTargetUriUi: string | null = null; + if (sourceConfig.uiEndpointFormatter) { + userTargetUriUi = sourceConfig.uiEndpointFormatter(targetUri, source.data); + } else if (sourceConfig.uiEndpoint) { + userTargetUriUi = sourceConfig.uiEndpoint + croppedTargetUri; + } + + if (sourceConfig.adapterWithContext) { + // adapterWithContext has 'unknown' return type but implementations + // return structures compatible with FetchSourceResult + return sourceConfig.adapterWithContext({ + targetUri: croppedTargetUri, + sourceName, + adapterContext, + ctx, + }) as Promise; + } + + const headers: IncomingHttpHeaders = Object.assign( + {}, + { + 'user-agent': 'Datalens Charts', + }, + ctx.getMetadata(), + ); + + if (sourceType === 'charts') { + const incomingHeader = originalReqHeaders.xChartsFetcherVia || ''; + + const {reqBody} = ctx.get('sources'); + + const scriptName = reqBody.params ? '/editor/' + reqBody.params.name : reqBody.path; + + if (incomingHeader && !Array.isArray(incomingHeader)) { + const circular = incomingHeader.split(',').some((someScriptName) => { + return scriptName === someScriptName; + }); + + if (circular) { + ctx.logError('Source is circullar'); + return { + sourceId: sourceName, + url: targetUri, + sourceType, + code: SOURCE_IS_CIRCULAR, + }; + } + } + + headers['x-charts-fetcher-via'] = incomingHeader + ? `${incomingHeader},${scriptName}` + : scriptName; + } + + if (originalReqHeaders.referer) { + headers.referer = ctx.utils.redactSensitiveQueryParams(originalReqHeaders.referer); + } + + const proxyHeaders = ctx.config.chartsEngineConfig.dataFetcherProxiedHeaders || [ + // fallback will be removed soon + SuperuserHeader.XDlAllowSuperuser, + SuperuserHeader.XDlSudo, + DL_CONTEXT_HEADER, + 'x-dl-debug-mode', + DL_EMBED_TOKEN_HEADER, + ]; + if (Array.isArray(proxyHeaders)) { + proxyHeaders.forEach((headerName) => { + if (sourceConfig.isExternal && headerName.toLowerCase().startsWith('x-dl')) { + return; + } + if (subrequestHeaders[headerName]) { + headers[headerName] = subrequestHeaders[headerName]; + } + }); + } + + if (workbookId) { + headers[WORKBOOK_ID_HEADER] = workbookId; + } + + if (authParams) { + addAuthHeaders({headers, authParams}); + } + + if (passedCredentials) { + const getSourceAuthorizationHeaders = registry.common.functions.get( + 'getSourceAuthorizationHeaders', + ); + + const sourceAuthorizationHeaders = getSourceAuthorizationHeaders({ + ctx, + sourceConfig, + subrequestHeaders, + }); + + Object.assign(headers, sourceAuthorizationHeaders); + } + + if (extraHeaders) { + if (typeof extraHeaders === 'function') { + const extraHeadersResult = extraHeaders(); + + Object.assign(headers, extraHeadersResult); + } else if (typeof extraHeaders === 'object') { + Object.assign(headers, extraHeaders); + } + } + + if (sourceConfig.args) { + Object.assign(parsedQuery, sourceConfig.args); + } + + delete parsedQuery['_sp_cache_duration']; + delete parsedQuery['_sp_stat_format']; + + parsedUrl.query = querystring.stringify(parsedQuery); + parsedUrl.search = parsedUrl.query ? '?' + parsedUrl.query : ''; + + targetUri = + sourceConfig.dataEndpoint + url.format(parsedUrl).replace('/_' + sourceType, ''); + + const requestHeaders = Object.assign({}, customHeaders, headers); + + if (sourceConfig.preprocess) { + targetUri = sourceConfig.preprocess(targetUri); + } + + const requestOptions: DataFetcherRequestOptions = { + method: sourceMethod, + uri: targetUri, + headers: requestHeaders, + timeout: singleFetchingTimeout, + spStatFormat: sourceStatFormat, + }; + + if (sourceConfig.maxRedirects) { + requestOptions.maxRedirects = sourceConfig.maxRedirects; + } + + if (useCaching) { + Object.assign(requestOptions, { + ctx, + spCacheDuration: sourceCache, + useCaching, + }); + } + + requestOptions.headers['x-forwarded-for'] = originalReqHeaders.xForwardedFor; + + if (!requestOptions.headers['x-real-ip']) { + requestOptions.headers['x-real-ip'] = originalReqHeaders.xRealIP; + } + + if (isSourceWithMiddlewareUrl(source)) { + const middlewareSourceConfig = DataFetcher.getSourceConfig({ + sourcesConfig, + sourcePath: source.middlewareUrl.sourceName, + }); + + if (middlewareSourceConfig?.middlewareAdapter) { + source = await middlewareSourceConfig.middlewareAdapter({ + ctx, + source, + sourceName, + iamToken: iamToken ?? undefined, + workbookId, + cacheClient, + userId: userId === undefined ? null : userId, + rejectFetchingSource, + authParams, + requestHeaders: requestOptions.headers, + }); + } + } + + const sourceData = isAPIConnectorSource(source) + ? {parameters: getApiConnectorParamsFromSource(source)} + : (!isString(source) && source.data) || null; + + if (sourceData) { + if (sourceFormat === 'form') { + requestOptions.form = sourceData; + } else { + requestOptions.body = sourceData; + + if (typeof sourceData === 'object') { + requestOptions.json = true; + } + } + } + + const publicTargetUri = hideSensitiveData(targetUri); + const publicSourceData = hideSensitiveData(sourceData); + + const traceId = ctx.getTraceId(); + const tenantId = ctx.get('tenantId'); + + return new Promise((fetchResolve) => { + ctx.log('Fetching', {publicTargetUri, xRealIp: requestOptions.headers['x-real-ip']}); + + if (useCaching) { + ctx.log('Using caching', {publicTargetUri}); + } + const abortController = new AbortController(); + const signal = abortController.signal; + const currentRequest: Promise = RequestPromise.request({ + requestOptions: {...requestOptions, signal}, + requestControl, + useCaching, + }) + // eslint-disable-next-line + .catch((error) => { + if (signal.aborted) { + return; + } + const latency = new Date().getTime() - fetchingStartTime; + + const statusCode = isFetchLimitError(error.message) ? 200 : error.statusCode; + + onDataFetchingFailed(error, { + sourceName: dataSourceName, + statusCode, + requestId: ctx.get(REQUEST_ID_PARAM_NAME) || '', + latency, + traceId, + tenantId, + url: publicTargetUri, + userId: userId || '', + }); + + if (error.response) { + if (error.response.req) { + error.response.req.headers = ctx.utils.redactSensitiveHeaders( + error.response.req.headers, + ); + } + + if (error.response.request) { + error.response.request.headers = ctx.utils.redactSensitiveHeaders( + error.response.request.headers, + ); + } + } + + const invalidJsonResponse = + typeof error?.response?.body === 'string' && + error?.response?.headers?.['content-type']?.includes('application/json'); + if (invalidJsonResponse) { + error.response.body = '[INVALID BODY]'; + } + + if (error.statusCode) { + ctx.log( + `Fetching failed with response code: ${error.statusCode} ${publicTargetUri}`, + ); + + fetchResolve({ + status: error.statusCode, + sourceId: sourceName, + sourceType, + source, + message: `${error.statusCode}`.replace( + 'ECONNABORTED', + 'Network error (source processing timed out)', + ), + body: (error.response && error.response.body) || {}, + uiUrl: userTargetUriUi, + dataUrl: publicTargetUri, + data: publicSourceData, + hideInInspector, + /** @deprecated use uiUrl or dataUrl */ + url: userTargetUriUi || publicTargetUri, + }); + } else { + ctx.logError(`Fetching failed unexpectedly ${publicTargetUri}`, error); + + const errorMessage = error.message; + let errorCode = error.code; + + if (isFetchLimitError(errorMessage)) { + errorCode = error.message.replace('Error: ', ''); + + let cancelCode = errorCode; + + if (errorCode === REQUEST_SIZE_LIMIT_EXCEEDED) { + cancelCode = null; + } + + cancelRequestsPromises(processingRequests, cancelCode, currentRequest); + + fetchResolve({ + sourceId: sourceName, + sourceType, + source, + code: errorCode, + responseHeaders: (error.response && error.response.headers) || null, + uiUrl: userTargetUriUi, + dataUrl: publicTargetUri, + data: publicSourceData, + hideInInspector, + /** @deprecated use uiUrl or dataUrl */ + url: userTargetUriUi || publicTargetUri, + }); + } else { + fetchResolve({ + sourceId: sourceName, + sourceType, + source, + message: errorMessage, + code: errorCode, + status: (error.response && error.response.statusCode) || null, + responseHeaders: (error.response && error.response.headers) || null, + body: error.response && error.response.body, + uiUrl: userTargetUriUi, + dataUrl: publicTargetUri, + data: publicSourceData, + hideInInspector, + /** @deprecated use uiUrl or dataUrl */ + url: userTargetUriUi || publicTargetUri, + }); + } + } + }) + .then((response) => { + if (response) { + let data = response.body || response; + + const latency = new Date().getTime() - fetchingStartTime; + + onDataFetched({ + sourceName: dataSourceName, + statusCode: response.statusCode, + requestId: ctx.get(REQUEST_ID_PARAM_NAME) || '', + latency, + url: publicTargetUri, + traceId, + tenantId, + userId: userId || '', + }); + + if (response.statusCode === 204 && data === '') { + fetchResolve({ + sourceId: sourceName, + sourceType, + source, + status: response.statusCode, + code: EMPTY_RESPONSE, + responseHeaders: response.headers, + uiUrl: userTargetUriUi, + dataUrl: publicTargetUri, + data: publicSourceData, + hideInInspector, + /** @deprecated use uiUrl or dataUrl */ + url: userTargetUriUi || publicTargetUri, + }); + } else if ( + response.statusCode === 301 || + response.statusCode === 302 || + response.statusCode === 307 + ) { + fetchResolve({ + sourceId: sourceName, + sourceType, + source, + status: response.statusCode, + code: REDIRECT, + body: data, + responseHeaders: response.headers, + uiUrl: userTargetUriUi, + dataUrl: publicTargetUri, + data: publicSourceData, + hideInInspector, + /** @deprecated use uiUrl or dataUrl */ + url: userTargetUriUi || publicTargetUri, + }); + } else { + if (sourceConfig.postprocess) { + data = sourceConfig.postprocess(data, requestOptions); + } + + const datasetId = getDatasetId(publicTargetUri); + + fetchResolve({ + sourceId: sourceName, + sourceType, + source, + body: data, + responseHeaders: response.headers, + status: response.statusCode, + latency, + size: sizeof(data), + uiUrl: userTargetUriUi, + dataUrl: publicTargetUri, + datasetId, + hideInInspector, + data: publicSourceData, + /** @deprecated use uiUrl or dataUrl */ + url: userTargetUriUi || publicTargetUri, + }); + } + } + }) + .catch((error) => { + ctx.logError('Unhandled internal fetcher error', error); + fetchResolve({ + sourceId: sourceName, + sourceType, + source, + code: UNHANDLED_INTERNAL_SERVER_ERROR, + uiUrl: userTargetUriUi, + dataUrl: publicTargetUri, + data: publicSourceData, + hideInInspector, + /** @deprecated use uiUrl or dataUrl */ + url: userTargetUriUi || publicTargetUri, + }); + }) + .finally(() => { + if (signal.aborted) { + const code = signal.reason || REQUEST_CANCELLED; + + fetchResolve({ + sourceId: sourceName, + sourceType, + source, + code, + uiUrl: userTargetUriUi, + dataUrl: publicTargetUri, + data: publicSourceData, + hideInInspector, + /** @deprecated use uiUrl or dataUrl */ + url: userTargetUriUi || publicTargetUri, + }); + } + + DataFetcher.removeFromProcessingRequests(currentRequest, processingRequests); + }); + + processingRequests.push([currentRequest, abortController]); + }); + } +} + +type SourceCheckResult = { + valid: boolean; + meta?: { + sourceId: string; + sourceType: string; + message: string; + }; +}; + +async function sourceConfigCheck({ + ctx, + sourceName, + sourceType, + sourceConfig, + targetUri, +}: { + ctx: AppContext; + sourceName: string; + sourceType: string; + sourceConfig: SourceConfig; + targetUri: string; +}): Promise { + if (!sourceConfig.check) { + return {valid: true}; + } + try { + const checkResult = await sourceConfig.check(targetUri); + if (checkResult === true) { + ctx.log('Access to source allowed'); + return {valid: true}; + } else if (checkResult === false) { + ctx.log('Access to source forbidden'); + + return { + valid: false, + meta: { + sourceId: sourceName, + sourceType, + message: 'Access to source forbidden', + }, + }; + } else { + ctx.logError('Source access check failed'); + + return { + valid: false, + meta: { + sourceId: sourceName, + sourceType, + message: 'Source access check failed', + }, + }; + } + } catch (error) { + ctx.logError('Failed to run source check', error); + + return { + valid: false, + meta: { + sourceId: sourceName, + sourceType, + message: 'Failed to run source check', + }, + }; + } +} diff --git a/src/server/components/charts-engine/components/processor/hooks.ts b/src/server/components/charts-engine/components/processor/hooks.ts new file mode 100644 index 0000000..63bf337 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/hooks.ts @@ -0,0 +1,107 @@ +import type {AppContext} from '@gravity-ui/nodekit'; +import {flow} from 'lodash'; + +import type {HooksContext} from '../../types'; + +export class HookError extends Error { + hookError: { + code?: string; + message?: string; + details?: Record; + debug?: Record; + } = {}; +} + +const isHookError = (err: {}): err is HookError => { + return 'hookError' in err; +}; + +export class ProcessorHooks { + _processorHooks: Record[]; + _hooks: Record[]; + constructor({processorHooks}: {processorHooks: Record[]}) { + this._processorHooks = processorHooks; + this._hooks = []; + } + + static get STATUS() { + return { + SUCCESS: 'success', + FAILED: 'failed', + }; + } + + getLogsFormatter() { + const hooksFormatters = this._hooks + .filter((hook) => typeof hook.logsFormatter === 'function') + .map((hook) => hook.logsFormatter); + return hooksFormatters.length > 0 && flow(hooksFormatters); + } + + getSandboxApiMethods() { + return this._hooks + .filter((hook) => typeof hook.getSandboxApiMethods === 'function') + .map((hook) => hook.getSandboxApiMethods()) + .reduce((acc, methods) => { + return { + ...acc, + ...methods, + }; + }, {}); + } + + async init({ + config, + isEditMode, + ctx, + hooksContext, + }: { + config: Record; + isEditMode: boolean; + ctx: AppContext; + hooksContext: HooksContext; + }) { + let hrStart; + + for (const processHook of this._processorHooks) { + hrStart = process.hrtime(); + const hookName = processHook.name || 'UnknownHook'; + + try { + ctx.log(`Hook ${hookName} init`); + const hook = processHook.init({ + config, + isEditMode, + ctx, + hooksContext, + }); + ctx.log(`Hook ${hookName} process`); + await hook.process(); + this._hooks.push(hook); + } catch (error) { + ctx.logError(`Hook ${hookName} failed`); + if (typeof error === 'object' && error !== null && isHookError(error)) { + return { + error, + hookError: error.hookError, + status: ProcessorHooks.STATUS.FAILED, + }; + } else { + return { + error, + hookError: {message: 'unknown hook error'}, + status: ProcessorHooks.STATUS.FAILED, + }; + } + } + + const text = `EditorEngine::${hookName}::HookResolved`; + const hrEnd = process.hrtime(hrStart); + ctx.log(`${text}: ${hrEnd[0]}s ${hrEnd[1] / 1000000}ms`); + } + + return { + status: ProcessorHooks.STATUS.SUCCESS, + }; + } +} diff --git a/src/server/components/charts-engine/components/processor/index.ts b/src/server/components/charts-engine/components/processor/index.ts new file mode 100644 index 0000000..24a3f17 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/index.ts @@ -0,0 +1,1098 @@ +import type {ChartData} from '@gravity-ui/chartkit/gravity-charts'; +import {transformParamsToActionParams} from '@gravity-ui/dashkit/helpers'; +import {type AppContext, REQUEST_ID_PARAM_NAME} from '@gravity-ui/nodekit'; +import {AxiosError} from 'axios'; +import JSONfn from 'json-fn'; +import {isEmpty, isNumber, isObject, isString, mapValues, merge, mergeWith} from 'lodash'; +import get from 'lodash/get'; + +import type {ChartsEngine} from '../..'; +import type { + ApiV2DataExportField, + ControlType, + DashWidgetConfig, + EDITOR_TYPE_CONFIG_TABS, + EntryPublicAuthor, + Palette, + StringParams, + WorkbookId, +} from '../../../../../shared'; +import {DL_CONTEXT_HEADER, Feature, WizardType} from '../../../../../shared'; +import {renderHTML} from '../../../../../shared/modules/markdown/markdown'; +import {selectServerPalette} from '../../../../constants'; +import {extractColorPalettesFromData} from '../../../../modes/charts/plugins/helpers/color-palettes'; +import {registry} from '../../../../registry'; +import type {CacheClient} from '../../../cache-client'; +import {config as configConstants} from '../../constants'; +import type {AdapterContext, HooksContext, Source, TelemetryCallbacks} from '../../types'; +import type {ResolvedConfig} from '../storage/types'; +import {getDuration, normalizeParams, resolveParams} from '../utils'; + +import type {CommentsFetcherPrepareCommentsParams} from './comments-fetcher'; +import {CommentsFetcher} from './comments-fetcher'; +import type {LogItem} from './console'; +import type {AuthParams, DataFetcherOriginalReqHeaders, DataFetcherResult} from './data-fetcher'; +import {DataFetcher} from './data-fetcher'; +import {ProcessorHooks} from './hooks'; +import {updateActionParams, updateParams} from './paramsUtils'; +import {getSourcesErrorStatusCode} from './sources'; +import {StackTracePreparer} from './stack-trace-prepaper'; +import type { + ChartBuilder, + ChartBuilderResult, + ProcessorErrorResponse, + ProcessorFiles, + ProcessorLogs, + ProcessorSuccessResponse, + UiTabExports, + UserConfig, +} from './types'; +import {cleanJSONFn, getMessageFromUnknownError, isChartWithJSAndHtmlAllowed} from './utils'; + +const { + CONFIG_LOADING_ERROR, + CONFIG_TYPE, + DATA_FETCHING_ERROR, + DEFAULT_OVERSIZE_ERROR_STATUS, + DEFAULT_RUNTIME_ERROR_STATUS, + DEFAULT_RUNTIME_TIMEOUT_STATUS, + DEPS_RESOLVE_ERROR, + HOOKS_ERROR, + ROWS_NUMBER_OVERSIZE, + RUNTIME_ERROR, + RUNTIME_TIMEOUT_ERROR, + SEGMENTS_OVERSIZE, + TABLE_OVERSIZE, +} = configConstants; + +export class SandboxError extends Error { + code: + | typeof RUNTIME_ERROR + | typeof RUNTIME_TIMEOUT_ERROR + | typeof CONFIG_LOADING_ERROR + | typeof DEPS_RESOLVE_ERROR + | typeof ROWS_NUMBER_OVERSIZE + | typeof DATA_FETCHING_ERROR + | typeof SEGMENTS_OVERSIZE + | typeof TABLE_OVERSIZE = RUNTIME_ERROR; + executionResult?: { + executionTiming: [number, number]; + filename: string; + logs: {type: string; value: string}[][]; + stackTrace?: string; + }; + details?: Record; + stackTrace?: string; +} + +function collectModulesLogs({ + processedModules, + logsStorage, +}: { + processedModules?: Record; + logsStorage: ProcessorLogs; +}) { + if (!processedModules) { + return; + } + + Object.keys(processedModules).forEach((moduleName) => { + const module = processedModules[moduleName]; + module.logs?.forEach((logLine) => { + logLine.unshift({type: 'string', value: `[${moduleName}]`}); + }); + logsStorage.modules = logsStorage.modules.concat(module.logs || []); + }); +} + +export function stringifyLogs({ + logs, + hooks, + ctx, +}: { + logs: ProcessorLogs; + hooks: ProcessorHooks; + ctx: AppContext; +}) { + try { + const formatter = hooks.getLogsFormatter(); + return JSON.stringify(logs, (_, value: string | number) => { + if (typeof value === 'number' && isNaN(value)) { + return '__special_value__NaN'; + } + if (value === Infinity) { + return '__special_value__Infinity'; + } + if (value === -Infinity) { + return '__special_value__-Infinity'; + } + return formatter ? formatter(value) : value; + }); + } catch (e) { + ctx.logError('Error during formatting logs', e); + + return ''; + } +} + +function mergeArrayWithObject(a: [], b: {}) { + // for example, for xAxis/yAxis, when there is one axis on one side and several on the other + // typeof === 'object' check in case there is, for example, a string + if (Array.isArray(a) && b && typeof b === 'object' && !Array.isArray(b)) { + return a.map((value) => merge(value, b)); + } + return; +} + +function logSandboxDuration(duration: [number, number], filename: string, ctx: AppContext) { + ctx.log( + `EditorEngineSandbox::Execution[${filename}]: ${duration[0]}s ${duration[1] / 1000000}ms`, + ); +} + +function logFetchingError(ctx: AppContext, error: unknown) { + const errMessage = 'Error fetching sources'; + if (error instanceof Error) { + ctx.logError(errMessage, error); + } else if (isString(error)) { + ctx.logError(errMessage, Error(error)); + } else if (isObject(error) && 'code' in error && isString(error.code)) { + ctx.logError(errMessage, Error(error.code)); + } else { + ctx.logError(errMessage); + } +} + +export type ProcessorParams = { + ctx: AppContext; + builder: ChartBuilder; + telemetryCallbacks: TelemetryCallbacks; + cacheClient: CacheClient; + hooks: ProcessorHooks; + sourcesConfig: ChartsEngine['sources']; +} & SerializableProcessorParams; + +export type SerializableProcessorParams = { + secureConfig?: {privateParams?: string[]}; + subrequestHeaders: Record; + paramsOverride: Record; + actionParamsOverride: Record; + widgetConfig?: DashWidgetConfig['widgetConfig']; + configOverride: { + data: Record; + key?: string; + entryId?: string; + type?: string; + meta: {stype: keyof typeof EDITOR_TYPE_CONFIG_TABS | ControlType.Dash}; + publicAuthor?: EntryPublicAuthor; + }; + userLogin: string | null; + userLang: string | null; + userId: string | null; + iamToken: string | null; + responseOptions?: Record; + uiOnly?: boolean; + isEditMode: boolean; + configResolving: number; + cacheToken: string | string[] | null; + workbookId?: WorkbookId; + forbiddenFields?: (keyof ProcessorSuccessResponse)[]; + disableJSONFnByCookie: boolean; + configName: string; + configId: string; + revId?: string; + isEmbed: boolean; + authParams: AuthParams | undefined; + originalReqHeaders: DataFetcherOriginalReqHeaders; + adapterContext: AdapterContext; + hooksContext: HooksContext; + defaultColorPaletteId?: string; + systemPalettes?: Record; +}; + +export class Processor { + // eslint-disable-next-line complexity + static async process({ + subrequestHeaders, + paramsOverride = {}, + widgetConfig = {}, + configOverride, + userLang, + userLogin, + userId = null, + iamToken = null, + responseOptions = {}, + uiOnly = false, + isEditMode, + configResolving, + ctx, + workbookId, + builder, + forbiddenFields, + disableJSONFnByCookie, + configName, + configId, + revId, + isEmbed, + authParams, + originalReqHeaders, + adapterContext, + hooksContext, + telemetryCallbacks, + cacheClient, + hooks, + sourcesConfig, + secureConfig, + defaultColorPaletteId, + systemPalettes, + }: ProcessorParams): Promise< + ProcessorSuccessResponse | ProcessorErrorResponse | {error: string} + > { + const requestId = ctx.get(REQUEST_ID_PARAM_NAME) || ''; + const logs: ProcessorLogs = { + modules: [], + }; + let processedModules: Record = {}; + let modulesLogsCollected = false; + let resolvedSources: Record | undefined; + const config: ResolvedConfig = configOverride as ResolvedConfig; + let params: Record | StringParams; + let actionParams: Record; + let usedParams: Record; + + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + + const timings: { + configResolving: number; + dataFetching: null | number; + jsExecution: null | number; + } = { + configResolving, + dataFetching: null, + jsExecution: null, + }; + + const onCodeExecuted = telemetryCallbacks.onCodeExecuted || (() => {}); + const onTabsExecuted = telemetryCallbacks.onTabsExecuted || (() => {}); + + function injectConfigAndParams({target}: {target: ProcessorSuccessResponse}) { + let responseConfig; + const useChartsEngineResponseConfig = Boolean( + isEnabledServerFeature(Feature.UseChartsEngineResponseConfig), + ); + + if (useChartsEngineResponseConfig && responseOptions.includeConfig && config) { + responseConfig = config; + } else { + responseConfig = { + type: config.type, + meta: config.meta, + entryId: config.entryId, + key: config.key, + revId: config.revId, + }; + } + responseConfig.key = config.key || configName; + responseConfig.entryId = config.entryId || configId; + responseConfig.revId = config.revId || revId; + + target._confStorageConfig = responseConfig; + + target.key = responseConfig.key; + target.id = responseConfig.entryId; + target.type = responseConfig.type; + target.revId = responseConfig.revId; + + if (params) { + target.params = params; + } + if (actionParams) { + target.actionParams = actionParams; + } + + return target; + } + + function injectLogs({ + target, + }: { + target: ProcessorSuccessResponse | Partial; + }) { + if (responseOptions.includeLogs) { + target.logs_v2 = stringifyLogs({logs, hooks, ctx}); + } + } + + try { + let hrStart = process.hrtime(); + + const type = config.meta.stype; + + config.type = type; + ctx.log('EditorEngine::ConfigResolved', {duration: getDuration(hrStart)}); + + const resultHooksInit = await hooks.init({ + config: { + ...config, + entryId: config.entryId || configId, + }, + isEditMode, + ctx, + hooksContext, + }); + + if (resultHooksInit.status === ProcessorHooks.STATUS.FAILED) { + const {hookError, error} = resultHooksInit; + if (hookError) { + return { + error: { + code: HOOKS_ERROR, + ...hookError, + }, + }; + } else { + return { + error: { + code: HOOKS_ERROR, + message: 'Unhandled error init hooks', + debug: { + message: getMessageFromUnknownError(error), + }, + }, + }; + } + } + + hrStart = process.hrtime(); + try { + processedModules = await builder.buildModules({ + ctx, + subrequestHeaders, + onModuleBuild: ({executionTiming, filename}) => { + logSandboxDuration(executionTiming, filename, ctx); + }, + }); + } catch (error) { + ctx.logError('DEPS_RESOLVE_ERROR', error); + + if (!isObject(error)) { + return { + error: { + code: DEPS_RESOLVE_ERROR, + details: { + stackTrace: 'Error resolving required modules: internal error', + }, + debug: {}, + }, + }; + } + + let reason = ('stackTrace' in error && error.stackTrace) || 'internal error'; + + if ('status' in error) { + if (error.status === 403) { + reason = 'access denied'; + } else if (error.status === 404) { + reason = 'not found'; + } + } + + const sandboxErrorFilename = + 'executionResult' in error && + isObject(error.executionResult) && + 'filename' in error.executionResult + ? error.executionResult?.filename + : null; + const axiosErrorFileName = + error instanceof AxiosError && 'description' in error + ? error.description + : null; + + const filename = sandboxErrorFilename || axiosErrorFileName || 'required modules'; + + const stackTraceText = + 'description' in error ? error.description : `module (${filename}): ${reason}`; + + return { + error: { + code: DEPS_RESOLVE_ERROR, + details: { + stackTrace: `Error resolving ${stackTraceText}`, + }, + statusCode: + 'status' in error && isNumber(error.status) ? error.status : undefined, + debug: { + message: getMessageFromUnknownError(error), + }, + }, + }; + } + + ctx.log('EditorEngine::DepsResolved', {duration: getDuration(hrStart)}); + + hrStart = process.hrtime(); + ctx.log('EditorEngine::DepsProcessed', {duration: getDuration(hrStart)}); + + try { + await builder.buildShared(); + } catch (error) { + ctx.logError('Error during shared tab parsing', error); + + logs.Shared = [[{type: 'string', value: 'Invalid JSON in Shared tab'}]]; + + const failedResponse = { + error: { + code: RUNTIME_ERROR, + details: { + description: 'Invalid JSON in Shared tab', + }, + debug: { + message: getMessageFromUnknownError(error), + }, + }, + }; + + injectLogs({target: failedResponse}); + + return failedResponse; + } + + const {params: normalizedParamsOverride, actionParams: normalizedActionParamsOverride} = + normalizeParams(paramsOverride); + + hrStart = process.hrtime(); + usedParams = {}; + const paramsTabResults = await builder.buildParams({ + params: normalizedParamsOverride, + usedParams: usedParams, + actionParams: normalizedActionParamsOverride, + hooks, + }); + logSandboxDuration(paramsTabResults.executionTiming, paramsTabResults.name, ctx); + const paramsTabError = paramsTabResults.runtimeMetadata.error; + if (paramsTabError) { + throw paramsTabError; + } + + ctx.log('EditorEngine::Params', {duration: getDuration(hrStart)}); + + usedParams = { + ...(paramsTabResults.exports as Record), + ...usedParams, + }; + + // Merge used to be here. Merge in this situation does not work as it should for arrays, so assign. + params = Object.assign({}, usedParams, normalizedParamsOverride); + actionParams = Object.assign({}, {}, normalizedActionParamsOverride); + + Object.keys(params).forEach((paramName) => { + const param = params[paramName]; + if (!Array.isArray(param)) { + params[paramName] = [param]; + } + }); + + // take values from params in usedParams there are always only defaults exported from the Params tab + // and in params new passed parameters + Object.keys(usedParams).forEach((paramName) => { + usedParams[paramName] = params[paramName]; + }); + + // Editor.updateParams() has the highest priority, + // therefore, now we take the parameters set through this method + updateParams({ + userParamsOverride: paramsTabResults.runtimeMetadata.userParamsOverride, + params, + usedParams, + }); + actionParams = updateActionParams({ + userActionParamsOverride: paramsTabResults.runtimeMetadata.userActionParamsOverride, + actionParams, + }); + + if (paramsTabResults.logs) { + logs.Params = paramsTabResults.logs; + } + + resolveParams(params as Record); + + hrStart = process.hrtime(); + + const sourcesTabResults = await builder.buildUrls({ + params, + actionParams: normalizedActionParamsOverride, + hooks, + }); + + logSandboxDuration(sourcesTabResults.executionTiming, sourcesTabResults.name, ctx); + ctx.log('EditorEngine::Sources', {duration: getDuration(hrStart)}); + logs.Sources = sourcesTabResults.logs; + + try { + hrStart = process.hrtime(); + + const sourcesTabError = sourcesTabResults.runtimeMetadata.error; + if (sourcesTabError) { + throw sourcesTabError; + } + + let sources = sourcesTabResults.exports as Record; + if (uiOnly) { + const filteredSources: Record = {}; + Object.keys(sources).forEach((key) => { + const source = sources[key]; + if (isObject(source) && source.ui) { + filteredSources[key] = source; + } + }); + sources = filteredSources; + } + + if (config?.entryId || configId) { + let dlContext: Record = {}; + if (subrequestHeaders[DL_CONTEXT_HEADER]) { + const dlContextHeader = subrequestHeaders[DL_CONTEXT_HEADER]; + dlContext = JSON.parse( + dlContextHeader && !Array.isArray(dlContextHeader) + ? dlContextHeader + : '', + ); + } + + dlContext.chartId = config?.entryId || configId; + + if (subrequestHeaders['x-chart-kind']) { + dlContext.chartKind = subrequestHeaders['x-chart-kind']; + } + + subrequestHeaders[DL_CONTEXT_HEADER] = JSON.stringify(dlContext); + } + + const dataFetcherOptions = { + ctx, + iamToken, + subrequestHeaders, + userId, + userLogin, + workbookId, + isEmbed, + authParams, + originalReqHeaders, + adapterContext, + telemetryCallbacks, + cacheClient, + sourcesConfig, + }; + const dataFetcherResult = await DataFetcher.fetchSources({ + sources, + ...dataFetcherOptions, + }); + resolvedSources = dataFetcherResult.result; + + if (builder.buildPaletteSources && dataFetcherResult.sources) { + const paletteSourcesResult = await builder.buildPaletteSources({ + sources: dataFetcherResult.sources, + }); + const resolvedPalettesResult = await DataFetcher.fetchSources({ + sources: paletteSourcesResult.exports as Record, + ...dataFetcherOptions, + }); + Object.assign(resolvedSources, resolvedPalettesResult.result); + } + + if (Object.keys(resolvedSources).length) { + timings.dataFetching = getDuration(hrStart); + } + + ctx.log('EditorEngine::DataFetched', {duration: getDuration(hrStart)}); + } catch (error) { + logFetchingError(ctx, error); + + if (!modulesLogsCollected) { + collectModulesLogs({logsStorage: logs, processedModules}); + } + + if (!isObject(error)) { + return {error: 'Internal fetching error'}; + } + + const response: ProcessorErrorResponse = { + error: { + code: DATA_FETCHING_ERROR, + debug: { + message: getMessageFromUnknownError(error), + ...('debug' in error && error.debug ? error.debug : {}), + }, + }, + }; + + if ('status' in error) { + if (error.status === 403) { + response.error.code = 'ENTRY_FORBIDDEN'; + } else if (error.status === 404) { + response.error.code = 'ENTRY_NOT_FOUND'; + } + } + + injectLogs({target: response}); + + if (error instanceof Error) { + return {error: 'Internal fetching error'}; + } else if (!response.error.details) { + response.error.details = { + sources: error, + }; + + response.error.statusCode = getSourcesErrorStatusCode(error); + } + + return response; + } + + const data = Object.keys(resolvedSources).reduce< + Record + >((acc, sourceName) => { + if (resolvedSources) { + acc[sourceName] = resolvedSources[sourceName].body; + // @ts-ignore body not optional; + delete resolvedSources[sourceName].body; + } + return acc; + }, {}); + + const {colorPalettes: tenantColorPalettes} = extractColorPalettesFromData(data); + + hrStart = process.hrtime(); + const libraryTabResult = await builder.buildChartLibraryConfig({ + data, + params, + actionParams: normalizedActionParamsOverride, + hooks, + }); + + ctx.log('EditorEngine::HighCharts', {duration: getDuration(hrStart)}); + + let libraryConfig; + if (libraryTabResult) { + logSandboxDuration(libraryTabResult.executionTiming, libraryTabResult.name, ctx); + libraryConfig = libraryTabResult.exports || {}; + logs.Highcharts = libraryTabResult.logs; + + const libraryError = libraryTabResult.runtimeMetadata.error; + if (libraryError) { + throw libraryTabResult.runtimeMetadata.error; + } + } else { + libraryConfig = {}; + } + + let userConfig: UserConfig = {}; + let processedData; + let jsTabResults; + if (!uiOnly) { + hrStart = process.hrtime(); + const configTabResults = await builder.buildChartConfig({ + data, + params, + actionParams: normalizedActionParamsOverride, + hooks, + }); + + logSandboxDuration(configTabResults.executionTiming, configTabResults.name, ctx); + ctx.log('EditorEngine::Config', {duration: getDuration(hrStart)}); + + logs.Config = configTabResults.logs; + userConfig = configTabResults.exports as UserConfig; + + hrStart = process.hrtime(); + jsTabResults = await builder.buildChart({ + data, + sources: resolvedSources, + params, + usedParams, + actionParams: normalizedActionParamsOverride, + hooks, + }); + logSandboxDuration(jsTabResults.executionTiming, jsTabResults.name, ctx); + + timings.jsExecution = getDuration(hrStart); + + const hrEnd = process.hrtime(); + const hrDuration = [hrEnd[0] - hrStart[0], hrEnd[1] - hrStart[1]]; + + onCodeExecuted({ + id: `${config.entryId || configId}:${config.key || configName}`, + requestId, + latency: (hrDuration[0] * 1e9 + hrDuration[1]) / 1e6, + }); + + ctx.log('EditorEngine::Prepare', {duration: getDuration(hrStart)}); + + processedData = jsTabResults.exports; + logs.Prepare = jsTabResults.logs; + + const jsError = jsTabResults.runtimeMetadata.error; + if (jsError) { + throw jsError; + } + + // Editor.updateParams() has the highest priority, + // so now we take the parameters set through this method + updateParams({ + userParamsOverride: jsTabResults.runtimeMetadata.userParamsOverride, + params, + usedParams, + }); + } + + const uiTabResults = await builder.buildUI({ + data, + params, + usedParams, + actionParams: normalizedActionParamsOverride, + hooks, + }); + logSandboxDuration(uiTabResults.executionTiming, uiTabResults.name, ctx); + + const uiTabExports = uiTabResults.exports; + let uiScheme: UiTabExports | null = null; + + if ( + uiTabExports && + (Array.isArray(uiTabExports) || + (isObject(uiTabExports) && + 'controls' in uiTabExports && + Array.isArray(uiTabExports.controls))) + ) { + uiScheme = uiTabExports as UiTabExports; + + if (secureConfig?.privateParams) { + const controls = Array.isArray(uiScheme) ? uiScheme : uiScheme.controls; + + controls.forEach((control) => { + if (secureConfig.privateParams?.includes(control.param)) { + control.disabled = true; + } + }); + } + } + + logs.Controls = uiTabResults.logs; + ctx.log('EditorEngine::Controls', {duration: getDuration(hrStart)}); + + // Editor.updateParams() has the highest priority, + // so now we take the parameters set through this method + updateParams({ + userParamsOverride: uiTabResults.runtimeMetadata.userParamsOverride, + params, + usedParams, + }); + + if (uiScheme && userConfig && !userConfig.overlayControls) { + userConfig.notOverlayControls = true; + } + + collectModulesLogs({processedModules, logsStorage: logs}); + modulesLogsCollected = true; + + const normalizedDefaultParams = normalizeParams( + paramsTabResults.exports as ProcessorSuccessResponse['defaultParams'], + ); + const result: ProcessorSuccessResponse = { + sources: resolvedSources, + uiScheme, + params: {...params, ...transformParamsToActionParams(actionParams)}, + usedParams, + actionParams, + widgetConfig, + defaultParams: normalizedDefaultParams.params, + extra: {}, + timings, + }; + + injectLogs({target: result}); + + if (!uiOnly && jsTabResults) { + result.data = processedData as ProcessorSuccessResponse['data']; + let resultConfig = merge( + {}, + userConfig, + jsTabResults.runtimeMetadata.userConfigOverride, + ); + + let resultLibraryConfig = mergeWith( + {}, + libraryConfig, + jsTabResults.runtimeMetadata.libraryConfigOverride, + (a, b) => { + return mergeArrayWithObject(a, b) || mergeArrayWithObject(b, a); + }, + ); + + onTabsExecuted({ + result: { + config: resultConfig, + highchartsConfig: resultLibraryConfig, + processedData, + sources: resolvedSources, + sourceData: data, + }, + entryId: config.entryId || configId, + }); + + const disableFnAndHtml = isEnabledServerFeature(Feature.DisableFnAndHtml); + if ( + disableFnAndHtml || + !isChartWithJSAndHtmlAllowed({createdAt: config.createdAt}) + ) { + resultConfig.enableJsAndHtml = false; + } + const enableJsAndHtml = get(resultConfig, 'enableJsAndHtml', false); + const disableJSONFn = + isEnabledServerFeature(Feature.NoJsonFn) || + disableJSONFnByCookie || + enableJsAndHtml === false; + const stringify = disableJSONFn ? JSON.stringify : JSONfn.stringify; + + if (builder.type === 'CHART_EDITOR' && disableJSONFn) { + resultConfig = cleanJSONFn(resultConfig); + resultLibraryConfig = cleanJSONFn(resultLibraryConfig); + } + + result.config = stringify(resultConfig); + result.publicAuthor = config.publicAuthor; + result.highchartsConfig = stringify(resultLibraryConfig); + result.extra = jsTabResults.runtimeMetadata.extra || {}; + result.extra.chartsInsights = jsTabResults.runtimeMetadata.chartsInsights; + result.extra.sideMarkdown = jsTabResults.runtimeMetadata.sideMarkdown; + + result.dataExport = mapValues(data, (sourceResponse) => { + if (sourceResponse) { + if (Array.isArray(sourceResponse)) { + const meta = sourceResponse.find( + (node) => + node && + typeof node === 'object' && + 'event' in node && + node.event === 'metadata', + ); + if ( + meta && + typeof meta === 'object' && + 'data' in meta && + 'data_export' in meta.data + ) { + return meta.data.data_export as ApiV2DataExportField; + } + } + if (typeof sourceResponse === 'object' && 'data_export' in sourceResponse) { + return sourceResponse.data_export as ApiV2DataExportField; + } + } + return undefined; + }); + + const colors = selectServerPalette({ + defaultColorPaletteId: defaultColorPaletteId ?? '', + customColorPalettes: tenantColorPalettes, + availablePalettes: systemPalettes ?? {}, + }); + if (!isEmpty(colors)) { + result.extra.colors = colors; + } + + result.sources = merge( + resolvedSources, + jsTabResults.runtimeMetadata.dataSourcesInfos, + ); + + if (jsTabResults.runtimeMetadata.exportFilename) { + result.extra.exportFilename = jsTabResults.runtimeMetadata.exportFilename; + } + + ctx.log('EditorEngine::Postprocessing', {duration: getDuration(hrStart)}); + + if ( + ctx.config.chartsEngineConfig.flags?.chartComments && + (type === CONFIG_TYPE.GRAPH_NODE || + type === CONFIG_TYPE.GRAPH_WIZARD_NODE || + type === CONFIG_TYPE.GRAPH_QL_NODE || + type === WizardType.GravityChartsWizardNode) + ) { + try { + const chartName = + type === CONFIG_TYPE.GRAPH_NODE || type === CONFIG_TYPE.GRAPH_QL_NODE + ? configName + : configId; + + hrStart = process.hrtime(); + + if (type === WizardType.GravityChartsWizardNode) { + result.comments = await CommentsFetcher.prepareGravityChartsComments( + { + chartName, + config: resultConfig.comments, + data: result.data as ChartData, + params, + }, + subrequestHeaders, + ctx, + ); + } else { + result.comments = await CommentsFetcher.prepareComments( + { + chartName, + config: resultConfig.comments, + data: result.data as CommentsFetcherPrepareCommentsParams['data'], + params, + }, + subrequestHeaders, + ctx, + ); + } + + ctx.log('EditorEngine::Comments', {duration: getDuration(hrStart)}); + } catch (error) { + ctx.logError('Error preparing comments', error); + } + } + + if (type === CONFIG_TYPE.MARKDOWN_NODE) { + try { + if (!(result.data?.markdown || result.data?.html)) { + throw Error('Empty markdown or html'); + } + const markdown = result.data.markdown || result.data.html; + const html = renderHTML({ + text: markdown || '', + lang: userLang || '', + plugins: registry.getYfmPlugins(), + }); + result.data.original_markdown = markdown; + delete result.data.markdown; + result.data.html = html.result; + result.data.meta = html.meta; + } catch (error) { + ctx.logError('Error render markdown', error); + } + } + } + + injectConfigAndParams({target: result}); + + if (forbiddenFields) { + forbiddenFields.forEach((field) => { + if (result[field]) { + delete result[field]; + } + }); + } + + return result; + } catch (error) { + ctx.logError('Run failed', error); + + const isError = (error: unknown): error is SandboxError => { + return isObject(error); + }; + + if (!isError(error)) { + throw error; + } + + const executionResult: { + filename?: string; + logs?: LogItem[][]; + stackTrace?: string; + stack?: string; + executionTiming?: [number, number]; + } = error.executionResult || {}; + if (!modulesLogsCollected) { + collectModulesLogs({logsStorage: logs, processedModules}); + } + + const failedLogs = executionResult.logs; + if (failedLogs) { + logs[(executionResult.filename as ProcessorFiles) || 'failed'] = failedLogs; + } + + const result: Partial = {}; + + injectLogs({target: result}); + + switch (error.code) { + case CONFIG_LOADING_ERROR: + case DEPS_RESOLVE_ERROR: + case DATA_FETCHING_ERROR: + result.error = error; + break; + + case ROWS_NUMBER_OVERSIZE: + case SEGMENTS_OVERSIZE: + case TABLE_OVERSIZE: + result.error = { + code: error.code, + details: error.details, + statusCode: DEFAULT_OVERSIZE_ERROR_STATUS, + }; + break; + + case RUNTIME_ERROR: + executionResult.stackTrace = + executionResult.stackTrace || executionResult.stack; + + if (resolvedSources) { + result.sources = resolvedSources; + } + + result.error = { + code: RUNTIME_ERROR, + details: { + stackTrace: executionResult.stackTrace + ? StackTracePreparer.prepare(executionResult.stackTrace) + : '', + tabName: error.executionResult ? error.executionResult.filename : '', + }, + statusCode: DEFAULT_RUNTIME_ERROR_STATUS, + }; + + break; + case RUNTIME_TIMEOUT_ERROR: + result.error = { + code: RUNTIME_TIMEOUT_ERROR, + statusCode: DEFAULT_RUNTIME_TIMEOUT_STATUS, + }; + + onCodeExecuted({ + id: `${configId}:${configName}`, + requestId, + latency: executionResult.executionTiming + ? (executionResult.executionTiming[0] * 1e9 + + executionResult.executionTiming[1]) / + 1e6 + : 0, + }); + + break; + default: + throw error; + } + + const tabName = (error.executionResult && error.executionResult.filename) || 'script'; + const message = `EXECUTION_ERROR Error processing ${tabName}\n${error.stackTrace}`; + + ctx.log(message, { + stackTrace: error.stackTrace, + tabName, + }); + + return { + error: result.error, + logs_v2: result.logs_v2, + sources: result.sources, + }; + } finally { + builder.dispose(); + } + } +} diff --git a/src/server/components/charts-engine/components/processor/paramsUtils.ts b/src/server/components/charts-engine/components/processor/paramsUtils.ts new file mode 100644 index 0000000..08ae893 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/paramsUtils.ts @@ -0,0 +1,87 @@ +import type {StringParams} from '../../../../../shared'; + +export const getSortParams = (params: StringParams) => { + const columnId = Array.isArray(params._columnId) ? params._columnId[0] : params._columnId; + const order = Array.isArray(params._sortOrder) ? params._sortOrder[0] : params._sortOrder; + const _sortRowMeta = Array.isArray(params._sortRowMeta) + ? params._sortRowMeta[0] + : params._sortRowMeta; + const _sortColumnMeta = Array.isArray(params._sortColumnMeta) + ? params._sortColumnMeta[0] + : params._sortColumnMeta; + + let meta: Record; + try { + meta = { + column: _sortColumnMeta ? JSON.parse(_sortColumnMeta) : {}, + row: _sortRowMeta ? JSON.parse(_sortRowMeta) : {}, + }; + } catch { + meta = {}; + } + + return {columnId, order: Number(order), meta}; +}; + +export const getCurrentPage = (params: StringParams): number => { + const page = Number(Array.isArray(params._page) ? params._page[0] : params._page); + return isNaN(page) ? 1 : page; +}; + +export const getParam = (paramName: string, params: StringParams) => { + return params[paramName] || []; +}; + +export function updateParams({ + userParamsOverride, + params, + usedParams, +}: { + userParamsOverride: Record | undefined; + params: StringParams; + usedParams: StringParams; +}) { + if (userParamsOverride) { + Object.keys(userParamsOverride).forEach((key) => { + const overridenItem = userParamsOverride[key]; + + if (params[key] && params[key].length > 0) { + if (Array.isArray(overridenItem) && overridenItem.length > 0) { + params[key] = overridenItem; + } + } else { + params[key] = overridenItem; + } + + usedParams[key] = params[key]; + }); + } +} + +export function updateActionParams({ + userActionParamsOverride, + actionParams, +}: { + userActionParamsOverride: Record | undefined; + actionParams: StringParams; +}) { + if (!userActionParamsOverride) { + return actionParams; + } + return userActionParamsOverride; + + // todo after usedParams has fixed CHARTS-6619 + /*Object.keys(userActionParamsOverride).forEach((key) => { + const overridenItem = userActionParamsOverride[key]; + + if (params[key] && params[key].length > 0) { + if (Array.isArray(overridenItem) && overridenItem.length > 0) { + params[key] = overridenItem; + } + } else { + params[key] = overridenItem; + } + + usedParams[key] = params[key]; + });*/ +} diff --git a/src/server/components/charts-engine/components/processor/source-alias.ts b/src/server/components/charts-engine/components/processor/source-alias.ts new file mode 100644 index 0000000..e179016 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/source-alias.ts @@ -0,0 +1,52 @@ +import {Feature} from '../../../../../shared/types/feature'; +import type {Source, SourceConfig, SourceWithAPIConnector} from '../../types'; + +type IsEnabledServerFeature = (feature: string) => boolean; + +export function shouldUseAlias( + source: Source, + sourceConfig: SourceConfig, + isEnabledServerFeature: IsEnabledServerFeature, +): boolean { + if (!sourceConfig.aliasTo) { + return false; + } + + if (!isEnabledServerFeature(Feature.UseSourceAlias)) { + return false; + } + + const authorization = source.headers?.authorization || source.headers?.Authorization; + if (typeof authorization === 'string' && authorization.startsWith('OAuth ')) { + return false; + } + + return true; +} + +export function convertToAPIConnectorSource( + source: Source, + sourceConfig: SourceConfig, +): SourceWithAPIConnector { + const {aliasTo} = sourceConfig; + if (!aliasTo) { + throw new Error('convertToAPIConnectorSource called without aliasTo config'); + } + + // Extract path: '/_example/issues?filter=open' -> '/issues?filter=open' + const path = source.url.replace(/^\/_[^/?]+/, ''); + + const firstAllowedMethod = sourceConfig.allowedMethods?.[0]; + const defaultMethod: 'GET' | 'POST' = + firstAllowedMethod === 'GET' || firstAllowedMethod === 'POST' ? firstAllowedMethod : 'GET'; + + const {url: _, ...sourceWithoutUrl} = source; + const converted = { + ...sourceWithoutUrl, + apiConnectionId: aliasTo.apiConnectionId, + method: source.method || defaultMethod, + path: path || '/', + } as SourceWithAPIConnector; + + return converted; +} diff --git a/src/server/components/charts-engine/components/processor/sources.ts b/src/server/components/charts-engine/components/processor/sources.ts new file mode 100644 index 0000000..e7e5c50 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/sources.ts @@ -0,0 +1,213 @@ +import {isObject, isString} from 'lodash'; + +import { + CONNECTIONS_DASHSQL, + CONNECTIONS_TYPED_QUERY_RAW_URL, + CONNECTION_ID_PLACEHOLDER, + DATASET_DISTINCTS_URL, + DATASET_FIELDS_URL, + DATASET_ID_PLACEHOLDER, + DATASET_RESULT_URL, +} from '../../../../modes/charts/plugins/control/url/constants'; +import {config as configConstants} from '../../constants'; +import type { + APIConnectorParams, + Source, + SourceWithAPIConnector, + SourceWithDatasetId, + SourceWithQLConnector, +} from '../../types'; + +const validateAPIConnectorSource = (source: Source): true => { + const requiredFields = [ + {field: 'apiConnectionId', valid: isString(source.apiConnectionId)}, + {field: 'method', valid: isString(source.method)}, + {field: 'path', valid: isString(source.path)}, + ]; + const missingFields = requiredFields.filter((item) => !item.valid).map((item) => item.field); + + if (missingFields.length > 0) { + throw new Error(`Missing or invalid API connector fields: ${missingFields.join(', ')}`); + } + + return true; +}; + +export const isAPIConnectorSource = (source: Source): source is SourceWithAPIConnector => { + return isString(source.apiConnectionId) && validateAPIConnectorSource(source); +}; + +export const getApiConnectorParamsFromSource = ( + source: SourceWithAPIConnector, +): APIConnectorParams => { + const originalSource = source._original; + if ( + !( + isObject(originalSource) && + 'method' in originalSource && + isString(originalSource.method) && + 'path' in originalSource && + isString(originalSource.path) + ) + ) { + throw new Error('ApiConnector source is not prepared'); + } + + const result: APIConnectorParams = { + method: originalSource.method, + body: {}, + path: originalSource.path, + }; + + if (originalSource.method === 'POST' && 'body' in originalSource) { + result.body = originalSource.body; + result.content_type = isString(originalSource.body) + ? 'text/plain;charset=utf-8' + : 'application/json'; + } + + return result; +}; + +export const prepareSourceWithAPIConnector = (source: SourceWithAPIConnector) => { + // Only save _original if it doesn't exist yet (avoid overwriting on second call) + if (!source._original) { + source._original = {...source}; + } + const sourceUrl = CONNECTIONS_TYPED_QUERY_RAW_URL.replace( + CONNECTION_ID_PLACEHOLDER, + encodeURIComponent(source.apiConnectionId), + ); + source.url = sourceUrl; + source.method = 'POST'; + return source; +}; + +const validateQLConnectionSource = (source: Source): true => { + const requiredFields = [ + {field: 'qlConnectionId', valid: isString(source.qlConnectionId)}, + {field: 'data', valid: isObject(source.data)}, + ]; + const missingFields = requiredFields.filter((item) => !item.valid).map((item) => item.field); + + if (missingFields.length > 0) { + throw new Error(`Missing or invalid QL connector fields: ${missingFields.join(', ')}`); + } + + return true; +}; + +export const isQLConnectionSource = (source: Source): source is SourceWithQLConnector => { + return isString(source.qlConnectionId) && validateQLConnectionSource(source); +}; + +export const prepareSourceWithQLConnection = (source: SourceWithQLConnector) => { + const sourceUrl = CONNECTIONS_DASHSQL.replace( + CONNECTION_ID_PLACEHOLDER, + encodeURIComponent(source.qlConnectionId), + ); + source.url = sourceUrl; + source.method = 'POST'; + return source; +}; + +export const isDatasetSource = (source: Source): source is SourceWithDatasetId => { + return isString(source.datasetId); +}; + +export const prepareSourceWithDataset = (source: SourceWithDatasetId) => { + const urlPath = + isObject(source) && 'path' in source && isString(source.path) ? source.path : 'result'; + + let template: string; + let method: 'POST' | 'GET'; + switch (urlPath) { + case 'result': { + template = DATASET_RESULT_URL; + method = 'POST'; + break; + } + case 'values/distinct': { + template = DATASET_DISTINCTS_URL; + method = 'POST'; + break; + } + case 'fields': { + template = DATASET_FIELDS_URL; + method = 'GET'; + break; + } + default: { + throw Error('Wrong path'); + } + } + + const sourceUrl = template.replace( + DATASET_ID_PLACEHOLDER, + encodeURIComponent(source.datasetId), + ); + + source.url = sourceUrl; + source.method = method; + return source; +}; + +export const prepareSource = (source: Source): Source => { + if (!isObject(source)) { + return source; + } + + if (isAPIConnectorSource(source)) { + return prepareSourceWithAPIConnector(source); + } + + if (isQLConnectionSource(source)) { + return prepareSourceWithQLConnection(source); + } + + if (isDatasetSource(source)) { + return prepareSourceWithDataset(source); + } + + return source; +}; + +const { + DEFAULT_SOURCE_FETCHING_ERROR_STATUS_400, + DEFAULT_SOURCE_FETCHING_ERROR_STATUS_500, + DEFAULT_SOURCE_FETCHING_LIMIT_EXCEEDED_STATUS, + REQUEST_SIZE_LIMIT_EXCEEDED, + ALL_REQUESTS_SIZE_LIMIT_EXCEEDED, +} = configConstants; + +export function getSourcesErrorStatusCode(error: object) { + let maybe400 = false; + let maybe500 = false; + let requestSizeLimitExceeded = false; + Object.values(error).forEach((sourceResult) => { + const possibleStatus = sourceResult?.status; + + if (399 < possibleStatus && possibleStatus < 500) { + maybe400 = true; + } else { + maybe500 = true; + } + + if ( + sourceResult.code === REQUEST_SIZE_LIMIT_EXCEEDED || + sourceResult.code === ALL_REQUESTS_SIZE_LIMIT_EXCEEDED + ) { + requestSizeLimitExceeded = true; + } + }); + + if (maybe400 && !maybe500) { + return DEFAULT_SOURCE_FETCHING_ERROR_STATUS_400; + } + + if (requestSizeLimitExceeded) { + return DEFAULT_SOURCE_FETCHING_LIMIT_EXCEEDED_STATUS; + } + + return DEFAULT_SOURCE_FETCHING_ERROR_STATUS_500; +} diff --git a/src/server/components/charts-engine/components/processor/stack-trace-prepaper.ts b/src/server/components/charts-engine/components/processor/stack-trace-prepaper.ts new file mode 100644 index 0000000..a4353b4 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/stack-trace-prepaper.ts @@ -0,0 +1,26 @@ +export class StackTracePreparer { + static prepare(trace: string) { + const TRACE_STOP_LINES = [ + 'ContextifyScript.Script.runInContext', + 'Object.exports.runInNewContext', + '/components/editor-engine/', + ]; + const TRACE_SANDBOX_ENTRY_LINE = 'components/editor-engine/sandbox'; + + const prepared = []; + const lines = trace.trim().split('\n'); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const containsStopLine = TRACE_STOP_LINES.some((stopLine) => line.includes(stopLine)); + if (containsStopLine) { + break; + } + if (!line.includes(TRACE_SANDBOX_ENTRY_LINE)) { + prepared.push(line); + } + } + + return prepared.join('\n'); + } +} diff --git a/src/server/components/charts-engine/components/processor/types.ts b/src/server/components/charts-engine/components/processor/types.ts new file mode 100644 index 0000000..0919ea7 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/types.ts @@ -0,0 +1,198 @@ +import type {AppContext} from '@gravity-ui/nodekit'; + +import type { + ApiV2DataExportField, + ChartsInsight, + DashWidgetConfig, + EntryPublicAuthor, + IChartEditor, + StringParams, +} from '../../../../../shared'; +import type {Source} from '../../types'; + +import type { + CommentsFetcherFetchResult, + CommentsFetcherPrepareCommentsParams, +} from './comments-fetcher'; +import type {Console, LogItem} from './console'; +import type {DataFetcherResult} from './data-fetcher'; +import type {ProcessorHooks} from './hooks'; + +export type UiTabExportsControl = { + type: string; + param: string; + content?: unknown[]; + updateOnChange?: boolean; + postUpdateOnChange?: boolean; + updateControlsOnChange?: boolean; + disabled?: boolean; +}; + +export type UiTabExports = + | { + lineBreaks?: 'wrap'; + controls: UiTabExportsControl[]; + } + | UiTabExportsControl[]; + +export type ProcessorErrorResponse = { + error: { + code?: string; + debug?: { + message?: string; + [x: string]: any; + }; + message?: string; + details?: Record; + statusCode?: number; + }; + logs_v2?: string; + sources?: Record; +}; + +export type ProcessorSuccessResponse = { + sources: Record; + uiScheme: UiTabExports | null; + params: Record; + actionParams: Record; + /** + * defaultParams is object from Params tab + * needed for reset filters logic + */ + defaultParams: Record; + usedParams: Record; + widgetConfig?: DashWidgetConfig['widgetConfig']; + extra: { + chartsInsights?: ChartsInsight[]; + sideMarkdown?: string; + exportFilename?: string; + colors?: string[]; + }; + dataExport?: Record; + timings: {}; + data?: { + markdown?: string; + html?: string; + meta?: object; + original_markdown?: string; + }; + logs_v2?: string; + config?: string; + highchartsConfig?: string; + comments?: CommentsFetcherFetchResult; + publicAuthor?: EntryPublicAuthor; + id?: string; + revId?: string; + _confStorageConfig?: object; + key?: string; + type?: string; +}; + +export type ProcessorFiles = + | 'Shared' + | 'Params' + | 'Sources' + | 'Highcharts' + | 'Config' + | 'Prepare' + | 'Controls' + | 'Activities'; +export type ProcessorLogs = {modules: LogItem[][]} & Partial< + Record +>; + +export type UserConfig = { + overlayControls?: Record; + notOverlayControls?: boolean; + comments?: CommentsFetcherPrepareCommentsParams['config']; + enableJsAndHtml?: boolean; +}; + +export type NativeModule = { + buildSources: () => {}; + buildGraph: () => {}; + buildUI?: () => {}; + buildChartsConfig?: () => {}; + buildHighchartsConfig?: () => {}; + setConsole?: (console: Console) => void; +}; + +export type RuntimeMetadata = { + error?: unknown; + userParamsOverride?: StringParams; + userConfigOverride?: unknown; + libraryConfigOverride?: unknown; + userActionParamsOverride?: StringParams; + exportFilename?: string; + dataSourcesInfos?: unknown; + sideMarkdown?: string; + extra?: { + chartsInsights?: ChartsInsight[]; + sideMarkdown?: string; + exportFilename?: string; + }; + chartsInsights?: ChartsInsight[]; +}; + +export type ChartApiContext = { + ChartEditor: IChartEditor; + __runtimeMetadata: RuntimeMetadata; +}; + +export type ChartBuilderResult = { + exports: unknown; + executionTiming: [number, number]; + runtimeMetadata: RuntimeMetadata; + name: string; + logs?: LogItem[][]; +}; + +export type ChartBuilder = { + type?: 'CHART_EDITOR' | 'WIZARD' | 'CONTROL'; + buildShared: () => Promise; + buildModules: (args: { + subrequestHeaders: Record; + ctx: AppContext; + onModuleBuild: (args: {executionTiming: [number, number]; filename: string}) => void; + }) => Promise>; + buildParams: (args: { + params: StringParams; + usedParams: StringParams; + actionParams: StringParams; + hooks: ProcessorHooks; + }) => Promise; + buildUrls: (args: { + params: StringParams; + actionParams: StringParams; + hooks: ProcessorHooks; + }) => Promise; + buildPaletteSources?: (args: {sources?: Record}) => Promise; + buildChartLibraryConfig: (args: { + data?: unknown; + params: StringParams; + actionParams: StringParams; + hooks: ProcessorHooks; + }) => Promise; + buildChartConfig: (args: { + data?: unknown; + params: StringParams; + actionParams: StringParams; + hooks: ProcessorHooks; + }) => Promise; + buildChart: (args: { + data: unknown; + sources?: Record; + params: StringParams; + usedParams: StringParams; + actionParams: StringParams; + hooks: ProcessorHooks; + }) => Promise; + buildUI: (args: { + data?: unknown; + params: StringParams; + usedParams: StringParams; + actionParams: StringParams; + hooks: ProcessorHooks; + }) => Promise; + dispose: () => void; +}; diff --git a/src/server/components/charts-engine/components/processor/utils.ts b/src/server/components/charts-engine/components/processor/utils.ts new file mode 100644 index 0000000..4ae5f21 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/utils.ts @@ -0,0 +1,65 @@ +import isObject from 'lodash/isObject'; +import isString from 'lodash/isString'; + +type ValidatedWrapFnArgs = { + fn: (...args: unknown[]) => unknown; + args?: unknown; + libs?: string[]; +}; + +// There is a user value here, it could have any type +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isWrapFnArgsValid = (value: any): value is ValidatedWrapFnArgs => { + if (!value || typeof value !== 'object') { + throw new Error('You should pass an object to Editor.wrapFn method'); + } + + if (typeof value.fn !== 'function') { + throw new Error('"fn" property should be a function'); + } + + if (typeof value.libs !== 'undefined' && !Array.isArray(value.libs)) { + throw new Error('"libs" property should be an array of strings'); + } + + return true; +}; + +export function getMessageFromUnknownError(e: unknown) { + return isObject(e) && 'message' in e && isString(e.message) ? e.message : ''; +} + +const ESCAPE_CHART_FIELDS_DATE = new Date('2030-01-01').valueOf(); +export function isChartWithJSAndHtmlAllowed(config: {createdAt: string}) { + if (!config.createdAt) { + return true; + } + + return new Date(config.createdAt).valueOf() < ESCAPE_CHART_FIELDS_DATE; +} + +export function cleanJSONFn(value: T): T { + if (Array.isArray(value)) { + return value.map(cleanJSONFn) as T; + } + if (typeof value === 'object' && value !== null) { + const replaced: Record = {}; + Object.keys(value).forEach((key) => { + const currentValue = (value as Record)[key]; + replaced[key] = cleanJSONFn(currentValue); + }); + return replaced as T; + } + if (typeof value !== 'string') { + return value; + } + if (value.length < 8) { + return value; + } + const prefix = value.substring(0, 8); + + if (prefix === '_NuFrRa_') { + return undefined as T; + } + return value; +} diff --git a/src/server/components/charts-engine/components/processor/worker-chart-builder.ts b/src/server/components/charts-engine/components/processor/worker-chart-builder.ts new file mode 100644 index 0000000..7e47110 --- /dev/null +++ b/src/server/components/charts-engine/components/processor/worker-chart-builder.ts @@ -0,0 +1,232 @@ +import type {Proxy} from 'workerpool'; + +import type { + DashWidgetConfig, + ServerChartsConfig, + Shared, + StringParams, + TenantSettings, +} from '../../../../../shared'; +import {getFieldUISettings, getServerFeatures} from '../../../../../shared'; +import type {SourceRequests} from '../../../../modes/charts/plugins/datalens/url/types'; +import {addColorPaletteRequest} from '../../../../modes/charts/plugins/helpers/color-palettes'; +import {registry} from '../../../../registry'; +import {getDefaultColorPaletteId} from '../utils'; +import type {WizardWorker} from '../wizard-worker/types'; +import {getChartApiContext} from '../wizard-worker/utils'; + +import type {ChartBuilder, ChartBuilderResult} from './types'; + +const ONE_SECOND = 1000; +const PREPARE_EXECUTION_TIMEOUT = ONE_SECOND * 9.5; + +type WizardChartBuilderArgs = { + timeouts?: { + params?: number; + prepare?: number; + config?: number; + libraryConfig?: number; + sources?: number; + }; + userLogin: string | null; + userLang: string; + config: { + data: { + shared: string; + }; + }; + widgetConfig?: DashWidgetConfig['widgetConfig']; + isScreenshoter: boolean; + worker: Proxy; + tenantSettings: TenantSettings; +}; + +export const getWizardChartBuilder = async ( + args: WizardChartBuilderArgs, +): Promise => { + const {config, widgetConfig, userLang, worker, timeouts = {}, tenantSettings} = args; + const wizardWorker = worker; + let shared: Record; + + const app = registry.getApp(); + const features = getServerFeatures(app.nodekit.ctx); + const {getAvailablePalettesMap} = registry.common.functions.getAll(); + const palettes = getAvailablePalettesMap(); + const defaultColorPaletteId = getDefaultColorPaletteId({ + ctx: app.nodekit.ctx, + tenantSettings, + }); + + // Nothing happens here - just for compatibility with the editor + const emptyStep = + (name: string) => async (options: {params: StringParams; actionParams: StringParams}) => { + const {params, actionParams} = options; + const timeStart = process.hrtime(); + const context = getChartApiContext({ + name, + shared, + params, + actionParams, + widgetConfig, + userLang, + }); + + return { + exports: {}, + executionTiming: process.hrtime(timeStart), + name, + runtimeMetadata: context.__runtimeMetadata, + }; + }; + + const chartBuilder: ChartBuilder = { + type: 'WIZARD', + buildShared: async () => { + if (typeof config.data.shared === 'string') { + shared = JSON.parse(config.data.shared); + } else { + shared = config.data.shared; + } + }, + buildModules: async () => { + return {}; + }, + + buildParams: async (args) => { + if (typeof wizardWorker.buildParams === 'function') { + const timeStart = process.hrtime(); + const execResult = await wizardWorker + .buildParams({shared, userLang}) + .timeout(timeouts.params || ONE_SECOND); + + return { + executionTiming: process.hrtime(timeStart), + name: 'Params', + ...execResult, + }; + } + + return emptyStep('Params')(args); + }, + + buildUrls: async (options) => { + const {params, actionParams} = options; + const timeStart = process.hrtime(); + const execResult = await wizardWorker + .buildSources({ + shared: shared as Shared, + params, + actionParams, + widgetConfig, + userLang, + palettes, + features, + }) + .timeout(timeouts.sources || ONE_SECOND); + + return { + executionTiming: process.hrtime(timeStart), + name: 'Sources', + ...execResult, + }; + }, + + buildPaletteSources: async (options) => { + const {sources = {}} = options; + const timeStart = process.hrtime(); + + const result: SourceRequests = {}; + + addColorPaletteRequest({result, colorPaletteId: defaultColorPaletteId, palettes}); + Object.values(sources).forEach((s) => { + (s.datasetFields ?? []).forEach((d) => { + const uiSettings = getFieldUISettings({field: d}); + const palette = uiSettings?.palette; + + if (palette) { + addColorPaletteRequest({result, colorPaletteId: palette, palettes}); + } + }); + }); + + return { + exports: result, + executionTiming: process.hrtime(timeStart), + name: 'PaletteSources', + runtimeMetadata: {}, + }; + }, + + buildChartLibraryConfig: async (options) => { + const {params, actionParams} = options; + const timeStart = process.hrtime(); + const execResult = await wizardWorker + .buildLibraryConfig({ + shared: shared as ServerChartsConfig, + params, + actionParams, + widgetConfig, + userLang, + features, + }) + .timeout(timeouts.libraryConfig || ONE_SECOND); + + return { + executionTiming: process.hrtime(timeStart), + name: 'Highcharts', + ...execResult, + } as ChartBuilderResult; + }, + + buildChartConfig: async (options) => { + const {params, actionParams} = options; + const timeStart = process.hrtime(); + const execResult = await wizardWorker + .buildChartConfig({ + shared: shared as ServerChartsConfig, + params, + actionParams, + widgetConfig, + userLang, + features, + }) + .timeout(timeouts.config || ONE_SECOND); + + return { + executionTiming: process.hrtime(timeStart), + name: 'Config', + ...execResult, + }; + }, + + buildChart: async (options) => { + const {data, sources, params, actionParams} = options; + const timeStart = process.hrtime(); + const execResult = await wizardWorker + .buildChart({ + shared: shared as ServerChartsConfig, + params, + actionParams, + widgetConfig, + userLang, + data, + palettes, + features, + defaultColorPaletteId, + sources, + }) + .timeout(timeouts.prepare || PREPARE_EXECUTION_TIMEOUT); + + return { + executionTiming: process.hrtime(timeStart), + name: 'Prepare', + ...execResult, + }; + }, + + buildUI: emptyStep('Controls'), + dispose: () => {}, + }; + + return chartBuilder; +}; diff --git a/src/server/components/charts-engine/components/request/axios.ts b/src/server/components/charts-engine/components/request/axios.ts new file mode 100644 index 0000000..eecfe2b --- /dev/null +++ b/src/server/components/charts-engine/components/request/axios.ts @@ -0,0 +1,559 @@ +import type {IncomingMessage, OutgoingHttpHeaders} from 'http'; +import querystring from 'node:querystring'; + +import type {AppConfig, AppContext} from '@gravity-ui/nodekit'; +import {crc32c} from '@node-rs/crc32'; +import type {AxiosInstance, AxiosRequestConfig, RawAxiosRequestHeaders} from 'axios'; +import axios from 'axios'; + +import {getAxios} from '../../../axios'; +import {CacheClient} from '../../../cache-client'; +import {config} from '../../constants'; +import {hideSensitiveData} from '../utils'; + +type RequestOptions = Omit & { + useCaching?: boolean; + signal: AbortSignal; + uri?: string; + form?: unknown; + json?: boolean; + qs?: Record; + body?: unknown; + headers?: OutgoingHttpHeaders | RawAxiosRequestHeaders; +}; + +type CachedRequestOptions = RequestOptions & { + uri: string; + ctx: AppContext; + spCacheDuration: string | number | null; +}; + +interface RequestInstanceLike { + abort: () => void; + emit: (event: string, error: Error) => void; + _started?: boolean; +} + +const isCachedRequestOptions = ( + options: RequestOptions | CachedRequestOptions, +): options is CachedRequestOptions => + 'useCaching' in options && Boolean(options.useCaching) && 'ctx' in options; + +const { + REQUEST_SIZE_LIMIT, + ALL_REQUESTS_SIZE_LIMIT, + REQUEST_SIZE_LIMIT_EXCEEDED, + ALL_REQUESTS_SIZE_LIMIT_EXCEEDED, +} = config; + +const CACHE_PREFIX = 'sp'; + +let cacheClient: CacheClient; + +function normalizeHeaders( + headers: OutgoingHttpHeaders | RawAxiosRequestHeaders | undefined, +): RawAxiosRequestHeaders | undefined { + if (!headers) { + return undefined; + } + + const normalized: RawAxiosRequestHeaders = {}; + + for (const [key, value] of Object.entries(headers)) { + if (value === undefined) { + continue; + } + + if (Array.isArray(value)) { + normalized[key] = value.join(', '); + } else if (typeof value === 'string' || typeof value === 'number') { + normalized[key] = value; + } + } + + return normalized; +} + +function normalizeResponseHeaders(headers: Record): Record { + const normalized: Record = {}; + + for (const [key, value] of Object.entries(headers)) { + if (typeof value === 'string') { + normalized[key] = value; + } else if (typeof value === 'number') { + normalized[key] = String(value); + } else if (Array.isArray(value)) { + normalized[key] = value.join(', '); + } + } + + return normalized; +} + +function isStreamLike(value: unknown): value is IncomingMessage { + return ( + value !== null && + typeof value === 'object' && + typeof (value as {pipe?: unknown}).pipe === 'function' + ); +} + +async function readErrorBody(data: unknown, headers: Record): Promise { + if (data === null || data === undefined) { + return data; + } + + if (isStreamLike(data)) { + return new Promise((resolve) => { + const chunks: Buffer[] = []; + + data.on('data', (chunk: Buffer) => { + chunks.push(chunk); + }); + + data.on('end', () => { + const buffer = Buffer.concat(chunks); + const body = buffer.toString('utf-8'); + const contentType = headers['content-type']; + + if (typeof contentType === 'string' && contentType.includes('application/json')) { + try { + resolve(JSON.parse(body)); + } catch { + resolve(body); + } + } else { + resolve(body); + } + }); + + data.on('error', () => { + resolve({}); + }); + }); + } + + return data; +} + +export class RequestAxios { + private static axiosInstance: AxiosInstance; + private static appConfig: AppConfig; + + static init({ + cacheClientInstance, + config: appConfig, + }: { + cacheClientInstance: CacheClient; + config: AppConfig; + }) { + cacheClient = cacheClientInstance; + this.appConfig = appConfig; + this.axiosInstance = getAxios(appConfig); + } + + static request({ + requestOptions, + useCaching = false, + requestControl, + }: { + requestOptions: RequestOptions | CachedRequestOptions; + useCaching?: boolean; + requestControl: { + allBuffersLength: number; + }; + }) { + const {signal} = requestOptions; + + if (signal?.aborted === true) { + throw new Error(signal.reason); + } + + function dataLengthCheck(requestInstance: RequestInstanceLike) { + let bufferLength = 0; + + return function (chunk: Buffer | string) { + const {length} = chunk; + + bufferLength += length; + requestControl.allBuffersLength += length; + + if ( + requestControl.allBuffersLength > ALL_REQUESTS_SIZE_LIMIT || + bufferLength > REQUEST_SIZE_LIMIT + ) { + if (requestInstance._started === true) { + requestInstance.abort(); + } + + const error = new Error( + bufferLength > REQUEST_SIZE_LIMIT + ? REQUEST_SIZE_LIMIT_EXCEEDED + : ALL_REQUESTS_SIZE_LIMIT_EXCEEDED, + ); + + requestInstance.emit('error', error); + } + }; + } + + if (useCaching && isCachedRequestOptions(requestOptions)) { + return this.cacheRequest({requestOptions, dataLengthCheck}); + } else { + return this.directRequest({requestOptions, dataLengthCheck}); + } + } + + static directRequest({ + requestOptions, + dataLengthCheck, + }: { + requestOptions: RequestOptions; + dataLengthCheck: (requestInstance: RequestInstanceLike) => (data: Buffer | string) => void; + }) { + const axiosConfig: AxiosRequestConfig = { + ...this.mapRequestOptions(requestOptions), + responseType: 'stream', + signal: requestOptions.signal, + }; + + return this.getAxiosInstance() + .request(axiosConfig) + .then((response) => { + const stream = response.data; + const chunks: Buffer[] = []; + + const requestInstance: RequestInstanceLike = { + abort: () => stream.destroy(), + emit: (event: string, error: Error) => { + if (event === 'error') { + stream.destroy(error); + } + }, + _started: true, + }; + + const onData = dataLengthCheck(requestInstance); + + return new Promise<{ + statusCode: number; + body: unknown; + headers: Record; + }>((resolve, reject) => { + stream.on('data', (chunk: Buffer) => { + chunks.push(chunk); + try { + onData(chunk); + } catch (error) { + const errorMessage = (error as Error).message; + if ( + errorMessage === REQUEST_SIZE_LIMIT_EXCEEDED || + errorMessage === ALL_REQUESTS_SIZE_LIMIT_EXCEEDED + ) { + const wrappedError = Object.assign( + new Error(`Error: ${errorMessage}`), + {code: errorMessage}, + ); + stream.destroy(); + reject(wrappedError); + } else { + stream.destroy(); + reject(error); + } + } + }); + + stream.on('end', () => { + const buffer = Buffer.concat(chunks); + const body = this.parseResponse(buffer, response.headers); + + const result = { + statusCode: response.status, + body, + headers: normalizeResponseHeaders( + response.headers as Record, + ), + }; + + resolve(result); + }); + + stream.on('error', (error) => { + reject(error); + }); + }); + }) + .catch(async (error) => { + throw await this.normalizeError(error); + }); + } + + static cacheRequest({ + requestOptions, + dataLengthCheck, + }: { + requestOptions: CachedRequestOptions; + dataLengthCheck: (requestInstance: RequestInstanceLike) => (data: Buffer | string) => void; + }) { + const {uri, ctx} = requestOptions; + let {spCacheDuration, useCaching} = requestOptions; + + const durationTime = Math.floor(Number(spCacheDuration)); + const removingCondition = spCacheDuration && durationTime < 7; + + const maxValue = 5184000; + + if ( + spCacheDuration && + (!isFinite(durationTime) || durationTime < 0 || durationTime > maxValue) + ) { + ctx.logError('Cache duration invalid', {uri: hideSensitiveData(uri)}); + + useCaching = false; + spCacheDuration = null; + } + + let dataHash; + if (requestOptions.body || requestOptions.form) { + try { + if (requestOptions.body) { + dataHash = crc32c(JSON.stringify(requestOptions.body)); + } else if (requestOptions.form) { + dataHash = crc32c(JSON.stringify(requestOptions.form)); + } + } catch (error) { + ctx.logError('Failed to calculate data hash', error); + } + } + + let key = `${CACHE_PREFIX}:${hideSensitiveData(uri)}`; + + if (dataHash) { + key += `:${dataHash}`; + } + + const debugInfo = { + url: key, + fromCache: false, + }; + + const sequence = Promise.resolve(); + + if (removingCondition) { + sequence + .then(() => { + return cacheClient.del({key}); + }) + .then(() => { + ctx.log('Cache value successfully removed', {key}); + }) + .catch(() => { + ctx.logError('Cache proxy deletion error', {key}); + }); + } + + const getData = (cacheServiceFailing: boolean) => { + return this.directRequest({requestOptions, dataLengthCheck}) + .then((response) => { + if (useCaching && !removingCondition && !cacheServiceFailing) { + cacheClient + .set({key, value: response.body, ttl: durationTime}) + .then(() => { + ctx.log('Cache set', {key, durationTime}); + }) + .catch((error) => { + ctx.logError('Cache set failed', error); + }); + } + + ctx.log('CACHE_SENDING_VALUE_DIRECTLY', {uri: hideSensitiveData(uri)}); + + return response.body; + }) + .catch((error) => { + if (error.response) { + ctx.logError('Cache proxy request error', error); + + throw error; + } else { + throw error; + } + }); + }; + + return sequence + .then(() => { + if (useCaching && spCacheDuration) { + ctx.log('Cache getting value...', {key}); + + return cacheClient.get({key}); + } else { + return null; + } + }) + .catch((error) => { + ctx.logError('Cache service failed', error); + + return {status: CacheClient.NOT_OK}; + }) + .then((cacheResponse) => { + const cacheResponseStatus = cacheResponse && cacheResponse.status; + + if ( + cacheResponseStatus === CacheClient.OK && + cacheResponse && + 'data' in cacheResponse + ) { + debugInfo.fromCache = true; + + ctx.log('Cache successfully recovered'); + + cacheResponse.data.debugInfo = debugInfo; + + return cacheResponse.data; + } else { + if (useCaching) { + ctx.log( + 'Cache failed, requesting without cache', + cacheResponse ?? undefined, + ); + } else { + ctx.log('Cache disabled, requesting without cache'); + } + + return getData(cacheResponseStatus === CacheClient.NOT_OK); + } + }) + .catch((error) => { + ctx.logError('Cache proxy error', error); + + throw error; + }); + } + + private static mapRequestOptions(options: RequestOptions): AxiosRequestConfig { + const normalizedHeaders = normalizeHeaders(options.headers); + + const axiosConfig: AxiosRequestConfig = { + url: options.uri || options.url, + method: options.method, + headers: normalizedHeaders, + timeout: options.timeout, + maxRedirects: options.maxRedirects ?? 0, + beforeRedirect: (redirectOptions) => { + // Preserve all original headers on redirect (matches request-promise-native behavior) + if (normalizedHeaders) { + Object.assign(redirectOptions.headers, normalizedHeaders); + } + }, + }; + + if (options.body !== undefined) { + axiosConfig.data = options.body; + } + + if (options.form !== undefined) { + axiosConfig.data = + typeof options.form === 'string' + ? options.form + : querystring.stringify( + options.form as Record, + ); + axiosConfig.headers = { + ...axiosConfig.headers, + 'content-type': 'application/x-www-form-urlencoded', + }; + } + + if (options.qs) { + axiosConfig.params = options.qs; + } + + return axiosConfig; + } + + private static parseResponse(buffer: Buffer, headers: Record): unknown { + const body = buffer.toString('utf-8'); + const contentType = headers['content-type']; + + if ( + typeof body === 'string' && + // Stat in qloud do no return content-type with code 204 + typeof contentType === 'string' && + contentType.indexOf('application/json') > -1 + ) { + try { + return JSON.parse(body); + } catch (e) { + return body; + } + } + + return body; + } + + private static async normalizeError(error: unknown): Promise< + Error & { + statusCode?: number; + code?: string; + response?: { + statusCode: number; + body: unknown; + headers: Record; + req?: { + headers: Record; + }; + request?: { + headers: Record; + }; + }; + } + > { + if (axios.isAxiosError(error)) { + // Serialize only primitive values from headers to avoid circular references + const safeHeaders = error.config?.headers + ? normalizeHeaders(error.config.headers as OutgoingHttpHeaders) + : undefined; + + const responseHeaders = error.response?.headers as Record; + const body = error.response + ? await readErrorBody(error.response.data, responseHeaders || {}) + : undefined; + + const normalized = Object.assign(new Error(error.message), { + statusCode: error.response?.status, + code: error.code, + stack: error.stack, + response: error.response + ? { + statusCode: error.response.status, + body, + headers: normalizeResponseHeaders(responseHeaders || {}), + req: safeHeaders ? {headers: safeHeaders} : undefined, + request: safeHeaders ? {headers: safeHeaders} : undefined, + } + : undefined, + }); + + return normalized; + } + + if (error instanceof Error) { + return error; + } + + return new Error(String(error)); + } + + private static getAxiosInstance(): AxiosInstance { + if (!this.axiosInstance) { + if (!this.appConfig) { + throw new Error( + 'RequestAxios not initialized. Call RequestAxios.init() before making requests.', + ); + } + this.axiosInstance = getAxios(this.appConfig); + } + return this.axiosInstance; + } +} diff --git a/src/server/components/charts-engine/components/request/index.ts b/src/server/components/charts-engine/components/request/index.ts new file mode 100644 index 0000000..f668d1e --- /dev/null +++ b/src/server/components/charts-engine/components/request/index.ts @@ -0,0 +1,29 @@ +import type {AppConfig} from '@gravity-ui/nodekit'; + +import type {CacheClient} from '../../../cache-client'; + +import {RequestAxios} from './axios'; + +export class Request { + static init({ + cacheClientInstance, + config: appConfig, + }: { + cacheClientInstance: CacheClient; + config: AppConfig; + }) { + RequestAxios.init({cacheClientInstance, config: appConfig}); + } + + static request({ + requestOptions, + useCaching = false, + requestControl, + }: Parameters[0]) { + return RequestAxios.request({ + requestOptions, + useCaching, + requestControl, + }); + } +} diff --git a/src/server/components/charts-engine/components/storage/base.ts b/src/server/components/charts-engine/components/storage/base.ts new file mode 100644 index 0000000..80baa61 --- /dev/null +++ b/src/server/components/charts-engine/components/storage/base.ts @@ -0,0 +1,257 @@ +import type {Request} from '@gravity-ui/expresskit'; +import {type AppConfig, type AppContext, REQUEST_ID_PARAM_NAME} from '@gravity-ui/nodekit'; +import {v4 as uuidv4} from 'uuid'; + +import type {WorkbookId} from '../../../../../shared'; +import type {TelemetryCallbacks} from '../../types'; + +import type {EmbeddingInfo, ResolvedConfig} from './types'; +import type {USProvider} from './united-storage/provider'; + +const DEFAULT_PRELOAD_FETCHING_INTERVAL = 120e3; + +export type ResolveConfigProps = { + id?: string; + key: string; + headers: Request['headers']; + unreleased?: boolean; + noCache?: boolean; + requestId?: string; + storageApiPath?: string; + extraAllowedHeaders?: string[]; + workbookId?: WorkbookId; + includeServicePlan?: boolean; + includeTenantFeatures?: boolean; + includeTenantSettings?: boolean; +}; + +export type EmbedResolveConfigProps = ResolveConfigProps & {embedToken: string; embedId: string}; + +export type ResolveConfigError = { + response?: {status: number}; + status?: number; + statusCode?: number; +} & Error; + +export type BaseStorageInitParams = { + initialPreloadFetchingInterval?: number; + initialOauthToken: string; + config: {usEndpoint: string; requestIdHeaderName: string}; + telemetryCallbacks: { + onConfigFetched?: TelemetryCallbacks['onConfigFetched']; + onConfigFetchingFailed?: TelemetryCallbacks['onConfigFetchingFailed']; + }; + flags: {alwaysUnreleased?: boolean}; +}; + +export class BaseStorage { + provider: typeof USProvider; + requestIdHeaderName = ''; + preloadFetchingInterval?: number; + oauthToken?: string; + cachedConfigs: Record = {}; + flags: {alwaysUnreleased?: boolean | undefined} = {}; + telemetryCallbacks: { + onConfigFetched?: TelemetryCallbacks['onConfigFetched']; + onConfigFetchingFailed?: TelemetryCallbacks['onConfigFetchingFailed']; + } = {}; + + constructor(provider: typeof USProvider) { + this.provider = provider; + } + + init({ + initialPreloadFetchingInterval = DEFAULT_PRELOAD_FETCHING_INTERVAL, + initialOauthToken, + config, + telemetryCallbacks, + flags, + }: BaseStorageInitParams) { + this.preloadFetchingInterval = initialPreloadFetchingInterval; + this.oauthToken = initialOauthToken; + this.cachedConfigs = {}; + this.requestIdHeaderName = config.requestIdHeaderName; + + this.flags = flags; + this.telemetryCallbacks = telemetryCallbacks; + + this.initProvider(config); + } + + async refreshPreloaded( + parentCtx: AppContext & {config: AppConfig}, + callback: (configs: Record) => void, + ) { + const requestId = uuidv4(); + const ctx = parentCtx.create('Configs preloading', {loggerPostfix: requestId}); + ctx.set(REQUEST_ID_PARAM_NAME, requestId); + ctx.setTag('request_id', requestId); + + ctx.log('STORAGE_REFRESHING_PRELOADED'); + const preloadList = parentCtx.config.preloadList || []; + try { + for (const key of preloadList) { + await this.resolveConfig(ctx, { + key, + headers: { + authorization: `OAuth ${this.oauthToken}`, + }, + noCache: true, + }) + .then((config) => { + this.cachedConfigs[key] = config; + }) + .catch((error) => { + ctx.logError('Error preloading config', error, { + key, + }); + }); + } + + setTimeout( + () => this.refreshPreloaded(parentCtx, callback), + this.preloadFetchingInterval, + ); + callback(this.cachedConfigs); + } catch (error) { + ctx.logError('Error preloading configs', error); + } finally { + ctx.end(); + } + } + + initPreloading(ctx: AppContext, callback: (configs: Record) => void) { + this.refreshPreloaded(ctx, callback).catch((error) => { + ctx.logError('Error preloading configs', error); + }); + } + + fetchConfig( + ctx: AppContext, + params: EmbedResolveConfigProps & {unreleased: boolean}, + ): Promise; + fetchConfig( + ctx: AppContext, + params: ResolveConfigProps & {unreleased: boolean}, + ): Promise; + fetchConfig( + ctx: AppContext, + params: (ResolveConfigProps | EmbedResolveConfigProps) & {unreleased: boolean}, + ): Promise { + const { + headers, + unreleased, + storageApiPath, + extraAllowedHeaders, + workbookId, + includeServicePlan, + includeTenantFeatures, + } = params; + const requestId = ctx.get(REQUEST_ID_PARAM_NAME); + if (requestId) { + headers[this.requestIdHeaderName] = requestId; + } + + const storageRetrieveArgs = { + headers, + unreleased: this.flags.alwaysUnreleased ? true : unreleased, + includePermissionsInfo: true, + includeServicePlan, + includeTenantFeatures, + includeTenantSettings: true, + storageApiPath, + extraAllowedHeaders, + }; + + const onConfigFetched = this.telemetryCallbacks.onConfigFetched || (() => {}); + const onConfigFetchingFailed = this.telemetryCallbacks.onConfigFetchingFailed || (() => {}); + + const startTime = new Date().getTime(); + + let retrieve: Promise; + let id: string; + + if (params.id && 'embedToken' in params && params.embedToken) { + retrieve = this.provider.retrieveByTokenAndId(ctx, { + token: params.embedToken, + id: params.id, + ...storageRetrieveArgs, + }); + id = `embed-${params.embedId}`; + } else if (params.id) { + retrieve = this.provider.retrieveById(ctx, { + id: params.id, + workbookId, + ...storageRetrieveArgs, + }); + id = params.id; + } else if ('embedToken' in params && params.embedToken) { + retrieve = this.provider.retrieveByToken(ctx, { + token: params.embedToken, + ...storageRetrieveArgs, + }); + id = `embed-${params.embedId}`; + } else if (params.key) { + retrieve = this.provider.retrieveByKey(ctx, {key: params.key, ...storageRetrieveArgs}); + id = params.key; + } else { + throw new Error('Wrong fetch config params'); + } + + const traceId = ctx.getTraceId(); + const tenantId = ctx.get('tenantId'); + const userId = ctx.get('userId'); + + return retrieve + .then((result) => { + onConfigFetched({ + id, + requestId, + traceId, + statusCode: 200, + latency: new Date().getTime() - startTime, + tenantId, + userId, + }); + return result; + }) + .catch((error: ResolveConfigError) => { + onConfigFetchingFailed(error, { + id, + requestId, + traceId, + tenantId, + statusCode: error.status || error.statusCode || error.response?.status || 500, + latency: new Date().getTime() - startTime, + userId, + }); + throw error; + }); + } + + setPreloaded(preloaded: Record) { + this.cachedConfigs = preloaded; + } + + resolveConfig(ctx: AppContext, props: ResolveConfigProps): Promise { + const {key, unreleased = false, noCache = false} = props; + if (!noCache && !unreleased && this.cachedConfigs[key]) { + ctx.log('STORAGE_CONF_PRELOAD_HIT', {key}); + return Promise.resolve(this.cachedConfigs[key]); + } + + return this.fetchConfig(ctx, {...props, unreleased}); + } + + resolveEmbedConfig(ctx: AppContext, props: EmbedResolveConfigProps): Promise { + const {unreleased = false} = props; + return this.fetchConfig(ctx, {...props, unreleased}); + } + + private initProvider(config: BaseStorageInitParams['config']) { + this.provider.init({ + endpoint: config.usEndpoint, + requestIdHeaderName: this.requestIdHeaderName, + }); + } +} diff --git a/src/server/components/charts-engine/components/storage/index.ts b/src/server/components/charts-engine/components/storage/index.ts new file mode 100644 index 0000000..7afc95a --- /dev/null +++ b/src/server/components/charts-engine/components/storage/index.ts @@ -0,0 +1,32 @@ +import type {AppContext} from '@gravity-ui/nodekit'; + +import type {BaseStorageInitParams, EmbedResolveConfigProps, ResolveConfigProps} from './base'; +import type {EmbeddingInfo, ResolvedConfig} from './types'; +import {UnitedStorage} from './united-storage'; +import {USProvider} from './united-storage/provider'; + +let storage: UnitedStorage; + +export function initStorage(data: BaseStorageInitParams) { + storage = new UnitedStorage(USProvider); + storage.init(data); +} + +export function resolveConfig( + ctx: AppContext, + options: ResolveConfigProps, +): Promise { + return storage.resolveConfig(ctx, options); +} + +export function resolveEmbedConfig( + ctx: AppContext, + options: EmbedResolveConfigProps, +): Promise { + return storage.resolveEmbedConfig(ctx, options); +} + +export function initPreloading(ctx: AppContext) { + ctx.log('ChartsEngine: initializing config preloading'); + return storage && storage.initPreloading(ctx, (preloaded) => storage.setPreloaded(preloaded)); +} diff --git a/src/server/components/charts-engine/components/storage/types.ts b/src/server/components/charts-engine/components/storage/types.ts new file mode 100644 index 0000000..6d38d7d --- /dev/null +++ b/src/server/components/charts-engine/components/storage/types.ts @@ -0,0 +1,93 @@ +import type { + ControlType, + DashData, + EntryAnnotation, + EntryPublicAuthor, + EntryScope, + TenantSettings, + WorkbookId, +} from '../../../../../shared'; +import type {ChartStorageType} from '../../types'; +import type {ChartTemplates} from '../chart-generator'; + +export type PublicAuthorData = {text?: string; link?: string}; + +export type ResolvedConfig = { + entryId: string; + data: { + js: string; + documentation_en: string; + documentation_ru: string; + shared: string; + ui: string; + url: string; + graph: string; + params: string; + statface_graph: string; + map?: string; + ymap?: string; + activities?: string; + }; + key: string; + links?: string[]; + meta: { + is_release?: boolean; + stype: ChartStorageType | ControlType.Dash; + owner?: string; + }; + permissions: {execute: boolean; read: boolean; edit: boolean; admin: boolean}; + scope: EntryScope; + type: string; + public: false; + isFavorite?: boolean; + revId?: string; + savedId: string; + publishedId: string; + createdAt: string; + createdBy: string; + updatedAt: string; + updatedBy: string; + workbookId: WorkbookId; + template?: keyof ChartTemplates; + owner?: string; + publicAuthor?: EntryPublicAuthor; + servicePlan?: string; + tenantFeatures?: Record; + tenantSettings?: TenantSettings; + annotation?: EntryAnnotation; +}; + +export type ReducedResolvedConfig = ResolvedConfig & {data: {shared: string | object}}; + +export type EmbeddingInfo = { + token: EmbeddingToken; + embed: { + embedId: string; + title: string; + embeddingSecretId: string; + entryId: string; + depsIds: string[]; + unsignedParams: string[]; + privateParams: string[]; + createdBy: string; + createdAt: string; + publicParamsMode: boolean; + settings: { + enableExport?: boolean; + }; + }; + entry: ChartEntryData | DashEntryData; +}; + +export type ChartEntryData = ResolvedConfig & { + scope: EntryScope.Widget; +}; + +export type DashEntryData = ResolvedConfig & {data: DashData; scope: EntryScope.Dash}; + +type EmbeddingToken = { + embedId: string; + iat: number; + exp: number; + params?: Record; +}; diff --git a/src/server/components/charts-engine/components/storage/united-storage/index.ts b/src/server/components/charts-engine/components/storage/united-storage/index.ts new file mode 100644 index 0000000..4025a43 --- /dev/null +++ b/src/server/components/charts-engine/components/storage/united-storage/index.ts @@ -0,0 +1,3 @@ +import {BaseStorage} from '../base'; + +export class UnitedStorage extends BaseStorage {} diff --git a/src/server/components/charts-engine/components/storage/united-storage/provider.ts b/src/server/components/charts-engine/components/storage/united-storage/provider.ts new file mode 100644 index 0000000..da038c8 --- /dev/null +++ b/src/server/components/charts-engine/components/storage/united-storage/provider.ts @@ -0,0 +1,811 @@ +import type {IncomingHttpHeaders} from 'http'; + +import type {Request} from '@gravity-ui/expresskit'; +import type {AppContext} from '@gravity-ui/nodekit'; +import type {AxiosRequestConfig} from 'axios'; +import axios from 'axios'; +import axiosRetry from 'axios-retry'; + +import type { + EntryAnnotation, + EntryAnnotationArgs, + EntryPublicAuthor, + TenantSettings, + WorkbookId, +} from '../../../../../../shared'; +import { + AuthHeader, + DL_COMPONENT_HEADER, + DL_CONTEXT_HEADER, + DL_EMBED_TOKEN_HEADER, + EntryUpdateMode, + FORWARDED_FOR_HEADER, + PROJECT_ID_HEADER, + SuperuserHeader, + TENANT_ID_HEADER, + TRACE_ID_HEADER, + US_PUBLIC_API_TOKEN_HEADER, + WORKBOOK_ID_HEADER, +} from '../../../../../../shared'; +import {ErrorCode, TIMEOUT_10_SEC} from '../../../../../../shared/constants'; +import {createErrorHandler} from '../../error-handler'; +import {getDuration} from '../../utils'; +import type {ChartEntryData, DashEntryData, EmbeddingInfo} from '../types'; + +const handleError = createErrorHandler({ + meta: { + tags: { + component: 'storage', + }, + }, +}); + +axiosRetry(axios, { + retries: 2, + retryDelay: (retryCount) => { + return 50 * retryCount; + }, +}); + +const ENTRY_NOT_FOUND = 'ENTRY_NOT_FOUND'; +const PASSED_PROPERTIES: (keyof Entry)[] = [ + 'entryId', + 'data', + 'key', + 'links', + 'meta', + 'permissions', + 'scope', + 'type', + 'public', + 'isFavorite', + 'revId', + 'savedId', + 'publishedId', + 'createdAt', + 'createdBy', + 'updatedAt', + 'updatedBy', + 'workbookId', + 'servicePlan', + 'tenantFeatures', + 'tenantSettings', + 'annotation', +]; + +export type Entry = { + entryId?: string; + meta?: { + entryId: string; + key: string; + stype?: string; + }; + key?: string; + type?: string; + data?: unknown; + links?: unknown; + permissions?: {execute: boolean; read: boolean; edit: boolean; admin: boolean}; + scope?: string; + public?: false; + isFavorite?: undefined; + revId?: string; + savedId?: string; + publishedId?: string; + createdAt?: string; + createdBy?: string; + unversionedData?: { + publicAuthor?: EntryPublicAuthor; + }; + updatedAt?: string; + updatedBy?: string; + workbookId?: WorkbookId; + servicePlan?: string; + tenantFeatures?: Record; + tenantSettings?: TenantSettings; + annotation?: EntryAnnotation; +}; + +const PASSED_HEADERS = [ + // Auth for domain + AuthHeader.Cookie, + + // Auth with OAuth token + AuthHeader.Authorization, + + // For correct Blackbox-auth on *.yandex.net + 'x-origin-host', + + SuperuserHeader.XDlAllowSuperuser, + SuperuserHeader.XDlSudo, + + DL_CONTEXT_HEADER, + + US_PUBLIC_API_TOKEN_HEADER, + + FORWARDED_FOR_HEADER, + + TRACE_ID_HEADER, + + // Token for embedded charts + DL_EMBED_TOKEN_HEADER, + + DL_COMPONENT_HEADER, +]; + +const DEFAULT_MAX_BODY_LENGTH = 15 * 1024 * 1024; // 100 MB +const DEFAULT_MAX_CONTENT_LENGTH = 15 * 1024 * 1024; // 100 MB + +function formatPassedHeaders( + headers: Request['headers'], + ctx: AppContext, + extraAllowedHeaders?: string[], +) { + const headersNew: Request['headers'] = {}; + + const {headersMap} = ctx.config; + + const passedHeaders = [ + ...PASSED_HEADERS, + + headersMap.folderId, + headersMap.subjectToken, + headersMap.serviceTicket, + headersMap.userTicket, + PROJECT_ID_HEADER, + TENANT_ID_HEADER, + ...(extraAllowedHeaders || []), + ]; + + if (headers) { + passedHeaders.forEach((name) => { + if (headers[name]) { + headersNew[name] = headers[name]; + } + }); + } + + return headersNew; +} + +function formatPassedProperties(entry: Entry = {}) { + // These fallbacks are needed to work with some old entities migrated to the US from the conf repository + const entryId: string | undefined = (entry.meta || {}).entryId || entry.entryId; + const workbookId: string | null | undefined = entry.workbookId; + const key: string | undefined = entry.key || (entry.meta || {}).key; + const type: string | undefined = (entry.meta || {}).stype || entry.type; + const publicAuthor = entry.unversionedData?.publicAuthor; + + const formattedData: Record = {}; + PASSED_PROPERTIES.forEach((field) => { + formattedData[field] = entry[field]; + }); + + // It's better not to remove this, a lot of things are tied to this meta format + const meta = { + ...entry.meta, + stype: type, + owner: entry.createdBy, + }; + + formattedData.entryId = entryId; + formattedData.workbookId = workbookId; + formattedData.key = key; + formattedData.type = type; + formattedData.meta = meta; + + // unversioned data + if (publicAuthor) { + formattedData.publicAuthor = publicAuthor; + } + + return formattedData as DashEntryData | ChartEntryData; +} + +let storageEndpoint: string; + +export type ProviderUpdateParams = { + entryId: string; + revId?: string; + data?: unknown; + type?: unknown; + mode?: string; + links?: unknown; + meta?: Record; + headers: Request['headers']; + skipSyncLinks?: boolean; + annotation?: EntryAnnotationArgs; +}; + +export type ProviderCreateParams = { + key: string; + data: unknown; + type: unknown; + scope: unknown; + links?: unknown; + headers: Request['headers']; + recursion?: boolean; + meta?: Record; + includePermissionsInfo?: boolean | string; + workbookId: string; + name: string; + mode?: EntryUpdateMode; + annotation?: EntryAnnotationArgs; +}; + +function injectMetadata(headers: IncomingHttpHeaders, ctx: AppContext): IncomingHttpHeaders { + const metadata = ctx.getMetadata(); + return {...headers, ...metadata}; +} + +export class USProvider { + static errors = { + ENTRY_NOT_FOUND, + }; + + static init({endpoint, requestIdHeaderName}: {endpoint: string; requestIdHeaderName: string}) { + storageEndpoint = endpoint; + + PASSED_HEADERS.push(requestIdHeaderName); + } + + static retrieveById( + ctx: AppContext, + { + id, + revId, + unreleased, + includeLinks, + includePermissionsInfo, + includeServicePlan, + includeTenantFeatures, + headers, + storageApiPath, + extraAllowedHeaders, + workbookId, + }: { + id: string; + storageApiPath?: string; + extraAllowedHeaders?: string[]; + unreleased: boolean | string; + includeLinks?: boolean | string; + includePermissionsInfo?: boolean | string; + revId?: string; + headers: Request['headers']; + workbookId?: WorkbookId; + includeServicePlan?: boolean; + includeTenantFeatures?: boolean; + }, + ) { + const hrStart = process.hrtime(); + + const params: { + branch: 'saved' | 'published'; + includeLinks?: boolean; + includePermissionsInfo?: boolean; + revId?: string; + includeServicePlan?: boolean; + includeTenantFeatures?: boolean; + includeFavorite?: boolean; + includeTenantSettings: boolean; + } = { + branch: unreleased ? 'saved' : 'published', + includeFavorite: true, + includeTenantSettings: true, + }; + + if (includeServicePlan) { + params.includeServicePlan = true; + } + + if (includeTenantFeatures) { + params.includeTenantFeatures = true; + } + + if (includeLinks) { + params.includeLinks = true; + } + + if (includePermissionsInfo) { + params.includePermissionsInfo = true; + } + + if (revId) { + params.revId = revId; + } + const formattedHeaders = formatPassedHeaders(headers, ctx, extraAllowedHeaders); + + if (workbookId) { + formattedHeaders[WORKBOOK_ID_HEADER] = workbookId; + } + + const axiosArgs: AxiosRequestConfig = { + url: storageApiPath + ? `${storageEndpoint}${storageApiPath}/${id}` + : `${storageEndpoint}/v1/entries/${id}`, + method: 'get', + headers: injectMetadata(formattedHeaders, ctx), + params, + timeout: TIMEOUT_10_SEC, + }; + + return axios + .request(axiosArgs) + .then((response) => { + ctx.log('UNITED_STORAGE_CONFIG_LOADED', {duration: getDuration(hrStart)}); + + return formatPassedProperties(response.data); + }) + .catch((error) => { + if (error.response && error.response.status === 404) { + error.description = id; + error.code = ENTRY_NOT_FOUND; + error.status = 404; + throw error; + } else if (error.response && error.response.status === 403) { + error.description = id; + error.code = ErrorCode.EntryForbidden; + error.status = 403; + throw error; + } else { + throw handleError({ + code: 'UNITED_STORAGE_OBJECT_RETRIEVE_ERROR', + meta: {extra: {id}}, + error, + rethrow: false, + }); + } + }); + } + + static retrieveByKey( + ctx: AppContext, + { + key, + unreleased, + includeLinks, + includePermissionsInfo, + headers, + }: { + key: string; + unreleased: boolean | string; + includeLinks?: boolean | string; + includePermissionsInfo?: boolean | string; + headers: Request['headers']; + }, + ) { + const hrStart = process.hrtime(); + + const params: { + key: string; + branch: 'saved' | 'published'; + includeLinks?: boolean; + includePermissionsInfo?: boolean; + } = { + key: key.replace(/^\//, ''), + branch: unreleased ? 'saved' : 'published', + }; + + if (includeLinks) { + params.includeLinks = true; + } + + if (includePermissionsInfo) { + params.includePermissionsInfo = true; + } + + const formattedHeaders = formatPassedHeaders(headers, ctx); + const axiosArgs: AxiosRequestConfig = { + url: `${storageEndpoint}/v1/entriesByKey`, + method: 'get', + headers: injectMetadata(formattedHeaders, ctx), + params, + timeout: TIMEOUT_10_SEC, + }; + + return axios + .request(axiosArgs) + .then((response) => { + ctx.log('UNITED_STORAGE_CONFIG_LOADED', {duration: getDuration(hrStart)}); + + return formatPassedProperties(response.data); + }) + .catch((error) => { + if (error.response && error.response.status === 404) { + error.description = key; + error.code = ENTRY_NOT_FOUND; + error.status = 404; + throw error; + } else if (error.response && error.response.status === 403) { + error.description = key; + error.code = ErrorCode.EntryForbidden; + error.status = 403; + throw error; + } else { + throw handleError({ + code: 'UNITED_STORAGE_OBJECT_RETRIEVE_ERROR', + meta: {extra: {key}}, + error, + rethrow: false, + }); + } + }); + } + + static retrieveByToken( + ctx: AppContext, + { + token, + headers, + includeServicePlan, + includeTenantFeatures, + }: { + token: string; + headers: Request['headers']; + includeServicePlan?: boolean; + includeTenantFeatures?: boolean; + }, + ): Promise { + const hrStart = process.hrtime(); + const headersWithToken = { + ...headers, + [DL_EMBED_TOKEN_HEADER]: token, + }; + const params: { + includeServicePlan?: boolean; + includeTenantFeatures?: boolean; + includeTenantSettings: boolean; + } = {includeTenantSettings: true}; + + if (includeServicePlan) { + params.includeServicePlan = true; + } + + if (includeTenantFeatures) { + params.includeTenantFeatures = true; + } + const formattedHeaders = formatPassedHeaders(headersWithToken, ctx); + const axiosArgs: AxiosRequestConfig = { + url: `${storageEndpoint}/v1/embedded-entry`, + method: 'get', + headers: injectMetadata(formattedHeaders, ctx), + timeout: TIMEOUT_10_SEC, + params, + }; + + return axios + .request(axiosArgs) + .then((response) => { + ctx.log('UNITED_STORAGE_CONFIG_LOADED', {duration: getDuration(hrStart)}); + + return { + token: response.data.token, + embed: response.data.embed, + entry: formatPassedProperties(response.data.entry), + }; + }) + .catch((error) => { + if (error.response && error.response.status === 404) { + error.description = 'embedToken'; + error.code = ENTRY_NOT_FOUND; + error.status = 404; + throw error; + } else if (error.response && error.response.status === 403) { + error.description = 'embedToken'; + error.code = ErrorCode.EntryForbidden; + error.status = 403; + throw error; + } else { + throw handleError({ + code: 'UNITED_STORAGE_OBJECT_RETRIEVE_ERROR', + meta: {extra: {type: 'embedToken'}}, + error, + rethrow: false, + }); + } + }); + } + + static retrieveByTokenAndId( + ctx: AppContext, + { + id, + token, + headers, + includeServicePlan, + includeTenantFeatures, + }: { + id: string; + token: string; + headers: Request['headers']; + includeServicePlan?: boolean; + includeTenantFeatures?: boolean; + }, + ): Promise { + const hrStart = process.hrtime(); + const headersWithToken = { + ...headers, + [DL_EMBED_TOKEN_HEADER]: token, + }; + const params: { + includeServicePlan?: boolean; + includeTenantFeatures?: boolean; + includeTenantSettings: boolean; + } = {includeTenantSettings: true}; + + if (includeServicePlan) { + params.includeServicePlan = true; + } + + if (includeTenantFeatures) { + params.includeTenantFeatures = true; + } + + const formattedHeaders = formatPassedHeaders(headersWithToken, ctx); + const axiosArgs: AxiosRequestConfig = { + url: `${storageEndpoint}/embeds/entries/${id}`, + method: 'get', + headers: injectMetadata(formattedHeaders, ctx), + timeout: TIMEOUT_10_SEC, + params, + }; + + return axios + .request(axiosArgs) + .then((response) => { + ctx.log('UNITED_STORAGE_CONFIG_LOADED', {duration: getDuration(hrStart)}); + + return { + token: response.data.embeddingInfo.token, + embed: response.data.embeddingInfo.embed, + entry: formatPassedProperties(response.data), + }; + }) + .catch((error) => { + if (error.response && error.response.status === 404) { + error.description = 'embedToken and id'; + error.code = ENTRY_NOT_FOUND; + error.status = 404; + throw error; + } else if (error.response && error.response.status === 403) { + error.description = 'embedToken and id'; + error.code = ErrorCode.EntryForbidden; + error.status = 403; + throw error; + } else { + throw handleError({ + code: 'UNITED_STORAGE_OBJECT_RETRIEVE_ERROR', + meta: {extra: {type: 'embedToken and id'}}, + error, + rethrow: false, + }); + } + }); + } + + static create( + ctx: AppContext, + { + key, + data, + type, + scope, + links, + headers, + recursion = true, + meta = {}, + includePermissionsInfo, + workbookId, + name, + mode = EntryUpdateMode.Publish, + annotation, + }: ProviderCreateParams, + ) { + const hrStart = process.hrtime(); + + const postedData: { + key: string; + data: unknown; + scope: unknown; + type: unknown; + recursion: boolean; + links?: unknown; + meta: Record; + workbookId: string; + name: string; + includePermissionsInfo?: boolean; + mode: EntryUpdateMode; + annotation?: EntryAnnotationArgs; + } = { + key, + data, + scope, + type, + recursion, + meta, + workbookId, + name, + mode, + }; + + if (links) { + postedData.links = links; + } + + if (includePermissionsInfo) { + postedData.includePermissionsInfo = true; + } + + if (annotation) { + postedData.annotation = annotation; + } + + const formattedHeaders = formatPassedHeaders(headers, ctx); + const axiosArgs: AxiosRequestConfig = { + url: `${storageEndpoint}/v1/entries`, + method: 'post', + headers: injectMetadata(formattedHeaders, ctx), + data: postedData, + timeout: TIMEOUT_10_SEC, + maxBodyLength: DEFAULT_MAX_BODY_LENGTH, + maxContentLength: DEFAULT_MAX_CONTENT_LENGTH, + }; + + return axios + .request(axiosArgs) + .then((response) => { + ctx.log('UNITED_STORAGE_OBJECT_CREATED', {duration: getDuration(hrStart)}); + + return formatPassedProperties(response.data); + }) + .catch((error) => { + if (error.response && error.response.status === 404) { + error.code = ENTRY_NOT_FOUND; + error.status = 404; + throw error; + } else if (error.response && error.response.status === 403) { + error.code = ErrorCode.EntryForbidden; + error.status = 403; + throw error; + } else { + throw handleError({ + code: 'UNITED_STORAGE_OBJECT_CREATE_ERROR', + meta: {extra: {key}}, + error, + rethrow: false, + }); + } + }); + } + + static update( + ctx: AppContext, + { + entryId, + revId, + data, + type, + mode = 'save', + links, + meta = {}, + headers, + skipSyncLinks, + annotation, + }: ProviderUpdateParams, + ) { + const hrStart = process.hrtime(); + + const postedData: { + mode: string; + meta: Record; + revId?: string; + data?: unknown; + type?: unknown; + links?: unknown; + skipSyncLinks?: boolean; + annotation?: EntryAnnotationArgs; + } = { + mode, + meta, + }; + + if (revId) { + postedData.revId = revId; + } + + if (data) { + postedData.data = data; + } + + if (type) { + postedData.type = type; + } + + if (links) { + postedData.links = links; + } + + if (skipSyncLinks) { + postedData.skipSyncLinks = true; + } + + if (annotation) { + postedData.annotation = annotation; + } + + const formattedHeaders = formatPassedHeaders(headers, ctx); + const axiosArgs: AxiosRequestConfig = { + url: `${storageEndpoint}/v1/entries/${entryId}`, + method: 'post', + headers: injectMetadata(formattedHeaders, ctx), + data: postedData, + timeout: TIMEOUT_10_SEC, + maxBodyLength: DEFAULT_MAX_BODY_LENGTH, + maxContentLength: DEFAULT_MAX_CONTENT_LENGTH, + }; + + return axios + .request(axiosArgs) + .then((response) => { + ctx.log('UNITED_STORAGE_OBJECT_UPDATED', {duration: getDuration(hrStart)}); + + return formatPassedProperties(response.data); + }) + .catch((error) => { + if (error.response && error.response.status === 404) { + error.code = ENTRY_NOT_FOUND; + error.status = 404; + throw error; + } else if (error.response && error.response.status === 403) { + error.code = ErrorCode.EntryForbidden; + error.status = 403; + throw error; + } else { + throw handleError({ + code: 'UNITED_STORAGE_OBJECT_UPDATE_ERROR', + meta: {extra: {entryId}}, + error, + rethrow: false, + }); + } + }); + } + + static delete( + ctx: AppContext, + {id, headers}: {id: Request['params']['entryId']; headers: Request['headers']}, + ) { + const hrStart = process.hrtime(); + const formattedHeaders = formatPassedHeaders(headers, ctx); + const axiosArgs: AxiosRequestConfig = { + url: `${storageEndpoint}/v1/entries/${id}`, + method: 'delete', + headers: injectMetadata(formattedHeaders, ctx), + timeout: TIMEOUT_10_SEC, + }; + + return axios + .request(axiosArgs) + .then((response) => { + ctx.log('UNITED_STORAGE_OBJECT_DELETED', {duration: getDuration(hrStart)}); + + return formatPassedProperties(response.data); + }) + .catch((error) => { + if (error.response && error.response.status === 404) { + error.code = ENTRY_NOT_FOUND; + error.status = 404; + throw error; + } else if (error.response && error.response.status === 403) { + error.code = ErrorCode.EntryForbidden; + error.status = 403; + throw error; + } else { + handleError({ + code: 'UNITED_STORAGE_OBJECT_UPDATE_ERROR', + meta: {extra: {id}}, + error, + }); + } + }); + } +} diff --git a/src/server/components/charts-engine/components/utils.ts b/src/server/components/charts-engine/components/utils.ts new file mode 100644 index 0000000..a769930 --- /dev/null +++ b/src/server/components/charts-engine/components/utils.ts @@ -0,0 +1,182 @@ +import type {AppContext} from '@gravity-ui/nodekit'; + +import type {FilterValue, IntervalPart, TenantSettings} from '../../../../shared'; +import { + EntryScope, + PALETTE_ID, + URL_ACTION_PARAMS_PREFIX, + resolveIntervalDate as sharedResolveIntervalDate, + resolveOperation as sharedResolveOperation, + resolveRelativeDate as sharedResolveRelativeDate, +} from '../../../../shared'; + +import type {DashEntryData, EmbeddingInfo} from './storage/types'; + +const SENSITIVE_PARAMS_NAMES = ['password', 'pass', 'token']; +const HIDDEN_SENSITIVE_VALUE = ''; + +function getValue(originalValue: [FilterValue] | FilterValue): string | null { + const value = Array.isArray(originalValue) ? originalValue[0] : originalValue; + + if (typeof value !== 'string') { + return null; + } + + return value; +} + +export function resolveRelativeDate( + originalValue: [FilterValue] | FilterValue, + intervalPart?: IntervalPart, +) { + const value = getValue(originalValue); + return (value && sharedResolveRelativeDate(value, intervalPart)) || null; +} + +export function resolveIntervalDate(originalValue: [FilterValue] | FilterValue) { + const value = getValue(originalValue); + return (value && sharedResolveIntervalDate(value)) || null; +} + +export function resolveOperation(originalValue: [FilterValue] | FilterValue) { + const value = getValue(originalValue); + return sharedResolveOperation(value); +} + +/* + * @deprecated mutation is bad practice, use resolveNormalizedParams. + */ + +// Done in order to change the values that come to params before transferring them to other tabs. +// Converts the entered date parameters. If the date is not valid, then it is forwarded as is. +// Apparently made only for __relative. Because according to the tests, just __interval_from_to is returned as is +export function resolveParams(params: Record) { + Object.keys(params).forEach((param) => { + const paramValues = params[param]; + paramValues.forEach((value, i) => { + if (typeof value === 'string') { + if (value.indexOf('__relative') === 0) { + const resolvedRelative = resolveRelativeDate(value); + + if (resolvedRelative) { + // BI-1308 + paramValues[i] = resolvedRelative; + } + } else if (value.indexOf('__interval') === 0) { + const resolvedInterval = resolveIntervalDate(value); + + if (resolvedInterval) { + // BI-1308 + const from = resolvedInterval.from; + const to = resolvedInterval.to; + + paramValues[i] = `__interval_${from}_${to}`; + } + } + } + }); + }); +} + +// Done in order to change the values that come to params before transferring them to other tabs. +// Converts the entered date parameters. If the date is not valid, then it is forwarded as is. +// Apparently made only for __relative. Because according to the tests, just __interval_from_to is returned as is +export function resolveNormalizedParams(params: Record = {}) { + return Object.entries(params).reduce( + (result: Record, [key, wrappedValue]) => { + result[key] = wrappedValue.map((value) => { + const resolvedRelative = resolveRelativeDate(value); + + if (resolvedRelative) { + // BI-1308 + return resolvedRelative; + } + + const resolvedInterval = resolveIntervalDate(value); + + if (resolvedInterval) { + // BI-1308 + const from = resolvedInterval.from; + const to = resolvedInterval.to; + + return `__interval_${from}_${to}`; + } + + return value; + }); + + return result; + }, + {}, + ); +} + +export function normalizeParams(params: Record = {}) { + const actionParams: Record = {}; + const preparedParams = Object.entries(params).reduce( + (result: Record, [key, value]) => { + const normalizedVal = Array.isArray(value) ? value : [value]; + if (key.startsWith(URL_ACTION_PARAMS_PREFIX)) { + actionParams[key.slice(URL_ACTION_PARAMS_PREFIX.length)] = normalizedVal; + } else { + result[key] = normalizedVal; + } + return result; + }, + {}, + ); + + return {params: preparedParams, actionParams}; +} + +export function getDuration(hrStart: [number, number]) { + const hrDuration = process.hrtime(hrStart); + return (hrDuration[0] * 1e9 + hrDuration[1]) / 1e6; +} + +type Test = string | Record> | null | ''; + +export function hideSensitiveData(data: T = '' as T): T { + if (typeof data === 'string') { + return data; + } + if (typeof data === 'object' && data !== null) { + const result = {...data}; + + SENSITIVE_PARAMS_NAMES.forEach((name) => { + if (result[name]) { + result[name] = HIDDEN_SENSITIVE_VALUE; + } + }); + return result; + } + return data; +} + +export function getSourceAuthorizationHeaders() { + return {}; +} + +export const isDashEntry = (entry: EmbeddingInfo['entry']): entry is DashEntryData => { + if (entry.scope === EntryScope.Dash) { + return true; + } else { + return false; + } +}; + +export function getDefaultColorPaletteId({ + ctx, + tenantSettings, +}: { + ctx: AppContext; + tenantSettings?: TenantSettings; + palettes?: Record; +}) { + const tenantDefaultPalette = tenantSettings?.defaultColorPaletteId; + if (tenantDefaultPalette) { + return tenantDefaultPalette; + } + + return ctx.config.defaultColorPaletteId ?? PALETTE_ID.CLASSIC_20; +} diff --git a/src/server/components/charts-engine/components/wizard-worker/index.ts b/src/server/components/charts-engine/components/wizard-worker/index.ts new file mode 100644 index 0000000..32ead0d --- /dev/null +++ b/src/server/components/charts-engine/components/wizard-worker/index.ts @@ -0,0 +1,183 @@ +import workerPool from 'workerpool'; + +import type {ServerChartsConfig, Shared} from '../../../../../shared'; +import {WizardVisualizationId, isGravityChartsVisualization} from '../../../../../shared'; +import {getTranslationFn} from '../../../../../shared/modules/language'; +import {datalensModule} from '../../../../modes/charts/plugins/datalens/private-module'; +import {mapChartsConfigToServerConfig} from '../../../../modes/charts/plugins/datalens/utils/config-helpers'; +import type {ResolvedSource} from '../../../../modes/charts/plugins/types'; +import {createI18nInstance} from '../../../../utils/language'; +import {getChartApiContext} from '../processor/chart-api-context'; +import {Console} from '../processor/console'; + +import type { + BuildChartArgs, + BuildChartConfigArgs, + BuildLibraryConfigArgs, + BuildSourceArgs, + WizardWorker, +} from './types'; + +const worker: WizardWorker = { + buildSources: async (args: BuildSourceArgs) => { + const {shared, params, actionParams, widgetConfig, userLang, palettes} = args; + const context = getChartApiContext({ + name: 'Sources', + shared, + params, + actionParams, + widgetConfig, + userLang, + }); + + const console = new Console({}); + datalensModule.setConsole(console); + + return { + exports: datalensModule.buildSources({ + apiVersion: '2', + shared: shared as Shared, + params, + palettes, + }), + runtimeMetadata: context.__runtimeMetadata, + logs: console.getLogs(), + }; + }, + buildLibraryConfig: async (args: BuildLibraryConfigArgs) => { + const {shared, params, actionParams, widgetConfig, userLang, features} = args; + const context = getChartApiContext({ + name: 'Highcharts', + shared, + params, + actionParams, + widgetConfig, + userLang, + }); + + const console = new Console({}); + datalensModule.setConsole(console); + + let result; + const serverChartConfig = mapChartsConfigToServerConfig(shared as ServerChartsConfig); + const visualizationId = serverChartConfig?.visualization?.id; + switch (visualizationId) { + case WizardVisualizationId.FlatTable: + case WizardVisualizationId.PivotTable: { + result = {}; + break; + } + default: { + if ( + isGravityChartsVisualization({ + id: visualizationId as WizardVisualizationId, + features, + }) + ) { + result = {}; + } else { + result = datalensModule.buildHighchartsConfig({ + shared: serverChartConfig, + features, + }); + } + } + } + + return { + exports: result, + runtimeMetadata: context.__runtimeMetadata, + logs: console.getLogs(), + }; + }, + + buildChartConfig: async (args: BuildChartConfigArgs) => { + const {shared, params, actionParams, widgetConfig, userLang, features} = args; + const context = getChartApiContext({ + name: 'Config', + shared, + params, + actionParams, + widgetConfig, + userLang, + }); + + const console = new Console({}); + datalensModule.setConsole(console); + + return { + exports: datalensModule.buildChartsConfig({ + shared: shared as ServerChartsConfig, + params, + widgetConfig, + features, + }), + runtimeMetadata: context.__runtimeMetadata, + logs: console.getLogs(), + }; + }, + + buildChart: async (args: BuildChartArgs) => { + const { + shared, + params, + actionParams, + widgetConfig, + userLang, + data, + palettes, + defaultColorPaletteId, + features, + sources, + } = args; + const context = getChartApiContext({ + name: 'Prepare', + shared, + params, + actionParams, + widgetConfig, + userLang, + }); + + const i18n = createI18nInstance({lang: userLang}); + context.ChartEditor.getTranslation = getTranslationFn(i18n.getI18nServer()); + + const console = new Console({}); + datalensModule.setConsole(console); + + const serverChartConfig = mapChartsConfigToServerConfig(shared as ServerChartsConfig); + const shouldUseGravityCharts = isGravityChartsVisualization({ + features, + id: serverChartConfig?.visualization?.id, + }); + const plugin = shouldUseGravityCharts ? 'gravity-charts' : undefined; + const resolvedSources: Record = {}; + if (sources) { + Object.values(sources).forEach((item) => { + resolvedSources[item.datasetId] = { + datasetId: item.datasetId, + datasetFields: item.datasetFields ?? item.source?.datasetFields ?? [], + }; + }); + } + + const result = datalensModule.buildGraph({ + data, + shared: serverChartConfig, + ChartEditor: context.ChartEditor, + palettes, + features, + plugin, + defaultColorPaletteId, + sources: resolvedSources, + }); + + return { + exports: result, + runtimeMetadata: context.__runtimeMetadata, + logs: console.getLogs(), + }; + }, +}; + +workerPool.worker(worker); diff --git a/src/server/components/charts-engine/components/wizard-worker/types.ts b/src/server/components/charts-engine/components/wizard-worker/types.ts new file mode 100644 index 0000000..2af41c7 --- /dev/null +++ b/src/server/components/charts-engine/components/wizard-worker/types.ts @@ -0,0 +1,90 @@ +import type { + DashWidgetConfig, + FeatureConfig, + Palette, + QlConfig, + ServerChartsConfig, + Shared, + StringParams, +} from '../../../../../shared'; +import type {SourceRequests} from '../../../../modes/charts/plugins/datalens/url/types'; +import type {LogItem} from '../processor/console'; +import type {DataFetcherResult} from '../processor/data-fetcher'; +import type {RuntimeMetadata} from '../processor/types'; + +export type ChartShared = Record | Shared | ServerChartsConfig | QlConfig; + +export type BuildParamsArgs = { + shared: unknown; + userLang: string; +}; + +export type BuildSourceArgs = { + shared: unknown; + userLang: string; + params: StringParams; + actionParams: StringParams; + widgetConfig?: DashWidgetConfig['widgetConfig']; + palettes: Record; + features: FeatureConfig; +}; + +export type BuildLibraryConfigArgs = { + shared: ChartShared; + userLang: string; + params: StringParams; + actionParams: StringParams; + widgetConfig?: DashWidgetConfig['widgetConfig']; + features: FeatureConfig; +}; + +export type BuildChartConfigArgs = { + shared: ChartShared; + userLang: string; + params: StringParams; + actionParams: StringParams; + widgetConfig?: DashWidgetConfig['widgetConfig']; + features: FeatureConfig; +}; + +export type BuildChartArgs = { + shared: ChartShared; + userLang: string; + params: StringParams; + actionParams: StringParams; + widgetConfig?: DashWidgetConfig['widgetConfig']; + data: unknown; + + features: FeatureConfig; + palettes: Record; + defaultColorPaletteId: string; + sources?: Record; +}; + +export type WizardWorker = { + buildParams?: (args: BuildParamsArgs) => Promise<{ + runtimeMetadata: RuntimeMetadata; + exports: unknown; + logs?: LogItem[][]; + }>; + buildSources: (args: BuildSourceArgs) => Promise<{ + runtimeMetadata: RuntimeMetadata; + exports: SourceRequests; + logs?: LogItem[][]; + }>; + buildLibraryConfig: (args: BuildLibraryConfigArgs) => Promise<{ + runtimeMetadata: RuntimeMetadata; + exports: Record; + logs?: LogItem[][]; + }>; + buildChartConfig: (args: BuildChartConfigArgs) => Promise<{ + runtimeMetadata: RuntimeMetadata; + exports: unknown; + logs?: LogItem[][]; + }>; + buildChart: (args: BuildChartArgs) => Promise<{ + runtimeMetadata: RuntimeMetadata; + exports: unknown; + logs?: LogItem[][]; + }>; +}; diff --git a/src/server/components/charts-engine/components/wizard-worker/utils.ts b/src/server/components/charts-engine/components/wizard-worker/utils.ts new file mode 100644 index 0000000..3481687 --- /dev/null +++ b/src/server/components/charts-engine/components/wizard-worker/utils.ts @@ -0,0 +1,15 @@ +import {getTranslationFn} from '../../../../../shared/modules/language'; +import {createI18nInstance} from '../../../../utils/language'; +import type {GetChartApiContextArgs} from '../processor/chart-api-context'; +import {getChartApiContext as getBaseChartApiContext} from '../processor/chart-api-context'; + +const DEFAULT_USER_LANG = 'ru'; + +export function getChartApiContext(args: GetChartApiContextArgs) { + const context = getBaseChartApiContext(args); + + const i18n = createI18nInstance({lang: args.userLang || DEFAULT_USER_LANG}); + context.ChartEditor.getTranslation = getTranslationFn(i18n.getI18nServer()); + + return context; +} diff --git a/src/server/components/charts-engine/constants/index.ts b/src/server/components/charts-engine/constants/index.ts new file mode 100644 index 0000000..0700a82 --- /dev/null +++ b/src/server/components/charts-engine/constants/index.ts @@ -0,0 +1,76 @@ +const CONFIG_TYPE = { + GRAPH_NODE: 'graph_node', + TABLE_NODE: 'table_node', + MAP_NODE: 'map_node', + METRIC_NODE: 'metric_node', + YMAP_NODE: 'ymap_node', + CONTROL_NODE: 'control_node', + MARKDOWN_NODE: 'markdown_node', + MARKUP_NODE: 'markup_node', + + GRAPH_WIZARD_NODE: 'graph_wizard_node', + TABLE_WIZARD_NODE: 'table_wizard_node', + + GRAPH_QL_NODE: 'graph_ql_node', + + // deprecated + TEXT_NODE: 'text_node', +} as const; + +export const config = { + ITS_FINE: 'EE_OK', + CONFIG_LOADING_ERROR: 'ERR.CHARTS.CONFIG_LOADING_ERROR', + DEPS_RESOLVE_ERROR: 'ERR.CHARTS.DEPS_RESOLVE_ERROR', + DATA_FETCHING_ERROR: 'ERR.CHARTS.DATA_FETCHING_ERROR', + RUNTIME_ERROR: 'ERR.CHARTS.RUNTIME_ERROR', + RUNTIME_TIMEOUT_ERROR: 'ERR.CHARTS.RUNTIME_TIMEOUT', + HOOKS_ERROR: 'ERR.CHARTS.HOOKS', + + ROWS_NUMBER_OVERSIZE: 'ERR.CHARTS.ROWS_NUMBER_OVERSIZE', + TABLE_OVERSIZE: 'ERR.CHARTS.TABLE_OVERSIZE', + SEGMENTS_OVERSIZE: 'ERR.CHARTS.SEGMENTS_OVERSIZE', + + // 100 seconds + DEFAULT_FETCHING_TIMEOUT: 100 * 1000, + + // 95 seconds + DEFAULT_SINGLE_FETCHING_TIMEOUT: 95 * 1000, + + // 100 MB + ALL_REQUESTS_SIZE_LIMIT: 100 * 1e6, + + // 50 MB + REQUEST_SIZE_LIMIT: 50 * 1e6, + + CONCURRENT_REQUESTS_LIMIT: 10, + + DEFAULT_RUNTIME_ERROR_STATUS: 420, + DEFAULT_RUNTIME_TIMEOUT_STATUS: 408, + DEFAULT_OVERSIZE_ERROR_STATUS: 422, + DEFAULT_SOURCE_FETCHING_ERROR_STATUS_500: 424, + DEFAULT_SOURCE_FETCHING_ERROR_STATUS_400: 427, + DEFAULT_SOURCE_FETCHING_LIMIT_EXCEEDED_STATUS: 413, + + OVERALL_TIMEOUT_EXCEEDED: 'ERR.CHARTS.OVERALL_TIMEOUT_EXCEEDED', + INVALID_SOURCE_FORMAT: 'ERR.CHARTS.INVALID_SOURCE_FORMAT', + SOURCE_IS_CIRCULAR: 'ERR.CHARTS.SOURCE_IS_CIRCULAR', + INVALID_SOURCES_FORMAT: 'ERR.CHARTS.INVALID_SOURCES_FORMAT', + UNKNOWN_SOURCE: 'ERR.CHARTS.UNKNOWN_SOURCE', + EMPTY_RESPONSE: 'ERR.CHARTS.EMPTY_RESPONSE', + REDIRECT: 'ERR.CHARTS.REDIRECT', + UNHANDLED_INTERNAL_SERVER_ERROR: 'ERR.CHARTS.UNHANDLED_INTERNAL_SERVER_ERROR', + + REQUEST_CANCELLED: 'ERR.CHARTS.REQUEST_CANCELLED', + REQUEST_SIZE_LIMIT_EXCEEDED: 'ERR.CHARTS.REQUEST_SIZE_LIMIT_EXCEEDED', + UNSUPPORTED_DATA_TYPE: 'ERR.CHARTS.UNSUPPORTED_DATA_TYPE', + ALL_REQUESTS_SIZE_LIMIT_EXCEEDED: 'ERR.CHARTS.ALL_REQUESTS_SIZE_LIMIT_EXCEEDED', + REQUEST_TIMEOUT_EXCEEDED: 'ERR.CHARTS.REQUEST_TIMEOUT_EXCEEDED', + ALL_REQUESTS_TIMEOUT_EXCEEDED: 'ERR.CHARTS.ALL_REQUESTS_TIMEOUT_EXCEEDED', + + CONFIG_TYPE, + + REDACTED_DATA_PLACEHOLDER: '[REDACTED]', +} as const; + +// TODO: Will be deleted after switching to typescript. +export default config; diff --git a/src/server/components/charts-engine/controllers/charts.ts b/src/server/components/charts-engine/controllers/charts.ts new file mode 100644 index 0000000..d0a27fd --- /dev/null +++ b/src/server/components/charts-engine/controllers/charts.ts @@ -0,0 +1,331 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import type {ChartsEngine} from '..'; +import {EntryUpdateMode} from '../../../../shared'; +import {DeveloperModeCheckStatus} from '../../../../shared/types'; +import Utils from '../../../utils'; +import type {ChartTemplates} from '../components/chart-generator'; +import {chartGenerator} from '../components/chart-generator'; +import {chartValidator as validator} from '../components/chart-validator'; +import type { + ProviderCreateParams, + ProviderUpdateParams, +} from '../components/storage/united-storage/provider'; +import {USProvider} from '../components/storage/united-storage/provider'; + +type ErrorWithStatusAndData = Error & {response?: {status: number; data: string}} & { + status?: number; +}; + +function responseWithError({ + error, + defaultMessage, + req, + res, +}: { + error: ErrorWithStatusAndData; + defaultMessage: string; + req: Request; + res: Response; +}) { + const {ctx} = req; + let readableError: unknown = {error: defaultMessage}; + + const {response} = error; + let status = 500; + + if (response) { + if (response.data) { + readableError = response.data; + } + + status = response.status; + } + + ctx.logError('FAILED_TO_HANDLE_CHART', error); + + res.status(status).send(readableError); +} + +export type ChartDataOptions = { + data: {type?: string; convert?: boolean}; + template?: keyof ChartTemplates; + type: string; +}; + +export function prepareChartData({data, template, type}: ChartDataOptions, req: Request) { + const {ctx} = req; + + let chart, links; + + try { + if (typeof template !== 'undefined') { + ({chart, type, links} = chartGenerator.generateChart({data, template, req, ctx})); + + // Convert from wizard to editor script + if (data.convert) { + type = type.replace(/_wizard/, ''); + } else { + chart = {shared: chart.shared}; + } + } else if (type) { + if (validator.validate({data, type})) { + chart = data; + } else { + throw new Error('Cannot create chart: invalid tabs for specified type'); + } + } else { + throw new Error('Cannot create chart: template/type required in body'); + } + } catch (error) { + ctx.logError('FAILED_TO_PREPARE_CHART_DATA', error); + return {error: error as Error}; + } + + return {chart, type, links, template}; +} + +const getHeaders = (req: Request) => { + const headers = { + ...req.headers, + ...(req.ctx.config.isAuthEnabled ? {...Utils.pickAuthHeaders(req)} : {}), + }; + + return headers; +}; + +export const prepareCreateParams = async ( + chartData: ReturnType, + req: Request, +) => { + const {chart, type, links, template} = chartData; + const {key, name, workbookId, description = '', annotation} = req.body; + + // If we save editor script + if (typeof template === 'undefined') { + const {checkRequestForDeveloperModeAccess} = req.ctx.get('gateway'); + + const checkResult = await checkRequestForDeveloperModeAccess({ctx: req.ctx}); + + if (checkResult === DeveloperModeCheckStatus.Forbidden) { + return; + } + } + + const createParams: ProviderCreateParams = { + key, + name, + workbookId, + data: chart, + type, + scope: 'widget', + headers: getHeaders(req), + includePermissionsInfo: true, + annotation: { + description: annotation?.description ?? description, + }, + }; + + if (links) { + createParams.links = links; + } + + return createParams; +}; + +export const chartsController = (_chartsEngine: ChartsEngine) => { + return { + create: async (req: Request, res: Response) => { + const {ctx} = req; + + const chartData = prepareChartData(req.body, req); + + if (chartData.error) { + res.status(400).send({ + error: chartData.error.message, + }); + return; + } + + const createParams = await prepareCreateParams(chartData, req); + + if (!createParams) { + res.status(403).send({ + error: { + code: 403, + details: { + message: 'Access to Editor developer mode was denied', + }, + }, + }); + return; + } + + USProvider.create(ctx, createParams) + .then((result) => { + res.send({ + ...result, + }); + }) + .catch((error) => { + responseWithError({ + error, + defaultMessage: 'Failed to create chart', + req, + res, + }); + }); + }, + update: async (req: Request, res: Response) => { + const {ctx} = req; + + const chartData = prepareChartData(req.body, req); + + if (chartData.error) { + res.status(400).send({ + error: chartData.error.message, + }); + return; + } + const {chart, type, links, template} = chartData; + + // If we save editor script + if (typeof template === 'undefined') { + const {checkRequestForDeveloperModeAccess} = req.ctx.get('gateway'); + + const checkResult = await checkRequestForDeveloperModeAccess({ctx: req.ctx}); + + if (checkResult === DeveloperModeCheckStatus.Forbidden) { + res.status(403).send({ + error: { + code: 403, + details: { + message: 'Access to Editor developer mode was denied', + }, + }, + }); + return; + } + } + + const entryId = req.params.entryId; + + const {mode, annotation, description = ''} = req.body; + + const updateParams: ProviderUpdateParams = { + entryId, + mode, + type, + data: chart, + headers: getHeaders(req), + annotation: { + description: annotation?.description ?? description, + }, + }; + + if (links) { + updateParams.links = links; + } + + if (mode !== EntryUpdateMode.Publish) { + updateParams.skipSyncLinks = true; + } + + USProvider.update(ctx, updateParams) + .then((result) => { + res.send({ + ...result, + }); + }) + .catch((error) => { + responseWithError({ + error, + defaultMessage: 'Failed to update chart', + req, + res, + }); + }); + }, + get: (req: Request, res: Response) => { + const {ctx} = req; + const {entryId} = req.params; + + const {unreleased, includeLinks, includePermissionsInfo, revId} = req.query; + + USProvider.retrieveById(ctx, { + id: entryId, + revId: revId as string, + unreleased: unreleased as string, + includeLinks: includeLinks as string, + includePermissionsInfo: includePermissionsInfo as string, + headers: getHeaders(req), + }) + .then((result) => { + let chartData; + + if (result.type.includes('wizard_node')) { + try { + chartData = JSON.parse(result.data.shared); + } catch (e) { + chartData = {}; + } + } else { + chartData = result.data; + } + + result.data = chartData; + + res.send(result); + }) + .catch((error) => { + responseWithError({ + error, + defaultMessage: 'Failed to get chart', + req, + res, + }); + }); + }, + delete: (req: Request, res: Response) => { + const {ctx} = req; + const entryId = req.params.entryId; + + USProvider.delete(ctx, { + id: entryId, + headers: getHeaders(req), + }) + .then(() => { + res.status(200).send(); + }) + .catch((error) => { + responseWithError({ + error, + defaultMessage: 'Failed to delete chart', + req, + res, + }); + }); + }, + entryByKey: (req: Request, res: Response) => { + const {ctx} = req; + const key = (req.query.key as string) || ''; + + USProvider.retrieveByKey(ctx, { + key, + unreleased: true, + headers: getHeaders(req), + }) + .then((result) => { + res.send(result); + }) + .catch((error) => { + responseWithError({ + error, + defaultMessage: 'Failed to retrieve entry by key', + req, + res, + }); + }); + }, + }; +}; diff --git a/src/server/components/charts-engine/controllers/config.ts b/src/server/components/charts-engine/controllers/config.ts new file mode 100644 index 0000000..89e010a --- /dev/null +++ b/src/server/components/charts-engine/controllers/config.ts @@ -0,0 +1,15 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import type {ChartsEngine} from '..'; +import {DataFetcher} from '../components/processor/data-fetcher'; + +export const configController = (chartsEngine: ChartsEngine) => { + return (_req: Request, res: Response) => { + res.status(200).send( + DataFetcher.getChartKitSources({ + sourcesConfig: chartsEngine.sources, + lang: res.locals.lang, + }), + ); + }; +}; diff --git a/src/server/components/charts-engine/controllers/csvConverter.ts b/src/server/components/charts-engine/controllers/csvConverter.ts new file mode 100644 index 0000000..e823948 --- /dev/null +++ b/src/server/components/charts-engine/controllers/csvConverter.ts @@ -0,0 +1,144 @@ +import {monitorEventLoopDelay, performance} from 'perf_hooks'; + +import type {Request, Response} from '@gravity-ui/expresskit'; +import iconv from 'iconv-lite'; +import moment from 'moment/moment'; + +import type {Graph} from '../components/processor/comments-fetcher'; + +const DAY_MS = 1000 * 60 * 60 * 24; +const CSV_DATA_LIMIT = 1024 * 1024 * 50; // 50MB + +export function csvConverter( + req: Request, + res: Response, + chartData: { + categories_ms?: number[]; + categories?: string[] | number[]; + graphs: Graph[]; + }, + dataArray: number[], + formSettings: { + delValues: string; + delNumbers: string; + encoding: 'cp1251' | 'win1251'; + }, + downloadConfig: { + filename: string; + }, +) { + const {ctx} = req; + ctx.log('EXPORT_CSV'); + const reqDataLength = req.body.data.length; + + const monitorHistogram = monitorEventLoopDelay(); + + if (reqDataLength > CSV_DATA_LIMIT) { + ctx.logError(`EXPORT_CSV_DATA_LIMIT_ERROR`, { + bytes: reqDataLength, + }); + req.ctx.stats('exportSizeStats', { + datetime: Date.now(), + exportType: 'csv', + sizeBytes: reqDataLength, + timings: 0, + rejected: 'true', + }); + res.sendStatus(413); + return; + } + const csvStart = performance.now(); + monitorHistogram.enable(); + + res.setHeader('Content-disposition', `attachment; filename=${downloadConfig.filename}.csv`); + res.setHeader('Content-type', 'text/csv'); + + if (formSettings.delValues === 'tab') { + formSettings.delValues = '\t'; + } + + if (formSettings.delValues === 'space') { + formSettings.delValues = ' '; + } + + const delValues = formSettings.delValues || ';'; + + let lines = []; + const header = []; + + if (chartData.categories_ms) { + header.push('"DateTime"'); + } + if (chartData.categories) { + header.push('"Categories"'); + } + + chartData.graphs.forEach((graph) => { + header.push(`"${graph.title}"`); + }); + + lines.push(header); + + ctx.log('EXPORT_CSV_START_PREPARING'); + + dataArray.forEach((item, i) => { + const line = []; + + if (item) { + const diff = item - dataArray[i - 1] || dataArray[i + 1] - item; + const format = diff < DAY_MS ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'; + const measure = chartData.categories_ms ? moment(item).format(format) : item; + line.push(`"${measure}"`); + } + + chartData.graphs.forEach((graph) => { + let currentValue = graph.data[i]; + let value = '""'; + + if (currentValue || currentValue === 0) { + if (graph.type === 'diff' && Array.isArray(currentValue)) { + currentValue = currentValue[0]; + } + value = String(currentValue); + + if (typeof currentValue === 'number') { + value = value.replace('.', formSettings.delNumbers || ','); + if (value.includes(delValues)) { + value = `"${value}"`; + } + } else if (typeof currentValue === 'string') { + value = `"${value.replace(/"/g, '""')}"`; + } + } + + line.push(value); + }); + + lines.push(line); + }); + + lines = lines.map((line) => { + return line.join(delValues); + }); + + let csvContent: Buffer | string = lines.join('\n'); + + if (formSettings.encoding === 'cp1251') { + csvContent = iconv.encode(csvContent, 'win1251'); + } + + ctx.log('EXPORT_CSV_FINISH_PREPARING'); + const csvStop = performance.now(); + monitorHistogram.disable(); + req.ctx.stats('exportSizeStats', { + datetime: Date.now(), + exportType: 'csv', + sizeBytes: reqDataLength, + timings: csvStop - csvStart, + rejected: 'false', + requestId: req.id, + eventLoopDelay: monitorHistogram.max / 1000000, + }); + + res.status(200).send(csvContent); +} diff --git a/src/server/components/charts-engine/controllers/embedded-entry.ts b/src/server/components/charts-engine/controllers/embedded-entry.ts new file mode 100644 index 0000000..bc57932 --- /dev/null +++ b/src/server/components/charts-engine/controllers/embedded-entry.ts @@ -0,0 +1,116 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import jwt from 'jsonwebtoken'; +import isObject from 'lodash/isObject'; + +import {DL_EMBED_TOKEN_HEADER, EntryScope, ErrorCode} from '../../../../shared'; +import {resolveEmbedConfig} from '../components/storage'; +import type {EmbedResolveConfigProps, ResolveConfigError} from '../components/storage/base'; + +export const embeddedEntryController = async (req: Request, res: Response) => { + const {ctx} = req; + + const embedToken = Array.isArray(req.headers[DL_EMBED_TOKEN_HEADER]) + ? '' + : req.headers[DL_EMBED_TOKEN_HEADER]; + + if (!embedToken) { + ctx.log('CHARTS_ENGINE_NO_TOKEN'); + res.status(400).send({ + code: ErrorCode.InvalidTokenFormat, + extra: {message: 'You must provide embedToken', hideRetry: true}, + }); + return; + } + + const payload = jwt.decode(embedToken); + + if (!payload || typeof payload === 'string' || !('embedId' in payload)) { + ctx.log('CHARTS_ENGINE_WRONG_TOKEN'); + res.status(400).send({ + code: ErrorCode.InvalidTokenFormat, + extra: {message: 'Wrong token format', hideRetry: true}, + }); + return; + } + + const embedId = payload.embedId; + + res.locals.subrequestHeaders = { + ...res.locals.subrequestHeaders, + [DL_EMBED_TOKEN_HEADER]: embedToken, + }; + + const configResolveArgs: EmbedResolveConfigProps = { + embedToken, + // Key is legacy but we using it deeply like cache key, so this is just for compatibility purposes + key: embedId, + embedId, + headers: { + ...res.locals.subrequestHeaders, + ...ctx.getMetadata(), + }, + }; + + const configPromise = ctx.call('configLoading', (cx) => + resolveEmbedConfig(cx, configResolveArgs), + ); + + ctx.log('CHARTS_ENGINE_LOADING_CONFIG', {embedId}); + + await Promise.resolve(configPromise) + .catch((err: unknown) => { + const error: ResolveConfigError = + isObject(err) && 'message' in err ? (err as Error) : new Error(err as string); + const result: { + error: { + code: string; + details: { + code: number | null; + }; + extra?: {hideRetry: boolean}; + }; + } = { + error: { + code: 'ERR.ENTRY.CONFIG_LOADING_ERROR', + details: { + code: (error.response && error.response.status) || error.status || null, + }, + extra: {hideRetry: false}, + }, + }; + + ctx.logError(`CHARTS_ENGINE_CONFIG_LOADING_ERROR "token"`, error); + const status = (error.response && error.response.status) || error.status || 500; + res.status(status).send(result); + }) + .then(async (response) => { + if (response && 'entry' in response) { + if (response.entry.scope === EntryScope.Dash) { + const { + entry: {entryId, scope, data}, + } = response; + + // Add only necessary fields without personal info like createdBy + res.status(200).send({ + entryId, + scope, + data, + }); + } else { + res.status(400).send({ + code: ErrorCode.InvalidToken, + extra: { + message: 'Invalid token', + hideRetry: true, + }, + }); + } + } + }) + .catch((error) => { + ctx.logError('CHARTS_ENGINE_RUNNER_ERROR', error); + res.status(500).send({ + error: 'Internal error', + }); + }); +}; diff --git a/src/server/components/charts-engine/controllers/embeds.ts b/src/server/components/charts-engine/controllers/embeds.ts new file mode 100644 index 0000000..25af759 --- /dev/null +++ b/src/server/components/charts-engine/controllers/embeds.ts @@ -0,0 +1,510 @@ +/* eslint-disable complexity */ +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {AppContext} from '@gravity-ui/nodekit'; +import type {AxiosError} from 'axios'; +import jwt from 'jsonwebtoken'; +import get from 'lodash/get'; +import isObject from 'lodash/isObject'; + +import type {ChartsEngine} from '..'; +import type { + DashTab, + DashTabItemControlData, + DashTabItemControlDataset, + DashTabItemControlManual, +} from '../../../../shared'; +import { + ControlType, + DL_EMBED_TOKEN_HEADER, + DashTabItemControlSourceType, + DashTabItemType, + EntryScope, + ErrorCode, + getAllTabItems, +} from '../../../../shared'; +import {registry} from '../../../registry'; +import {resolveEmbedConfig} from '../components/storage'; +import type {EmbedResolveConfigProps, ResolveConfigError} from '../components/storage/base'; +import type {EmbeddingInfo, ReducedResolvedConfig} from '../components/storage/types'; +import {getDuration, isDashEntry} from '../components/utils'; +import type {RunnerLocals} from '../runners'; + +const isControlDisabled = ( + controlData: DashTabItemControlData, + embeddingInfo: EmbeddingInfo, + controlTab: DashTab, +): boolean => { + if ( + (controlData.sourceType !== DashTabItemControlSourceType.Dataset && + controlData.sourceType !== DashTabItemControlSourceType.Manual) || + // dash doesn't support publicParamsMode + embeddingInfo.embed.publicParamsMode + ) { + return false; + } + const controlSource = controlData.source as + | DashTabItemControlDataset['source'] + | DashTabItemControlManual['source']; + + const controlParam = + 'datasetFieldId' in controlSource ? controlSource.datasetFieldId : controlSource.fieldName; + + const tabAliases = controlData.namespace + ? controlTab.aliases[controlData.namespace] + : undefined; + + const aliasesParamsList = tabAliases?.find((alias) => alias.includes(controlParam)); + + const forbiddenParams = embeddingInfo.embed.privateParams.concat( + embeddingInfo.token.params ? Object.keys(embeddingInfo.token.params) : [], + ); + + return aliasesParamsList + ? aliasesParamsList.some((alias) => forbiddenParams.includes(alias)) + : forbiddenParams.includes(controlParam); +}; + +const isResponseError = (error: unknown): error is AxiosError<{code: string}> => { + return Boolean(isObject(error) && 'response' in error && error.response); +}; + +function validateEmbedToken( + req: Request, + ctx: AppContext, + res: Response, +): {embedToken: string; embedId: string} | null { + const embedToken = Array.isArray(req.headers[DL_EMBED_TOKEN_HEADER]) + ? '' + : req.headers[DL_EMBED_TOKEN_HEADER]; + + if (!embedToken) { + ctx.log('CHARTS_ENGINE_NO_TOKEN'); + res.status(400).send({ + code: ErrorCode.TokenNotFound, + extra: { + message: 'You must provide embedToken', + hideRetry: true, + }, + }); + return null; + } + + const payload = jwt.decode(embedToken); + + if (!payload || typeof payload === 'string' || !('embedId' in payload)) { + ctx.log('CHARTS_ENGINE_WRONG_TOKEN'); + res.status(400).send({ + code: ErrorCode.InvalidToken, + extra: {message: 'Wrong token format', hideRetry: true}, + }); + return null; + } + + const embedId = payload.embedId; + + // Update subrequest headers + res.locals.subrequestHeaders = { + ...res.locals.subrequestHeaders, + [DL_EMBED_TOKEN_HEADER]: embedToken, + }; + + return {embedToken, embedId}; +} + +function handleError( + error: unknown, + ctx: AppContext, + res: Response, + defaultCode = 'ERR.CHARTS.CONFIG_LOADING_ERROR', + errorLog = 'CHARTS_ENGINE_RUNNER_ERROR', +): void { + // Handle specific error cases for outdated dependencies + if ( + isResponseError(error) && + (error.response?.data.code === ErrorCode.IncorrectEntry || + error.response?.data.code === ErrorCode.IncorrectDepsIds) + ) { + res.status(409).send({ + code: ErrorCode.OutdatedDependencies, + extra: { + message: 'Dependencies of embed are outdated', + hideRetry: true, + }, + }); + return; + } + + const typedError: ResolveConfigError = + isObject(error) && 'message' in error ? (error as Error) : new Error(error as string); + + const status = (typedError.response && typedError.response.status) || typedError.status || 500; + + const errorCode = + (typedError.response && typedError.response.status) || typedError.status || defaultCode; + + ctx.logError(errorLog, typedError); + + res.status(status).send({ + code: errorCode, + error: { + code: errorCode, + details: { + code: status, + }, + extra: { + hideRetry: false, + }, + }, + }); +} + +function processControlWidget( + controlData: { + id: string; + widgetId?: string; + tabId: string; + }, + embeddingInfo: EmbeddingInfo, + res: Response, +): ReducedResolvedConfig | null { + if (!isDashEntry(embeddingInfo.entry)) { + return null; + } + + const controlTab = controlData?.tabId + ? embeddingInfo.entry.data.tabs.find((tab) => tab.id === controlData?.tabId) + : null; + + // Support group and old single selectors + const controlWidgetId = controlData.widgetId || controlData.id; + + const controlWidgetConfig = getAllTabItems(controlTab).find( + ({id}: {id: string}) => id === controlWidgetId, + ); + + // Early return if control widget config is not found or has invalid type + if ( + !controlTab || + !controlWidgetConfig || + (controlWidgetConfig.type !== DashTabItemType.Control && + controlWidgetConfig.type !== DashTabItemType.GroupControl) + ) { + res.status(404).send({ + error: 'Сonfig was not found', + }); + return null; + } + + const sharedData: (DashTabItemControlData & {disabled?: boolean}) | undefined = + controlWidgetConfig.type === DashTabItemType.GroupControl + ? controlWidgetConfig.data.group.find(({id}: {id: string}) => id === controlData.id) + : controlWidgetConfig.data; + + if (!sharedData) { + res.status(404).send({ + error: 'Сonfig was not found', + }); + return null; + } + + sharedData.disabled = isControlDisabled(sharedData, embeddingInfo, controlTab); + + return { + data: {shared: sharedData}, + meta: {stype: ControlType.Dash}, + } as ReducedResolvedConfig; +} + +function processEntry( + controlData: + | { + id: string; + widgetId?: string; + tabId: string; + } + | undefined, + embeddingInfo: EmbeddingInfo, + res: Response, +): ReducedResolvedConfig | null { + if (controlData && isDashEntry(embeddingInfo.entry)) { + return processControlWidget(controlData, embeddingInfo, res); + } + + if (embeddingInfo.entry.scope === EntryScope.Widget) { + return embeddingInfo.entry; + } + + // Invalid entry type + res.status(400).send({ + code: ErrorCode.InvalidToken, + extra: { + message: 'Invalid token', + hideRetry: true, + }, + }); + return null; +} + +async function filterParams({ + params = {}, + embeddingInfo, +}: { + params: Record; + embeddingInfo: EmbeddingInfo; +}): Promise<{params: Record; privateParams?: Set}> { + if (!params || Object.keys(params).length === 0) { + return {params: {...embeddingInfo.token.params}}; + } + + const filteredParams: Record = {}; + + let forbiddenParamsSet: Set | undefined; + + if (embeddingInfo.embed.publicParamsMode && embeddingInfo.embed.unsignedParams?.length > 0) { + // public params mode is enabled + + const unsignedParamsSet = new Set(embeddingInfo.embed.unsignedParams); + + Object.keys(params).forEach((key) => { + if (unsignedParamsSet.has(key)) { + filteredParams[key] = params[key]; + } + }); + } else if ( + !embeddingInfo.embed.publicParamsMode && + embeddingInfo.embed.privateParams?.length === 0 + ) { + // privateParams mode is enabled, but params are not added + + Object.assign(filteredParams, params); + } else if ( + !embeddingInfo.embed.publicParamsMode && + embeddingInfo.embed.privateParams?.length > 0 + ) { + const fillingForbiddenParamsSet = new Set(embeddingInfo.embed.privateParams); + + for (const [key, value] of Object.entries(params)) { + if (!fillingForbiddenParamsSet.has(key)) { + filteredParams[key] = value; + } + } + + forbiddenParamsSet = fillingForbiddenParamsSet; + } + + if (embeddingInfo.token.params) { + Object.keys(embeddingInfo.token.params).forEach((param) => forbiddenParamsSet?.add(param)); + } + + const finalParams = { + // params from token are considered as constant + // they have the most priority over incoming params + ...filteredParams, + ...embeddingInfo.token.params, + }; + + return { + params: finalParams, + privateParams: forbiddenParamsSet, + }; +} + +async function findAndExecuteRunner({ + entry, + chartsEngine, + ctx, + req, + res, + configResolving, + embeddingInfo, + privateParams, +}: { + entry: ReducedResolvedConfig; + chartsEngine: ChartsEngine; + ctx: AppContext; + req: Request; + res: Response; + configResolving: number; + embeddingInfo: EmbeddingInfo; + privateParams?: Set; +}) { + const configType = entry?.meta?.stype; + + ctx.log('CHARTS_ENGINE_CONFIG_TYPE', {configType}); + + const runnerFound = chartsEngine.runners.find((runner) => { + return runner.trigger.has(configType); + }); + + if (!runnerFound) { + ctx.log('CHARTS_ENGINE_UNKNOWN_CONFIG_TYPE', {configType}); + res.status(400).send({ + error: `Unknown config type ${configType}`, + }); + return; + } + + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + if (!isEnabledServerFeature('EnableChartEditor') && runnerFound.name === 'editor') { + ctx.log('CHARTS_ENGINE_EDITOR_DISABLED'); + res.status(400).send({ + error: 'Editor is disabled', + }); + return; + } + + req.body.config = entry; + req.body.key = entry.key; + + req.body.widgetConfig = { + ...req.body.widgetConfig, + enableExport: embeddingInfo.embed.settings?.enableExport === true, + }; + + const runnerLocals: RunnerLocals = { + subrequestHeaders: res.locals.subrequestHeaders, + editMode: Boolean(res.locals.editMode), + login: res.locals.login ?? null, + iamToken: res.locals.iamToken ?? null, + }; + + const runnerHandlerResult = await runnerFound.handler(ctx, { + chartsEngine, + req, + runnerLocals, + config: { + ...entry, + data: { + ...entry.data, + url: get(entry.data, 'sources') || get(entry.data, 'url'), + js: get(entry.data, 'prepare') || get(entry.data, 'js'), + ui: get(entry.data, 'controls') || get(entry.data, 'ui'), + }, + }, + configResolving, + // converting it to an array, since for some runners the fields must be serializable + secureConfig: {privateParams: privateParams ? Array.from(privateParams) : undefined}, + forbiddenFields: ['_confStorageConfig', 'timings', 'key'], + }); + + const onEmbedsControllerBeforeResponse = registry.common.functions.get( + 'onEmbedsControllerBeforeResponse', + ); + + await onEmbedsControllerBeforeResponse({ + req, + res, + runnerName: runnerFound.name, + runnerHandlerResult, + }); + + if (res.headersSent) { + // If response sent in onEmbedsControllerBeforeResponse + return; + } else { + res.status(runnerHandlerResult.status).send(runnerHandlerResult.payload); + } +} + +export const embedsController = (chartsEngine: ChartsEngine) => { + return async function chartsRunController(req: Request, res: Response) { + const {ctx} = req; + + // We need it because of timeout error after 120 seconds + // https://forum.nginx.org/read.php?2,214230,214239#msg-214239 + req.socket.setTimeout(0); + + const hrStart = process.hrtime(); + + const {id, controlData} = req.body; + + const tokenData = validateEmbedToken(req, ctx, res); + if (!tokenData) { + return; // Token validation failed, response already sent + } + + const {embedToken, embedId} = tokenData; + + const onEmbedsControllerStart = registry.common.functions.get('onEmbedsControllerStart'); + + await onEmbedsControllerStart({req, res}).catch((error) => { + handleError(error, ctx, res); + }); + // If response sent in onEmbedsControllerStart + if (res.headersSent) { + return; + } + + const configResolveArgs: EmbedResolveConfigProps = { + id, + embedToken, + // Key is legacy but we using it deeply like cache key, so this is just for compatibility purposes + key: embedId, + embedId, + headers: { + ...res.locals.subrequestHeaders, + ...ctx.getMetadata(), + }, + includeServicePlan: true, + includeTenantFeatures: true, + includeTenantSettings: true, + }; + + // 1. it's embedded chart, id is not used, chart is resolved by token + // 2. it's widget from embedded dash, id is used, chart is resolved by + // token and id + // 3. it's selector from embedded dash, id is not used, dash is resolved by + // token to get embeddedInfo and check token + const configPromise = ctx.call('configLoading', (cx) => + resolveEmbedConfig(cx, configResolveArgs), + ); + + ctx.log('CHARTS_ENGINE_LOADING_CONFIG', {embedId}); + + await Promise.resolve(configPromise) + .catch((err: unknown) => { + handleError( + err, + ctx, + res, + 'ERR.CHARTS.CONFIG_LOADING_ERROR', + 'CHARTS_ENGINE_CONFIG_LOADING_ERROR "token"', + ); + }) + .then(async (embeddingInfo) => { + if (!embeddingInfo || !('token' in embeddingInfo)) { + return null; + } + + const {params, privateParams} = await filterParams({ + params: req.body.params, + embeddingInfo, + }); + + req.body.params = params; + + const entry = processEntry(controlData, embeddingInfo, res); + + // If entry processing failed, the response has already been sent + if (!entry) { + return null; + } + + const configResolving = getDuration(hrStart); + + return findAndExecuteRunner({ + entry, + chartsEngine, + ctx, + req, + res, + configResolving, + embeddingInfo, + privateParams, + }); + }) + .catch((error) => { + handleError(error, ctx, res, 'ERR.CHARTS.CHARTS_ENGINE_RUNNER_ERROR'); + }); + }; +}; diff --git a/src/server/components/charts-engine/controllers/export.ts b/src/server/components/charts-engine/controllers/export.ts new file mode 100644 index 0000000..44d270b --- /dev/null +++ b/src/server/components/charts-engine/controllers/export.ts @@ -0,0 +1,83 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import moment from 'moment'; + +import {registry} from '../../../registry'; + +import {csvConverter} from './csvConverter'; + +const DATA_LIMIT = 1024 * 1024 * 100; // 100MB + +export const exportController = () => { + return async (req: Request, res: Response) => { + const {ctx} = req; + + ctx.log('EXPORT_START'); + + const reqDataLength = req.body.data.length; + + if (reqDataLength > DATA_LIMIT) { + ctx.logError(`EXPORT_DATA_LIMIT_ERROR`, { + bytes: reqDataLength, + }); + req.ctx.stats('exportSizeStats', { + datetime: Date.now(), + exportType: 'unknown', + sizeBytes: reqDataLength, + timings: 0, + rejected: 'true', + requestId: req.id, + }); + res.sendStatus(413); + return; + } + + let POST; + + try { + POST = JSON.parse(decodeURIComponent(req.body.data.replace(/\+/g, ' '))); + } catch (e) { + res.sendStatus(400); + return; + } + + ctx.log('EXPORT_BODY_DATA_PARSED'); + + const chartData = POST.chartData; + + const dataArray = + chartData.categories_ms || + chartData.categories_ms || + chartData.categories || + (chartData.graphs && Array(chartData.graphs[0].data.length).fill(undefined)); + + if (!Array.isArray(dataArray)) { + ctx.log(`Unsupported format`); + res.sendStatus(400); + return; + } + + const dataArrayLength = dataArray && dataArray.length; + + ctx.log('EXPORT_DATA_ARRAY_LENGTH', { + length: dataArrayLength, + }); + + const downloadConfig = POST.downloadConfig || { + filename: 'ChartExportData', + }; + + downloadConfig.filename += `_${moment().format('YYYY_MM_DD_HH_mm')}`; + + if (POST.formSettings.format === 'csv') { + csvConverter(req, res, chartData, dataArray, POST.formSettings, downloadConfig); + } else if (POST.formSettings.format === 'xlsx') { + const xlsxConverter = registry.getXlsxConverter(); + if (xlsxConverter !== undefined) { + xlsxConverter(req, res, chartData, dataArray, downloadConfig); + } + } else { + ctx.log(`Unsupported format`); + res.sendStatus(400); + } + }; +}; diff --git a/src/server/components/charts-engine/controllers/markdown.ts b/src/server/components/charts-engine/controllers/markdown.ts new file mode 100644 index 0000000..f8b0930 --- /dev/null +++ b/src/server/components/charts-engine/controllers/markdown.ts @@ -0,0 +1,46 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import {renderHTML} from '../../../../shared/modules/markdown/markdown'; +import {registry} from '../../../registry'; + +export const markdownController = { + render: (req: Request, res: Response) => { + try { + const {body} = req; + + const result = renderHTML({ + text: body.text, + lang: res.locals.lang, + plugins: registry.getYfmPlugins(), + }); + + res.status(200).send(result); + } catch (error) { + const {ctx} = req; + ctx.logError('Error rendering markdown', error); + res.status(500).send(error); + } + }, + + batchRender: (req: Request, res: Response) => { + try { + const input = req.body.texts; + const results: Record = {}; + + for (const key of Object.keys(input)) { + const text = input[key]; + results[key] = renderHTML({ + text, + lang: res.locals.lang, + plugins: registry.getYfmPlugins(), + }); + } + + res.status(200).send(results); + } catch (error) { + const {ctx} = req; + ctx.logError('Error rendering markdown', error); + res.status(500).send(error); + } + }, +}; diff --git a/src/server/components/charts-engine/controllers/run.ts b/src/server/components/charts-engine/controllers/run.ts new file mode 100644 index 0000000..b9d4b5b --- /dev/null +++ b/src/server/components/charts-engine/controllers/run.ts @@ -0,0 +1,193 @@ +import querystring from 'querystring'; +import url from 'url'; + +import type {Request, Response} from '@gravity-ui/expresskit'; +import get from 'lodash/get'; + +import type {ChartsEngine} from '..'; +import {Feature} from '../../../../shared'; +import {DeveloperModeCheckStatus} from '../../../../shared/types'; +import type {ResolvedConfig} from '../components/storage/types'; +import {getDuration} from '../components/utils'; +import type {RunnerLocals} from '../runners'; + +import {resolveChartConfig} from './utils'; + +type RunControllerExtraSettings = { + storageApiPath?: string; + extraAllowedHeaders?: string[]; + includeServicePlan?: boolean; + includeTenantFeatures?: boolean; +}; + +export const runController = ( + chartsEngine: ChartsEngine, + extraSettings?: RunControllerExtraSettings, +) => { + // eslint-disable-next-line complexity + return async function chartsRunController(req: Request, res: Response) { + const {ctx} = req; + + // We need it because of timeout error after 120 seconds + // https://forum.nginx.org/read.php?2,214230,214239#msg-214239 + req.socket.setTimeout(0); + + const hrStart = process.hrtime(); + + const {id, workbookId, expectedType = null, config: chartConfig} = req.body; + + let {key, params} = req.body; + + if (!id && !key) { + key = req.body.path || (params && params.name); + } + + if (!id && !key && !chartConfig) { + ctx.log('CHARTS_ENGINE_NO_KEY_NO_ID_NO_CONFIG'); + res.status(400).send({ + error: 'You must provide at least one of: id, key, config (if supported)', + }); + return; + } + + let config: ResolvedConfig | {error: unknown}; + if (chartConfig) { + config = { + ...chartConfig, + }; + } else { + if (!params && key) { + const parsedUrl = url.parse(key); + if (parsedUrl.query) { + if (!req.body.params) { + req.body.params = {}; + } + + req.body.params = params = { + ...req.body.params, + ...querystring.parse(parsedUrl.query), + }; + } + } + + config = await resolveChartConfig({ + subrequestHeaders: res.locals.subrequestHeaders, + request: req, + params, + id, + key, + workbookId, + extraSettings, + }); + } + + if ('error' in config) { + const status = get(config, 'error.details.code', 500); + res.status(status).send(config); + return; + } + + try { + const configResolving = getDuration(hrStart); + const configType = config && config.meta && config.meta.stype; + + ctx.log('CHARTS_ENGINE_CONFIG_TYPE', {configType}); + + if (expectedType && expectedType !== configType) { + ctx.log('CHARTS_ENGINE_CONFIG_TYPE_MISMATCH'); + res.status(400).send({ + error: `Config type "${configType}" does not match expected type "${expectedType}"`, + }); + return; + } + + const runnerFound = chartsEngine.runners.find((runner) => { + return runner.trigger.has(configType); + }); + + if (!runnerFound) { + ctx.log('CHARTS_ENGINE_UNKNOWN_CONFIG_TYPE', {configType}); + res.status(400).send({ + error: `Unknown config type ${configType}`, + }); + return; + } + + if (req.body.config) { + if (!ctx.config.allowBodyConfig && !runnerFound.safeConfig) { + ctx.log('UNSAFE_CONFIG_OVERRIDE'); + res.status(400).send({ + error: `It is forbidden to pass config in body for "${configType}"`, + }); + return; + } + + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + if ( + isEnabledServerFeature(Feature.ShouldCheckEditorAccess) && + runnerFound.name === 'editor' + ) { + const {checkRequestForDeveloperModeAccess} = ctx.get('gateway'); + const checkResult = await checkRequestForDeveloperModeAccess({ + ctx, + }); + + if (checkResult === DeveloperModeCheckStatus.Forbidden) { + res.status(403).send({ + error: { + code: 403, + details: { + message: 'Access to Editor developer mode was denied', + }, + }, + }); + return; + } + } + } + + if (req.body.config) { + res.locals.editMode = true; + } + + req.body.config = config; + + req.body.key = req.body.key || config.key; + + const runnerLocals: RunnerLocals = { + subrequestHeaders: res.locals.subrequestHeaders, + editMode: Boolean(res.locals.editMode), + login: res.locals.login ?? null, + iamToken: res.locals.iamToken ?? null, + }; + + const runnerHandlerResult = await runnerFound.handler(ctx, { + chartsEngine, + req, + runnerLocals, + config: { + ...config, + data: { + ...config.data, + url: get(config.data, 'sources') || get(config.data, 'url'), + js: get(config.data, 'prepare') || get(config.data, 'js'), + ui: get(config.data, 'controls') || get(config.data, 'ui'), + }, + }, + configResolving, + workbookId, + }); + + if (runnerHandlerResult) { + res.status(runnerHandlerResult.status).send(runnerHandlerResult.payload); + } + + return; + } catch (error) { + ctx.logError('CHARTS_ENGINE_RUNNER_ERROR', error); + res.status(500).send({ + error: 'Internal error', + }); + } + }; +}; diff --git a/src/server/components/charts-engine/controllers/utils.ts b/src/server/components/charts-engine/controllers/utils.ts new file mode 100644 index 0000000..3cd1b44 --- /dev/null +++ b/src/server/components/charts-engine/controllers/utils.ts @@ -0,0 +1,91 @@ +import type {Request} from '@gravity-ui/expresskit'; +import isObject from 'lodash/isObject'; + +import type {StringParams} from '../../../../shared'; +import Utils from '../../../utils'; +import {resolveConfig} from '../components/storage'; +import type {ResolveConfigError, ResolveConfigProps} from '../components/storage/base'; + +export function shouldUseUnreleasedConfig(args: {request: Request; params?: StringParams}) { + const {request, params} = args; + + let useUnreleasedConfig = request.body.unreleased === '1'; + if (params) { + if (Array.isArray(params.unreleased)) { + useUnreleasedConfig = useUnreleasedConfig || params.unreleased[0] === '1'; + } else { + useUnreleasedConfig = useUnreleasedConfig || params.unreleased === '1'; + } + } + + return useUnreleasedConfig; +} + +type LoadChartConfigArgs = { + extraSettings?: any; + subrequestHeaders?: any; + request: Request; + params?: StringParams; + id?: string; + key?: string; + workbookId?: string; +}; + +export async function resolveChartConfig(args: LoadChartConfigArgs) { + const {params, id, key, workbookId, request, extraSettings, subrequestHeaders} = args; + const {ctx} = request; + + const configResolveArgs: ResolveConfigProps = { + unreleased: shouldUseUnreleasedConfig({request, params}), + key, + id, + workbookId, + headers: { + ...subrequestHeaders, + ...ctx.getMetadata(), + ...(ctx.config.isAuthEnabled ? {...Utils.pickAuthHeaders(request)} : {}), + }, + requestId: request.id, + ...extraSettings, + }; + + try { + ctx.log('CHARTS_ENGINE_LOADING_CONFIG', {key, id}); + return await ctx.call('configLoading', (cx) => resolveConfig(cx, configResolveArgs)); + } catch (err) { + const error: ResolveConfigError = + isObject(err) && 'message' in err ? (err as Error) : new Error(err as string); + const result: { + error: { + code: string; + details: { + code: number | null; + entryId?: string; + }; + debug?: { + message: unknown; + }; + }; + } = { + error: { + code: 'ERR.CHARTS.CONFIG_LOADING_ERROR', + details: { + code: (error.response && error.response.status) || error.status || null, + entryId: id, + }, + debug: { + message: error.message, + }, + }, + }; + + // TODO use ShowChartsEngineDebugInfo flag + if (ctx.config.appInstallation !== 'internal') { + delete result.error.debug; + } + + ctx.logError(`CHARTS_ENGINE_CONFIG_LOADING_ERROR "${key || id}"`, error); + + return result; + } +} diff --git a/src/server/components/charts-engine/index.ts b/src/server/components/charts-engine/index.ts new file mode 100644 index 0000000..becb12a --- /dev/null +++ b/src/server/components/charts-engine/index.ts @@ -0,0 +1,155 @@ +import type {AppMiddleware} from '@gravity-ui/expresskit'; +import type {AppConfig, AppContext} from '@gravity-ui/nodekit'; + +import type CacheClient from '../cache-client'; + +import {CommentsFetcher} from './components/processor/comments-fetcher'; +import {Console} from './components/processor/console'; +import {DataFetcher} from './components/processor/data-fetcher'; +import {Request} from './components/request'; +import {initPreloading, initStorage} from './components/storage'; +import {chartsController} from './controllers/charts'; +import {configController} from './controllers/config'; +import {embeddedEntryController} from './controllers/embedded-entry'; +import {embedsController} from './controllers/embeds'; +import {exportController} from './controllers/export'; +import {markdownController} from './controllers/markdown'; +import {runController} from './controllers/run'; +const defaultControllers = { + export: exportController, + markdown: markdownController, + run: runController, + config: configController, + charts: chartsController, + embeds: embedsController, + embeddedEntry: embeddedEntryController, +}; +import type {Runner} from './runners'; +import type {ChartEngineController, Plugin, SourceConfig, TelemetryCallbacks} from './types'; +import {MiddlewareStage} from './types'; + +type Controllers = { + export: ReturnType; + markdown: typeof markdownController; + run: ReturnType; + config: ReturnType; + charts: ReturnType; + embeds: ReturnType; + embeddedEntry: typeof embeddedEntryController; +}; + +class ChartsEngine { + runners: Runner[]; + sources: Record; + telemetryCallbacks: TelemetryCallbacks; + processorHooks: Record[]; + flags: Record; + cacheClient: CacheClient; + controllers: Controllers; + plugins: Plugin[]; + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; + + constructor({ + config, + secrets, + plugins, + telemetryCallbacks = {}, + flags = {}, + cacheClient, + beforeAuth, + afterAuth, + runners, + }: { + config: AppConfig; + secrets: Record; + plugins: Plugin[]; + telemetryCallbacks?: TelemetryCallbacks; + flags?: Record; + cacheClient: CacheClient; + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; + runners: Runner[]; + }) { + this.runners = runners; + this.sources = config.sources; + this.telemetryCallbacks = telemetryCallbacks; + this.processorHooks = []; + this.flags = flags; + this.plugins = plugins; + this.beforeAuth = beforeAuth; + this.afterAuth = afterAuth; + + initStorage({ + initialOauthToken: secrets.ROBOT_OAUTH_TOKEN, + config, + telemetryCallbacks, + flags, + }); + + this.cacheClient = cacheClient; + + Request.init({cacheClientInstance: this.cacheClient, config}); + + const {includeServicePlan, includeTenantFeatures} = config.chartsEngineConfig; + this.controllers = { + export: defaultControllers.export(), + markdown: defaultControllers.markdown, + run: defaultControllers.run(this, {includeServicePlan, includeTenantFeatures}), + config: defaultControllers.config(this), + charts: defaultControllers.charts(this), + embeds: defaultControllers.embeds(this), + embeddedEntry: defaultControllers.embeddedEntry, + }; + + if (plugins) { + // Init plugins + plugins.forEach((plugin) => { + if (plugin.sources) { + this.sources = { + ...this.sources, + ...plugin.sources, + }; + } + + // Apply plugin middlewares + if (plugin.middlewares) { + plugin.middlewares.forEach((middleware) => { + if (middleware.stage === MiddlewareStage.BeforeAuth) { + this.beforeAuth.push(middleware.fn); + } + if (middleware.stage === MiddlewareStage.AfterAuth) { + this.afterAuth.push(middleware.fn); + } + }); + } + + // Apply plugin runners + if (plugin.runners) { + this.runners = [...this.runners, ...plugin.runners]; + } + + // Apply sandbox hooks + if (Array.isArray(plugin.processorHooks)) { + this.processorHooks = [...this.processorHooks, ...plugin.processorHooks]; + } + + if (plugin.controllers) { + const controllers = Object.entries(plugin.controllers).reduce< + Record + >((acc, [key, value]) => { + acc[key] = value(this); + return acc; + }, {}); + Object.assign(this.controllers, controllers); + } + }); + } + } + + initPreloading(ctx: AppContext) { + initPreloading(ctx); + } +} + +export {ChartsEngine, DataFetcher, CommentsFetcher, Console}; diff --git a/src/server/components/charts-engine/runners/common.ts b/src/server/components/charts-engine/runners/common.ts new file mode 100644 index 0000000..c0501ae --- /dev/null +++ b/src/server/components/charts-engine/runners/common.ts @@ -0,0 +1,375 @@ +import type {Request} from '@gravity-ui/expresskit'; +import {type AppContext, USER_ID_PARAM_NAME, USER_LANGUAGE_PARAM_NAME} from '@gravity-ui/nodekit'; +import {isObject} from 'lodash'; + +import type {ControlType, EntryPublicAuthor, WorkbookId} from '../../../../shared'; +import { + DISABLE, + DISABLE_JSONFN_SWITCH_MODE_COOKIE_NAME, + DL_EMBED_TOKEN_HEADER, + Feature, +} from '../../../../shared'; +import {registry} from '../../../registry'; +import type {ProcessorParams, SerializableProcessorParams} from '../components/processor'; +import {Processor} from '../components/processor'; +import {ProcessorHooks} from '../components/processor/hooks'; +import type {ChartBuilder} from '../components/processor/types'; +import type {ResolvedConfig} from '../components/storage/types'; +import {getDefaultColorPaletteId, getDuration} from '../components/utils'; +import type {ChartsEngine} from '../index'; +import type {ChartStorageType} from '../types'; + +import {prepareErrorForLogger} from './utils'; + +import type {RunnerHandlerResult, RunnerLocals} from '.'; + +export type Runners = 'Worker' | 'Wizard' | 'Ql' | 'Editor' | 'Control'; + +export function engineProcessingCallback({ + ctx, + hrStart, + processorParams, + runnerType, +}: { + ctx: AppContext; + hrStart: [number, number]; + processorParams: Omit; + runnerType: Runners; +}): Promise<{status: number; payload: unknown}> { + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + const enableChartEditor = + isEnabledServerFeature('EnableChartEditor') && runnerType === 'Editor'; + const showChartsEngineDebugInfo = Boolean( + isEnabledServerFeature(Feature.ShowChartsEngineDebugInfo), + ); + + return Processor.process({...processorParams, ctx: ctx}) + .then((result) => { + ctx.log(`${runnerType}::FullRun`, {duration: getDuration(hrStart)}); + + if (result) { + const showLogs = + showChartsEngineDebugInfo || (enableChartEditor && processorParams.isEditMode); + if ('logs_v2' in result && !showLogs) { + delete result.logs_v2; + } + + if ('error' in result) { + const resultCopy = {...result}; + + if ('_confStorageConfig' in resultCopy) { + delete resultCopy._confStorageConfig; + } + + const logError = prepareErrorForLogger(result.error); + + ctx.log('PROCESSED_WITH_ERRORS', {error: logError}); + + let statusCode = 500; + + if (isObject(result.error) && !showChartsEngineDebugInfo) { + const {error} = result; + if ('debug' in error) { + delete error.debug; + } + + const {details} = error; + + if (details) { + delete details.stackTrace; + + if (details.sources) { + const {sources} = details; + + Object.keys(sources).forEach((source) => { + if (sources[source]) { + const {body} = sources[source]; + + if (body) { + delete body.debug; + } + } + }); + } + } + } + + if (isObject(result.error) && result.error.statusCode) { + statusCode = result.error.statusCode; + + delete result.error.statusCode; + } + return {status: statusCode, payload: result}; + } else { + ctx.log('PROCESSED_SUCCESSFULLY'); + return {status: 200, payload: result}; + } + } else { + throw new Error('INVALID_PROCESSING_RESULT'); + } + }) + .catch((error) => { + ctx.logError('PROCESSING_FAILED', error); + + if (Number(error.statusCode) >= 200 && Number(error.statusCode) < 400) { + return { + status: 500, + payload: { + error: { + code: 'ERR.CHARTS.INVALID_SET_ERROR_USAGE', + message: 'Only 4xx/5xx error status codes valid for .setError', + }, + }, + }; + } else { + const result = { + error: { + ...error, + code: error.code || 'ERR.CHARTS.UNHANDLED_ERROR', + debug: { + message: error.message, + ...(error.debug || {}), + }, + }, + }; + + if (!showChartsEngineDebugInfo) { + delete result.error.debug; + + if (result.error.details) { + delete result.error.details.stackTrace; + } + } + + return {status: error.statusCode || 500, payload: result}; + } + }); +} + +export const getSerializableProcessorParams = ({ + runnerLocals, + req, + ctx, + configResolving, + generatedConfig, + workbookId, + localConfig, + subrequestHeadersKind, + forbiddenFields, + secureConfig, +}: { + runnerLocals: RunnerLocals; + req: Request; + ctx: AppContext; + configResolving: number; + generatedConfig: { + data: Record; + meta: { + stype: ChartStorageType | ControlType.Dash; + }; + publicAuthor?: EntryPublicAuthor; + }; + localConfig?: ResolvedConfig; + workbookId?: WorkbookId; + subrequestHeadersKind?: string; + forbiddenFields?: ProcessorParams['forbiddenFields']; + secureConfig?: ProcessorParams['secureConfig']; +}): SerializableProcessorParams => { + const locals = runnerLocals; + + const {params, actionParams, widgetConfig} = req.body; + + const iamToken = + locals.iamToken ?? (req.headers[ctx.config.headersMap.subjectToken] as string) ?? null; + + const configName = req.body.key; + const configId = req.body.id; + const disableJSONFnByCookie = req.cookies[DISABLE_JSONFN_SWITCH_MODE_COOKIE_NAME] === DISABLE; + + const isEmbed = req.headers[DL_EMBED_TOKEN_HEADER] !== undefined; + + const authParams = ctx.config.isAuthEnabled + ? { + accessToken: req.ctx.get('user')?.accessToken, + } + : undefined; + + const originalReqHeaders = { + xRealIP: req.headers['x-real-ip'], + xForwardedFor: req.headers['x-forwarded-for'], + xChartsFetcherVia: req.headers['x-charts-fetcher-via'], + referer: req.headers.referer, + }; + const adapterContext = { + headers: { + ['x-forwarded-for']: req.headers['x-forwarded-for'], + cookie: req.headers.cookie, + }, + }; + + const hooksContext = { + headers: { + cookie: req.headers.cookie, + authorization: req.headers.authorization, + }, + }; + + const getAvailablePalettesMap = registry.common.functions.get('getAvailablePalettesMap'); + + const processorParams: SerializableProcessorParams = { + paramsOverride: params, + actionParamsOverride: actionParams, + widgetConfig, + userLang: ctx.get(USER_LANGUAGE_PARAM_NAME) || null, + userLogin: locals.login, + userId: ctx.get(USER_ID_PARAM_NAME) || null, + subrequestHeaders: locals.subrequestHeaders, + iamToken, + isEditMode: locals.editMode, + configResolving, + cacheToken: req.headers['x-charts-cache-token'] || null, + forbiddenFields, + secureConfig, + configName, + configId, + revId: localConfig?.revId, + disableJSONFnByCookie, + isEmbed, + authParams, + originalReqHeaders, + adapterContext, + hooksContext, + configOverride: generatedConfig, + defaultColorPaletteId: getDefaultColorPaletteId({ + ctx, + tenantSettings: localConfig?.tenantSettings, + }), + systemPalettes: getAvailablePalettesMap(), + }; + + const configWorkbook = workbookId ?? localConfig?.workbookId; + if (configWorkbook) { + processorParams.workbookId = configWorkbook; + } + + if (req.body.uiOnly) { + processorParams.uiOnly = true; + } + + processorParams.responseOptions = req.body.responseOptions || {}; + + if ( + processorParams.responseOptions && + typeof processorParams.responseOptions.includeLogs === 'undefined' + ) { + processorParams.responseOptions.includeLogs = true; + } + + if ( + subrequestHeadersKind && + processorParams.subrequestHeaders && + typeof processorParams.subrequestHeaders['x-chart-kind'] === 'undefined' + ) { + processorParams.subrequestHeaders['x-chart-kind'] = subrequestHeadersKind; + } + + return processorParams; +}; + +export function commonRunner({ + runnerLocals, + req, + ctx, + chartType, + chartsEngine, + configResolving, + builder, + generatedConfig, + workbookId, + localConfig, + runnerType, + hrStart, + subrequestHeadersKind, + forbiddenFields, + secureConfig, +}: { + runnerLocals: RunnerLocals; + req: Request; + ctx: AppContext; + chartType?: string; + chartsEngine: ChartsEngine; + configResolving: number; + builder: ChartBuilder; + generatedConfig: { + data: Record; + meta: { + stype: ChartStorageType | ControlType.Dash; + }; + publicAuthor?: EntryPublicAuthor; + }; + localConfig?: ResolvedConfig; + workbookId?: WorkbookId; + runnerType: Runners; + hrStart: [number, number]; + subrequestHeadersKind?: string; + forbiddenFields?: ProcessorParams['forbiddenFields']; + secureConfig?: ProcessorParams['secureConfig']; +}): Promise { + const telemetryCallbacks = chartsEngine.telemetryCallbacks; + const cacheClient = chartsEngine.cacheClient; + const sourcesConfig = chartsEngine.sources; + const hooks = new ProcessorHooks({processorHooks: chartsEngine.processorHooks}); + + if (chartType) { + runnerLocals.subrequestHeaders['x-chart-kind'] = chartType; + } + + const serializableProcessorParams = getSerializableProcessorParams({ + runnerLocals, + req, + ctx, + configResolving, + generatedConfig, + workbookId, + localConfig, + subrequestHeadersKind, + forbiddenFields, + }); + + ctx.log(`${runnerType}::PreRun`, {duration: getDuration(hrStart)}); + + return ctx + .call('engineProcessing', (cx) => { + return engineProcessingCallback({ + ctx: cx, + hrStart, + processorParams: { + ...serializableProcessorParams, + telemetryCallbacks, + cacheClient, + builder, + hooks, + sourcesConfig, + secureConfig, + }, + runnerType: runnerType as Runners, + }); + }) + .then((result) => { + return { + status: result.status, + payload: result.payload, + }; + }) + .catch((error) => { + ctx.logError('CHARTS_ENGINE_PROCESSOR_UNHANDLED_ERROR', error); + + return { + status: 500, + payload: 'Internal error', + }; + }) + .finally(() => { + ctx.end(); + }); +} diff --git a/src/server/components/charts-engine/runners/constants.ts b/src/server/components/charts-engine/runners/constants.ts new file mode 100644 index 0000000..be37303 --- /dev/null +++ b/src/server/components/charts-engine/runners/constants.ts @@ -0,0 +1,4 @@ +export const RUNNER_NAME = { + WIZARD: 'wizard', + DASH_CONTROLS: 'dashControls', +} as const; diff --git a/src/server/components/charts-engine/runners/control.ts b/src/server/components/charts-engine/runners/control.ts new file mode 100644 index 0000000..f2d4c9f --- /dev/null +++ b/src/server/components/charts-engine/runners/control.ts @@ -0,0 +1,80 @@ +import type {AppContext} from '@gravity-ui/nodekit'; + +import {ControlType} from '../../../../shared'; +import {getControlBuilder} from '../components/processor/control-builder'; +import type {ResolvedConfig} from '../components/storage/types'; + +import {commonRunner} from './common'; + +import type {RunnerHandler, RunnerHandlerProps} from '.'; + +export const runControl: RunnerHandler = async (cx: AppContext, props: RunnerHandlerProps) => { + const { + chartsEngine, + req, + runnerLocals, + config, + configResolving, + workbookId, + forbiddenFields, + secureConfig, + } = props; + + const ctx = cx.create('templateControlRunner'); + + if ( + !config || + !('data' in config) || + !('shared' in config.data) || + config.meta?.stype !== ControlType.Dash + ) { + const error = new Error('CONTROL_RUNNER_CONFIG_MISSING'); + ctx.logError('CONTROL_RUNNER_CONFIG_MISSING', error); + ctx.end(); + + return { + status: 400, + payload: error, + }; + } + + const generatedConfig = { + data: { + js: '', + documentation_en: '', + documentation_ru: '', + ui: '', + url: '', + graph: '', + params: '', + statface_graph: '', + shared: config.data.shared, + }, + meta: { + stype: ControlType.Dash, + }, + publicAuthor: config.publicAuthor, + } as ResolvedConfig; + + const hrStart = process.hrtime(); + + const controlBuilder = await getControlBuilder({ + config: generatedConfig, + }); + + return commonRunner({ + runnerLocals, + req, + ctx, + chartsEngine, + configResolving, + builder: controlBuilder, + generatedConfig, + workbookId, + runnerType: 'Control', + hrStart, + localConfig: config, + forbiddenFields, + secureConfig, + }); +}; diff --git a/src/server/components/charts-engine/runners/index.ts b/src/server/components/charts-engine/runners/index.ts new file mode 100644 index 0000000..e1965ba --- /dev/null +++ b/src/server/components/charts-engine/runners/index.ts @@ -0,0 +1,76 @@ +import type {Request} from '@gravity-ui/expresskit'; +import type {AppContext} from '@gravity-ui/nodekit'; + +import type {ChartsEngine} from '..'; +import type {WorkbookId} from '../../../../shared'; +import {ControlType} from '../../../../shared'; +import type {ProcessorParams} from '../components/processor'; +import type {ReducedResolvedConfig, ResolvedConfig} from '../components/storage/types'; + +import {RUNNER_NAME} from './constants'; +import {runControl} from './control'; +import {runWizardChart} from './wizard'; + +export type RunnerLocals = { + subrequestHeaders: Record; + editMode: boolean; + login: string | null; + iamToken: string | null; +}; + +export type Runner = { + name: string; + trigger: Set; + handler: RunnerHandler; + safeConfig?: boolean; +}; + +export type RunnerHandlerResult = { + status: number; + payload: unknown; +}; + +export type RunnerHandler = ( + ctx: AppContext, + {chartsEngine, req, config, configResolving}: RunnerHandlerProps, +) => RunnerHandlerResult | Promise; + +export type RunnerHandlerProps = { + chartsEngine: ChartsEngine; + req: Request; + runnerLocals: RunnerLocals; + config: ResolvedConfig | ReducedResolvedConfig; + configResolving: number; + workbookId?: WorkbookId; + isWizard?: boolean; + forbiddenFields?: ProcessorParams['forbiddenFields']; + secureConfig?: ProcessorParams['secureConfig']; +}; + +export function getDefaultRunners() { + const runners: Runner[] = [ + { + name: RUNNER_NAME.WIZARD, + trigger: new Set([ + 'graph_wizard_node', + 'table_wizard_node', + 'ymap_wizard_node', + 'metric_wizard_node', + 'markup_wizard_node', + 'timeseries_wizard_node', + 'd3_wizard_node', + ]), + safeConfig: true, + handler: runWizardChart, + }, + { + // for all types of controls except editor control + name: RUNNER_NAME.DASH_CONTROLS, + trigger: new Set([ControlType.Dash]), + safeConfig: true, + handler: runControl, + }, + ]; + + return runners; +} diff --git a/src/server/components/charts-engine/runners/utils.ts b/src/server/components/charts-engine/runners/utils.ts new file mode 100644 index 0000000..ea0b36a --- /dev/null +++ b/src/server/components/charts-engine/runners/utils.ts @@ -0,0 +1,42 @@ +import {isObject} from 'lodash'; + +import {config} from '../constants'; + +const {DATA_FETCHING_ERROR} = config; + +export function prepareErrorForLogger(error: unknown) { + if (isObject(error) && 'code' in error && error.code === DATA_FETCHING_ERROR) { + let errorDetails = {}; + + if ( + 'details' in error && + isObject(error.details) && + 'sources' in error.details && + isObject(error.details.sources) + ) { + const sources = error.details.sources; + errorDetails = Object.keys(sources).map((key) => { + const source = (sources as Record>)[key]; + const body = 'body' in source && isObject(source.body) ? source.body : {}; + return { + [key]: { + sourceType: source.sourceType, + status: source.status, + body: { + message: 'message' in body ? body.message : undefined, + code: 'code' in body ? body.code : undefined, + }, + }, + }; + }); + } + + return { + code: DATA_FETCHING_ERROR, + message: 'message' in error ? error.message : 'Data fetching error', + statusCode: 'statusCode' in error ? error.statusCode : undefined, + details: errorDetails, + }; + } + return isObject(error) ? error : {error}; +} diff --git a/src/server/components/charts-engine/runners/wizard.ts b/src/server/components/charts-engine/runners/wizard.ts new file mode 100644 index 0000000..ab3bde1 --- /dev/null +++ b/src/server/components/charts-engine/runners/wizard.ts @@ -0,0 +1,44 @@ +import path from 'path'; + +import {type AppContext, USER_LANGUAGE_PARAM_NAME} from '@gravity-ui/nodekit'; +import type {Pool, Proxy, WorkerPoolOptions} from 'workerpool'; +import workerpool from 'workerpool'; + +import {getWizardChartBuilder} from '../components/processor/worker-chart-builder'; +import type {ResolvedConfig} from '../components/storage/types'; +import type {WizardWorker} from '../components/wizard-worker/types'; + +import {runWorkerChart} from './worker'; + +import type {RunnerHandler, RunnerHandlerProps} from './index'; + +let wizardWorkersPool: Pool | null = null; +async function getWizardWorker(options?: WorkerPoolOptions): Promise> { + if (wizardWorkersPool === null) { + const scriptPath = path.resolve(__dirname, '../components/wizard-worker'); + wizardWorkersPool = workerpool.pool(scriptPath, options); + } + + return wizardWorkersPool.proxy(); +} + +export const runWizardChart: RunnerHandler = async (cx: AppContext, props: RunnerHandlerProps) => { + const {req, runnerLocals, config} = props; + const timeouts = cx.config.runnerExecutionTimeouts?.wizard; + const {widgetConfig} = req.body; + + const chartBuilder = await getWizardChartBuilder({ + userLang: cx.get(USER_LANGUAGE_PARAM_NAME) || '', + userLogin: runnerLocals.login || '', + widgetConfig, + config: config as ResolvedConfig, + isScreenshoter: Boolean(req.headers['x-charts-scr']), + worker: await getWizardWorker({ + maxWorkers: cx.config.chartsEngineConfig.maxWorkers ?? 1, + }), + timeouts, + tenantSettings: {defaultColorPaletteId: config.tenantSettings?.defaultColorPaletteId}, + }); + + return runWorkerChart(cx, {...props, chartBuilder, runnerType: 'Wizard'}); +}; diff --git a/src/server/components/charts-engine/runners/worker.ts b/src/server/components/charts-engine/runners/worker.ts new file mode 100644 index 0000000..048abe4 --- /dev/null +++ b/src/server/components/charts-engine/runners/worker.ts @@ -0,0 +1,129 @@ +import type {AppContext} from '@gravity-ui/nodekit'; + +import {chartGenerator} from '../components/chart-generator'; +import type {ChartBuilder} from '../components/processor/types'; +import type {ChartStorageType} from '../types'; + +import type {Runners} from './common'; +import {commonRunner} from './common'; + +import type {RunnerHandlerProps, RunnerHandlerResult} from '.'; + +// eslint-disable-next-line complexity +export const runWorkerChart = async ( + cx: AppContext, + props: RunnerHandlerProps & {chartBuilder: ChartBuilder; runnerType?: Runners}, +): Promise => { + const { + chartsEngine, + req, + runnerLocals, + config, + configResolving, + workbookId, + chartBuilder, + runnerType = 'Worker', + forbiddenFields, + } = props; + let generatedConfig; + let chartType; + const {template} = config; + + const ctx = cx.create('templateChartRunner'); + + if (config) { + let result; + let metadata = null; + + try { + if (typeof config.data.shared === 'string') { + const data = JSON.parse(config.data.shared); + + if (!template && !data.type) { + data.type = config.meta && config.meta.stype; + } + + result = chartGenerator.generateChart({ + data, + template, + req, + ctx, + }); + + metadata = { + entryId: config.entryId, + key: config.key, + owner: config.owner, + scope: config.scope, + }; + + chartType = template || data.type; + } else { + // This is some kind of legacy edge cases. + // Just for compatibility purposes; + const data = config.data.shared as {type: string}; + + if (!template && !data.type) { + data.type = config.meta && config.meta.stype; + } + + result = chartGenerator.generateChart({ + data, + template, + req, + ctx, + }); + + chartType = template || data.type; + } + } catch (error) { + ctx.logError('Failed to generate chart in chart runner', error); + ctx.end(); + + return { + status: 400, + payload: error, + }; + } + + generatedConfig = { + data: result.chart as Record, + meta: { + stype: result.type as ChartStorageType, + }, + publicAuthor: config.publicAuthor, + }; + + if (metadata) { + Object.assign(generatedConfig, metadata); + } + } else { + const error = new Error('CHART_RUNNER_CONFIG_MISSING'); + + ctx.logError('CHART_RUNNER_CONFIG_MISSING', error); + ctx.end(); + + return { + status: 400, + payload: error, + }; + } + + const hrStart = process.hrtime(); + + return commonRunner({ + runnerLocals, + req, + ctx, + chartType, + chartsEngine, + configResolving, + builder: chartBuilder, + generatedConfig, + workbookId, + runnerType, + hrStart, + localConfig: config, + forbiddenFields, + }); +}; diff --git a/src/server/components/charts-engine/types.ts b/src/server/components/charts-engine/types.ts new file mode 100644 index 0000000..07803c1 --- /dev/null +++ b/src/server/components/charts-engine/types.ts @@ -0,0 +1,246 @@ +import type {IncomingHttpHeaders, OutgoingHttpHeaders} from 'http'; + +import type {AppMiddleware, AppRouteDescription, Request, Response} from '@gravity-ui/expresskit'; +import type {HttpMethod} from '@gravity-ui/expresskit/dist/types'; +import type {AppContext} from '@gravity-ui/nodekit'; + +import type {EDITOR_TYPE_CONFIG_TABS} from '../../../shared'; +import type {PartialDatasetField} from '../../../shared/schema/types'; +import type {SourcesArgs} from '../../modes/charts/plugins/datalens/url/types'; +import type {MiddlewareSourceAdapterArgs, MiddlewareUrl} from '../../modes/charts/plugins/types'; + +import type {Runner} from './runners'; + +export type TelemetryCallbacks = { + onConfigFetched?: ({ + id, + statusCode, + requestId, + traceId, + latency, + tenantId, + }: { + id: string; + statusCode: number; + requestId?: string; + traceId?: string; + latency?: number; + tenantId?: string; + userId?: string; + }) => void; + onConfigFetchingFailed?: ( + error: Error, + { + id, + statusCode, + requestId, + traceId, + tenantId, + latency, + }: { + id: string; + statusCode: number; + requestId?: string; + traceId?: string; + userId?: string; + tenantId?: string; + latency?: number; + }, + ) => void; + onDataFetched?: ({ + sourceName, + url, + requestId, + traceId, + tenantId, + statusCode, + latency, + userId, + }: { + sourceName: string; + url: string; + requestId: string; + traceId?: string; + tenantId?: string; + userId?: string; + statusCode: number; + latency: number; + }) => void; + onDataFetchingFailed?: ( + error: Error, + { + sourceName, + url, + requestId, + traceId, + tenantId, + userId, + statusCode, + latency, + }: { + sourceName: string; + url: string; + requestId: string; + traceId?: string; + tenantId?: string; + userId?: string; + statusCode: number; + latency: number; + }, + ) => void; + onCodeExecuted?: ({ + id, + requestId, + latency, + }: { + id: string; + requestId: string; + latency: number; + }) => void; + onTabsExecuted?: ({ + result, + entryId, + }: { + result: { + config: unknown; + highchartsConfig: unknown; + sources: unknown; + processedData: unknown; + sourceData: unknown; + }; + entryId: string; + }) => void; +}; + +export type Source> = { + url: string; + method?: 'GET' | 'POST'; + headers?: OutgoingHttpHeaders; + cache?: string; + statFormat?: string; + format?: 'json' | 'form' | 'text' | string; + middlewareUrl?: MiddlewareUrl; + data?: T; + hideInInspector?: boolean; + ui?: boolean; + sourceArgs?: SourcesArgs; + apiConnectionId?: string; + qlConnectionId?: string; + datasetId?: string; + datasetFields?: PartialDatasetField[]; + body?: Record; + path?: string; + _original?: unknown; +}; + +export type APIConnectorContentType = 'application/json' | 'text/plain;charset=utf-8'; + +export type APIConnectorParams = { + method: string; + body: unknown; + path: string; + content_type?: APIConnectorContentType; +}; + +export type SourceWithAPIConnector = Source & + Required>; + +export type SourceWithQLConnector = Source & + Required>; + +export type SourceWithDatasetId = Source & + Required>; + +export type AdapterContext = { + headers: { + cookie: IncomingHttpHeaders['cookie']; + ['x-forwarded-for']: IncomingHttpHeaders['x-forwarded-for']; + }; +}; + +export type HooksContext = { + headers: { + cookie: IncomingHttpHeaders['cookie']; + authorization: IncomingHttpHeaders['authorization']; + }; +}; + +export type SourceAliasConfig = { + apiConnectionId: string; + unwrapResponse?: boolean; // If true, unwrap {data: {body: {result: X}}} -> X +}; + +export type SourceConfig = { + description?: { + title: { + ru: string; + en: string; + }; + }; + tvmServiceName?: string; + postprocess?: (data: unknown, requestOptions: unknown) => unknown; + aliases?: Set; + useCaching?: boolean; + uiEndpointFormatter?: (url: string, sourceData?: Source['data']) => string | null; + uiEndpoint?: string; + passedCredentials?: Record; + extraHeaders?: Record | (() => Record); + sourceType?: string; + dataEndpoint?: string; + preprocess?: (url: string) => string; + allowedMethods?: ('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE')[]; + + adapterWithContext?: ({ + targetUri, + sourceName, + adapterContext, + ctx, + }: { + targetUri: string; + sourceName: string; + adapterContext: AdapterContext; + ctx: AppContext; + }) => unknown; + + middlewareAdapter?: (args: MiddlewareSourceAdapterArgs) => Promise; + check?: (targetUri: string, params?: Request['body']['params']) => Promise; + + args?: Record; + maxRedirects?: number; + isExternal?: boolean; + aliasTo?: SourceAliasConfig; +}; + +export enum MiddlewareStage { + BeforeAuth = 'beforeAuth', + AfterAuth = 'afterAuth', +} + +export type Middleware = {stage: MiddlewareStage; fn: AppMiddleware}; +export interface PluginRoute { + method: Uppercase; + path: string; + handler: AppRouteDescription['handler']; + handlerName?: AppRouteDescription['handlerName']; + authPolicy?: AppRouteDescription['authPolicy']; + disableCsrf?: boolean; + validationConfig?: { + [key: string]: any; + query?: Object; + params?: Object; + body?: Object; + }; +} + +export type ChartEngineController = (req: Request, res: Response) => Promise | unknown; + +export interface Plugin { + middlewares?: Middleware[]; + sources?: Record; + runners?: Runner[]; + processorHooks?: Record[]; + routes?: PluginRoute[]; + controllers?: Record ChartEngineController>; +} + +export type ChartStorageType = keyof typeof EDITOR_TYPE_CONFIG_TABS; diff --git a/src/server/components/features/features-list/AsideHeaderEnabled.ts b/src/server/components/features/features-list/AsideHeaderEnabled.ts new file mode 100644 index 0000000..9354fca --- /dev/null +++ b/src/server/components/features/features-list/AsideHeaderEnabled.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.AsideHeaderEnabled, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/AuthUpdateWithTimeout.ts b/src/server/components/features/features-list/AuthUpdateWithTimeout.ts new file mode 100644 index 0000000..b615b3e --- /dev/null +++ b/src/server/components/features/features-list/AuthUpdateWithTimeout.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.AuthUpdateWithTimeout, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/ChartEditorDeveloperModeCheck.ts b/src/server/components/features/features-list/ChartEditorDeveloperModeCheck.ts new file mode 100644 index 0000000..12445c6 --- /dev/null +++ b/src/server/components/features/features-list/ChartEditorDeveloperModeCheck.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.ChartEditorDeveloperModeCheck, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/ChartModeling.ts b/src/server/components/features/features-list/ChartModeling.ts new file mode 100644 index 0000000..1a548ef --- /dev/null +++ b/src/server/components/features/features-list/ChartModeling.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.ChartModeling, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/ChartkitAlerts.ts b/src/server/components/features/features-list/ChartkitAlerts.ts new file mode 100644 index 0000000..0f0cbb7 --- /dev/null +++ b/src/server/components/features/features-list/ChartkitAlerts.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.ChartkitAlerts, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/CollectionsAccessEnabled.ts b/src/server/components/features/features-list/CollectionsAccessEnabled.ts new file mode 100644 index 0000000..8b04778 --- /dev/null +++ b/src/server/components/features/features-list/CollectionsAccessEnabled.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.CollectionsAccessEnabled, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/CollectionsE2ETestsMode.ts b/src/server/components/features/features-list/CollectionsE2ETestsMode.ts new file mode 100644 index 0000000..4affe90 --- /dev/null +++ b/src/server/components/features/features-list/CollectionsE2ETestsMode.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.CollectionsE2ETestsMode, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/CollectionsEnabled.ts b/src/server/components/features/features-list/CollectionsEnabled.ts new file mode 100644 index 0000000..d55eac5 --- /dev/null +++ b/src/server/components/features/features-list/CollectionsEnabled.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.CollectionsEnabled, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/Comments.ts b/src/server/components/features/features-list/Comments.ts new file mode 100644 index 0000000..bd503a4 --- /dev/null +++ b/src/server/components/features/features-list/Comments.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.Comments, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/ConnectionBasedControl.ts b/src/server/components/features/features-list/ConnectionBasedControl.ts new file mode 100644 index 0000000..0bbc75f --- /dev/null +++ b/src/server/components/features/features-list/ConnectionBasedControl.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.ConnectionBasedControl, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/CopyEntriesToWorkbook.ts b/src/server/components/features/features-list/CopyEntriesToWorkbook.ts new file mode 100644 index 0000000..0211fd2 --- /dev/null +++ b/src/server/components/features/features-list/CopyEntriesToWorkbook.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.CopyEntriesToWorkbook, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/DashBoardSupportDescription.ts b/src/server/components/features/features-list/DashBoardSupportDescription.ts new file mode 100644 index 0000000..1b305b7 --- /dev/null +++ b/src/server/components/features/features-list/DashBoardSupportDescription.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.DashBoardSupportDescription, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/DashBoardWidgetParamsStrictValidation.ts b/src/server/components/features/features-list/DashBoardWidgetParamsStrictValidation.ts new file mode 100644 index 0000000..a5fd73b --- /dev/null +++ b/src/server/components/features/features-list/DashBoardWidgetParamsStrictValidation.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.DashBoardWidgetParamsStrictValidation, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/DashServerMigrationEnable.ts b/src/server/components/features/features-list/DashServerMigrationEnable.ts new file mode 100644 index 0000000..135d348 --- /dev/null +++ b/src/server/components/features/features-list/DashServerMigrationEnable.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.DashServerMigrationEnable, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/DashServerValidationEnabled.ts b/src/server/components/features/features-list/DashServerValidationEnabled.ts new file mode 100644 index 0000000..b2664fb --- /dev/null +++ b/src/server/components/features/features-list/DashServerValidationEnabled.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.DashServerValidationEnable, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/DatasetsRLS.ts b/src/server/components/features/features-list/DatasetsRLS.ts new file mode 100644 index 0000000..8c15915 --- /dev/null +++ b/src/server/components/features/features-list/DatasetsRLS.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.DatasetsRLS, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/DisableFnAndHtml.ts b/src/server/components/features/features-list/DisableFnAndHtml.ts new file mode 100644 index 0000000..9e6cbf5 --- /dev/null +++ b/src/server/components/features/features-list/DisableFnAndHtml.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.DisableFnAndHtml, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableAutocreateDataset.ts b/src/server/components/features/features-list/EnableAutocreateDataset.ts new file mode 100644 index 0000000..c92a934 --- /dev/null +++ b/src/server/components/features/features-list/EnableAutocreateDataset.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableAutocreateDataset, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableChartEditorDocs.ts b/src/server/components/features/features-list/EnableChartEditorDocs.ts new file mode 100644 index 0000000..dcec34f --- /dev/null +++ b/src/server/components/features/features-list/EnableChartEditorDocs.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableChartEditorDocs, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableCommonChartDashSettings.ts b/src/server/components/features/features-list/EnableCommonChartDashSettings.ts new file mode 100644 index 0000000..4a1b1d5 --- /dev/null +++ b/src/server/components/features/features-list/EnableCommonChartDashSettings.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableCommonChartDashSettings, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableCustomDashMargins.ts b/src/server/components/features/features-list/EnableCustomDashMargins.ts new file mode 100644 index 0000000..b8f8679 --- /dev/null +++ b/src/server/components/features/features-list/EnableCustomDashMargins.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableCustomDashMargins, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableCustomMonitoring.ts b/src/server/components/features/features-list/EnableCustomMonitoring.ts new file mode 100644 index 0000000..f1cca97 --- /dev/null +++ b/src/server/components/features/features-list/EnableCustomMonitoring.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableCustomMonitoring, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableDashAutoFocus.ts b/src/server/components/features/features-list/EnableDashAutoFocus.ts new file mode 100644 index 0000000..c2d9642 --- /dev/null +++ b/src/server/components/features/features-list/EnableDashAutoFocus.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableDashAutoFocus, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableDashChartStat.ts b/src/server/components/features/features-list/EnableDashChartStat.ts new file mode 100644 index 0000000..8909610 --- /dev/null +++ b/src/server/components/features/features-list/EnableDashChartStat.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableDashChartStat, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableDashColorPickersByTheme.ts b/src/server/components/features/features-list/EnableDashColorPickersByTheme.ts new file mode 100644 index 0000000..18250ec --- /dev/null +++ b/src/server/components/features/features-list/EnableDashColorPickersByTheme.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableDashColorPickersByTheme, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableDatasetEarlyInvalidationCache.ts b/src/server/components/features/features-list/EnableDatasetEarlyInvalidationCache.ts new file mode 100644 index 0000000..9168fc4 --- /dev/null +++ b/src/server/components/features/features-list/EnableDatasetEarlyInvalidationCache.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableDatasetEarlyInvalidationCache, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableEmbedsInDialogShare.ts b/src/server/components/features/features-list/EnableEmbedsInDialogShare.ts new file mode 100644 index 0000000..2bbd4a2 --- /dev/null +++ b/src/server/components/features/features-list/EnableEmbedsInDialogShare.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableEmbedsInDialogShare, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableEntryMenuItemShare.ts b/src/server/components/features/features-list/EnableEntryMenuItemShare.ts new file mode 100644 index 0000000..bac7cc2 --- /dev/null +++ b/src/server/components/features/features-list/EnableEntryMenuItemShare.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableEntryMenuItemShare, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableExportSettings.ts b/src/server/components/features/features-list/EnableExportSettings.ts new file mode 100644 index 0000000..52290e9 --- /dev/null +++ b/src/server/components/features/features-list/EnableExportSettings.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableExportSettings, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableExportWorkbookFile.ts b/src/server/components/features/features-list/EnableExportWorkbookFile.ts new file mode 100644 index 0000000..3b9e91f --- /dev/null +++ b/src/server/components/features/features-list/EnableExportWorkbookFile.ts @@ -0,0 +1,10 @@ +import {Feature, isTrueArg} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableExportWorkbookFile, + state: { + development: isTrueArg(process.env.EXPORT_WORKBOOK_ENABLED), + production: isTrueArg(process.env.EXPORT_WORKBOOK_ENABLED), + }, +}); diff --git a/src/server/components/features/features-list/EnableFooter.ts b/src/server/components/features/features-list/EnableFooter.ts new file mode 100644 index 0000000..f9f2c33 --- /dev/null +++ b/src/server/components/features/features-list/EnableFooter.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableFooter, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableMobileFixedHeader.ts b/src/server/components/features/features-list/EnableMobileFixedHeader.ts new file mode 100644 index 0000000..4d9caf8 --- /dev/null +++ b/src/server/components/features/features-list/EnableMobileFixedHeader.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableMobileFixedHeader, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/EnableMobileHeader.ts b/src/server/components/features/features-list/EnableMobileHeader.ts new file mode 100644 index 0000000..1d98a6a --- /dev/null +++ b/src/server/components/features/features-list/EnableMobileHeader.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableMobileHeader, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/EnableNewDashSettings.ts b/src/server/components/features/features-list/EnableNewDashSettings.ts new file mode 100644 index 0000000..f3cbc98 --- /dev/null +++ b/src/server/components/features/features-list/EnableNewDashSettings.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableNewDashSettings, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableNewServiceSettings.ts b/src/server/components/features/features-list/EnableNewServiceSettings.ts new file mode 100644 index 0000000..e6e1646 --- /dev/null +++ b/src/server/components/features/features-list/EnableNewServiceSettings.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableNewServiceSettings, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnablePublishEntry.ts b/src/server/components/features/features-list/EnablePublishEntry.ts new file mode 100644 index 0000000..f6aa352 --- /dev/null +++ b/src/server/components/features/features-list/EnablePublishEntry.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnablePublishEntry, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableRLSV2.ts b/src/server/components/features/features-list/EnableRLSV2.ts new file mode 100644 index 0000000..16d9a67 --- /dev/null +++ b/src/server/components/features/features-list/EnableRLSV2.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableRLSV2, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableSaveAsEditorScript.ts b/src/server/components/features/features-list/EnableSaveAsEditorScript.ts new file mode 100644 index 0000000..587d58d --- /dev/null +++ b/src/server/components/features/features-list/EnableSaveAsEditorScript.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableSaveAsEditorScript, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableServerlessEditor.ts b/src/server/components/features/features-list/EnableServerlessEditor.ts new file mode 100644 index 0000000..b48d75a --- /dev/null +++ b/src/server/components/features/features-list/EnableServerlessEditor.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableServerlessEditor, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableSharedEntries.ts b/src/server/components/features/features-list/EnableSharedEntries.ts new file mode 100644 index 0000000..a8e79ac --- /dev/null +++ b/src/server/components/features/features-list/EnableSharedEntries.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableSharedEntries, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EnableUpdatingDsSettingsByAction.ts b/src/server/components/features/features-list/EnableUpdatingDsSettingsByAction.ts new file mode 100644 index 0000000..99ebd77 --- /dev/null +++ b/src/server/components/features/features-list/EnableUpdatingDsSettingsByAction.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EnableUpdatingDsSettingsByAction, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/EntryMenuItemCopy.ts b/src/server/components/features/features-list/EntryMenuItemCopy.ts new file mode 100644 index 0000000..76c2f5a --- /dev/null +++ b/src/server/components/features/features-list/EntryMenuItemCopy.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EntryMenuItemCopy, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/EntryMenuItemMove.ts b/src/server/components/features/features-list/EntryMenuItemMove.ts new file mode 100644 index 0000000..be75431 --- /dev/null +++ b/src/server/components/features/features-list/EntryMenuItemMove.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.EntryMenuItemMove, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/ExternalSelectors.ts b/src/server/components/features/features-list/ExternalSelectors.ts new file mode 100644 index 0000000..b0c9534 --- /dev/null +++ b/src/server/components/features/features-list/ExternalSelectors.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.ExternalSelectors, + state: { + development: true, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/FetchDocumentation.ts b/src/server/components/features/features-list/FetchDocumentation.ts new file mode 100644 index 0000000..6ca5b86 --- /dev/null +++ b/src/server/components/features/features-list/FetchDocumentation.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.FetchDocumentation, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/FieldEditorDocSection.ts b/src/server/components/features/features-list/FieldEditorDocSection.ts new file mode 100644 index 0000000..d98d6a3 --- /dev/null +++ b/src/server/components/features/features-list/FieldEditorDocSection.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.FieldEditorDocSection, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/FunnelChart.ts b/src/server/components/features/features-list/FunnelChart.ts new file mode 100644 index 0000000..42d5f9b --- /dev/null +++ b/src/server/components/features/features-list/FunnelChart.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.FunnelChart, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/GravityChartsForBarYAndScatter.ts b/src/server/components/features/features-list/GravityChartsForBarYAndScatter.ts new file mode 100644 index 0000000..bc7370a --- /dev/null +++ b/src/server/components/features/features-list/GravityChartsForBarYAndScatter.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.GravityChartsForBarYAndScatter, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/GravityChartsForLineAreaAndBarX.ts b/src/server/components/features/features-list/GravityChartsForLineAreaAndBarX.ts new file mode 100644 index 0000000..4294b4e --- /dev/null +++ b/src/server/components/features/features-list/GravityChartsForLineAreaAndBarX.ts @@ -0,0 +1,10 @@ +import {Feature, isTrueArg} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.GravityChartsForLineAreaAndBarX, + state: { + development: !isTrueArg(process.env.HC), + production: !isTrueArg(process.env.HC), + }, +}); diff --git a/src/server/components/features/features-list/GravityChartsForPieAndTreemap.ts b/src/server/components/features/features-list/GravityChartsForPieAndTreemap.ts new file mode 100644 index 0000000..66789d7 --- /dev/null +++ b/src/server/components/features/features-list/GravityChartsForPieAndTreemap.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.GravityChartsForPieAndTreemap, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/HideMultitenant.ts b/src/server/components/features/features-list/HideMultitenant.ts new file mode 100644 index 0000000..1bfaded --- /dev/null +++ b/src/server/components/features/features-list/HideMultitenant.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.HideMultitenant, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/HolidaysOnChart.ts b/src/server/components/features/features-list/HolidaysOnChart.ts new file mode 100644 index 0000000..678d60c --- /dev/null +++ b/src/server/components/features/features-list/HolidaysOnChart.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.HolidaysOnChart, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/MassRemoveCollectionsWorkbooks.ts b/src/server/components/features/features-list/MassRemoveCollectionsWorkbooks.ts new file mode 100644 index 0000000..eab7ec4 --- /dev/null +++ b/src/server/components/features/features-list/MassRemoveCollectionsWorkbooks.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.MassRemoveCollectionsWorkbooks, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/MenuItemsFlatView.ts b/src/server/components/features/features-list/MenuItemsFlatView.ts new file mode 100644 index 0000000..e4a68cb --- /dev/null +++ b/src/server/components/features/features-list/MenuItemsFlatView.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.MenuItemsFlatView, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/MultiDatasets.ts b/src/server/components/features/features-list/MultiDatasets.ts new file mode 100644 index 0000000..cc9668d --- /dev/null +++ b/src/server/components/features/features-list/MultiDatasets.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.HideMultiDatasets, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/MultipleColorsInVisualization.ts b/src/server/components/features/features-list/MultipleColorsInVisualization.ts new file mode 100644 index 0000000..8725efb --- /dev/null +++ b/src/server/components/features/features-list/MultipleColorsInVisualization.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.MultipleColorsInVisualization, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/NewTableWidgetForCE.ts b/src/server/components/features/features-list/NewTableWidgetForCE.ts new file mode 100644 index 0000000..e65d15f --- /dev/null +++ b/src/server/components/features/features-list/NewTableWidgetForCE.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.NewTableWidgetForCE, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/NoJsonFn.ts b/src/server/components/features/features-list/NoJsonFn.ts new file mode 100644 index 0000000..f6dbb72 --- /dev/null +++ b/src/server/components/features/features-list/NoJsonFn.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.NoJsonFn, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/QLMonitoring.ts b/src/server/components/features/features-list/QLMonitoring.ts new file mode 100644 index 0000000..972bd36 --- /dev/null +++ b/src/server/components/features/features-list/QLMonitoring.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.QLMonitoring, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/QLPrometheus.ts b/src/server/components/features/features-list/QLPrometheus.ts new file mode 100644 index 0000000..65f34fb --- /dev/null +++ b/src/server/components/features/features-list/QLPrometheus.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.QLPrometheus, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/QlAutoExecuteMonitoringChart.ts b/src/server/components/features/features-list/QlAutoExecuteMonitoringChart.ts new file mode 100644 index 0000000..035fa5d --- /dev/null +++ b/src/server/components/features/features-list/QlAutoExecuteMonitoringChart.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.QlAutoExecuteMonitoringChart, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/ReadOnlyMode.ts b/src/server/components/features/features-list/ReadOnlyMode.ts new file mode 100644 index 0000000..b1ce706 --- /dev/null +++ b/src/server/components/features/features-list/ReadOnlyMode.ts @@ -0,0 +1,10 @@ +import {Feature, isTrueArg} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.ReadOnlyMode, + state: { + development: false, + production: isTrueArg(process.env.READ_ONLY_MODE), + }, +}); diff --git a/src/server/components/features/features-list/RevisionsListNoLimit.ts b/src/server/components/features/features-list/RevisionsListNoLimit.ts new file mode 100644 index 0000000..d5043a6 --- /dev/null +++ b/src/server/components/features/features-list/RevisionsListNoLimit.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.RevisionsListNoLimit, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/ShouldCheckEditorAccess.ts b/src/server/components/features/features-list/ShouldCheckEditorAccess.ts new file mode 100644 index 0000000..b10119d --- /dev/null +++ b/src/server/components/features/features-list/ShouldCheckEditorAccess.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.ShouldCheckEditorAccess, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/ShowChartsEngineDebugInfo.ts b/src/server/components/features/features-list/ShowChartsEngineDebugInfo.ts new file mode 100644 index 0000000..54dc699 --- /dev/null +++ b/src/server/components/features/features-list/ShowChartsEngineDebugInfo.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.ShowChartsEngineDebugInfo, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/ShowCreateEntryWithMenu.ts b/src/server/components/features/features-list/ShowCreateEntryWithMenu.ts new file mode 100644 index 0000000..ba52d5e --- /dev/null +++ b/src/server/components/features/features-list/ShowCreateEntryWithMenu.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.ShowCreateEntryWithMenu, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/ShowInspectorDetails.ts b/src/server/components/features/features-list/ShowInspectorDetails.ts new file mode 100644 index 0000000..ae85f02 --- /dev/null +++ b/src/server/components/features/features-list/ShowInspectorDetails.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.ShowInspectorDetails, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/UsDynamicMasterToken.ts b/src/server/components/features/features-list/UsDynamicMasterToken.ts new file mode 100644 index 0000000..f9aebf5 --- /dev/null +++ b/src/server/components/features/features-list/UsDynamicMasterToken.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.UsDynamicMasterToken, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/UsDynamicMasterTokenInProxy.ts b/src/server/components/features/features-list/UsDynamicMasterTokenInProxy.ts new file mode 100644 index 0000000..d9326f2 --- /dev/null +++ b/src/server/components/features/features-list/UsDynamicMasterTokenInProxy.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.UsDynamicMasterTokenInProxy, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/UseChartsEngineLogin.ts b/src/server/components/features/features-list/UseChartsEngineLogin.ts new file mode 100644 index 0000000..f26f898 --- /dev/null +++ b/src/server/components/features/features-list/UseChartsEngineLogin.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.UseChartsEngineLogin, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/UseChartsEngineResponseConfig.ts b/src/server/components/features/features-list/UseChartsEngineResponseConfig.ts new file mode 100644 index 0000000..990b82f --- /dev/null +++ b/src/server/components/features/features-list/UseChartsEngineResponseConfig.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.UseChartsEngineResponseConfig, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/UseComponentHeader.ts b/src/server/components/features/features-list/UseComponentHeader.ts new file mode 100644 index 0000000..3bdb2ed --- /dev/null +++ b/src/server/components/features/features-list/UseComponentHeader.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.UseComponentHeader, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/UseConfigurableChartkit.ts b/src/server/components/features/features-list/UseConfigurableChartkit.ts new file mode 100644 index 0000000..b78db16 --- /dev/null +++ b/src/server/components/features/features-list/UseConfigurableChartkit.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.UseConfigurableChartkit, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/UsePublicDistincts.ts b/src/server/components/features/features-list/UsePublicDistincts.ts new file mode 100644 index 0000000..df481ae --- /dev/null +++ b/src/server/components/features/features-list/UsePublicDistincts.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.UsePublicDistincts, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/UseSourceAlias.ts b/src/server/components/features/features-list/UseSourceAlias.ts new file mode 100644 index 0000000..b9f0e40 --- /dev/null +++ b/src/server/components/features/features-list/UseSourceAlias.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.UseSourceAlias, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/UseYqlFolderKey.ts b/src/server/components/features/features-list/UseYqlFolderKey.ts new file mode 100644 index 0000000..e10b5b1 --- /dev/null +++ b/src/server/components/features/features-list/UseYqlFolderKey.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.UseYqlFolderKey, + state: { + development: false, + production: false, + }, +}); diff --git a/src/server/components/features/features-list/XlsxChartExportEnabled.ts b/src/server/components/features/features-list/XlsxChartExportEnabled.ts new file mode 100644 index 0000000..3830133 --- /dev/null +++ b/src/server/components/features/features-list/XlsxChartExportEnabled.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.XlsxChartExportEnabled, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/XlsxFilesEnabled.ts b/src/server/components/features/features-list/XlsxFilesEnabled.ts new file mode 100644 index 0000000..4cc8d2d --- /dev/null +++ b/src/server/components/features/features-list/XlsxFilesEnabled.ts @@ -0,0 +1,10 @@ +import {Feature} from '../../../../shared'; +import {createFeatureConfig} from '../utils'; + +export default createFeatureConfig({ + name: Feature.XlsxFilesEnabled, + state: { + development: true, + production: true, + }, +}); diff --git a/src/server/components/features/features-list/index.ts b/src/server/components/features/features-list/index.ts new file mode 100644 index 0000000..0a9a139 --- /dev/null +++ b/src/server/components/features/features-list/index.ts @@ -0,0 +1,21 @@ +import fs from 'fs'; +import path from 'path'; + +import type {FeatureConfig} from '../utils'; + +const normalizedPath = path.join(__dirname); + +const FEATURES_LIST: FeatureConfig[] = []; + +const isBuildMetaFile = (file: string) => { + return file.endsWith('d.ts') || file.endsWith('js.map'); +}; + +fs.readdirSync(normalizedPath).forEach(function (file: string) { + if (file !== 'index.js' && file !== 'index.ts' && !isBuildMetaFile(file)) { + // eslint-disable-next-line security/detect-non-literal-require + FEATURES_LIST.push(require(`./${file}`).default); + } +}); + +export default FEATURES_LIST; diff --git a/src/server/components/features/index.ts b/src/server/components/features/index.ts new file mode 100644 index 0000000..86875f2 --- /dev/null +++ b/src/server/components/features/index.ts @@ -0,0 +1,40 @@ +import type {FeatureConfig} from '../../../shared'; +import {Feature} from '../../../shared'; + +import FEATURES_LIST from './features-list'; + +const FEATURES: FeatureConfig = {}; + +export function getFeaturesConfig(appEnvironment?: string) { + if (!appEnvironment) { + throw new Error('Environment must be specified'); + } + + FEATURES_LIST.forEach(({name, state}) => { + // @ts-ignore + registerFeature(name, state[appEnvironment]); + }); + + let featureConfigMissed = false; + + Object.values(Feature).forEach((feature) => { + if (typeof FEATURES[feature] === 'undefined') { + featureConfigMissed = true; + console.error(`Missed config for feature: ${feature}`); + } + }); + + if (featureConfigMissed) { + console.error('Missed config for some feature, see output above'); + } + + return FEATURES; +} + +const registerFeature = (featureName: Feature, FeatureStatus: boolean) => { + if (typeof FEATURES[featureName] !== 'undefined') { + throw new Error(`Feature ${featureName} is already registered!`); + } + + FEATURES[featureName] = FeatureStatus; +}; diff --git a/src/server/components/features/utils.ts b/src/server/components/features/utils.ts new file mode 100644 index 0000000..e474497 --- /dev/null +++ b/src/server/components/features/utils.ts @@ -0,0 +1,26 @@ +import type {Feature} from '../../../shared'; + +export type FeatureState = { + development: boolean; + production: boolean; +}; + +export type FeatureConfig = { + name: Feature; + state: FeatureState; +}; + +type RawFeatureConfig = { + name: Feature; + state: Partial; +}; + +export const createFeatureConfig = (featureConfig: RawFeatureConfig): FeatureConfig => { + return { + name: featureConfig.name, + state: { + development: featureConfig.state.development || false, + production: featureConfig.state.production || false, + }, + }; +}; diff --git a/src/server/components/gateway-auth-helpers/auth-args-middleware.ts b/src/server/components/gateway-auth-helpers/auth-args-middleware.ts new file mode 100644 index 0000000..ab1a77a --- /dev/null +++ b/src/server/components/gateway-auth-helpers/auth-args-middleware.ts @@ -0,0 +1,64 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; +import type {AppConfig} from '@gravity-ui/nodekit'; +import {AppError} from '@gravity-ui/nodekit'; +import jwt from 'jsonwebtoken'; +import NodeCache from 'node-cache'; + +export type AuthArgsMiddlewareSettings = { + usDynamicMasterTokenPrivateKey?: string; +}; + +const SERVICE_ID = 'ui'; +const CACHE_KEY = 'usDynamicMasterToken'; +const CACHE_TTL_SECONDS = 300; // 5 minutes +const JWT_EXPIRATION_SECONDS = 3600; // 1 hour + +const ERROR_CODE = 'AUTH_ARGS_ERROR'; + +export function createAuthArgsMiddleware(config: AppConfig) { + const {usDynamicMasterTokenPrivateKey: privateKey} = config; + + if (!privateKey) { + throw new AppError('Private key not set', { + code: ERROR_CODE, + }); + } + + const cache = new NodeCache(); + + return async function authArgsMiddleware( + req: Request, + _res: Response, + next: NextFunction, + ): Promise { + try { + let token: string | undefined = cache.get(CACHE_KEY); + + if (!token) { + const now = Math.floor(Date.now() / 1000); + const payload = { + serviceId: SERVICE_ID, + iat: now, + exp: now + JWT_EXPIRATION_SECONDS, + }; + + token = jwt.sign(payload, privateKey, { + algorithm: 'RS256', + }); + + cache.set(CACHE_KEY, token, CACHE_TTL_SECONDS); + } + + req.originalContext.set('usDynamicMasterToken', token); + + return next(); + } catch (error) { + req.ctx.logError('AUTH_ARGS_ERROR', error); + return next( + AppError.wrap(error as unknown as Error, { + code: ERROR_CODE, + }), + ); + } + }; +} diff --git a/src/server/components/gateway-auth-helpers/bi-auth-helpers.ts b/src/server/components/gateway-auth-helpers/bi-auth-helpers.ts new file mode 100644 index 0000000..1bfb7c0 --- /dev/null +++ b/src/server/components/gateway-auth-helpers/bi-auth-helpers.ts @@ -0,0 +1,46 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {GetAuthHeaders} from '@gravity-ui/gateway'; + +import { + US_DYNAMIC_MASTER_TOKEN_HEADER, + US_MASTER_TOKEN_HEADER, +} from '../../../shared/constants/header'; +import {Feature} from '../../../shared/types/feature'; + +type AuthArgsData = { + usMasterToken?: string; + usDynamicMasterToken?: string; +}; + +export const getAuthHeadersBIPrivate: GetAuthHeaders = ({authArgs}) => { + const usMasterToken = authArgs?.usMasterToken as string; + return { + [US_MASTER_TOKEN_HEADER]: usMasterToken, + }; +}; + +export const hasValidWorkbookTransferAuthHeaders = async (req: Request) => { + const isEnabledServerFeature = req.ctx.get('isEnabledServerFeature'); + + if (isEnabledServerFeature(Feature.UsDynamicMasterTokenInProxy)) { + return req.headers[US_DYNAMIC_MASTER_TOKEN_HEADER] !== undefined; + } + + return req.headers[US_MASTER_TOKEN_HEADER] !== undefined; +}; + +export const getAuthArgsProxyBIPrivate = ( + {ctx, headers}: Request, + _res: Response, +): AuthArgsData => { + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + + if (isEnabledServerFeature(Feature.UsDynamicMasterTokenInProxy)) { + return { + usDynamicMasterToken: headers[US_DYNAMIC_MASTER_TOKEN_HEADER] as string, + }; + } + return { + usMasterToken: headers[US_MASTER_TOKEN_HEADER] as string, + }; +}; diff --git a/src/server/components/gateway-auth-helpers/index.ts b/src/server/components/gateway-auth-helpers/index.ts new file mode 100644 index 0000000..0e7af6e --- /dev/null +++ b/src/server/components/gateway-auth-helpers/index.ts @@ -0,0 +1,3 @@ +export * from './auth-args-middleware'; +export * from './bi-auth-helpers'; +export * from './us-auth-helpers'; diff --git a/src/server/components/gateway-auth-helpers/us-auth-helpers.ts b/src/server/components/gateway-auth-helpers/us-auth-helpers.ts new file mode 100644 index 0000000..68ca085 --- /dev/null +++ b/src/server/components/gateway-auth-helpers/us-auth-helpers.ts @@ -0,0 +1,53 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {GetAuthHeaders} from '@gravity-ui/gateway'; + +import { + US_DYNAMIC_MASTER_TOKEN_HEADER, + US_MASTER_TOKEN_HEADER, +} from '../../../shared/constants/header'; +import {Feature} from '../../../shared/types/feature'; + +type AuthArgsData = { + usMasterToken?: string; + usDynamicMasterToken?: string; +}; + +export const getAuthArgsUSPrivate = ({ctx}: Request, _res: Response): AuthArgsData => { + const usDynamicMasterToken = ctx.get('usDynamicMasterToken'); + + if (usDynamicMasterToken) { + return { + usDynamicMasterToken, + }; + } + + return { + usMasterToken: ctx.config.usMasterToken as string, + }; +}; + +export const getAuthHeadersUSPrivate: GetAuthHeaders = ({authArgs}) => { + const {usMasterToken, usDynamicMasterToken} = authArgs || {}; + + return { + ...(usMasterToken && {[US_MASTER_TOKEN_HEADER]: usMasterToken}), + ...(usDynamicMasterToken && {[US_DYNAMIC_MASTER_TOKEN_HEADER]: usDynamicMasterToken}), + }; +}; + +export const getAuthArgsProxyUSPrivate = ( + {ctx, headers}: Request, + _res: Response, +): AuthArgsData => { + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + + if (isEnabledServerFeature(Feature.UsDynamicMasterTokenInProxy)) { + return { + usDynamicMasterToken: headers[US_DYNAMIC_MASTER_TOKEN_HEADER] as string, + }; + } + + return { + usMasterToken: headers[US_MASTER_TOKEN_HEADER] as string, + }; +}; diff --git a/src/server/components/index.ts b/src/server/components/index.ts new file mode 100644 index 0000000..b9647dc --- /dev/null +++ b/src/server/components/index.ts @@ -0,0 +1,7 @@ +import Utils from '../utils'; + +export * from '../constants'; +export * from './layout'; +export * from './gateway-auth-helpers'; + +export {Utils}; diff --git a/src/server/components/layout/index.ts b/src/server/components/layout/index.ts new file mode 100644 index 0000000..7f2f75d --- /dev/null +++ b/src/server/components/layout/index.ts @@ -0,0 +1,2 @@ +export {getLandingLayout} from './landing-layout'; +export {getChartkitLayoutSettings, getPlatform} from './utils'; diff --git a/src/server/components/layout/landing-layout.ts b/src/server/components/layout/landing-layout.ts new file mode 100644 index 0000000..dc05f83 --- /dev/null +++ b/src/server/components/layout/landing-layout.ts @@ -0,0 +1,21 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import type {LandingPageSettings} from '../../../shared'; +import {registry} from '../../registry'; + +export const getLandingLayout = async ( + req: Request, + res: Response, + pageSettings: LandingPageSettings, +) => { + req.originalContext.set('landingPageSettings', pageSettings); + req.ctx.set('landingPageSettings', pageSettings); + + const layoutConfig = await registry.useGetLayoutConfig({ + req, + res, + settingsId: 'landing-layout', + }); + + return res.renderDatalensLayout(layoutConfig); +}; diff --git a/src/server/components/layout/opensource-layout-config.ts b/src/server/components/layout/opensource-layout-config.ts new file mode 100644 index 0000000..581dde9 --- /dev/null +++ b/src/server/components/layout/opensource-layout-config.ts @@ -0,0 +1,119 @@ +import type {RenderParams} from '@gravity-ui/app-layout'; + +import type { + AppEnvironment, + AppInstallation, + DLGlobalData, + DLUser, + TenantSettings, +} from '../../../shared'; +import {FALLBACK_LANGUAGES, Language, USER_SETTINGS_KEY} from '../../../shared'; +import type {AppLayoutSettings, GetLayoutConfig} from '../../types/app-layout'; +import {addTranslationsScript} from '../../utils/language'; + +import {getChartkitLayoutSettings, getPlatform} from './utils'; + +export const getOpensourceLayoutConfig: GetLayoutConfig = async (args) => { + const {req, res, settingsId} = args; + + const config = req.ctx.config; + const requestId = req.id || ''; + + const getAppLayoutSettings = req.ctx.get('getAppLayoutSettings'); + + const appLayoutSettings: AppLayoutSettings = getAppLayoutSettings(req, res, settingsId); + + const regionalEnvConfig = req.ctx.config.regionalEnvConfig; + + const allowLanguages = (regionalEnvConfig?.allowLanguages || FALLBACK_LANGUAGES) as Language[]; + + const cookie = req.cookies[USER_SETTINGS_KEY]; + let lang = Language.En; + let theme; + try { + const preparedCookie = JSON.parse(cookie); + lang = preparedCookie.language; + theme = preparedCookie.theme; + } catch { + console.warn('no userSettings in cookie'); + } + + const isAllowed = allowLanguages.includes(lang || ''); + if (!isAllowed) { + lang = Language.En; + } + + const isAuthEnabled = req.ctx.config.isAuthEnabled; + + // TODO: check and remove optional props; + let user: DLUser = {lang} as DLUser; + const userSettings = {}; + let iamUserId = ''; + const {scripts: chartkitScripts, inlineScripts: chartkitInlineScripts} = + getChartkitLayoutSettings(config.chartkitSettings); + + if (isAuthEnabled) { + const authUser = req.ctx.get('user'); + iamUserId = authUser?.userId as string; + const profile = authUser?.profile; + user = { + ...user, + uid: iamUserId, + roles: authUser?.roles, + ...profile, + }; + } + + const tenantSettings: TenantSettings = { + defaultColorPaletteId: res.locals.tenantDefaultColorPaletteId, + }; + + const DL: DLGlobalData = { + user, + userSettings, + iamUserId, + deviceType: getPlatform(req.headers['user-agent']), + requestId, + env: config.appEnv as AppEnvironment, + installationType: config.appInstallation as AppInstallation, + serviceName: config.serviceName, + endpoints: config.endpoints.ui, + features: config.features, + meta: req.ctx.getMetadata(), + chartkitSettings: config.chartkitSettings, + defaultColorPaletteId: config.defaultColorPaletteId, + allowLanguages, + headersMap: req.ctx.config.headersMap, + isAuthEnabled, + ymapApiKey: config.chartkitSettings?.yandexMap?.token, + connectorIcons: res.locals.connectorIcons, + apiPrefix: config.apiPrefix, + releaseVersion: config.releaseVersion, + docsUrl: config.docsUrl, + orderedAuthRoles: config.orderedAuthRoles, + authSignupDisabled: req.ctx.config.authSignupDisabled, + authCookieName: req.ctx.config.authCookieName, + tenantSettings, + ...appLayoutSettings.DL, + }; + const renderConfig: RenderParams<{DL: DLGlobalData}> = { + nonce: req.nonce, + data: {DL}, + lang, + icon: { + type: 'image/ico', + href: config.faviconUrl, + sizes: '32x32', + }, + inlineScripts: ['window.DL = window.__DATA__.DL', ...chartkitInlineScripts], + scripts: [addTranslationsScript({allowLanguages, lang}), ...chartkitScripts], + links: [], + pluginsOptions: { + layout: {name: appLayoutSettings.bundleName}, + ...(theme ? {uikit: {theme}} : {}), + }, + ...appLayoutSettings.renderConfig, + }; + + return renderConfig; +}; diff --git a/src/server/components/layout/utils.ts b/src/server/components/layout/utils.ts new file mode 100644 index 0000000..f5b528b --- /dev/null +++ b/src/server/components/layout/utils.ts @@ -0,0 +1,57 @@ +import type {Script} from '@gravity-ui/app-layout'; +import isMobile from 'ismobilejs'; + +import type {ChartkitGlobalSettings} from '../../../shared'; +import {DeviceType} from '../../../shared'; + +export function getPlatform(userAgent: string | undefined) { + const ua = isMobile(userAgent); + + if (ua.phone) { + return DeviceType.Phone; + } else if (ua.tablet) { + return DeviceType.Tablet; + } + + return DeviceType.Desktop; +} + +type ChartkitLayoutConfig = { + scripts: Script[]; + inlineScripts: string[]; +}; + +export function getChartkitLayoutSettings( + chartkitSettings: ChartkitGlobalSettings = {}, +): ChartkitLayoutConfig { + const chartkitScripts = []; + const chartkitInlineScripts = []; + + if (!chartkitSettings.highcharts?.enabled) { + chartkitInlineScripts.push(`window.Highcharts = {enabled: false};`); + } else if (chartkitSettings.highcharts.external) { + const { + protocol = 'https', + domain = 'code.highcharts.com', + version, + modules = [], + } = chartkitSettings.highcharts; + const items = [ + 'highcharts', + 'highcharts-more', + ...modules.map((item) => `modules/${item}`), + ]; + + chartkitScripts.push( + ...items.map((item) => ({ + src: `${protocol}://${domain}${version ? `/${version}` : ''}/${item}.js`, + defer: true, + })), + ); + } + + return { + scripts: chartkitScripts, + inlineScripts: chartkitInlineScripts, + }; +} diff --git a/src/server/components/metrika-data-formatter.js b/src/server/components/metrika-data-formatter.js new file mode 100644 index 0000000..418ca2e --- /dev/null +++ b/src/server/components/metrika-data-formatter.js @@ -0,0 +1,345 @@ +const moment = require('moment'); + +// eslint-disable-next-line +moment.locale('ru'); + +const groupingValuesArray = [ + 'date', + 'startOfYear', + 'startOfQuarter', + 'startOfMonth', + 'startOfWeek', + 'startOfHour', + 'startOfDekaminute', + 'startOfMinute', + 'dateTime', +]; + +/** + * Check dimensions from Metrica + * @param {Object} metricsData + * @returns {Number} + */ +function checkDimensions(metricsData) { + let index = -1; + + const values = metricsData.query.dimensions.filter((dimension, dimensionIndex) => { + const value = dimension.split(':').pop(); + if (groupingValuesArray.indexOf(value) > -1) { + index = dimensionIndex; + return true; + } else { + return false; + } + }).length; + + if (values.length > 1) { + throw new Error('need less dimensions: ' + values.join(',')); + } + + return index; +} + +/** + * Generate array with time interval + * @param {String} firstDateString + * @param {String} secondDateString + * @param {String} group + * @returns {Array} + */ +function prepareDatesMs(firstDateString, secondDateString, group) { + const fixedGroup = group || 'day'; + + const intervals = []; + + const date1 = moment.utc(firstDateString); + const date2 = moment.utc(secondDateString); + + date1.startOf(fixedGroup); + + intervals.push(moment.utc(date1.add(0, fixedGroup)).valueOf()); + while (moment.duration(date2.valueOf() - date1.valueOf()) >= moment.duration(1, fixedGroup)) { + intervals.push(moment.utc(date1.add(1, fixedGroup)).valueOf()); + } + + return intervals; +} + +/** + * Check limit + * @param {Object} metricasData + * @returns {undefined} + */ +function checkLimit(metricasData) { + const limit = metricasData.query.limit; + const totalRows = metricasData.data.length; + if (limit < totalRows) { + throw new Error( + 'rows limit exceeded: ' + limit + '(limit) < ' + totalRows + '(total_rows)', + ); + } +} + +/** + * Normalize dimension + * @param {Object} dimensionObj + * @returns {Object} + */ +function prepareDimension(dimensionObj) { + const keys = Object.keys(dimensionObj); + + switch (true) { + case dimensionObj.id === undefined && dimensionObj.name === undefined: + throw new Error('id and name properties are undefined, please report to developers.'); + case dimensionObj.id === undefined && dimensionObj.name !== undefined: + dimensionObj.id = dimensionObj.name; + break; + case dimensionObj.name === undefined && dimensionObj.id !== undefined: + dimensionObj.name = dimensionObj.id; + } + + keys.forEach((key) => { + dimensionObj[key] = String(dimensionObj[key]); + }); + + return dimensionObj; +} + +/** + * Map data from Metric format to Stat format + * @param {Object} metrikasData + * @returns {Object} + */ +function buildSimpleStatFormatData(metrikasData) { + let localMetrikasData = metrikasData; + + if (metrikasData && !(metrikasData instanceof Object)) { + localMetrikasData = JSON.parse(metrikasData); + } + + checkLimit(localMetrikasData); + + const query = localMetrikasData.query; + const data = localMetrikasData.data; + const metrics = query.metrics; + + const indexDateDimension = checkDimensions(localMetrikasData); + if (indexDateDimension === -1) { + throw new Error('no scale in dimensions'); + } + + const group = /startOf(.*)/.exec(localMetrikasData.query.dimensions[indexDateDimension]); + const categories = prepareDatesMs( + query.date1, + query.date2, + group && group[1] && group[1].toLowerCase(), + ); + const graphs = {}; + const sequences = []; + + function addValueToGraph(value, time) { + if (!graphs[value.title]) { + graphs[value.title] = []; + } + + graphs[value.title].push({graph: value, time: time}); + + const condition = sequences.some((graph) => { + return value.title === graph.title; + }); + + if (!condition) { + sequences.push({ + data: [], + title: value.title, + fname: value.fname, + sname: value.sname, + }); + } + } + + function buildSequences() { + const graphsNames = Object.keys(graphs); + categories.forEach((category) => { + sequences.forEach((sequence) => { + graphsNames.some((graphName) => { + if (sequence.title === graphName) { + const graph = graphs[graphName]; + const condition = graph.some((value) => { + if (value.time === category) { + sequence.data = sequence.data.concat(value.graph.data); + return true; + } else { + return false; + } + }); + + if (!condition) { + sequence.data.push(null); + } + + return true; + } else { + return false; + } + }); + }); + }); + return sequences; + } + + data.forEach((valueObj) => { + const dimensions = valueObj.dimensions + .filter((dimensionObj, index) => index !== indexDateDimension) + .map(prepareDimension); + + const title = dimensions.map((dimensionObj) => dimensionObj.name).join(', '); + const fname = dimensions + .map((dimensionObj) => [dimensionObj.id, dimensionObj.name].join(':')) + .join('&'); + const sname = dimensions.map((dimensionObj) => dimensionObj.id).join('&'); + const time = moment.utc(valueObj.dimensions[indexDateDimension].name).valueOf(); + + metrics.forEach((metricName, metricIndex) => { + const lastPartOfMetricName = metricName.split(':').pop(); + const graph = { + data: [valueObj.metrics[metricIndex]], + title: title ? [title, metricName].join(' ::: ') : metricName, + fname: fname ? [fname, lastPartOfMetricName].join('&') : lastPartOfMetricName, + sname: sname ? [sname, lastPartOfMetricName].join('&') : lastPartOfMetricName, + }; + addValueToGraph(graph, time); + }); + }); + + if (!data.length) { + metrics.forEach((metricName) => { + const lastPartOfMetricName = metricName.split(':').pop(); + + const graph = { + data: [], + title: metricName, + fname: lastPartOfMetricName, + sname: lastPartOfMetricName, + }; + addValueToGraph(graph, ''); + }); + } + + return { + graphs: buildSequences(), + categories_ms: categories, + }; +} + +/** + * Map data from Metric format to Stat format + * @param {Object} metrikasData + * @returns {Array} + */ +function buildRawStatFormatData(metrikasData) { + let localMetrikasData = metrikasData; + if (metrikasData && !(metrikasData instanceof Object)) { + localMetrikasData = JSON.parse(metrikasData); + } + + checkLimit(localMetrikasData); + + const query = localMetrikasData.query; + const data = localMetrikasData.data; + + if (!(query instanceof Object) || !Array.isArray(data)) { + throw new Error('data is not valid for the reformatting as statdata'); + } + + const queryMetrics = query.metrics; + const queryDimensions = query.dimensions; + const indexDateDimension = checkDimensions(localMetrikasData); + const result = []; + + data.forEach((valueObj) => { + const rawDimensions = valueObj.dimensions; + + rawDimensions + .filter((dimension, index) => { + return index !== indexDateDimension; + }) + .forEach((dimensionObj) => prepareDimension(dimensionObj)); + + const row = {}; + + if (query.ids) { + row.ids = query.ids; + } + + if (indexDateDimension !== -1) { + row.fielddate = rawDimensions[indexDateDimension].name; + row.fielddate__ms = moment(rawDimensions[indexDateDimension].name).valueOf(); + } + + rawDimensions.forEach((dimension, index) => { + if (index !== indexDateDimension) { + row[queryDimensions[index]] = dimension.id; + row[queryDimensions[index] + '_override_by_dictionary'] = dimension.name; + } + }); + + queryMetrics.forEach((metricName, metricIndex) => { + row[metricName] = valueObj.metrics[metricIndex]; + }); + + result.push(row); + }); + + return {values: result}; +} + +function switchStatFormatter(formatValue, value, debugInfo) { + let newValue = value; + + switch (formatValue) { + case '1': + case 'simple': + newValue = buildSimpleStatFormatData(value); + break; + case 'raw': + newValue = buildRawStatFormatData(value); + break; + } + + if (newValue && newValue instanceof Object) { + newValue.debugInfo = [debugInfo]; + } + + return newValue; +} + +function fixDateInterval(url) { + const regexp = /(date_min|date_max)=([^&]*)/g; + + const metrikaDateTransform = { + date_min: 'date1', + date_max: 'date2', + }; + + return url.replace(regexp, (matcher, param1, param2) => { + const replacedParam2 = param2.match(/[0-9-]*/)[0]; + + return `${metrikaDateTransform[param1]}=${replacedParam2}`; + }); +} + +function format(data, requestOptions) { + const availableMetrikaValues = ['1', 'simple', 'raw']; + + const spStatFormat = requestOptions.spStatFormat || null; + + const isMetrikaNeedBuild = availableMetrikaValues.indexOf(spStatFormat) !== -1; + + return switchStatFormatter(isMetrikaNeedBuild ? spStatFormat : null, data); +} + +module.exports = { + fixDateInterval, + format, +}; diff --git a/src/server/components/public-api/config/index.ts b/src/server/components/public-api/config/index.ts new file mode 100644 index 0000000..5b98253 --- /dev/null +++ b/src/server/components/public-api/config/index.ts @@ -0,0 +1 @@ +export * from './v1'; diff --git a/src/server/components/public-api/config/v1.ts b/src/server/components/public-api/config/v1.ts new file mode 100644 index 0000000..eb57f8e --- /dev/null +++ b/src/server/components/public-api/config/v1.ts @@ -0,0 +1,437 @@ +import type {BaseSchema} from '@gravity-ui/gateway'; + +import {Feature} from '../../../../shared'; +import type {AnyApiServiceActionConfig, DatalensGatewaySchemas} from '../../../types/gateway'; +import {ApiTag} from '../constants'; +import {PUBLIC_API_ACTION_NAME} from '../constants/action-names'; +import type {PublicApiVersionActions} from '../types'; + +type OverrideActions = { + [K in keyof T]: Omit & { + actions: { + [A in keyof T[K]['actions']]: AnyApiServiceActionConfig; + }; + }; +}; + +export const getPublicApiActionsV1 = < + TSchema extends { + root: OverrideActions< + Pick + >; + }, +>() => { + return { + // Collection + createCollection: { + resolve: (api) => api.us.createCollection, + openApi: { + summary: 'Create collection', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + deleteCollection: { + resolve: (api) => api.us.deleteCollection, + openApi: { + summary: 'Delete collection', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + deleteCollections: { + resolve: (api) => api.us.deleteCollections, + openApi: { + summary: 'Delete collections', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + getCollectionBreadcrumbs: { + resolve: (api) => api.us.getCollectionBreadcrumbs, + openApi: { + summary: 'Get collection breadcrumbs', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + getCollection: { + resolve: (api) => api.us.getCollection, + openApi: { + summary: 'Get collection', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + getCollectionsByIds: { + resolve: (api) => api.us.getCollectionsByIds, + openApi: { + summary: 'Get collections list by ids', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + getCollectionContent: { + resolve: (api) => api.us.getStructureItems, + openApi: { + summary: 'Get collection content', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + getRootCollectionPermissions: { + resolve: (api) => api.us.getRootCollectionPermissions, + openApi: { + summary: 'Get root collection permissions', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + moveCollection: { + resolve: (api) => api.us.moveCollection, + openApi: { + summary: 'Move collection', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + moveCollections: { + resolve: (api) => api.us.moveCollections, + openApi: { + summary: 'Move collections', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + updateCollection: { + resolve: (api) => api.us.updateCollection, + openApi: { + summary: 'Update collection', + tags: [ApiTag.Collection], + }, + features: [Feature.CollectionsEnabled], + }, + + // Connection + getConnection: { + resolve: (api) => api.bi.getConnection, + openApi: { + summary: 'Get connection', + tags: [ApiTag.Connection], + }, + }, + createConnection: { + resolve: (api) => api.bi.createConnection, + openApi: { + summary: 'Create connection', + tags: [ApiTag.Connection], + }, + }, + updateConnection: { + resolve: (api) => api.bi.updateConnection, + openApi: { + summary: 'Update connection', + tags: [ApiTag.Connection], + }, + }, + deleteConnection: { + resolve: (api) => api.bi.deleteConnection, + openApi: { + summary: 'Delete connection', + tags: [ApiTag.Connection], + }, + }, + + // Dashboard + getDashboard: { + resolve: (api) => api.mix.getDashboardV1, + openApi: { + summary: 'Get dashboard', + tags: [ApiTag.Dashboard], + experimental: true, + }, + }, + createDashboard: { + resolve: (api) => api.mix.createDashboardV1, + openApi: { + summary: 'Create dashboard', + tags: [ApiTag.Dashboard], + experimental: true, + }, + }, + updateDashboard: { + resolve: (api) => api.mix.updateDashboardV1, + openApi: { + summary: 'Update dashboard', + tags: [ApiTag.Dashboard], + experimental: true, + }, + }, + deleteDashboard: { + resolve: (api) => api.mix.deleteDashboard, + openApi: { + summary: 'Delete dashboard', + tags: [ApiTag.Dashboard], + }, + }, + + // Dataset + getDataset: { + resolve: (api) => api.bi.getDatasetByVersion, + openApi: { + summary: 'Get dataset', + tags: [ApiTag.Dataset], + }, + }, + createDataset: { + resolve: (api) => api.bi.createDataset, + openApi: { + summary: 'Create dataset', + tags: [ApiTag.Dataset], + }, + }, + updateDataset: { + resolve: (api) => api.bi.updateDataset, + openApi: { + summary: 'Update dataset', + tags: [ApiTag.Dataset], + }, + }, + deleteDataset: { + resolve: (api) => api.bi.deleteDataset, + openApi: { + summary: 'Delete dataset', + tags: [ApiTag.Dataset], + }, + }, + validateDataset: { + resolve: (api) => api.bi.validateDataset, + openApi: { + summary: 'Validate dataset', + tags: [ApiTag.Dataset], + }, + }, + + // Entries + getEntriesRelations: { + resolve: (api) => api.us.getEntriesRelations, + openApi: { + summary: 'Get entries relations', + tags: [ApiTag.Entries], + }, + }, + + // Navigation + getEntries: { + resolve: (api) => api.us.getEntries, + openApi: { + summary: 'Get entries', + tags: [ApiTag.Navigation], + }, + }, + + // QL + getQLChart: { + resolve: (api) => api.mix.__getQLChart__, + openApi: { + summary: 'Get QL chart', + tags: [ApiTag.QL], + experimental: true, + }, + }, + deleteQLChart: { + resolve: (api) => api.mix.deleteQLChart, + openApi: { + summary: 'Delete QL chart', + tags: [ApiTag.QL], + }, + }, + updateQLChart: { + resolve: (api) => api.mix.__updateQLChart__, + openApi: { + summary: 'Update QL chart', + tags: [ApiTag.QL], + experimental: true, + }, + rawAction: true, + }, + createQLChart: { + resolve: (api) => api.mix.__createQLChart__, + openApi: { + summary: 'Create QL chart', + tags: [ApiTag.QL], + experimental: true, + }, + rawAction: true, + }, + + // Wizard + getWizardChart: { + resolve: (api) => api.mix.__getWizardChart__, + openApi: { + summary: 'Get wizard chart', + tags: [ApiTag.Wizard], + experimental: true, + }, + }, + deleteWizardChart: { + resolve: (api) => api.mix.deleteWizardChart, + openApi: { + summary: 'Delete wizard chart', + tags: [ApiTag.Wizard], + }, + }, + updateWizardChart: { + resolve: (api) => api.mix.__updateWizardChart__, + openApi: { + summary: 'Update wizard chart', + tags: [ApiTag.Wizard], + experimental: true, + }, + rawAction: true, + }, + createWizardChart: { + resolve: (api) => api.mix.__createWizardChart__, + openApi: { + summary: 'Create wizard chart', + tags: [ApiTag.Wizard], + experimental: true, + }, + rawAction: true, + }, + + // Workbook + createWorkbook: { + resolve: (api) => api.us.createWorkbook, + openApi: { + summary: 'Create workbook', + tags: [ApiTag.Workbook], + }, + features: [Feature.CollectionsEnabled], + }, + deleteWorkbook: { + resolve: (api) => api.us.deleteWorkbook, + openApi: { + summary: 'Delete workbook', + tags: [ApiTag.Workbook], + }, + features: [Feature.CollectionsEnabled], + }, + deleteWorkbooks: { + resolve: (api) => api.us.deleteWorkbooks, + openApi: { + summary: 'Delete workbooks', + tags: [ApiTag.Workbook], + }, + features: [Feature.CollectionsEnabled], + }, + getWorkbook: { + resolve: (api) => api.us.getWorkbook, + openApi: { + summary: 'Get workbook', + tags: [ApiTag.Workbook], + }, + features: [Feature.CollectionsEnabled], + }, + getWorkbooksList: { + resolve: (api) => api.us.getWorkbooksList, + openApi: { + summary: 'Get workbooks list', + tags: [ApiTag.Workbook], + }, + features: [Feature.CollectionsEnabled], + }, + getWorkbooksByIds: { + resolve: (api) => api.us.getWorkbooksByIds, + openApi: { + summary: 'Get workbook list by ids', + tags: [ApiTag.Workbook], + }, + features: [Feature.CollectionsEnabled], + }, + moveWorkbook: { + resolve: (api) => api.us.moveWorkbook, + openApi: { + summary: 'Move workbook', + tags: [ApiTag.Workbook], + }, + features: [Feature.CollectionsEnabled], + }, + moveWorkbooks: { + resolve: (api) => api.us.moveWorkbooks, + openApi: { + summary: 'Move workbooks', + tags: [ApiTag.Workbook], + }, + features: [Feature.CollectionsEnabled], + }, + updateWorkbook: { + resolve: (api) => api.us.updateWorkbook, + openApi: { + summary: 'Update workbook', + tags: [ApiTag.Workbook], + }, + features: [Feature.CollectionsEnabled], + }, + getWorkbookEntries: { + resolve: (api) => api.us.getWorkbookEntries, + openApi: { + summary: 'Get workbook entries', + tags: [ApiTag.Workbook], + }, + features: [Feature.CollectionsEnabled], + }, + + // Workbook Export + [PUBLIC_API_ACTION_NAME.START_WORKBOOK_EXPORT]: { + resolve: (api) => api.metaManager.startWorkbookExport, + openApi: { + summary: 'Start workbook export', + tags: [ApiTag.WorkbookExport], + }, + features: [Feature.EnableExportWorkbookFile, Feature.CollectionsEnabled], + }, + getWorkbookExportStatus: { + resolve: (api) => api.metaManager.getWorkbookExportStatus, + openApi: { + summary: 'Get workbook export status', + tags: [ApiTag.WorkbookExport], + }, + features: [Feature.EnableExportWorkbookFile, Feature.CollectionsEnabled], + }, + getWorkbookExportResult: { + resolve: (api) => api.metaManager.getWorkbookExportResult, + openApi: { + summary: 'Get workbook export result', + tags: [ApiTag.WorkbookExport], + }, + features: [Feature.EnableExportWorkbookFile, Feature.CollectionsEnabled], + }, + cancelWorkbookExport: { + resolve: (api) => api.metaManager.cancelWorkbookExport, + openApi: { + summary: 'Cancel workbook export', + tags: [ApiTag.WorkbookExport], + }, + features: [Feature.EnableExportWorkbookFile, Feature.CollectionsEnabled], + }, + + // Workbook Import + [PUBLIC_API_ACTION_NAME.START_WORKBOOK_IMPORT]: { + resolve: (api) => api.metaManager.startWorkbookImport, + openApi: { + summary: 'Start workbook import', + tags: [ApiTag.WorkbookImport], + }, + features: [Feature.EnableExportWorkbookFile, Feature.CollectionsEnabled], + }, + getWorkbookImportStatus: { + resolve: (api) => api.metaManager.getWorkbookImportStatus, + openApi: { + summary: 'Get workbook import status', + tags: [ApiTag.WorkbookImport], + }, + features: [Feature.EnableExportWorkbookFile, Feature.CollectionsEnabled], + }, + } satisfies PublicApiVersionActions; +}; diff --git a/src/server/components/public-api/constants/action-names.ts b/src/server/components/public-api/constants/action-names.ts new file mode 100644 index 0000000..08467a4 --- /dev/null +++ b/src/server/components/public-api/constants/action-names.ts @@ -0,0 +1,4 @@ +export const PUBLIC_API_ACTION_NAME = { + START_WORKBOOK_IMPORT: 'startWorkbookImport', + START_WORKBOOK_EXPORT: 'startWorkbookExport', +} as const; diff --git a/src/server/components/public-api/constants/common.ts b/src/server/components/public-api/constants/common.ts new file mode 100644 index 0000000..e7d0727 --- /dev/null +++ b/src/server/components/public-api/constants/common.ts @@ -0,0 +1,29 @@ +export enum ApiTag { + Connection = 'Connection', + Dataset = 'Dataset', + Wizard = 'Wizard', + Dashboard = 'Dashboard', + QL = 'QL', + Navigation = 'Navigation', + Workbook = 'Workbook', + Collection = 'Collection', + Entries = 'Entries', + WorkbookImport = 'WorkbookImport', + WorkbookExport = 'WorkbookExport', +} + +export const PUBLIC_API_VERSION_HEADER = 'x-dl-api-version'; + +export const PUBLIC_API_VERSION_HEADER_LATEST_VALUE = 'latest'; + +export const OPEN_API_VERSION_HEADER_COMPONENT_NAME = 'ApiVersionHeader'; + +export const PUBLIC_API_HTTP_METHOD = 'POST'; +export const PUBLIC_API_ACTION_REQ_PARAM = 'action'; +export const PUBLIC_API_URL = `/rpc/:${PUBLIC_API_ACTION_REQ_PARAM}`; +export const PUBLIC_API_ROUTE = `${PUBLIC_API_HTTP_METHOD} ${PUBLIC_API_URL}`; + +export const PUBLIC_API_VERSION = { + v0: 0, + v1: 1, +} as const satisfies Record; diff --git a/src/server/components/public-api/constants/index.ts b/src/server/components/public-api/constants/index.ts new file mode 100644 index 0000000..5cc3036 --- /dev/null +++ b/src/server/components/public-api/constants/index.ts @@ -0,0 +1,2 @@ +export * from './common'; +export * from './action-names'; diff --git a/src/server/components/public-api/index.ts b/src/server/components/public-api/index.ts new file mode 100644 index 0000000..006ce30 --- /dev/null +++ b/src/server/components/public-api/index.ts @@ -0,0 +1,10 @@ +export { + PUBLIC_API_HTTP_METHOD, + PUBLIC_API_ROUTE, + PUBLIC_API_URL, + PUBLIC_API_VERSION, + PUBLIC_API_VERSION_HEADER, + PUBLIC_API_ACTION_REQ_PARAM, + PUBLIC_API_ACTION_NAME, +} from './constants'; +export {initPublicApiSwagger, registerActionToOpenApi} from './utils'; diff --git a/src/server/components/public-api/types.ts b/src/server/components/public-api/types.ts new file mode 100644 index 0000000..daa27c5 --- /dev/null +++ b/src/server/components/public-api/types.ts @@ -0,0 +1,57 @@ +import type {OpenAPIRegistry} from '@asteasolutions/zod-to-openapi'; +import type {ComponentsObject} from '@asteasolutions/zod-to-openapi/dist/types'; +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {ApiWithRoot, SchemasByScope} from '@gravity-ui/gateway'; +import type z from 'zod'; + +import type {DatalensGatewaySchemas} from '../../types/gateway'; +import type {SecuritySchemeObject} from '../api-docs'; + +export type PublicApiActionOpenApi = { + summary: string; + description?: string; + tags?: string[]; + headers?: z.ZodObject<{[k: string]: z.ZodType}>; + experimental?: boolean; +}; + +export type PublicApiAction = { + resolve: ( + api: ApiWithRoot, + ) => (params: any) => Promise; + openApi: PublicApiActionOpenApi; + features?: TFeature[]; + rawAction?: boolean; +}; + +export type PublicApiVersionActions = Record< + string, + PublicApiAction +>; + +type PublicApiVersionOpenApi = { + registry: OpenAPIRegistry; +}; + +export type PublicApiVersionConfig< + TSchema extends SchemasByScope = DatalensGatewaySchemas, + TFeature = string, +> = { + actions: PublicApiVersionActions; + openApi: PublicApiVersionOpenApi; +}; + +export type PublicApiBaseConfig< + TSchema extends SchemasByScope = DatalensGatewaySchemas, + TFeature extends string = string, +> = Record>; + +export type PublicApiSecuritySchemes = Record; + +export type PublicApiConfig = { + baseConfig: PublicApiBaseConfig; + securitySchemes: PublicApiSecuritySchemes; + securityTypes: string[]; + biOpenapiSchemas?: ComponentsObject['schemas']; + getAuthArgs?: (req: Request, res: Response) => Record; +}; diff --git a/src/server/components/public-api/utils/index.ts b/src/server/components/public-api/utils/index.ts new file mode 100644 index 0000000..ac631b8 --- /dev/null +++ b/src/server/components/public-api/utils/index.ts @@ -0,0 +1,5 @@ +export {initPublicApiSwagger} from './init-public-api-swagger'; +export {registerActionToOpenApi} from './register-action-to-open-api'; +export {preparePublicApiBaseConfig} from './prepare-public-api-base-config'; +export {pickAdditionalPublicApiHeaders} from './pick-additional-public-api-headers'; +export {parsePublicApiVersionHeader} from './parse-public-api-version-header'; diff --git a/src/server/components/public-api/utils/init-public-api-swagger.ts b/src/server/components/public-api/utils/init-public-api-swagger.ts new file mode 100644 index 0000000..5b50bb0 --- /dev/null +++ b/src/server/components/public-api/utils/init-public-api-swagger.ts @@ -0,0 +1,111 @@ +import {OpenApiGeneratorV31} from '@asteasolutions/zod-to-openapi'; +import type {OpenAPIObjectConfigV31} from '@asteasolutions/zod-to-openapi/dist/v3.1/openapi-generator'; +import type {ExpressKit} from '@gravity-ui/expresskit'; +import swaggerUi from 'swagger-ui-express'; + +import {objectKeys} from '../../../../shared'; +import {registry} from '../../../registry'; +import {OPEN_API_VERSION_HEADER_COMPONENT_NAME, PUBLIC_API_VERSION_HEADER} from '../constants'; + +export const initPublicApiSwagger = (app: ExpressKit) => { + const {config} = app; + + const {baseConfig, securitySchemes, biOpenapiSchemas} = registry.getPublicApiConfig(); + + const latestVersion = Math.max(...Object.keys(baseConfig).map(Number)); + + setImmediate(() => { + const versionToDocument = Object.entries(baseConfig).reduce< + Record> + >((acc, [version, {openApi}]) => { + if (securitySchemes) { + Object.keys(securitySchemes).forEach((securityType) => { + openApi.registry.registerComponent('securitySchemes', securityType, { + ...securitySchemes[securityType], + }); + }); + } + + openApi.registry.registerComponent( + 'parameters', + OPEN_API_VERSION_HEADER_COMPONENT_NAME, + { + name: PUBLIC_API_VERSION_HEADER, + in: 'header', + required: true, + schema: { + type: 'string', + const: version, + example: version, + }, + description: `API version header.`, + }, + ); + + const generator = new OpenApiGeneratorV31(openApi.registry.definitions); + + const generateDocumentParams: OpenAPIObjectConfigV31 = { + openapi: '3.1.0', + info: { + version, + title: `DataLens API `, + }, + servers: [{url: '/'}], + }; + + acc[version] = generator.generateDocument(generateDocumentParams); + + return acc; + }, {}); + + const versions = objectKeys(baseConfig).map(Number); + + versions.forEach((version) => { + const openApiDocument = versionToDocument[version]; + + openApiDocument.components = openApiDocument.components ?? {}; + + openApiDocument.components.schemas = { + ...openApiDocument.components?.schemas, + ...biOpenapiSchemas, + }; + const versionPath = `/${version}/`; + const isLatest = version === latestVersion; + + const addSwaggerRoutes = (basePath: string) => { + const jsonPath = `${basePath}json/`; + + app.express.get(jsonPath, (req, res) => { + const host = req.get('host'); + const serverUrl = `https://${host}`; + + openApiDocument.servers = [{url: serverUrl}]; + + return res.json(openApiDocument); + }); + + const options = { + customfavIcon: config.faviconUrl, + customSiteTitle: 'DataLens API', + customCss: '.swagger-ui .topbar { display: none }', + swaggerOptions: { + url: jsonPath, + validatorUrl: null, + tagsSorter: 'alpha', + operationsSorter: 'alpha', + }, + }; + + app.express.use(basePath, swaggerUi.serveFiles(undefined, options)); + + app.express.get(basePath, swaggerUi.setup(null, options)); + }; + + addSwaggerRoutes(versionPath); + + if (isLatest) { + addSwaggerRoutes('/'); + } + }); + }); +}; diff --git a/src/server/components/public-api/utils/parse-public-api-version-header.ts b/src/server/components/public-api/utils/parse-public-api-version-header.ts new file mode 100644 index 0000000..4d64175 --- /dev/null +++ b/src/server/components/public-api/utils/parse-public-api-version-header.ts @@ -0,0 +1,25 @@ +import {PUBLIC_API_VERSION_HEADER_LATEST_VALUE} from '../constants'; + +export const parsePublicApiVersionHeader = ({ + versionHeader, + versions, + latestVersion, +}: { + versionHeader: string | string[] | undefined; + versions: number[]; + latestVersion: number; +}): number | null => { + if (versionHeader) { + const parsedVersion = Number(versionHeader); + + if (versions.includes(parsedVersion)) { + return parsedVersion; + } + + if (versionHeader === PUBLIC_API_VERSION_HEADER_LATEST_VALUE) { + return latestVersion; + } + } + + return null; +}; diff --git a/src/server/components/public-api/utils/pick-additional-public-api-headers.ts b/src/server/components/public-api/utils/pick-additional-public-api-headers.ts new file mode 100644 index 0000000..9401b06 --- /dev/null +++ b/src/server/components/public-api/utils/pick-additional-public-api-headers.ts @@ -0,0 +1,3 @@ +import type {Request} from '@gravity-ui/expresskit'; + +export const pickAdditionalPublicApiHeaders = (_: Request) => ({}); diff --git a/src/server/components/public-api/utils/prepare-public-api-base-config.ts b/src/server/components/public-api/utils/prepare-public-api-base-config.ts new file mode 100644 index 0000000..718178d --- /dev/null +++ b/src/server/components/public-api/utils/prepare-public-api-base-config.ts @@ -0,0 +1,33 @@ +import type {SchemasByScope} from '@gravity-ui/gateway'; +import type {NodeKit} from '@gravity-ui/nodekit'; + +import {isEnabledServerFeature} from '../../../../shared'; +import type {PublicApiBaseConfig} from '../types'; + +export const preparePublicApiBaseConfig = ( + nodekit: NodeKit, + config: PublicApiBaseConfig, +): PublicApiBaseConfig => { + const result = {} as PublicApiBaseConfig; + + for (const [versionKey, versionConfig] of Object.entries(config)) { + const version = versionKey; + const {actions, openApi} = versionConfig; + + const filteredActions = Object.fromEntries( + Object.entries(actions).filter(([, action]) => { + return ( + !action.features || + action.features.every((feature) => isEnabledServerFeature(nodekit.ctx, feature)) + ); + }), + ); + + result[version] = { + actions: filteredActions, + openApi, + }; + } + + return result; +}; diff --git a/src/server/components/public-api/utils/register-action-to-open-api.ts b/src/server/components/public-api/utils/register-action-to-open-api.ts new file mode 100644 index 0000000..5dd5e12 --- /dev/null +++ b/src/server/components/public-api/utils/register-action-to-open-api.ts @@ -0,0 +1,77 @@ +import type {OpenAPIRegistry} from '@asteasolutions/zod-to-openapi'; + +import {getValidationSchema} from '../../../../shared/schema/gateway-utils'; +import {registry} from '../../../registry'; +import type {AnyApiServiceActionConfig} from '../../../types/gateway'; +import {CONTENT_TYPE_JSON} from '../../api-docs/constants'; +import { + OPEN_API_VERSION_HEADER_COMPONENT_NAME, + PUBLIC_API_HTTP_METHOD, + PUBLIC_API_URL, +} from '../constants'; +import type {PublicApiActionOpenApi} from '../types'; + +const resolveUrl = ({actionName}: {actionName: string}) => { + return PUBLIC_API_URL.replace(':action', actionName); +}; + +export const registerActionToOpenApi = ({ + actionConfig, + actionName, + openApi, + openApiRegistry, +}: { + actionConfig: AnyApiServiceActionConfig; + actionName: string; + openApi: PublicApiActionOpenApi; + openApiRegistry: OpenAPIRegistry; +}) => { + const {securityTypes} = registry.getPublicApiConfig(); + const actionSchema = getValidationSchema(actionConfig); + + const security = securityTypes.map((type) => ({ + [type]: [], + })); + + if (!actionSchema) { + throw new Error(`Action schema not found for action: ${actionName}`); + } + + const {summary, description, tags, experimental, headers} = openApi; + + openApiRegistry.registerPath({ + method: PUBLIC_API_HTTP_METHOD.toLocaleLowerCase() as Lowercase< + typeof PUBLIC_API_HTTP_METHOD + >, + path: resolveUrl({actionName}), + tags, + summary: experimental ? `🚧 [Experimental] ${summary}` : summary, + description, + request: { + ...(actionSchema.paramsSchema + ? { + body: { + content: { + [CONTENT_TYPE_JSON]: { + schema: actionSchema.paramsSchema, + }, + }, + }, + } + : {}), + headers, + }, + responses: { + 200: { + description: 'Response', + content: { + [CONTENT_TYPE_JSON]: { + schema: actionSchema.resultSchema, + }, + }, + }, + }, + security, + parameters: [{$ref: `#/components/parameters/${OPEN_API_VERSION_HEADER_COMPONENT_NAME}`}], + }); +}; diff --git a/src/server/components/resolve-entry-by-link.ts b/src/server/components/resolve-entry-by-link.ts new file mode 100644 index 0000000..a887634 --- /dev/null +++ b/src/server/components/resolve-entry-by-link.ts @@ -0,0 +1,96 @@ +import {parse} from 'querystring'; + +import {AppError} from '@gravity-ui/nodekit'; + +import type {StringParams} from '../../shared'; +import type { + GetEntryByKeyResponse, + GetEntryMetaResponse, + ResolveEntryByLinkComponentArgs, + ResolveEntryByLinkComponentResponse, +} from '../../shared/schema'; +import {registry} from '../registry'; + +export enum ErrorCode { + IncorrectURL = 'INCORRECT_URL', + NotFound = 'NOT_FOUND', + Forbidden = 'FORBIDDEN', + Unknown = 'UNKNOWN', +} + +// eslint-disable-next-line complexity +async function resolveEntryByLink({ + originalUrl, + ctx, + getEntryMeta, +}: ResolveEntryByLinkComponentArgs): Promise { + try { + const url = new URL(originalUrl, 'http://stub'); + + const idOrKeyOrReport = + url.pathname + .replace(/^\/navigation\//, '') + .replace(/^\/navigate\//, '') + .replace(/^\/wizard\/preview\//, '') + .replace( + /^(\/(ChartPreview|preview))?(\/(ChartWizard|wizard|ChartEditor|editor))?\/?/, + '', + ) || url.searchParams.get('name'); + + if (!idOrKeyOrReport) { + throw new AppError("Url doesn't contain a valid entry identificator", { + code: ErrorCode.IncorrectURL, + }); + } + + const params = parse(url.searchParams.toString()) as StringParams; + + let entry: GetEntryMetaResponse | GetEntryByKeyResponse; + + const {extractEntryId} = registry.common.functions.getAll(); + + const possibleEntryId = extractEntryId(idOrKeyOrReport); + + if (possibleEntryId) { + entry = await getEntryMeta({entryId: possibleEntryId}); + } else { + throw new AppError('Incorrect entry identificator', { + code: ErrorCode.IncorrectURL, + }); + } + + ctx.log('RESOLVE_ENTRY_BY_LINK_SUCCESS', {originalUrl, entryId: entry.entryId, params}); + + return { + entry, + params, + }; + } catch (error) { + ctx.logError('RESOLVE_ENTRY_BY_LINK_FAILED', error, {originalUrl}); + + if (typeof error === 'object' && error !== null) { + if (error instanceof AppError || ('error' in error && Boolean(error.error))) { + throw error; + } else if ('response' in error && error.response) { + const response = error.response; + if (typeof response === 'object' && response !== null && 'status' in response) { + if (response.status === 403) { + throw AppError.wrap(error as unknown as Error, { + code: ErrorCode.Forbidden, + }); + } + + if (response.status === 404) { + throw AppError.wrap(error as unknown as Error, { + code: ErrorCode.NotFound, + }); + } + } + } + } + + throw AppError.wrap(error as Error, {code: ErrorCode.Unknown}); + } +} + +export default resolveEntryByLink; diff --git a/src/server/components/sdk/dash.ts b/src/server/components/sdk/dash.ts new file mode 100644 index 0000000..38cd127 --- /dev/null +++ b/src/server/components/sdk/dash.ts @@ -0,0 +1,493 @@ +import type {IncomingHttpHeaders} from 'http'; + +import type {AppContext} from '@gravity-ui/nodekit'; +import Hashids from 'hashids'; +import assign from 'lodash/assign'; +import intersection from 'lodash/intersection'; + +import type {ServerI18n} from '../../../i18n/types'; +import {getAllTabItems} from '../../../shared'; +import {DASH_CURRENT_SCHEME_VERSION, DASH_DATA_REQUIRED_FIELDS} from '../../../shared/constants'; +import {DashSchemeConverter} from '../../../shared/modules'; +import type { + CreateEntryRequest, + DashData, + DashEntry, + DashEntryCreateParams, + DashTab, + DashTabItemControlData, + Dictionary, + EntryReadParams, + UpdateEntryRequest, +} from '../../../shared/types'; +import { + DashTabItemControlSourceType, + DashTabItemType, + EntryScope, + EntryUpdateMode, + Feature, +} from '../../../shared/types'; + +import US from './us'; + +type MatchCallback = (value: string, obj: Record, key: string) => string; + +function processControlLinkToResult( + result: Dictionary, + data: DashTabItemControlData, + matchCallback?: MatchCallback, +) { + if (data.sourceType === DashTabItemControlSourceType.Dataset && 'datasetId' in data.source) { + const {datasetId} = data.source; + + result[datasetId] = matchCallback + ? matchCallback(datasetId, data.source, 'datasetId') + : datasetId; + } + + return result; +} + +function processLinksForItems(tabData: DashTab, matchCallback?: MatchCallback) { + return getAllTabItems(tabData).reduce((result: Dictionary, item) => { + const {type, data} = item; + + if (type === DashTabItemType.Widget && 'tabs' in data) { + return data.tabs.reduce((result, widget) => { + const {chartId} = widget; + + result[chartId] = matchCallback + ? matchCallback(chartId, widget, 'chartId') + : chartId; + return result; + }, result); + } else if (type === DashTabItemType.GroupControl) { + data.group.forEach((groupItem) => { + result = processControlLinkToResult(result, groupItem, matchCallback); + }); + } else if (type === DashTabItemType.Control && 'sourceType' in data) { + result = processControlLinkToResult(result, data, matchCallback); + + if ( + data.sourceType === DashTabItemControlSourceType.External && + 'chartId' in data.source + ) { + const {chartId} = data.source; + + result[chartId] = matchCallback + ? matchCallback(chartId, data.source, 'chartId') + : chartId; + } + } + + return result; + }, {}); +} + +function processLinks(data: DashData, matchCallback?: MatchCallback) { + return data.tabs.reduce( + (result: Dictionary, tab) => ({ + ...result, + ...processLinksForItems(tab, matchCallback), + }), + {}, + ); +} + +export function gatherLinks(data: DashData) { + return processLinks(data); +} + +function setDefaultData( + I18n: ServerI18n, + requestData: DashData, + initialData: Partial = {}, +) { + const i18n = I18n.keyset('dash.tabs-dialog.edit'); + + let counter = 2; + if (initialData?.tabs && !initialData?.counter) { + counter = initialData.tabs.reduce((acc, tab) => { + return acc + 1 + (getAllTabItems(tab)?.length || 0); // + 1 tabId + n items ids + }, 0); + } + const salt = Math.random().toString(); + const hashids = new Hashids(salt); + + const data: DashData = { + salt, + counter, + schemeVersion: DASH_CURRENT_SCHEME_VERSION, + tabs: [ + { + id: hashids.encode(1), + title: i18n('value_default', {index: 1}), + items: [], + layout: [], + aliases: {}, + connections: [], + }, + ], + settings: { + autoupdateInterval: null, + maxConcurrentRequests: null, + silentLoading: false, + dependentSelectors: true, + hideTabs: false, + hideDashTitle: false, + expandTOC: false, + }, + }; + + return assign(data, initialData, requestData); +} + +const needSetDefaultData = (data: DashData) => + DASH_DATA_REQUIRED_FIELDS.some((fieldName) => !(fieldName in data)); + +export function validateData(data: DashData) { + const allTabsIds: Set = new Set(); + const allItemsIds: Set = new Set(); + const allWidgetTabsIds: Set = new Set(); + + const isIdUniq = (id: string) => { + if (allTabsIds.has(id) || allItemsIds.has(id) || allWidgetTabsIds.has(id)) { + throw new Error(`Duplicated id ${id}`); + } + return true; + }; + + data.tabs.forEach(({id: tabId, title: tabTitle, items, layout, connections, globalItems}) => { + const currentItemsIds: Set = new Set(); + const currentWidgetTabsIds: Set = new Set(); + const currentControlsIds: Set = new Set(); + + if (isIdUniq(tabId)) { + allTabsIds.add(tabId); + } + + globalItems?.forEach(({id: itemId, type, data}) => { + allItemsIds.add(itemId); + currentItemsIds.add(itemId); + + // to avoid isIdUniq check + if (type === DashTabItemType.Control || type === DashTabItemType.GroupControl) { + // if it is group control all connections set on its items + if ('group' in data) { + data.group.forEach((widgetItem) => { + currentControlsIds.add(widgetItem.id); + }); + } else { + currentControlsIds.add(itemId); + } + } + }); + + items.forEach(({id: itemId, type, data}) => { + if (isIdUniq(itemId)) { + allItemsIds.add(itemId); + currentItemsIds.add(itemId); + } + + if (type === DashTabItemType.Control || type === DashTabItemType.GroupControl) { + // if it is group control all connections set on its items + if ('group' in data) { + data.group.forEach((widgetItem) => { + currentControlsIds.add(widgetItem.id); + }); + } else { + currentControlsIds.add(itemId); + } + } else if (type === DashTabItemType.Widget && 'tabs' in data) { + data.tabs.forEach(({id: widgetTabId}) => { + if (isIdUniq(widgetTabId)) { + allWidgetTabsIds.add(widgetTabId); + currentWidgetTabsIds.add(widgetTabId); + } + }); + } + }); + + const allItemsLength = items.length + (globalItems?.length ?? 0); + + // checking that layout has all the ids from item, i.e. positions are set for all elements + if ( + allItemsLength !== layout.length || + allItemsLength !== + intersection( + Array.from(currentItemsIds), + layout.map(({i}) => i), + ).length + ) { + throw new Error(`Not consistent items and layout on tab ${tabTitle}`); + } + + connections.forEach(({from, to}) => { + if ( + (!currentWidgetTabsIds.has(from) && !currentControlsIds.has(from)) || + (!currentWidgetTabsIds.has(to) && !currentControlsIds.has(to)) + ) { + throw new Error(`Items ${from} and ${to} could not be in connection`); + } + }); + }); +} + +class Dash { + static validateData = validateData; + static processLinks = processLinks; + static processLinksForItems = processLinksForItems; + static gatherLinks = gatherLinks; + + static async create( + data: CreateEntryRequest, + headers: IncomingHttpHeaders, + ctx: AppContext, + I18n: ServerI18n, + ): Promise { + try { + let usData: CreateEntryRequest = { + ...data, + scope: EntryScope.Dash, + type: '', + mode: data.mode || EntryUpdateMode.Publish, + }; + + if (data.asNew) { + usData = { + ...data, + key: data.key, + scope: EntryScope.Dash, + type: '', + }; + } else if (needSetDefaultData(usData.data)) { + usData.data = setDefaultData(I18n, usData.data); + } + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + const isServerMigrationEnabled = Boolean( + isEnabledServerFeature(Feature.DashServerMigrationEnable), + ); + if (isServerMigrationEnabled && DashSchemeConverter.isUpdateNeeded(usData.data)) { + usData.data = DashSchemeConverter.update(usData.data); + } + + usData.links = gatherLinks(usData.data); + + validateData(usData.data); + + const headersWithMetadata = { + ...headers, + ...ctx.getMetadata(), + }; + + const createdEntry = (await US.createEntry( + Dash.migrateDescriptionForSave(usData), + headersWithMetadata, + ctx, + )) as DashEntry & { + revId: string; + }; + + ctx.log('SDK_DASH_CREATE_SUCCESS', US.getLoggedEntry(createdEntry)); + + return Dash.migrateDescriptionForClient(createdEntry); + } catch (error) { + ctx.logError('SDK_DASH_CREATE_FAILED', error, US.getLoggedErrorEntry(data)); + + throw error; + } + } + + static async read( + entryId: string, + params: EntryReadParams | null, + headers: IncomingHttpHeaders, + ctx: AppContext, + options?: {forceMigrate?: boolean}, + ): Promise { + try { + const headersWithMetadata = { + ...headers, + ...ctx.getMetadata(), + }; + const result = await US.readEntry(entryId, params, headersWithMetadata, ctx).then( + (entry) => Dash.migrateDescriptionForClient(entry as DashEntry), + ); + + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + const isServerMigrationEnabled = Boolean( + isEnabledServerFeature(Feature.DashServerMigrationEnable), + ); + if ( + (options?.forceMigrate || isServerMigrationEnabled) && + DashSchemeConverter.isUpdateNeeded(result.data) + ) { + result.data = Dash.migrate(result.data); + } + + ctx.log('SDK_DASH_READ_SUCCESS', US.getLoggedEntry(result)); + + return result; + } catch (error) { + ctx.logError('SDK_DASH_READ_FAILED', error, {entryId, params}); + + throw error; + } + } + + static migrate(data: DashEntry['data']) { + return DashSchemeConverter.update(data); + } + + static migrateDescription>(prevEntry: T) { + if (prevEntry.data && 'description' in prevEntry.data) { + const entry = { + ...prevEntry, + annotation: { + description: prevEntry.data.description ?? '', + }, + }; + delete entry.data.description; + return entry; + } + + return prevEntry; + } + + static migrateDescriptionForClient(prevEntry: DashEntry) { + if (prevEntry.data && 'description' in prevEntry.data && !prevEntry.annotation) { + return { + ...prevEntry, + annotation: { + description: prevEntry.data.description, + }, + }; + } + + if (prevEntry.annotation?.description && !prevEntry.data.description) { + return { + ...prevEntry, + data: { + ...prevEntry.data, + description: prevEntry.annotation.description, + }, + }; + } + + return prevEntry; + } + + static migrateDescriptionForSave>( + prevEntry: T, + ) { + if (prevEntry.annotation) { + return { + ...prevEntry, + annotation: { + description: prevEntry.annotation.description ?? '', + }, + }; + } + + if (prevEntry.data && 'description' in prevEntry.data) { + const entry = { + ...prevEntry, + annotation: { + description: prevEntry.data.description ?? '', + }, + }; + delete entry.data.description; + + return entry; + } + + if (prevEntry && 'description' in prevEntry) { + const entry = { + ...prevEntry, + annotation: { + description: prevEntry.description ?? '', + }, + }; + delete entry.description; + + return entry; + } + + return prevEntry; + } + + static async update( + entryId: string, + data: UpdateEntryRequest, + headers: IncomingHttpHeaders, + ctx: AppContext, + I18n: ServerI18n, + options?: {forceMigrate?: boolean}, + ): Promise { + try { + const usData: typeof data & {skipSyncLinks?: boolean} = {...data}; + const mode = data.mode || EntryUpdateMode.Publish; + + const needDataSend = !(mode === EntryUpdateMode.Publish && data.revId); + if (needDataSend) { + if (needSetDefaultData(usData.data)) { + const initialData = await Dash.read(entryId, null, headers, ctx, options); + usData.data = setDefaultData(I18n, usData.data, initialData.data); + } + + usData.links = gatherLinks(usData.data); + + validateData(usData.data); + } + + if (mode !== EntryUpdateMode.Publish) { + usData.skipSyncLinks = true; + } + + const headersWithMetadata = { + ...headers, + ...ctx.getMetadata(), + }; + + const result = (await US.updateEntry( + entryId, + mode, + Dash.migrateDescriptionForSave(usData), + headersWithMetadata, + ctx, + )) as DashEntry; + + ctx.log('SDK_DASH_UPDATE_SUCCESS', US.getLoggedEntry(result)); + + return Dash.migrateDescriptionForClient(result); + } catch (error) { + ctx.logError('SDK_DASH_UPDATE_FAILED', error, { + entryId, + ...US.getLoggedErrorEntry(data), + }); + + throw error; + } + } + + static async delete(entryId: string, headers: IncomingHttpHeaders, ctx: AppContext) { + try { + const headersWithMetadata = { + ...headers, + ...ctx.getMetadata(), + }; + + const result = (await US.deleteEntry(entryId, headersWithMetadata, ctx)) as DashEntry; + + ctx.log('SDK_DASH_DELETE_SUCCESS', US.getLoggedEntry(result)); + + return result; + } catch (error) { + ctx.logError('SDK_DASH_DELETE_FAILED', error, {entryId}); + + throw error; + } + } +} + +export default Dash; diff --git a/src/server/components/sdk/index.ts b/src/server/components/sdk/index.ts new file mode 100644 index 0000000..555431e --- /dev/null +++ b/src/server/components/sdk/index.ts @@ -0,0 +1,3 @@ +import Dash from './dash'; + +export {Dash}; diff --git a/src/server/components/sdk/us.ts b/src/server/components/sdk/us.ts new file mode 100644 index 0000000..05f855f --- /dev/null +++ b/src/server/components/sdk/us.ts @@ -0,0 +1,239 @@ +import type {IncomingHttpHeaders} from 'http'; +import {stringify} from 'querystring'; + +import type {AppContext} from '@gravity-ui/nodekit'; +import pick from 'lodash/pick'; + +import type {GetEntryByKeyResponse, GetEntryMetaResponse} from '../../../shared/schema'; +import {filterUrlFragment} from '../../../shared/schema/utils'; +import type { + CreateEntryRequest, + Entry, + EntryReadParams, + EntryScope, + EntryUpdateMode, + State, + UpdateEntryRequest, +} from '../../../shared/types'; +import {getAxios} from '../axios'; + +class US { + static async createEntry( + data: CreateEntryRequest, + headers: IncomingHttpHeaders, + ctx: AppContext, + ): Promise { + try { + const {data: result} = await getAxios(ctx.config)({ + method: 'POST', + url: `${ctx.config.endpoints.api.us}/v1/entries`, + headers, + data, + }); + + ctx.log('SDK_US_CREATE_ENTRY_SUCCESS', US.getLoggedEntry(result)); + + return result; + } catch (error) { + ctx.logError('SDK_US_CREATE_ENTRY_FAILED', error, US.getLoggedErrorEntry(data)); + + throw error; + } + } + + static async readEntry( + entryId: string, + params: EntryReadParams | null, + headers: IncomingHttpHeaders, + ctx: AppContext, + ): Promise { + try { + const {data} = await getAxios(ctx.config)({ + method: 'GET', + url: `${ctx.config.endpoints.api.us}/v1/entries/${filterUrlFragment(entryId)}`, + headers, + params, + 'axios-retry': {retries: 1}, + }); + + ctx.log('SDK_US_READ_ENTRY_SUCCESS', US.getLoggedEntry(data)); + + return data; + } catch (error) { + ctx.logError('SDK_US_READ_ENTRY_FAILED', error, {entryId, params}); + + throw error; + } + } + + static async updateEntry( + entryId: string, + mode: EntryUpdateMode, + data: UpdateEntryRequest, + headers: IncomingHttpHeaders, + ctx: AppContext, + ): Promise { + try { + const {data: result} = await getAxios(ctx.config)({ + method: 'POST', + url: `${ctx.config.endpoints.api.us}/v1/entries/${filterUrlFragment(entryId)}`, + headers, + data: { + ...data, + mode, + }, + }); + + ctx.log('SDK_US_UPDATE_ENTRY_SUCCESS', US.getLoggedEntry(result)); + + return result; + } catch (error) { + ctx.logError('SDK_US_UPDATE_ENTRY_FAILED', error, { + entryId, + ...US.getLoggedErrorEntry(data), + }); + + throw error; + } + } + + static async deleteEntry( + entryId: string, + headers: IncomingHttpHeaders, + ctx: AppContext, + ): Promise { + try { + const {data} = await getAxios(ctx.config)({ + method: 'delete', + url: `${ctx.config.endpoints.api.us}/v1/entries/${filterUrlFragment(entryId)}`, + headers, + }); + + ctx.log('SDK_US_DELETE_ENTRY_SUCCESS', US.getLoggedEntry(data)); + + return data; + } catch (error) { + ctx.logError('SDK_US_DELETE_ENTRY_FAILED', error, {entryId}); + + throw error; + } + } + + static async getEntryMeta( + entryId: string, + params: EntryReadParams | null, + headers: IncomingHttpHeaders, + ctx: AppContext, + ): Promise { + try { + const {data} = await getAxios(ctx.config)({ + method: 'GET', + url: `${ctx.config.endpoints.api.us}/v1/entries/${filterUrlFragment(entryId)}/meta`, + headers, + params, + 'axios-retry': {retries: 1}, + }); + + ctx.log('SDK_US_READ_ENTRY_META_SUCCESS', US.getLoggedEntry(data)); + + return data; + } catch (error) { + ctx.logError('SDK_US_READ_ENTRY_META_FAILED', error, {entryId, params}); + + throw error; + } + } + + static async getEntryByKey( + key: string, + params: EntryReadParams | null, + headers: IncomingHttpHeaders, + ctx: AppContext, + ): Promise { + try { + const {data} = await getAxios(ctx.config)({ + method: 'GET', + url: `${ctx.config.endpoints.api.us}/v1/entriesByKey`, + headers, + params: params ? {...params, key} : {key}, + 'axios-retry': {retries: 1}, + }); + + ctx.log('SDK_US_GET_ENTRY_BY_KEY_SUCCESS', US.getLoggedEntry(data)); + + return data; + } catch (error) { + ctx.logError('SDK_US_GET_ENTRY_BY_KEY_FAILED', error, {key, params}); + + throw error; + } + } + + static async getEntries( + params: { + ids: string[]; + scope: EntryScope; + }, + headers: IncomingHttpHeaders, + ctx: AppContext, + ): Promise<{ + entries: Entry[]; + }> { + try { + const {data} = await getAxios(ctx.config)({ + method: 'GET', + url: `${ctx.config.endpoints.api.us}/v1/entries`, + headers, + params, + paramsSerializer: (params) => stringify(params), + 'axios-retry': {retries: 1}, + }); + + ctx.log('SDK_US_GET_ENTRIES_SUCCESS', {count: data.length}); + + return data; + } catch (error) { + ctx.logError('SDK_US_GET_ENTRIES_FAILED', error); + + throw error; + } + } + + static async getState( + entryId: string, + hash: string, + headers: IncomingHttpHeaders, + ctx: AppContext, + ): Promise { + const logData = {entryId, hash}; + + try { + const {data} = await getAxios(ctx.config)({ + method: 'GET', + url: `${ctx.config.endpoints.api.us}/v1/states/${filterUrlFragment( + entryId, + )}/${filterUrlFragment(hash)}`, + headers, + 'axios-retry': {retries: 1}, + }); + + ctx.log('SDK_US_GET_STATE_SUCCESS', logData); + + return data; + } catch (error) { + ctx.logError('SDK_US_GET_STATE_FAILED', error, logData); + + throw error; + } + } + + static getLoggedEntry(entry: Entry) { + return pick(entry, ['key', 'entryId', 'scope', 'type']); + } + + static getLoggedErrorEntry(data: CreateEntryRequest | UpdateEntryRequest) { + return pick(data, ['key', 'entryId', 'scope', 'type']); + } +} + +export default US; diff --git a/src/server/components/workbook-transfer/charts.ts b/src/server/components/workbook-transfer/charts.ts new file mode 100644 index 0000000..87d2744 --- /dev/null +++ b/src/server/components/workbook-transfer/charts.ts @@ -0,0 +1,321 @@ +import type {Request} from '@gravity-ui/expresskit'; +import forIn from 'lodash/forIn'; +import isArray from 'lodash/isArray'; + +import {getEntryNameByKey} from '../../../shared'; +import { + TRANSFER_UNKNOWN_ENTRY_ID, + TransferErrorCode, +} from '../../../shared/constants/workbook-transfer'; +import type {EntryFields} from '../../../shared/schema'; +import { + type EntryAnnotation, + EntryScope, + EntryUpdateMode, + type TransferIdMapping, + type TransferNotification, +} from '../../../shared/types'; +import {type ChartTemplates, chartGenerator} from '../charts-engine/components/chart-generator'; + +import { + criticalTransferNotification, + warningTransferNotification, +} from './create-transfer-notifications'; + +type MappingWarnings = {missedMapping: boolean}; + +type MatchCallback = (value: string, obj: Record, key: string | number) => string; + +type TransferChartDataOptions = { + data: { + shared: Record; + }; + type: string; + key?: string; + name: string; + annotation?: EntryAnnotation | null; +}; + +const validateChartShared = (chartOptions: TransferChartDataOptions) => { + const requiredChartOptionsKeys: Array = [ + 'data', + 'name', + 'type', + ]; + + requiredChartOptionsKeys.forEach((key) => { + if (!(key in chartOptions)) { + throw new Error('Invalid chart options'); + } + }); + + if (typeof chartOptions.data.shared !== 'object') { + throw new Error('Invalid chart chared object'); + } +}; + +const traverseDatasetFields = (obj: any, matchCallback: MatchCallback) => { + const {result_schema, sources} = obj || {}; + + // Legacy dataset properties placeholder replacer + if (result_schema && Array.isArray(result_schema)) { + for (let i = 0; i <= result_schema.length; i++) { + const item = result_schema[i]; + + if (item?.datasetId) { + item.datasetId = matchCallback(item.datasetId, item, 'datasetId'); + } + } + } + + // This is v1 field schema but saved and cause we are not migrating wizard charts + // we need to migrate this fields before export\import + if (sources && Array.isArray(sources)) { + for (let i = 0; i <= sources.length; i++) { + const item = sources[i]; + + if (item?.connection_id) { + item.connection_id = matchCallback(item.connection_id, item, 'connection_id'); + } + } + } +}; + +const traverseWizardFieldsRecursive = (obj: any, matchCallback: MatchCallback) => { + forIn(obj, (val, key) => { + if (key === 'datasetId' && typeof val === 'string') { + // Array<{datasetId: string}> + obj[key] = matchCallback(val, obj, key); + } else if (key === 'datasets' && Array.isArray(val)) { + // datasets: [{id: string}] + for (let i = 0; i <= val.length; i++) { + const item = val[i]; + if (item?.id) { + item.id = matchCallback(item.id, item, 'id'); + } + + traverseDatasetFields(item, matchCallback); + } + } else if (key === 'dataset' && typeof val === 'object' && typeof val.id === 'string') { + // dataset: {id: string} + val.id = matchCallback(val.id, val, 'id'); + + traverseDatasetFields(val, matchCallback); + } else if (typeof val === 'object') { + traverseWizardFieldsRecursive(val, matchCallback); + } + }); +}; + +const traverseWizardFields = (obj: any, idMapping: TransferIdMapping) => { + const warnings: MappingWarnings = {missedMapping: false}; + + const matchCallback: MatchCallback = (val, obj, key) => { + const mappedValue = idMapping[val]; + if (mappedValue) { + obj[key] = mappedValue; + return mappedValue; + } + + warnings.missedMapping = true; + return TRANSFER_UNKNOWN_ENTRY_ID; + }; + // datasetsIds: string[] + if ('datasetsIds' in obj && isArray(obj.datasetsIds)) { + const {datasetsIds} = obj; + + obj.datasetsIds = (datasetsIds as string[]).map((id, index, list) => { + return matchCallback(id, list, index); + }); + } + + traverseWizardFieldsRecursive(obj, matchCallback); + + return warnings; +}; + +const traverseQlFields = (obj: any, idMapping: TransferIdMapping) => { + const connection = (obj as any).connection; + const warnings: MappingWarnings = {missedMapping: false}; + + const entryId = connection?.entryId; + if (connection?.entryId) { + if (idMapping[entryId]) { + connection.entryId = idMapping[entryId]; + } else { + warnings.missedMapping = true; + } + } + + return warnings; +}; + +export const prepareImportChartData = async ( + chartOptions: TransferChartDataOptions, + req: Request, + idMapping: TransferIdMapping, +) => { + const {ctx} = req; + + const description = chartOptions.annotation?.description ?? ''; + const defaults = { + key: chartOptions.key, + name: chartOptions.name, + data: null, + links: {}, + scope: EntryScope.Widget, + mode: EntryUpdateMode.Publish, + annotation: {description}, + }; + const notifications: TransferNotification[] = []; + + let template; + let chartTemplate; + let shared: Record = {}; + let warnings: MappingWarnings | null = null; + + try { + shared = chartOptions.data.shared; + + const chartTemplateObj = chartGenerator.identifyChartTemplate({ctx, shared}); + + template = chartTemplateObj.type; + chartTemplate = chartTemplateObj.chartTemplate; + + validateChartShared(chartOptions); + } catch (err) { + return { + widget: null, + notifications: [ + criticalTransferNotification(TransferErrorCode.TransferInvalidEntryData, { + error: (err as Error).message, + }), + ], + }; + } + + switch (template) { + case 'datalens': + warnings = traverseWizardFields(shared, idMapping); + break; + case 'ql': + warnings = traverseQlFields(shared, idMapping); + break; + default: + return { + widget: null, + notifications: [ + criticalTransferNotification(TransferErrorCode.TransferInvalidEntryData), + ], + }; + } + + if (warnings && warnings.missedMapping) { + notifications.push( + warningTransferNotification(TransferErrorCode.TransferMissingLinkedEndtry), + ); + } + + try { + const links = Object.entries( + chartGenerator.gatherChartLinks({ + req, + shared, + chartTemplate, + }) || {}, + ).reduce>((acc, [key, value]) => { + if (value !== TRANSFER_UNKNOWN_ENTRY_ID) { + acc[key] = value; + } + + return acc; + }, {}); + + const serializedData = chartGenerator.serializeShared({ + ctx, + shared, + links, + }); + + return { + widget: { + ...defaults, + data: serializedData, + type: chartOptions.type, + links, + }, + notifications, + }; + } catch (err) { + return { + widget: null, + notifications: [ + criticalTransferNotification(TransferErrorCode.TransferInvalidEntryData, { + error: (err as Error).message, + }), + ], + }; + } +}; + +export const prepareExportChartData = async (entry: EntryFields, idMapping: TransferIdMapping) => { + let data; + + const {key, type, annotation} = entry; + const name = getEntryNameByKey({key}); + + const widget = { + data, + name, + type, + annotation, + }; + + const notifications: TransferNotification[] = []; + let warnings: MappingWarnings | null = null; + let shared: Record = {}; + + try { + shared = JSON.parse((entry.data?.shared || '') as string); + const template = (shared?.type || '') as keyof ChartTemplates; + + switch (template) { + case 'datalens': + warnings = traverseWizardFields(shared, idMapping); + break; + case 'ql': + warnings = traverseQlFields(shared, idMapping); + break; + default: + return { + widget: null, + notifications: [ + criticalTransferNotification(TransferErrorCode.TransferInvalidEntryData), + ], + }; + } + } catch (err) { + return { + widget: null, + notifications: [ + criticalTransferNotification(TransferErrorCode.TransferInvalidEntryData, { + error: (err as Error).message, + }), + ], + }; + } + + if (warnings && warnings.missedMapping) { + notifications.push( + warningTransferNotification(TransferErrorCode.TransferMissingLinkedEndtry), + ); + } + + return { + widget: { + ...widget, + data: {shared}, + }, + notifications, + }; +}; diff --git a/src/server/components/workbook-transfer/create-transfer-notifications.ts b/src/server/components/workbook-transfer/create-transfer-notifications.ts new file mode 100644 index 0000000..c215262 --- /dev/null +++ b/src/server/components/workbook-transfer/create-transfer-notifications.ts @@ -0,0 +1,26 @@ +import type {TransferNotification} from '../../../shared'; + +export const createTransferNotification = ( + level: TransferNotification['level'], + code: TransferNotification['code'], + details?: TransferNotification['details'], +) => ({ + code, + level, + ...(details ? {details} : {}), +}); + +export const infoTransferNotification = ( + code: TransferNotification['code'], + details?: TransferNotification['details'], +) => createTransferNotification('info', code, details); + +export const warningTransferNotification = ( + code: TransferNotification['code'], + details?: TransferNotification['details'], +) => createTransferNotification('warning', code, details); + +export const criticalTransferNotification = ( + code: TransferNotification['code'], + details?: TransferNotification['details'], +) => createTransferNotification('critical', code, details); diff --git a/src/server/components/workbook-transfer/dash.ts b/src/server/components/workbook-transfer/dash.ts new file mode 100644 index 0000000..31421ed --- /dev/null +++ b/src/server/components/workbook-transfer/dash.ts @@ -0,0 +1,122 @@ +import { + type DashEntry, + type EntryAnnotation, + EntryScope, + EntryUpdateMode, + type TransferIdMapping, + type TransferNotification, + getEntryNameByKey, +} from '../../../shared'; +import { + TRANSFER_UNKNOWN_ENTRY_ID, + TransferErrorCode, +} from '../../../shared/constants/workbook-transfer'; +import Dash from '../sdk/dash'; + +import { + criticalTransferNotification, + warningTransferNotification, +} from './create-transfer-notifications'; + +export function prepareDashImportData( + entryData: {data: DashEntry['data']; name: string; annotation?: EntryAnnotation}, + idMapping: TransferIdMapping, +) { + const data = Dash.migrate(entryData.data); + const notifications: TransferNotification[] = []; + const description = entryData.annotation?.description ?? ''; + const defaults = { + name: entryData.name, + scope: EntryScope.Dash, + mode: EntryUpdateMode.Publish, + links: {}, + type: '', + key: '', + annotation: {description}, + }; + + try { + let isMissingMapping = false; + + Dash.processLinks(data, (value, obj, key) => { + if (idMapping[value]) { + obj[key] = idMapping[value]; + return idMapping[value]; + } else { + isMissingMapping = true; + } + + return value; + }); + + if (isMissingMapping) { + notifications.push( + warningTransferNotification(TransferErrorCode.TransferMissingLinkedEndtry), + ); + } + + Dash.validateData(data); + } catch (err) { + return { + dash: null, + notifications: [ + criticalTransferNotification(TransferErrorCode.TransferInvalidEntryData), + ], + }; + } + + const links = Object.entries(Dash.gatherLinks(data) || {}).reduce>( + (acc, [key, value]) => { + if (value !== TRANSFER_UNKNOWN_ENTRY_ID) { + acc[key] = value; + } + + return acc; + }, + {}, + ); + + return { + dash: { + ...defaults, + data, + links, + }, + notifications, + }; +} + +export function prepareDashExportData(entry: DashEntry, idMapping: TransferIdMapping) { + const data = Dash.migrate(entry.data); + const notifications: TransferNotification[] = []; + let isMissingMapping = false; + + Dash.processLinks(data, (val, obj, key) => { + const mappedValue = idMapping[val]; + if (mappedValue) { + obj[key] = mappedValue; + return mappedValue; + } + + isMissingMapping = true; + return TRANSFER_UNKNOWN_ENTRY_ID; + }); + + if (isMissingMapping) { + notifications.push( + warningTransferNotification(TransferErrorCode.TransferMissingLinkedEndtry), + ); + } + const name = getEntryNameByKey({key: entry.key}); + + const dash = { + name, + data, + annotation: entry.annotation, + }; + + return { + dash, + notifications, + }; +} diff --git a/src/server/configs/common.ts b/src/server/configs/common.ts new file mode 100644 index 0000000..20ab0cd --- /dev/null +++ b/src/server/configs/common.ts @@ -0,0 +1,52 @@ +import {DEFAULT_PROXY_HEADERS} from '@gravity-ui/gateway/build/constants'; +import type {AppConfig} from '@gravity-ui/nodekit'; + +import { + AuthHeader, + CSRF_TOKEN_HEADER, + DL_COMPONENT_HEADER, + DL_EMBED_TOKEN_HEADER, + PROJECT_ID_HEADER, + SuperuserHeader, + TENANT_ID_HEADER, +} from '../../shared'; +import {docsUrl, releaseVersion} from '../app-env'; +import {SERVICE_NAME_DATALENS} from '../components'; + +export default { + appName: `datalens-${process.env.APP_MODE}`, + appSocket: 'dist/run/server.sock', + expressBodyParserJSONConfig: { + limit: '50mb', + }, + expressBodyParserURLEncodedConfig: { + limit: '50mb', + extended: false, + }, + expressTrustProxyNumber: 2, + workers: process.env.WORKERS ? parseInt(process.env.WORKERS, 10) : 1, + fetchingTimeout: process.env.DATA_FETCHING_TIMEOUT_MS + ? parseInt(process.env.DATA_FETCHING_TIMEOUT_MS, 10) + : undefined, + singleFetchingTimeout: process.env.DATA_SINGLE_FETCHING_TIMEOUT_MS + ? parseInt(process.env.DATA_SINGLE_FETCHING_TIMEOUT_MS, 10) + : undefined, + faviconUrl: '/os-favicon.ico', + appMode: process.env.APP_MODE, + serviceName: SERVICE_NAME_DATALENS, + gatewayProxyHeaders: [ + ...DEFAULT_PROXY_HEADERS, + PROJECT_ID_HEADER, + TENANT_ID_HEADER, + SuperuserHeader.XDlAllowSuperuser, + SuperuserHeader.XDlSudo, + AuthHeader.Authorization, + CSRF_TOKEN_HEADER, + DL_COMPONENT_HEADER, + DL_EMBED_TOKEN_HEADER, + ], + headersMap: {}, + requestIdHeaderName: 'x-request-id', + releaseVersion: releaseVersion, + docsUrl: docsUrl, +} satisfies Partial; diff --git a/src/server/configs/opensource/common.ts b/src/server/configs/opensource/common.ts new file mode 100644 index 0000000..8851535 --- /dev/null +++ b/src/server/configs/opensource/common.ts @@ -0,0 +1,236 @@ +import type {AppConfig} from '@gravity-ui/nodekit'; + +import type {AppEnvironment} from '../../../shared'; +import { + AppInstallation, + CSP_HEADER, + CSP_REPORT_TO_HEADER, + DL_CONTEXT_HEADER, + Language, + PALETTE_ID, + US_DYNAMIC_MASTER_TOKEN_HEADER, + US_MASTER_TOKEN_HEADER, + isTrueArg, +} from '../../../shared'; +import {UserRole} from '../../../shared/components/auth/constants/role'; +import {resolveSource} from '../../../shared/endpoints/sources'; +import type {SourceConfig} from '../../components/charts-engine/types'; +import {SERVICE_NAME_DATALENS} from '../../constants'; +import {getEnvCert} from '../../utils/env-utils'; +import controlDashChartTemplate from '../shared/control-dash-chart-template'; +import datalensChartTemplate from '../shared/datalens-chart-template'; +import qlChartTemplate from '../shared/ql-chart-template'; + +const sensitiveHeaders = [ + CSP_HEADER, + CSP_REPORT_TO_HEADER, + US_DYNAMIC_MASTER_TOKEN_HEADER, + US_MASTER_TOKEN_HEADER, +]; + +export default { + // DATALENS MODE + serviceName: SERVICE_NAME_DATALENS, + + expressCookieSecret: process.env.COOKIE_SECRET, + + appAuthPolicy: 'required', + runResponseWhitelist: [ + 'sourceId', + 'sourceType', + 'body', + 'status', + 'latency', + 'size', + 'data', + 'datasetId', + 'code', + 'details', + ], + + regionalEnvConfig: { + defaultLang: Language.En, + allowLanguages: [Language.En, Language.Ru], + }, + + appLangQueryParamName: '_lang', + + expressBodyParserRawConfig: { + type: 'multipart/form-data', + limit: '21mb', + }, + usMasterToken: process.env.US_MASTER_TOKEN || 'fake-us-master-token', + usDynamicMasterTokenPrivateKey: getEnvCert(process.env.US_DYNAMIC_MASTER_TOKEN_PRIVATE_KEY), + + // CHARTS MODE + + allowBodyConfig: true, + + chartTemplates: { + ql: qlChartTemplate, + datalens: datalensChartTemplate, + control_dash: controlDashChartTemplate, + }, + + getSourcesByEnv: (env: AppEnvironment): Record => { + const sources = resolveSource(AppInstallation.Opensource, env); + + return { + bi: { + dataEndpoint: sources.bi, + passedCredentials: {}, + description: { + title: { + ru: 'DataLens BI', + en: 'DataLens BI', + }, + }, + }, + bi_connections: { + dataEndpoint: sources.bi_connections, + passedCredentials: {}, + description: { + title: { + ru: 'DataLens BI Connections', + en: 'DataLens BI Connections', + }, + }, + }, + bi_datasets: { + dataEndpoint: sources.bi_datasets, + passedCredentials: {}, + description: { + title: { + ru: 'DataLens BI Datasets', + en: 'DataLens BI Datasets', + }, + }, + }, + bi_datasets_embed: { + dataEndpoint: sources.bi_datasets_embed, + passedCredentials: {}, + description: { + title: { + ru: 'DataLens BI Datasets Embed', + en: 'DataLens BI Datasets Embed', + }, + }, + }, + bi_connections_embed: { + dataEndpoint: sources.bi_connections_embed, + passedCredentials: {}, + description: { + title: { + ru: 'DataLens BI Connections Embed', + en: 'DataLens BI Connections Embed', + }, + }, + }, + us_color_palettes: { + description: { + title: { + ru: 'US color palettes', + en: 'US color palettes', + }, + }, + dataEndpoint: `${process.env.US_ENDPOINT || sources.us}/v1/color-palettes`, + passedCredentials: {}, + }, + }; + }, + + redis: null, + + iamResources: { + collection: { + roles: { + admin: 'datalens.collections.admin', + editor: 'datalens.collections.editor', + viewer: 'datalens.collections.viewer', + limitedViewer: 'datalens.collections.limitedViewer', + entryBindingCreator: 'datalens.collections.entryBindingCreator', + limitedEntryBindingCreator: 'datalens.collections.limitedEntryBindingCreator', + }, + }, + workbook: { + roles: { + admin: 'datalens.workbooks.admin', + editor: 'datalens.workbooks.editor', + viewer: 'datalens.workbooks.viewer', + limitedViewer: 'datalens.workbooks.limitedViewer', + }, + }, + sharedEntry: { + roles: { + admin: 'datalens.sharedEntries.admin', + editor: 'datalens.sharedEntries.editor', + viewer: 'datalens.sharedEntries.viewer', + limitedViewer: 'datalens.sharedEntries.limitedViewer', + entryBindingCreator: 'datalens.sharedEntries.entryBindingCreator', + limitedEntryBindingCreator: 'datalens.sharedEntries.limitedEntryBindingCreator', + }, + }, + }, + + orderedAuthRoles: [UserRole.Admin, UserRole.Editor, UserRole.Viewer], + + chartsEngineConfig: { + secrets: {}, + enableTelemetry: true, + usEndpointPostfix: '', + dataFetcherProxiedHeaders: [DL_CONTEXT_HEADER], + }, + + chartkitSettings: { + highcharts: { + enabled: isTrueArg(process.env.HC), + external: true, + domain: process.env.HC_ENDPOINT || 'code.highcharts.com', + protocol: process.env.HC_PROTOCOL || 'https', + modules: process.env.HC_MODULES + ? process.env.HC_MODULES.split(',').map((m) => m.trim()) + : [ + 'exporting', + 'export-data', + 'stock', + 'solid-gauge', + 'funnel', + 'histogram-bellcurve', + 'sankey', + 'heatmap', + 'treemap', + 'variwide', + 'streamgraph', + 'drilldown', + 'parallel-coordinates', + 'pattern-fill', + 'wordcloud', + 'xrange', + 'networkgraph', + 'timeline', + 'bullet', + 'annotations', + 'series-label', + 'venn', + ], + version: '8.2.2', + }, + yandexMap: { + enabled: isTrueArg(process.env.YANDEX_MAP_ENABLED), + token: process.env.YANDEX_MAP_TOKEN, + }, + }, + defaultColorPaletteId: PALETTE_ID.DEFAULT_20, + + appSensitiveKeys: sensitiveHeaders, + appSensitiveHeaders: sensitiveHeaders, + + // auth + isAuthEnabled: isTrueArg(process.env.AUTH_ENABLED), + authTokenPublicKey: getEnvCert(process.env.AUTH_TOKEN_PUBLIC_KEY), + authManageLocalUsersDisabled: isTrueArg(process.env.AUTH_MANAGE_LOCAL_USERS_DISABLED), + authSignupDisabled: isTrueArg(process.env.AUTH_SIGNUP_DISABLED), + authCookieName: process.env.AUTH_COOKIE_NAME, + + apiPrefix: '/api', +} satisfies Partial; diff --git a/src/server/configs/opensource/development.ts b/src/server/configs/opensource/development.ts new file mode 100644 index 0000000..c773f6a --- /dev/null +++ b/src/server/configs/opensource/development.ts @@ -0,0 +1,5 @@ +export default { + // DATALENS MODE + + csp: 'disabled', +}; diff --git a/src/server/configs/secrets.js b/src/server/configs/secrets.js new file mode 100644 index 0000000..db06daa --- /dev/null +++ b/src/server/configs/secrets.js @@ -0,0 +1,5 @@ +module.exports = { + POSTGRES_DSN: '', + ROBOT_OAUTH_TOKEN: process.env.ROBOT_OAUTH_TOKEN || '', + REDIS_PWD: process.env.REDIS_PWD || '', +}; diff --git a/src/server/configs/shared/control-dash-chart-template.ts b/src/server/configs/shared/control-dash-chart-template.ts new file mode 100644 index 0000000..3c454b3 --- /dev/null +++ b/src/server/configs/shared/control-dash-chart-template.ts @@ -0,0 +1,14 @@ +import {ControlType} from '../../../shared/types'; + +export default { + module: 'libs/control/v1', + identifyParams: () => { + return {}; + }, + identifyChartType: () => { + return ControlType.Dash; + }, + identifyLinks: () => { + return {}; + }, +}; diff --git a/src/server/configs/shared/datalens-chart-template.ts b/src/server/configs/shared/datalens-chart-template.ts new file mode 100644 index 0000000..1f66379 --- /dev/null +++ b/src/server/configs/shared/datalens-chart-template.ts @@ -0,0 +1,80 @@ +import type {Request} from '@gravity-ui/expresskit'; + +import type {ExtendedChartsConfig} from '../../../shared'; +import { + Feature, + WizardType, + WizardVisualizationId, + getDatasetLinks, + isGravityChartsVisualization, + mapChartsConfigToLatestVersion, +} from '../../../shared'; + +export default { + module: 'libs/datalens/v3', + identifyParams: () => { + return {}; + }, + identifyChartType: (chart: ExtendedChartsConfig, req: Request) => { + const config = mapChartsConfigToLatestVersion(chart); + let visualizationId; + + if (config.visualization?.id && /[a-zA-Z]+/.test(chart.visualization.id)) { + visualizationId = config.visualization.id; + } else { + throw new Error('UNABLE_TO_IDENTIFY_CHART_TYPE'); + } + + const {ctx} = req; + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + const features = { + GravityChartsForPieAndTreemap: isEnabledServerFeature( + Feature.GravityChartsForPieAndTreemap, + ), + GravityChartsForBarYAndScatter: isEnabledServerFeature( + Feature.GravityChartsForBarYAndScatter, + ), + GravityChartsForLineAreaAndBarX: isEnabledServerFeature( + Feature.GravityChartsForLineAreaAndBarX, + ), + }; + + if ( + isGravityChartsVisualization({id: visualizationId as WizardVisualizationId, features}) + ) { + return WizardType.GravityChartsWizardNode; + } + + switch (visualizationId) { + case WizardVisualizationId.FlatTable: + case WizardVisualizationId.PivotTable: { + return 'table_wizard_node'; + } + case WizardVisualizationId.Geolayer: + case 'geopoint': + case 'geopolygon': + case 'heatmap': { + return 'ymap_wizard_node'; + } + case WizardVisualizationId.Metric: { + const {placeholders} = config.visualization; + // @ts-ignore will be removed after migration to v5 + const dataType = placeholders.find((p) => p.id === 'measures')?.items[0]?.data_type; + const useMarkup = dataType === 'markup'; + + if (useMarkup) { + return 'markup_wizard_node'; + } else { + return 'metric_wizard_node'; + } + } + default: { + return 'graph_wizard_node'; + } + } + }, + identifyLinks: (chart: ExtendedChartsConfig) => { + const config = mapChartsConfigToLatestVersion(chart); + return getDatasetLinks(config); + }, +}; diff --git a/src/server/configs/shared/ql-chart-template.ts b/src/server/configs/shared/ql-chart-template.ts new file mode 100644 index 0000000..eadbc34 --- /dev/null +++ b/src/server/configs/shared/ql-chart-template.ts @@ -0,0 +1,106 @@ +import type {Request} from '@gravity-ui/expresskit'; + +import type {ServerI18n} from '../../../i18n/types'; +import type {QlExtendedConfig} from '../../../shared'; +import { + Feature, + QL_TYPE, + WizardVisualizationId, + isGravityChartsVisualization, + isMonitoringOrPrometheusChart, +} from '../../../shared'; +import {mapQlConfigToLatestVersion} from '../../../shared/modules/config/ql'; +import {getTranslationFn} from '../../../shared/modules/language'; +import {identifyParams} from '../../modes/charts/plugins/ql/utils/identify-params'; + +export default { + module: 'libs/qlchart/v1', + identifyParams: (chart: QlExtendedConfig, req: Request) => { + const i18nServer: ServerI18n = req.ctx.get('i18n'); + const getTranslation = getTranslationFn(i18nServer.getI18nServer()); + return identifyParams({chart, getTranslation}); + }, + identifyChartType: (chart: QlExtendedConfig, req: Request) => { + const i18nServer: ServerI18n = req.ctx.get('i18n'); + + const config = mapQlConfigToLatestVersion(chart, { + i18n: getTranslationFn(i18nServer.getI18nServer()), + }); + + const {visualization, chartType} = config; + const id = visualization.id; + + const {ctx} = req; + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + const features = { + GravityChartsForPieAndTreemap: isEnabledServerFeature( + Feature.GravityChartsForPieAndTreemap, + ), + GravityChartsForBarYAndScatter: isEnabledServerFeature( + Feature.GravityChartsForBarYAndScatter, + ), + GravityChartsForLineAreaAndBarX: isEnabledServerFeature( + Feature.GravityChartsForLineAreaAndBarX, + ), + }; + + switch (id) { + case 'table': // Legacy + case WizardVisualizationId.FlatTable: // Available with WizardQLCommonVisualization feature + return QL_TYPE.TABLE_QL_NODE; + + case WizardVisualizationId.Line: + case WizardVisualizationId.Area: + case WizardVisualizationId.Area100p: + case WizardVisualizationId.Column: + case WizardVisualizationId.Column100p: + if (isMonitoringOrPrometheusChart(chartType)) { + return QL_TYPE.TIMESERIES_QL_NODE; + } + + if (isGravityChartsVisualization({id, features})) { + return QL_TYPE.D3_QL_NODE; + } + + return QL_TYPE.GRAPH_QL_NODE; + case WizardVisualizationId.Metric: { + const {placeholders} = chart.visualization; + + let useMarkup; + + if (placeholders) { + const dataType = placeholders.find((p) => p.id === 'measures')?.items[0] + ?.data_type; + + useMarkup = dataType === 'markup'; + } else { + // Case for legacy ql charts before integration with wizard + useMarkup = true; + } + + if (useMarkup) { + return QL_TYPE.MARKUP_QL_NODE; + } else { + return QL_TYPE.METRIC_QL_NODE; + } + } + default: { + if (isGravityChartsVisualization({id, features})) { + return QL_TYPE.D3_QL_NODE; + } + + return QL_TYPE.GRAPH_QL_NODE; + } + } + }, + identifyLinks: (chart: QlExtendedConfig, req: Request) => { + const i18nServer: ServerI18n = req.ctx.get('i18n'); + + const config = mapQlConfigToLatestVersion(chart, { + i18n: getTranslationFn(i18nServer.getI18nServer()), + }); + return { + connection: config.connection.entryId, + }; + }, +}; diff --git a/src/server/configs/sources-secrets.json b/src/server/configs/sources-secrets.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/server/configs/sources-secrets.json @@ -0,0 +1 @@ +{} diff --git a/src/server/constants/axios.ts b/src/server/constants/axios.ts new file mode 100644 index 0000000..f355f98 --- /dev/null +++ b/src/server/constants/axios.ts @@ -0,0 +1,3 @@ +import {getAgents} from '../../shared/server/axios'; + +export const IPV6_AXIOS_OPTIONS = getAgents({family: 6}); diff --git a/src/server/constants/index.ts b/src/server/constants/index.ts new file mode 100644 index 0000000..594b3a0 --- /dev/null +++ b/src/server/constants/index.ts @@ -0,0 +1,88 @@ +import type {ColorPalette, Palette} from '../../shared'; + +const DASH_API_BASE_URL = '/api/dash/v1/dashboards'; +const CHARTS_API_BASE_URL = '/api/charts/v1/charts'; + +const DASH_DEFAULT_NAMESPACE = 'default'; + +const DASH_ENTRY_RELEVANT_FIELDS = [ + 'entryId', + 'data', + 'key', + 'links', + 'meta', + 'permissions', + 'scope', + 'type', + 'public', + 'isFavorite', + 'annotation', + + 'createdAt', + 'createdBy', + 'updatedAt', + 'updatedBy', + + 'revId', + 'savedId', + 'publishedId', + + 'workbookId', +]; + +const BASE_PROJECT_NAME = 'datalens'; + +const BLOCK_STAT = { + RU: '28', + DATALENS: '3995', + EXTERNAL: '1105', + INTERNAL: '1649', + UI: '2819', + SERVER: '2443', +}; + +const WORLD_REGION = 10000; + +// if the palette is not specified or not found, it returns the default palette +export const selectServerPalette = (args: { + palette?: string; + availablePalettes: Record; + customColorPalettes?: Record; + defaultColorPaletteId: string; +}) => { + const { + defaultColorPaletteId, + palette: selectedPalleteId, + availablePalettes, + customColorPalettes, + } = args; + + if (selectedPalleteId) { + if (customColorPalettes?.[selectedPalleteId]) { + return customColorPalettes[selectedPalleteId].colors; + } + + if (availablePalettes?.[selectedPalleteId]) { + return availablePalettes[selectedPalleteId].scheme; + } + } + + if (customColorPalettes?.[defaultColorPaletteId]) { + return customColorPalettes[defaultColorPaletteId].colors; + } + + return availablePalettes?.[defaultColorPaletteId]?.scheme ?? []; +}; + +const SERVICE_NAME_DATALENS = 'DataLens'; + +export { + DASH_API_BASE_URL, + CHARTS_API_BASE_URL, + DASH_DEFAULT_NAMESPACE, + DASH_ENTRY_RELEVANT_FIELDS, + BASE_PROJECT_NAME, + BLOCK_STAT, + WORLD_REGION, + SERVICE_NAME_DATALENS, +}; diff --git a/src/server/constants/public-api.ts b/src/server/constants/public-api.ts new file mode 100644 index 0000000..105acce --- /dev/null +++ b/src/server/constants/public-api.ts @@ -0,0 +1 @@ +export const PUBLIC_API_ORG_ID_HEADER = 'x-dl-org-id'; diff --git a/src/server/controllers/api.ts b/src/server/controllers/api.ts new file mode 100644 index 0000000..254820e --- /dev/null +++ b/src/server/controllers/api.ts @@ -0,0 +1,31 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import {Utils} from '../components'; +import {registry} from '../registry'; +import type {DatalensGatewaySchemas} from '../types/gateway'; +import type {GatewayApiErrorResponse} from '../utils/gateway'; + +export const apiControllers = { + deleteLock: async (req: Request, res: Response) => { + try { + const {entryId, params} = req.body; + + const {gatewayApi} = registry.getGatewayApi(); + const {responseData} = await gatewayApi.us.deleteLock({ + authArgs: {iamToken: res.locals.iamToken}, + headers: Utils.pickHeaders(req), + ctx: req.ctx, + requestId: req.id, + args: { + entryId, + params, + }, + }); + + res.status(200).send(responseData); + } catch (ex) { + const {error} = ex as GatewayApiErrorResponse; + res.status(error.status).send(error); + } + }, +}; diff --git a/src/server/controllers/dl-main.ts b/src/server/controllers/dl-main.ts new file mode 100644 index 0000000..6c07bac --- /dev/null +++ b/src/server/controllers/dl-main.ts @@ -0,0 +1,10 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import {registry} from '../registry'; + +export const dlMainController = async (req: Request, res: Response) => { + const layoutConfig = await registry.useGetLayoutConfig({req, res, settingsId: 'dl-main'}); + + res.send(res.renderDatalensLayout(layoutConfig)); + return; +}; diff --git a/src/server/controllers/index.ts b/src/server/controllers/index.ts new file mode 100644 index 0000000..bc303a6 --- /dev/null +++ b/src/server/controllers/index.ts @@ -0,0 +1,13 @@ +import {apiControllers} from './api'; +import {dlMainController} from './dl-main'; +import {navigateController} from './navigate'; +import {navigationController} from './navigation'; +import {createPublicApiController} from './public-api'; + +export { + apiControllers, + dlMainController, + navigateController, + navigationController, + createPublicApiController, +}; diff --git a/src/server/controllers/navigate.ts b/src/server/controllers/navigate.ts new file mode 100644 index 0000000..ff1d4ce --- /dev/null +++ b/src/server/controllers/navigate.ts @@ -0,0 +1,85 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import {TENANT_ID_HEADER, isEntryId} from '../../shared'; +import {isEditorEntryType} from '../../shared/utils/entry'; +import {registry} from '../registry'; +import type {DatalensGatewaySchemas} from '../types/gateway'; +import Utils from '../utils'; +import type {GatewayApiErrorResponse} from '../utils/gateway'; + +function navigateDefault(reqPath: string, res: Response) { + return res.redirect(302, reqPath.replace('navigate', 'navigation')); +} + +// eslint-disable-next-line complexity +export const navigateController = async (req: Request, res: Response) => { + const {url: reqUrl} = req; + + req.ctx.log('Navigate init', {reqUrl}); + + try { + req.ctx.log('Navigate redirect', {reqUrl}); + + const possibleEntryId = req.params.entryId; + + if (!isEntryId(possibleEntryId)) { + req.ctx.log('Invalid entry id, navigate default', {reqUrl}); + + return navigateDefault(reqUrl, res); + } + + const {gatewayApi} = registry.getGatewayApi(); + + const {getAuthArgsUSPrivate} = registry.common.auth.getAll(); + + const {responseData: entryMeta} = await gatewayApi.usPrivate._getEntryMeta({ + ctx: req.ctx, + headers: { + ...req.headers, + [TENANT_ID_HEADER]: res.locals.currentTenantId, + ...(req.ctx.config.isAuthEnabled ? {...Utils.pickAuthHeaders(req)} : {}), + }, + requestId: req.id, + authArgs: getAuthArgsUSPrivate(req, res), + args: {entryId: possibleEntryId}, + }); + + if (entryMeta && entryMeta.scope === 'widget') { + const {type} = entryMeta; + if (type.includes('ql')) { + const qlUrl = reqUrl.replace('navigate', 'ql'); + + req.ctx.log('Navigate to ql', {qlUrl}); + + return res.redirect(302, qlUrl); + } else if (type.includes('wizard')) { + const wizardUrl = reqUrl.replace('navigate', 'wizard'); + + req.ctx.log('Navigate to wizard', {wizardUrl}); + + return res.redirect(302, wizardUrl); + } else if (isEditorEntryType(type)) { + const editorUrl = reqUrl.replace('navigate', 'editor'); + + req.ctx.log('Navigate to editor', {editorUrl}); + + return res.redirect(302, editorUrl); + } else { + req.ctx.log('Unknown widget type, navigate default', {reqUrl}); + + return navigateDefault(reqUrl, res); + } + } else { + req.ctx.log('Entry is not widget, navigate default', {reqUrl}); + + return navigateDefault(reqUrl, res); + } + } catch (error) { + req.ctx.logError( + 'Error occured in navigate', + ((error as GatewayApiErrorResponse).error || error) as Error, + ); + + return navigateDefault(reqUrl, res); + } +}; diff --git a/src/server/controllers/navigation.ts b/src/server/controllers/navigation.ts new file mode 100644 index 0000000..e0e0141 --- /dev/null +++ b/src/server/controllers/navigation.ts @@ -0,0 +1,63 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import {TENANT_ID_HEADER} from '../../shared'; +import {registry} from '../registry'; +import type {DatalensGatewaySchemas} from '../types/gateway'; +import Utils from '../utils'; +import type {GatewayApiErrorResponse} from '../utils/gateway'; + +/* eslint-disable consistent-return */ +export const navigationController = async (req: Request, res: Response): Promise => { + const {query, ctx} = req; + + const layoutConfig = await registry.useGetLayoutConfig({ + req, + res, + settingsId: 'navigation', + }); + + let entry; + + if (query && query.path) { + try { + const {currentTenantId} = res.locals; + + const key = (query.path as string).replace(/^\//, ''); + + ctx.log('GET_ENTRY_BY_KEY_REQUEST', {key}); + + const {gatewayApi} = registry.getGatewayApi(); + const {responseData} = await gatewayApi.us.getEntryByKey({ + ctx, + headers: { + ...req.headers, + [TENANT_ID_HEADER]: currentTenantId, + ...(req.ctx.config.isAuthEnabled ? {...Utils.pickAuthHeaders(req)} : {}), + }, + requestId: req.id, + authArgs: {iamToken: res.locals.iamToken}, + args: {key}, + }); + + entry = responseData; + + ctx.log('GET_ENTRY_BY_KEY_SUCCESS', {entryId: entry.entryId}); + } catch (e) { + const {error} = e as GatewayApiErrorResponse; + ctx.logError('GET_ENTRY_BY_KEY_FAILED', error); + + res.send(res.renderDatalensLayout(layoutConfig)); + + return; + } + } + + if (entry) { + const {handleEntryRedirect} = registry.common.functions.getAll(); + + return handleEntryRedirect(entry, res); + } else { + res.send(res.renderDatalensLayout(layoutConfig)); + return; + } +}; diff --git a/src/server/controllers/ping.ts b/src/server/controllers/ping.ts new file mode 100644 index 0000000..13ee988 --- /dev/null +++ b/src/server/controllers/ping.ts @@ -0,0 +1,5 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +export function ping(_: Request, res: Response) { + res.status(200).send('pong'); +} diff --git a/src/server/controllers/public-api/constants.ts b/src/server/controllers/public-api/constants.ts new file mode 100644 index 0000000..50978cd --- /dev/null +++ b/src/server/controllers/public-api/constants.ts @@ -0,0 +1,11 @@ +import {AppError} from '@gravity-ui/nodekit'; + +export class PublicApiError extends AppError {} + +export const PUBLIC_API_ERRORS = { + VALIDATION_ERROR: 'VALIDATION_ERROR', + ENDPOINT_NOT_FOUND: 'ENDPOINT_NOT_FOUND', + ACTION_CONFIG_NOT_FOUND: 'ACTION_CONFIG_NOT_FOUND', + INVALID_API_VERSION_HEADER: 'INVALID_API_VERSION_HEADER', + ACTION_VALIDATION_SCHEMA_NOT_FOUND: 'ACTION_VALIDATION_SCHEMA_NOT_FOUND', +} as const; diff --git a/src/server/controllers/public-api/index.ts b/src/server/controllers/public-api/index.ts new file mode 100644 index 0000000..a462d7d --- /dev/null +++ b/src/server/controllers/public-api/index.ts @@ -0,0 +1,157 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {SchemasByScope} from '@gravity-ui/gateway'; +import {AppError, REQUEST_ID_PARAM_NAME} from '@gravity-ui/nodekit'; + +import {getValidationSchema} from '../../../shared/schema/gateway-utils'; +import type {ChartsEngine} from '../../components/charts-engine'; +import {registerActionToOpenApi} from '../../components/public-api'; +import { + PUBLIC_API_ACTION_REQ_PARAM, + PUBLIC_API_VERSION_HEADER, +} from '../../components/public-api/constants'; +import {parsePublicApiVersionHeader} from '../../components/public-api/utils'; +import {registry} from '../../registry'; +import type {AnyApiServiceActionConfig} from '../../types/gateway'; +import Utils from '../../utils'; + +import {PUBLIC_API_ERRORS, PublicApiError} from './constants'; +import {prepareError, validateRequestBody} from './utils'; + +export const createPublicApiController = (chartsEngine?: ChartsEngine) => { + const {gatewayApi} = registry.getGatewayApi(); + const {baseConfig, getAuthArgs} = registry.getPublicApiConfig(); + + const schemasByScope = registry.getGatewaySchemasByScope(); + + const actionToPathMap = new Map(); + + Object.entries(gatewayApi).forEach(([serviceName, actions]) => { + Object.entries(actions).forEach(([actionName, action]) => { + actionToPathMap.set(action, {serviceName, actionName}); + }); + }); + + const actionToConfigMap = new Map(); + + Object.values(baseConfig).forEach(({actions, openApi: versionOpenApi}) => { + Object.entries(actions).forEach(([actionName, {resolve, openApi}]) => { + const gatewayAction = resolve(gatewayApi); + const pathObject = actionToPathMap.get(gatewayAction); + + if (!pathObject) { + throw new AppError( + `Public api config action "${actionName}" not found in gatewayApi.`, + ); + } + + const actionConfig = + schemasByScope.root[pathObject.serviceName].actions[pathObject.actionName]; + + actionToConfigMap.set(gatewayAction, actionConfig); + + registerActionToOpenApi({ + actionConfig, + actionName, + openApi, + openApiRegistry: versionOpenApi.registry, + }); + }); + }); + + const apiVersions = Object.keys(baseConfig).map(Number); + const latestApiVersion = Math.max(...apiVersions); + + return async function publicApiController(req: Request, res: Response) { + try { + const actionName = req.params[PUBLIC_API_ACTION_REQ_PARAM]; + + const version = parsePublicApiVersionHeader({ + versionHeader: req.headers[PUBLIC_API_VERSION_HEADER], + versions: apiVersions, + latestVersion: latestApiVersion, + }); + + if (version === null) { + throw new PublicApiError( + `Invalid or empty ${PUBLIC_API_VERSION_HEADER} header value`, + { + code: PUBLIC_API_ERRORS.INVALID_API_VERSION_HEADER, + }, + ); + } + + if (!actionName || !baseConfig[version].actions[actionName]) { + throw new PublicApiError(`Endpoint ${req.path} does not exist`, { + code: PUBLIC_API_ERRORS.ENDPOINT_NOT_FOUND, + }); + } + + const action = baseConfig[version].actions[actionName]; + + const {ctx} = req; + + const headers = Utils.pickPublicApiHeaders(req); + + const requestId = ctx.get(REQUEST_ID_PARAM_NAME) || ''; + + const gatewayAction = action.resolve(gatewayApi); + const gatewayActionConfig = actionToConfigMap.get(gatewayAction); + + if (!gatewayActionConfig) { + req.ctx.logError(`Couldn't find action config in actionToConfigMap`); + throw new PublicApiError(PUBLIC_API_ERRORS.ACTION_CONFIG_NOT_FOUND, { + code: PUBLIC_API_ERRORS.ACTION_CONFIG_NOT_FOUND, + }); + } + + const validationSchema = getValidationSchema(gatewayActionConfig); + + if (!validationSchema) { + req.ctx.logError(`Couldn't find action validation schema [${actionName}]`); + throw new PublicApiError(PUBLIC_API_ERRORS.ACTION_VALIDATION_SCHEMA_NOT_FOUND, { + code: PUBLIC_API_ERRORS.ACTION_VALIDATION_SCHEMA_NOT_FOUND, + }); + } + + const {paramsSchema} = validationSchema; + + if (action.rawAction) { + if (paramsSchema) { + await validateRequestBody(paramsSchema, req.body); + } + + await gatewayAction({ + headers, + args: {req, res, chartsEngine}, + ctx, + requestId, + authArgs: getAuthArgs?.(req, res), + }); + } else { + const validatedArgs = paramsSchema + ? await validateRequestBody(paramsSchema, req.body) + : undefined; + + const result = await gatewayAction({ + headers, + args: validatedArgs, + ctx, + requestId, + authArgs: getAuthArgs?.(req, res), + }); + + res.status(200).send(result.responseData); + } + } catch (err: unknown) { + const {status, message, code, details} = prepareError(err); + + res.status(status).send({ + status, + code, + message, + requestId: req.ctx.get(REQUEST_ID_PARAM_NAME) || '', + details, + }); + } + }; +}; diff --git a/src/server/controllers/public-api/utils.ts b/src/server/controllers/public-api/utils.ts new file mode 100644 index 0000000..f8d87e2 --- /dev/null +++ b/src/server/controllers/public-api/utils.ts @@ -0,0 +1,122 @@ +import {AppError} from '@gravity-ui/nodekit'; +import {AxiosError} from 'axios'; +import isObject from 'lodash/isObject'; +import type z from 'zod'; +import {ZodError} from 'zod'; + +import {ServerError} from '../../../shared/constants/error'; +import {isGatewayError} from '../../utils/gateway'; + +import {PUBLIC_API_ERRORS, PublicApiError} from './constants'; + +// eslint-disable-next-line complexity +export const prepareError = ( + error: unknown, +): {status: number; message: string; code?: string; details?: unknown} => { + if (error instanceof PublicApiError) { + const {code, message, details} = error; + + switch (code) { + case PUBLIC_API_ERRORS.VALIDATION_ERROR: { + return {status: 400, message, code, details}; + } + + case PUBLIC_API_ERRORS.ENDPOINT_NOT_FOUND: { + return {status: 404, message, code, details}; + } + + case PUBLIC_API_ERRORS.INVALID_API_VERSION_HEADER: { + return {status: 400, message, code}; + } + + default: { + return { + status: 500, + message: 'Internal server error', + }; + } + } + } + + if (isGatewayError(error)) { + const {error: innerError} = error; + + if (innerError.status !== 500) { + return { + status: innerError.status, + code: innerError.code, + message: innerError.message, + details: innerError.details, + }; + } + + const originalError = innerError.debug?.originalError; + + if (originalError instanceof AxiosError) { + const status = originalError.status ?? 500; + let message = originalError.message; + let code: string | undefined; + let details: unknown; + + const data = originalError.response?.data; + + if (isObject(data)) { + if ('message' in data && typeof data.message === 'string') { + message = data.message; + } + + if ('code' in data && typeof data.code === 'string') { + code = data.code; + } + + if ('details' in data) { + details = data.details; + } + } + + return {status, message, code, details}; + } + + if (originalError instanceof AppError) { + const message = originalError.message; + const code = originalError.code ? String(originalError.code) : undefined; + const details = originalError.details; + + return {status: innerError.status, message, code, details}; + } + + if (originalError instanceof ServerError) { + const {status, message, code, details} = originalError; + + return {status, message, code, details}; + } + + if ( + !(originalError instanceof TypeError) && + !(originalError instanceof ReferenceError) && + !(originalError instanceof SyntaxError) + ) { + return {status: innerError.status, message: innerError.message}; + } + } + + return { + status: 500, + message: 'Internal server error', + }; +}; + +export const validateRequestBody = async (schema: z.ZodType, data: unknown): Promise => { + try { + return await schema.parseAsync(data); + } catch (error) { + if (error instanceof ZodError) { + throw new PublicApiError('Validation error', { + code: PUBLIC_API_ERRORS.VALIDATION_ERROR, + details: error.issues, + }); + } + + throw error; + } +}; diff --git a/src/server/controllers/stats.js b/src/server/controllers/stats.js new file mode 100644 index 0000000..1e1ec00 --- /dev/null +++ b/src/server/controllers/stats.js @@ -0,0 +1,20 @@ +const getTime = () => new Date().toISOString().replace('T', ' ').split('.')[0]; + +module.exports = async (req, res) => { + try { + req.ctx.log('ChartKit stats collect', {rowsCount: req.body.length}); + + req.body.forEach((data) => { + req.ctx.stats('chartKitStats', { + datetime: getTime(), + ...data, + }); + }); + + return res.status(200).send({status: 'success', rowsCount: req.body.length}); + } catch (error) { + req.ctx.logError('ChartKit stats collect failed', error); + + return res.status(500).send({status: 'error', message: error.message, requestId: req.id}); + } +}; diff --git a/src/server/controllers/utils/handle-entry-redirect.ts b/src/server/controllers/utils/handle-entry-redirect.ts new file mode 100644 index 0000000..4eeea3a --- /dev/null +++ b/src/server/controllers/utils/handle-entry-redirect.ts @@ -0,0 +1,21 @@ +import type {Response} from '@gravity-ui/expresskit'; + +import {EntryScope} from '../../../shared'; +import type {GetEntryByKeyResponse} from '../../../shared/schema'; + +export function handleEntryRedirect(entry: GetEntryByKeyResponse, res: Response) { + switch (entry.scope) { + case EntryScope.Folder: + return res.redirect(`/navigation/${entry.entryId}`); + case EntryScope.Dataset: + return res.redirect(`/datasets/${entry.entryId}`); + case EntryScope.Widget: + return res.redirect(`/wizard/${entry.entryId}`); + case EntryScope.Dash: + return res.redirect(`/${entry.entryId}`); + case EntryScope.Connection: + return res.redirect(`/connections/${entry.entryId}`); + default: + return res.redirect('/navigation'); + } +} diff --git a/src/server/controllers/workbook-transfer.ts b/src/server/controllers/workbook-transfer.ts new file mode 100644 index 0000000..7dc7884 --- /dev/null +++ b/src/server/controllers/workbook-transfer.ts @@ -0,0 +1,349 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import {REQUEST_ID_PARAM_NAME} from '@gravity-ui/nodekit'; + +import type {DashEntry, TransferNotification} from '../../shared'; +import {EntryScope} from '../../shared'; +import {TransferCapabilities, TransferErrorCode} from '../../shared/constants/workbook-transfer'; +import type {EntryFieldData, EntryFieldLinks} from '../../shared/schema'; +import {Utils} from '../components'; +import {Dash} from '../components/sdk'; +import { + prepareExportChartData, + prepareImportChartData, +} from '../components/workbook-transfer/charts'; +import { + criticalTransferNotification, + warningTransferNotification, +} from '../components/workbook-transfer/create-transfer-notifications'; +import {prepareDashExportData, prepareDashImportData} from '../components/workbook-transfer/dash'; +import {registry} from '../registry'; +import type {DatalensGatewaySchemas} from '../types/gateway'; +import type {GatewayApiErrorResponse} from '../utils/gateway'; + +export const createExportResponseData = ( + notifications: TransferNotification[] = [], + entryData: Record | null = null, +) => { + return { + notifications, + entryData, + }; +}; + +export const createImportResponseData = ( + notifications: TransferNotification[] = [], + id: string | null = null, +) => { + return { + id, + notifications, + }; +}; + +export const sendResponse = ( + res: Response, + data: ReturnType, +) => { + res.status(200).send(data); +}; + +const getRequestId = (ctx: Request['ctx']) => ctx.get(REQUEST_ID_PARAM_NAME) || ''; + +export const proxyGetEntry = async ( + req: Request, + res: Response, + args: { + workbookId: string | null; + entryId: string; + }, +) => { + const {ctx} = req; + const {gatewayApi} = registry.getGatewayApi(); + + const headers = { + ...Utils.pickHeaders(req), + }; + const requestId = getRequestId(ctx); + + const {getAuthArgsProxyUSPrivate} = registry.common.auth.getAll(); + const authArgs = getAuthArgsProxyUSPrivate(req, res); + + try { + return await gatewayApi.usPrivate._proxyGetEntry({ + headers, + args: { + ...args, + branch: 'published', + }, + authArgs, + ctx, + requestId, + }); + } catch (ex) { + const {error} = ex as GatewayApiErrorResponse; + + // If failed to find published entry, at least take current saved + if (error.status === 404) { + return gatewayApi.usPrivate._proxyGetEntry({ + headers, + args, + authArgs, + ctx, + requestId, + }); + } + + throw ex; + } +}; + +const resolveScopeForEntryData = (entryData: Record) => { + return Object.values(EntryScope).find((key) => key in entryData); +}; + +export const prepareExportData = async (req: Request, res: Response) => { + const {ctx} = req; + const headers = { + ...Utils.pickHeaders(req), + }; + + const {gatewayApi} = registry.getGatewayApi(); + const {exportId, scope, idMapping} = req.body; + const workbookId = (req.body?.workbookId as string) ?? null; + + const {getAuthArgsProxyBIPrivate} = registry.common.auth.getAll(); + + switch (scope) { + case EntryScope.Dash: { + const {responseData: entry} = await proxyGetEntry(req, res, { + entryId: exportId, + workbookId, + }); + + if (entry.scope !== scope) { + return createExportResponseData([ + criticalTransferNotification(TransferErrorCode.TransferInvalidEntryData), + ]); + } + + const {dash, notifications} = prepareDashExportData( + Dash.migrateDescription(entry as unknown as DashEntry), + idMapping, + ); + + return createExportResponseData(notifications, { + dash, + }); + } + case EntryScope.Widget: { + const {responseData: entry} = await proxyGetEntry(req, res, { + entryId: exportId, + workbookId, + }); + + if (entry.scope !== scope) { + return createExportResponseData([ + criticalTransferNotification(TransferErrorCode.TransferInvalidEntryData), + ]); + } + + const {widget, notifications} = await prepareExportChartData(entry, idMapping); + + return createExportResponseData(notifications, {widget}); + } + case EntryScope.Connection: { + const {responseData} = await gatewayApi.bi._proxyExportConnection({ + headers, + args: { + connectionId: exportId, + workbookId, + }, + authArgs: getAuthArgsProxyBIPrivate(req, res), + ctx, + requestId: getRequestId(ctx), + }); + + return createExportResponseData(responseData.notifications, { + connection: responseData.connection, + }); + } + case EntryScope.Dataset: { + const {responseData} = await gatewayApi.bi._proxyExportDataset({ + headers, + args: { + datasetId: exportId, + idMapping, + workbookId, + }, + authArgs: getAuthArgsProxyBIPrivate(req, res), + ctx, + requestId: getRequestId(ctx), + }); + + return createExportResponseData(responseData.notifications, { + dataset: responseData.dataset, + }); + } + default: { + return createExportResponseData([ + criticalTransferNotification(TransferErrorCode.TransferInvalidEntryScope), + ]); + } + } +}; + +export const prepareImportData = async (req: Request, res: Response) => { + const {ctx} = req; + const headers = { + ...Utils.pickHeaders(req), + }; + + const {idMapping = {}, entryData = {}, workbookId} = req.body; + const scope = resolveScopeForEntryData(entryData); + + const {gatewayApi} = registry.getGatewayApi(); + + const {getAuthArgsProxyBIPrivate, getAuthArgsProxyUSPrivate} = registry.common.auth.getAll(); + + switch (scope) { + case EntryScope.Connection: { + const {responseData} = await gatewayApi.bi._proxyImportConnection({ + headers, + args: { + workbookId, + connection: entryData.connection, + }, + ctx, + requestId: getRequestId(ctx), + authArgs: getAuthArgsProxyBIPrivate(req, res), + }); + + return createImportResponseData(responseData.notifications, responseData.id); + } + case EntryScope.Dataset: { + const {responseData} = await gatewayApi.bi._proxyImportDataset({ + headers, + args: { + workbookId, + dataset: entryData.dataset, + idMapping, + }, + ctx, + requestId: getRequestId(ctx), + authArgs: getAuthArgsProxyBIPrivate(req, res), + }); + + return createImportResponseData(responseData.notifications, responseData.id); + } + case EntryScope.Widget: { + const {widget, notifications} = await prepareImportChartData( + entryData.widget, + req, + idMapping, + ); + + if (!widget) { + return createImportResponseData(notifications); + } + + const {responseData} = await gatewayApi.usPrivate._proxyCreateEntry({ + headers: { + ...headers, + metadata: ctx.getMetadata(), + }, + args: { + workbookId, + data: widget.data as EntryFieldData, + key: widget.key, + name: widget.name, + type: widget.type, + scope: widget.scope, + mode: widget.mode, + links: widget.links as EntryFieldLinks, + annotation: widget.annotation, + }, + ctx, + authArgs: getAuthArgsProxyUSPrivate(req, res), + requestId: getRequestId(ctx), + }); + + return createImportResponseData(notifications, responseData.entryId); + } + case EntryScope.Dash: { + const {dash, notifications} = prepareDashImportData(entryData.dash, idMapping); + + if (!dash) { + return createImportResponseData(notifications); + } + + const {data, annotation} = Dash.migrateDescription(dash); + + const {responseData} = await gatewayApi.usPrivate._proxyCreateEntry({ + headers, + args: { + workbookId, + data: data as unknown as EntryFieldData, + key: dash.key, + name: dash.name, + type: dash.type, + scope: dash.scope, + mode: dash.mode, + links: dash.links, + annotation: { + description: annotation?.description ?? '', + }, + }, + ctx, + authArgs: getAuthArgsProxyUSPrivate(req, res), + requestId: getRequestId(ctx), + }); + + return createImportResponseData(notifications, responseData.entryId); + } + default: { + return createImportResponseData([ + warningTransferNotification(TransferErrorCode.TransferInvalidEntryScope), + ]); + } + } +}; + +export const workbooksTransferController = { + capabilities: async (_: Request, res: Response) => { + res.status(200).send(TransferCapabilities); + }, + export: async (req: Request, res: Response) => { + try { + const {hasValidWorkbookTransferAuthHeaders} = registry.common.auth.getAll(); + + if (!(await hasValidWorkbookTransferAuthHeaders(req))) { + res.status(403).send({ + code: TransferErrorCode.TransferInvalidToken, + }); + return; + } + + sendResponse(res, await prepareExportData(req, res)); + } catch (ex) { + const {error} = ex as GatewayApiErrorResponse; + res.status(error?.status || 500).send(error); + } + }, + import: async (req: Request, res: Response) => { + try { + const {hasValidWorkbookTransferAuthHeaders} = registry.common.auth.getAll(); + + if (!(await hasValidWorkbookTransferAuthHeaders(req))) { + res.status(403).send({ + code: TransferErrorCode.TransferInvalidToken, + }); + return; + } + + sendResponse(res, await prepareImportData(req, res)); + } catch (ex) { + const {error} = ex as GatewayApiErrorResponse; + res.status(error?.status || 500).send(error); + } + }, +}; diff --git a/src/server/controllers/xlsx-converter.ts b/src/server/controllers/xlsx-converter.ts new file mode 100644 index 0000000..db44e59 --- /dev/null +++ b/src/server/controllers/xlsx-converter.ts @@ -0,0 +1,134 @@ +import fs from 'fs'; +import {unlink} from 'fs/promises'; + +import XLSX from '@datalens-tech/xlsx'; +import {dateTime} from '@gravity-ui/date-utils'; +import type {Request, Response} from '@gravity-ui/expresskit'; +import {isObject} from 'lodash'; +import mime from 'mime'; +import {v4 as uuidv4} from 'uuid'; + +import type {Graph} from '../components/charts-engine/components/processor/comments-fetcher'; + +const XLS_DATA_LIMIT = 1024 * 1024 * 50; // 50MB +const MAX_EXCEL_CELL_LENGTH = 32767; + +type Cell = {x: number; t: string; v: Date}; +type Row = (number | Date | string | Cell)[]; + +function isCell(cellData: unknown): cellData is Cell { + return isObject(cellData) && 't' in cellData && 'v' in cellData; +} + +export function xlsxConverter( + req: Request, + res: Response, + chartData: { + categories_ms?: number[]; + categories?: string[] | number[]; + graphs: Graph[]; + }, + dataArray: number[], + downloadConfig: { + filename: string; + }, +) { + const {ctx} = req; + ctx.log('EXPORT_XLS'); + const reqDataLength = req.body.data.length; + + if (reqDataLength > XLS_DATA_LIMIT) { + ctx.logError(`EXPORT_XLS_DATA_LIMIT_ERROR`, { + bytes: reqDataLength, + }); + res.sendStatus(413); + return; + } + + const columns = []; + columns.push({wch: 15}); + + const titleRows = []; + + if (chartData.categories_ms || chartData.categories) { + titleRows.push(''); + } + + chartData.graphs.forEach((graph) => { + titleRows.push(graph.title); + columns.push({wch: 12}); + }); + + const rows: Row[] = []; + + ctx.log('EXPORT_XLS_START_PREPARING'); + + dataArray.forEach((item, i) => { + let row: Row; + if (item) { + row = chartData.categories_ms ? [new Date(item)] : [item]; + } else { + row = []; + } + + chartData.graphs.forEach((graph) => { + const dataItem = graph.data[i] as number[]; + if (dataItem && dataItem.length >= MAX_EXCEL_CELL_LENGTH) { + const cell = dataItem.slice(0, MAX_EXCEL_CELL_LENGTH - 3) + '...'; + (row as string[]).push(cell); + } else { + let cellData = graph.data[i] as Cell; + if (graph.type === 'diff' && Array.isArray(cellData)) { + cellData = cellData[0]; + } + + if (isCell(cellData) && cellData.t === 'd') { + cellData.v = dateTime({input: cellData.v}).utc().toDate(); + } + + row.push(cellData); + } + }); + + rows.push(row); + }); + ctx.log('EXPORT_XLS_FINISH_PREPARING'); + + ctx.log('EXPORT_XLS_ADD_WORKBOOK_SHEET'); + + const worksheet = XLSX.utils.json_to_sheet(rows, {dense: true, cellDates: true}); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Chart data'); + ctx.log('EXPORT_XLS_FINISH_ADD_WORKBOOK_SHEET'); + XLSX.utils.sheet_add_aoa(worksheet, [titleRows], {origin: 'A1'}); + worksheet['!cols'] = [...columns]; + + const mimeType = mime.lookup('.xlsx'); + + res.setHeader('Content-disposition', `attachment; filename=${downloadConfig.filename}.xlsx`); + res.setHeader('Content-type', mimeType); + const file = `/tmp/${uuidv4()}.xlsx`; + try { + XLSX.writeFileAsync(file, workbook, {}, () => { + ctx.log('EXPORT_XLS_FILE_IS_WRITTEN'); + + const fileStream = fs.createReadStream(file); + fileStream.pipe(res); + + fileStream.on('close', async (error: Error) => { + if (error) { + res.send(500); + ctx.log(`error ${error.message}`); + } + unlink(file).catch((e) => ctx.log(`error ${e.message}`)); + }); + }); + } catch (error) { + let errorMessage = 'Failed to do something exceptional'; + if (error instanceof Error) { + errorMessage = `error ${error.message}`; + } + ctx.log(errorMessage); + res.sendStatus(500); + } +} diff --git a/src/server/expresskit.ts b/src/server/expresskit.ts new file mode 100644 index 0000000..66e12cc --- /dev/null +++ b/src/server/expresskit.ts @@ -0,0 +1,26 @@ +import type {AppRoutes} from '@gravity-ui/expresskit'; +import {ExpressKit} from '@gravity-ui/expresskit'; +import type {NodeKit} from '@gravity-ui/nodekit'; + +import type {ExtendedAppRouteDescription} from './types/controllers'; + +export function getExpressKit({ + extendedRoutes, + nodekit, +}: { + extendedRoutes: Record; + nodekit: NodeKit; +}) { + const routes: AppRoutes = {}; + Object.keys(extendedRoutes).forEach((key) => { + const {route, guard, ...params} = extendedRoutes[key]; + if (guard) { + params.afterAuth = [...params.afterAuth, guard]; + } + routes[route] = params; + }); + + const app = new ExpressKit(nodekit, routes); + + return app; +} diff --git a/src/server/index.ts b/src/server/index.ts new file mode 100644 index 0000000..7ea96cf --- /dev/null +++ b/src/server/index.ts @@ -0,0 +1,49 @@ +import cluster from 'cluster'; + +// Without this import shared object is empty in runtime and it should be exactly here +import '../shared'; +import type {AppEnvironment} from '../shared/constants/common'; +import {getAppEndpointsConfig} from '../shared/endpoints'; + +import {appEnv} from './app-env'; +import {appAuth} from './components/auth/middlewares/auth'; +import {getOpensourceLayoutConfig} from './components/layout/opensource-layout-config'; +import {serverFeatureWithBoundedContext} from './middlewares'; +import {getConnectorToQlConnectionTypeMap} from './modes/charts/plugins/ql/utils/connection'; +import initOpensourceApp from './modes/opensource/app'; +import {nodekit} from './nodekit'; +import {registry} from './registry'; +import {registerAppPlugins} from './registry/utils/register-app-plugins'; + +registry.registerGetLayoutConfig(getOpensourceLayoutConfig); +registry.setupQLConnectionTypeMap(getConnectorToQlConnectionTypeMap()); + +registerAppPlugins(); + +nodekit.config.endpoints = getAppEndpointsConfig( + appEnv as AppEnvironment.Production | AppEnvironment.Development, +); + +if (nodekit.config.isAuthEnabled) { + nodekit.config.appAuthHandler = appAuth; +} + +nodekit.config.appAllowedLangs = nodekit.config.regionalEnvConfig?.allowLanguages; +nodekit.config.appDefaultLang = nodekit.config.regionalEnvConfig?.defaultLang; + +nodekit.config.appBeforeAuthMiddleware = [serverFeatureWithBoundedContext]; + +const app = initOpensourceApp(nodekit); +registry.setupApp(app); + +if (nodekit.config.workers && nodekit.config.workers > 1 && cluster.isPrimary) { + for (let i = 0; i < nodekit.config.workers; i++) { + cluster.fork(); + } +} else { + app.run(); +} + +if (process.env?.['LOCAL_DEV_PORT']) { + import('./local-dev'); +} diff --git a/src/server/lib/validation/index.ts b/src/server/lib/validation/index.ts new file mode 100644 index 0000000..d473cf6 --- /dev/null +++ b/src/server/lib/validation/index.ts @@ -0,0 +1,27 @@ +import type {Request} from '@gravity-ui/expresskit'; +import Ajv from 'ajv'; + +import type {ValidationConfig, ValidationResult} from './types'; + +const ajv = new Ajv(); + +export function checkValidation(req: Request, validate: ValidationConfig): ValidationResult { + let result: ValidationResult = {success: true}; + Object.keys(validate).every((propName: string): boolean => { + if (validate[propName]) { + const validator = ajv.compile(validate[propName]); + + if (!validator(req[propName as keyof Request])) { + result = { + success: false, + message: ajv.errorsText(validator.errors), + details: validator.errors, + }; + return false; + } + } + + return true; + }); + return result; +} diff --git a/src/server/lib/validation/types.ts b/src/server/lib/validation/types.ts new file mode 100644 index 0000000..c825379 --- /dev/null +++ b/src/server/lib/validation/types.ts @@ -0,0 +1,15 @@ +import type {ErrorObject} from 'ajv'; + +export type ValidationConfig = { + [key: string]: any; + + query?: Object; + params?: Object; + body?: Object; +}; + +export type ValidationResult = { + success: boolean; + message?: string; + details?: ErrorObject[] | null | undefined; +}; diff --git a/src/server/local-dev.ts b/src/server/local-dev.ts new file mode 100644 index 0000000..02aca49 --- /dev/null +++ b/src/server/local-dev.ts @@ -0,0 +1,50 @@ +import fs from 'fs'; +import http from 'http'; + +import httpProxy from 'http-proxy'; + +const proxy = httpProxy.createProxyServer({}); + +const port = process.env?.['LOCAL_DEV_PORT']; +const devClientPort = process.env?.['DEV_CLIENT_PORT']; +const devServerPort = process.env?.['DEV_SERVER_PORT']; + +http.createServer((req, res) => { + if (devClientPort && devServerPort) { + const target = req.url?.startsWith('/build') + ? `http://localhost:${devClientPort}` + : `http://localhost:${devServerPort}`; + + proxy.web( + req, + res, + { + // @ts-ignore + target, + ws: true, + }, + (err, _, errRes) => { + if (err) { + errRes.end('Proxy request error: ' + err.message); + } + }, + ); + } else { + const socketPath = req.url?.startsWith('/build') + ? './dist/run/client.sock' + : './dist/run/server.sock'; + + if (fs.existsSync(socketPath)) { + proxy.web(req, res, { + // @ts-ignore + target: { + socketPath, + }, + }); + } else { + res.writeHead(500); + res.write('Socket file missing'); + res.end(); + } + } +}).listen(port); diff --git a/src/server/middlewares/app-layout.ts b/src/server/middlewares/app-layout.ts new file mode 100644 index 0000000..382662a --- /dev/null +++ b/src/server/middlewares/app-layout.ts @@ -0,0 +1,20 @@ +import type {Plugin} from '@gravity-ui/app-layout'; +import {createRenderFunction} from '@gravity-ui/app-layout'; +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; + +import type {AppLayoutSettings} from '../types/app-layout'; + +type CreateAppLayoutMiddlewareArgs = { + plugins: Plugin[]; + getAppLayoutSettings: (req: Request, res: Response, name?: string) => AppLayoutSettings; +}; +export function createAppLayoutMiddleware(args: CreateAppLayoutMiddlewareArgs) { + const {plugins, getAppLayoutSettings} = args; + + return function appLayoutMiddleware(req: Request, res: Response, next: NextFunction) { + req.originalContext.set('getAppLayoutSettings', getAppLayoutSettings); + // eslint-disable-next-line no-param-reassign + res.renderDatalensLayout = createRenderFunction([...plugins]); + next(); + }; +} diff --git a/src/server/middlewares/before-auth-defaults.ts b/src/server/middlewares/before-auth-defaults.ts new file mode 100644 index 0000000..2a1cf9d --- /dev/null +++ b/src/server/middlewares/before-auth-defaults.ts @@ -0,0 +1,18 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; +import {USER_LANGUAGE_PARAM_NAME} from '@gravity-ui/nodekit'; + +import {createI18nInstance} from '../utils/language'; + +export async function beforeAuthDefaults(req: Request, res: Response, next: NextFunction) { + res.locals.userSettings = {}; + res.locals.userSettings.language = req.ctx.get(USER_LANGUAGE_PARAM_NAME); + res.locals.lang = req.ctx.get(USER_LANGUAGE_PARAM_NAME); + + // use `lang` from closure `res` object + // maybe changed after user auth middleware and fill `userSettings` + const i18n = createI18nInstance(res.locals as {lang: string}); + + req.originalContext.set('i18n', i18n); + + next(); +} diff --git a/src/server/middlewares/connector-icons.ts b/src/server/middlewares/connector-icons.ts new file mode 100644 index 0000000..fea3bb4 --- /dev/null +++ b/src/server/middlewares/connector-icons.ts @@ -0,0 +1,50 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; +import type {ApiActionConfig} from '@gravity-ui/gateway'; +import type {AppContext} from '@gravity-ui/nodekit'; +import merge from 'lodash/merge'; + +import type {ConnectorIconData, ListConnectorIconsResponse} from '../../shared/schema/types'; +import {registry} from '../registry'; +import type {DatalensGatewaySchemas} from '../types/gateway'; + +type ActionArgs = ApiActionConfig; +type GetAdditionalArgs = (req: Request, res: Response) => Partial; + +export function getConnectorIconsMiddleware({ + getAdditionalArgs, +}: {getAdditionalArgs?: GetAdditionalArgs} = {}) { + return async function connectorIconsMiddleware( + req: Request, + res: Response, + next: NextFunction, + ) { + let icons: ConnectorIconData[] = []; + + try { + req.ctx.log('REQUEST_CONNECTOR_ITEMS'); + const {gatewayApi} = registry.getGatewayApi(); + const actionArgs: ActionArgs = { + args: undefined, + ctx: req.ctx, + headers: {...req.headers}, + requestId: req.id, + }; + const additionalArgs = getAdditionalArgs?.(req, res); + + if (additionalArgs) { + merge(actionArgs, additionalArgs); + } + + ({ + responseData: {icons}, + } = await gatewayApi.bi.listConnectorIcons(actionArgs)); + } catch (e) { + req.ctx.logError('REQUEST_CONNECTOR_ITEMS_FAILED', e); + } + + // eslint-disable-next-line no-param-reassign + res.locals.connectorIcons = icons; + + next(); + }; +} diff --git a/src/server/middlewares/ctx.ts b/src/server/middlewares/ctx.ts new file mode 100644 index 0000000..63a499e --- /dev/null +++ b/src/server/middlewares/ctx.ts @@ -0,0 +1,22 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; +import {type AppContextParams, REQUEST_ID_PARAM_NAME} from '@gravity-ui/nodekit'; + +import {checkRequestForDeveloperModeAccess} from '../components/chart-editor-developer-mode-access-check'; +import {renderHTML} from '../components/charts-engine/components/markdown'; +import resolveEntryByLink from '../components/resolve-entry-by-link'; + +export function getCtxMiddleware() { + return async function ctxMiddleware(req: Request, _res: Response, next: NextFunction) { + req.originalContext.set('gateway', { + reqBody: req.body, + requestId: req.ctx.get(REQUEST_ID_PARAM_NAME), + + markdown: renderHTML, + resolveEntryByLink, + + checkRequestForDeveloperModeAccess, + } as unknown as AppContextParams['gateway']); + + next(); + }; +} diff --git a/src/server/middlewares/index.ts b/src/server/middlewares/index.ts new file mode 100644 index 0000000..71058fb --- /dev/null +++ b/src/server/middlewares/index.ts @@ -0,0 +1,19 @@ +import {createAppLayoutMiddleware} from './app-layout'; +import {beforeAuthDefaults} from './before-auth-defaults'; +import {getConnectorIconsMiddleware} from './connector-icons'; +import {getCtxMiddleware} from './ctx'; +import {patchLogger} from './patch-logger'; +import {scrRequests} from './scr-requests'; +import {serverFeatureWithBoundedContext} from './server-feature-with-bounded-context'; +import {xDlContext} from './x-dl-context'; + +export { + xDlContext, + scrRequests, + getCtxMiddleware, + beforeAuthDefaults, + serverFeatureWithBoundedContext, + patchLogger, + createAppLayoutMiddleware, + getConnectorIconsMiddleware, +}; diff --git a/src/server/middlewares/patch-logger.ts b/src/server/middlewares/patch-logger.ts new file mode 100644 index 0000000..be0e4cb --- /dev/null +++ b/src/server/middlewares/patch-logger.ts @@ -0,0 +1,21 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; + +export function patchLogger(req: Request, _res: Response, next: NextFunction) { + const logErrorCore = req.ctx.logError; + // It is suspicious that monkey patching is only for utils, because there is still ctx + req.ctx.logError = (message, error: any, extra) => { + if (error && error.response) { + if (error.response.request) { + delete error.response.request.headers; + } + + if (error.response.req) { + delete error.response.req.headers; + } + } + + logErrorCore(message, error, extra); + }; + + next(); +} diff --git a/src/server/middlewares/scr-requests.ts b/src/server/middlewares/scr-requests.ts new file mode 100644 index 0000000..9cd82eb --- /dev/null +++ b/src/server/middlewares/scr-requests.ts @@ -0,0 +1,13 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; + +import {SCR_USER_AGENT_HEADER_VALUE} from '../../shared'; + +export function scrRequests() { + return function scrRequestsMiddleware(req: Request, res: Response, next: NextFunction) { + if (req.headers['user-agent']?.includes(SCR_USER_AGENT_HEADER_VALUE)) { + res.locals.isRobot = true; + } + + next(); + }; +} diff --git a/src/server/middlewares/server-feature-with-bounded-context.ts b/src/server/middlewares/server-feature-with-bounded-context.ts new file mode 100644 index 0000000..374ebb6 --- /dev/null +++ b/src/server/middlewares/server-feature-with-bounded-context.ts @@ -0,0 +1,16 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; + +import {getEnabledServerFeatureWithBoundedContext} from '../../shared'; + +export async function serverFeatureWithBoundedContext( + req: Request, + _: Response, + next: NextFunction, +) { + req.originalContext.set( + 'isEnabledServerFeature', + getEnabledServerFeatureWithBoundedContext(req.originalContext), + ); + + next(); +} diff --git a/src/server/middlewares/tenant-settings.ts b/src/server/middlewares/tenant-settings.ts new file mode 100644 index 0000000..6a22dcd --- /dev/null +++ b/src/server/middlewares/tenant-settings.ts @@ -0,0 +1,44 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; +import {REQUEST_ID_PARAM_NAME} from '@gravity-ui/nodekit'; + +import {onFail} from '../callbacks'; +import {registry} from '../registry'; +import type {DatalensGatewaySchemas} from '../types/gateway'; + +export function getTenantSettingsMiddleware() { + async function resolveTenantSettings(req: Request, res: Response, next: NextFunction) { + const {ctx} = req; + + const requestId = req.ctx.get(REQUEST_ID_PARAM_NAME); + const currentTenantId = 'common'; + + const {gatewayApi} = registry.getGatewayApi(); + const {getAuthArgsUSPrivate} = registry.common.auth.getAll(); + const authArgsUSPrivate = getAuthArgsUSPrivate(req, res); + + const tenantDetailsResponce = await gatewayApi.usPrivate._getTenantDetails({ + ctx, + headers: req.headers, + requestId: requestId ?? '', + authArgs: authArgsUSPrivate, + args: {tenantId: currentTenantId}, + }); + const resolvedTenant = tenantDetailsResponce.responseData; + res.locals.tenantDefaultColorPaletteId = resolvedTenant.settings?.defaultColorPaletteId; + + next(); + } + + return function resolveTenantSettingsMiddleware( + req: Request, + res: Response, + next: NextFunction, + ) { + resolveTenantSettings(req, res, next) + .catch((error) => { + req.ctx.logError('TENANT_RESOLVED_FAILED', error); + onFail(req, res); + }) + .catch((error) => next(error)); + }; +} diff --git a/src/server/middlewares/x-dl-context.ts b/src/server/middlewares/x-dl-context.ts new file mode 100644 index 0000000..2f7a715 --- /dev/null +++ b/src/server/middlewares/x-dl-context.ts @@ -0,0 +1,71 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; + +import { + DASH_INFO_HEADER, + DISPLAY_MODE_HEADER, + DL_CONTEXT_HEADER, + REQUEST_ID_HEADER, + TENANT_ID_HEADER, +} from '../../shared'; + +type DlContext = Record; + +export function xDlContext({ + plugins = [], +}: { + plugins?: Array<(req: Request, context: DlContext) => DlContext>; +} = {}) { + return function xDlContextMiddleware(req: Request, _: Response, next: NextFunction) { + const {folderId: folderIdHeader} = req.ctx.config.headersMap; + + const folderId = req.headers[folderIdHeader]; + const tenantId = req.headers[TENANT_ID_HEADER]; + + const context: DlContext = { + remoteAddress: req.headers['x-real-ip'], + userAgent: req.headers['user-agent'], + folderId: folderId || tenantId, + tenantId, + requestId: req.headers[REQUEST_ID_HEADER], + referer: req.ctx.utils.redactSensitiveQueryParams(req.headers['referer']), + }; + + if (req.headers[DASH_INFO_HEADER]) { + const parsedDashInfo = new URLSearchParams( + (req.headers[DASH_INFO_HEADER] || '').toString(), + ); + + const dashId = parsedDashInfo.get('dashId'); + const dashTabId = parsedDashInfo.get('dashTabId'); + + if (dashId) { + context.dashId = dashId; + } + if (dashTabId) { + context.dashTabId = dashTabId; + } + } + + const dispayMode = req.headers[DISPLAY_MODE_HEADER]; + + if (dispayMode) { + context.displayMode = dispayMode; + } + + req.headers[DL_CONTEXT_HEADER] = JSON.stringify( + plugins.length > 0 + ? plugins.reduce( + (memo, plugin) => ({...memo, ...plugin(req, memo)}), + context, + ) + : context, + ); + + req.originalContext.set( + 'tenantId', + tenantId && Array.isArray(tenantId) ? tenantId.join(',') : tenantId, + ); + + next(); + }; +} diff --git a/src/server/modes/charts/init-charts-engine.ts b/src/server/modes/charts/init-charts-engine.ts new file mode 100644 index 0000000..dd78c12 --- /dev/null +++ b/src/server/modes/charts/init-charts-engine.ts @@ -0,0 +1,109 @@ +import type {AppMiddleware, Request, Response} from '@gravity-ui/expresskit'; +import type {AppConfig, AppContext} from '@gravity-ui/nodekit'; + +import {AppEnvironment} from '../../../shared'; +import CacheClient from '../../components/cache-client'; +import {ChartsEngine} from '../../components/charts-engine'; +import {getDefaultRunners} from '../../components/charts-engine/runners'; +import type {Plugin} from '../../components/charts-engine/types'; +import {checkValidation} from '../../lib/validation'; +import type {ExtendedAppRouteDescription} from '../../types/controllers'; + +import {getTelemetryCallbacks} from './telemetry'; + +export function initChartsEngine({ + plugins, + ctx, + config, + beforeAuth, + afterAuth, +}: { + plugins: Plugin[]; + ctx: AppContext; + config: AppConfig; + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; +}) { + const telemetryCallbacks = getTelemetryCallbacks(ctx); + const {appEnv, endpoints, chartsEngineConfig} = config; + if (!appEnv) { + throw new Error('App environment is not defined'); + } + + const typedAppEnv = appEnv as AppEnvironment; + + if (!Object.values(AppEnvironment).includes(typedAppEnv)) { + throw new Error(`Unknown app environment: ${appEnv}`); + } + + config.sources = config.getSourcesByEnv(typedAppEnv); + config.usEndpoint = endpoints.api.us + chartsEngineConfig.usEndpointPostfix; + + const cacheClient = new CacheClient({config}); + + return new ChartsEngine({ + config, + secrets: chartsEngineConfig.secrets, + flags: chartsEngineConfig.flags, + plugins, + telemetryCallbacks: chartsEngineConfig.enableTelemetry ? telemetryCallbacks : undefined, + cacheClient, + beforeAuth, + afterAuth, + runners: getDefaultRunners(), + }); +} + +export function applyPluginRoutes({ + chartsEngine, + routes, + beforeAuth, + afterAuth, +}: { + chartsEngine: ChartsEngine; + routes: Record; + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; +}) { + chartsEngine.plugins.forEach((plugin) => { + const pluginRoutes = plugin.routes || []; + + pluginRoutes.forEach((pluginRoute) => { + const routeValidationConfig = pluginRoute.validationConfig; + let handler = pluginRoute.handler; + if (routeValidationConfig) { + handler = (req: Request, res: Response) => { + const validationResult = checkValidation(req, routeValidationConfig); + if (!validationResult.success) { + res.status(400).send({ + message: validationResult.message, + details: validationResult.details, + }); + return; + } + return pluginRoute.handler(req, res); + }; + } + + const appRoute: Omit = { + handler, + handlerName: pluginRoute.handlerName, + beforeAuth, + afterAuth, + }; + + if (pluginRoute.authPolicy) { + appRoute.authPolicy = pluginRoute.authPolicy; + } + + if (typeof pluginRoute.disableCsrf !== 'undefined') { + appRoute.disableCsrf = pluginRoute.disableCsrf; + } + + routes[`${pluginRoute.method} ${pluginRoute.path}`] = { + ...appRoute, + route: `${pluginRoute.method} ${pluginRoute.path}`, + }; + }); + }); +} diff --git a/src/server/modes/charts/plugins/constants/middleware-urls.ts b/src/server/modes/charts/plugins/constants/middleware-urls.ts new file mode 100644 index 0000000..4ef7991 --- /dev/null +++ b/src/server/modes/charts/plugins/constants/middleware-urls.ts @@ -0,0 +1,4 @@ +export const REQUEST_WITH_DATASET_SOURCE_NAME = '/_request_with_dataset/'; + +export const CHARTS_MIDDLEWARE_URL_TYPE = 'charts_with_dataset'; +export const CONTROL_MIDDLEWARE_URL_TYPE = 'control_with_dataset'; diff --git a/src/server/modes/charts/plugins/constants/misc.ts b/src/server/modes/charts/plugins/constants/misc.ts new file mode 100644 index 0000000..490b3af --- /dev/null +++ b/src/server/modes/charts/plugins/constants/misc.ts @@ -0,0 +1,14 @@ +export const DATASET_ID_KEY_PLACEHOLDER = '{datasetIdKey}'; +export const LAYER_ID_KEY_PLACEHOLDER = '_{layerIdKey}'; + +export const DATASET_ID_LAYER_ID_SEPARATOR = '_result'; + +export const DATASET_ID_KEY_TEMPLATE = '{datasetId}'; +export const LAYER_ID_KEY_TEMPLATE = '{layerId}'; + +export const DATASET_ID_LAYER_ID_KEY_TEMPLATE = '{datasetIdKey}_result_{layerIdKey}'; + +export const TABLE_TOTALS_STYLES = { + 'background-color': 'var(--g-color-base-generic)', + 'font-weight': 500, +}; diff --git a/src/server/modes/charts/plugins/control/constants/misc.ts b/src/server/modes/charts/plugins/control/constants/misc.ts new file mode 100644 index 0000000..8f16564 --- /dev/null +++ b/src/server/modes/charts/plugins/control/constants/misc.ts @@ -0,0 +1 @@ +export const DAY = 24 * 60 * 60 * 1000; diff --git a/src/server/modes/charts/plugins/control/controls/index.ts b/src/server/modes/charts/plugins/control/controls/index.ts new file mode 100644 index 0000000..98a8718 --- /dev/null +++ b/src/server/modes/charts/plugins/control/controls/index.ts @@ -0,0 +1,5 @@ +import type {ControlShared} from '../types'; + +export const buildUI = ({shared}: {shared: ControlShared}) => { + return [shared.uiControl]; +}; diff --git a/src/server/modes/charts/plugins/control/helpers/misc.ts b/src/server/modes/charts/plugins/control/helpers/misc.ts new file mode 100644 index 0000000..41a0b21 --- /dev/null +++ b/src/server/modes/charts/plugins/control/helpers/misc.ts @@ -0,0 +1,31 @@ +import {DAY} from '../constants/misc'; + +export function getISO(date: Date) { + return date.toISOString(); +} + +export function getISOFromToday(daysOffset = 0) { + return getISO(new Date(Date.now() - daysOffset * DAY)); +} + +type FormatRelativeRangeDatePayload = {from: number; to: number}; + +export function formatRelativeRangeDate(value: FormatRelativeRangeDatePayload) { + const {from, to} = value; + + const fromDate = getISOFromToday(Number(from)); + const toDate = getISOFromToday(Number(to)); + + return fromDate && toDate ? `__interval_${fromDate}_${toDate}` : ''; +} + +type FormatIntervalRangeDatePayload = {from: string; to: string}; + +export function formatIntervalRangeDate(value: FormatIntervalRangeDatePayload) { + const {from, to} = value; + + const fromDate = from ? from : '0001-01-01'; + const toDate = to ? to : '9999-12-31'; + + return fromDate && toDate ? `__interval_${fromDate}_${toDate}` : ''; +} diff --git a/src/server/modes/charts/plugins/control/index.ts b/src/server/modes/charts/plugins/control/index.ts new file mode 100644 index 0000000..f88dd2b --- /dev/null +++ b/src/server/modes/charts/plugins/control/index.ts @@ -0,0 +1,20 @@ +import {buildUI} from './controls'; +import {buildGraph} from './js'; +import {buildSources} from './url'; + +export default { + // Url + buildSources, + // JavaScript + buildGraph, + // Controls + buildUI, + // Config + buildChartsConfig: () => { + return {}; + }, + // Highcharts + buildHighchartsConfig: () => { + return {}; + }, +}; diff --git a/src/server/modes/charts/plugins/control/js/helpers/dataset/process-dataset-content.ts b/src/server/modes/charts/plugins/control/js/helpers/dataset/process-dataset-content.ts new file mode 100644 index 0000000..158a387 --- /dev/null +++ b/src/server/modes/charts/plugins/control/js/helpers/dataset/process-dataset-content.ts @@ -0,0 +1,61 @@ +import type {ApiV2ResultData, ApiV2ResultDataRow} from '../../../../../../../../shared'; +import {DashTabItemControlElementType} from '../../../../../../../../shared'; +import type {ControlShared} from '../../../types'; + +type ProcessDistinctsContentArgs = { + shared: ControlShared; + distincts: ApiV2ResultData; +}; + +const processDistinctsContent = ({ + shared, + distincts, +}: ProcessDistinctsContentArgs): ControlShared['content'] => { + // https://stackoverflow.com/questions/40107588 numeric collation doesn't work correctly with float type + const needCollator = shared.source.fieldType !== 'float'; + + const collator = new Intl.Collator(undefined, { + numeric: true, + sensitivity: 'base', + }); + + const mappedDistincts = distincts.result_data[0].rows.map((row: ApiV2ResultDataRow) => { + const value = row.data[0]; + return {title: value, value}; + }); + + mappedDistincts.sort((a: {title: string}, b: {title: string}) => { + return needCollator + ? collator.compare(a.title, b.title) + : Number(a.title) - Number(b.title); + }); + + return mappedDistincts; +}; + +const processAcceptableValuesContent = (shared: ControlShared) => + shared.source.acceptableValues as ControlShared['content']; + +const processEmptyContent = (): ControlShared['content'] => []; + +type ProcessDatasetSourceTypeContentArgs = { + shared: ControlShared; + distincts: ApiV2ResultData | undefined; +}; + +export const processDatasetSourceTypeContent = ({ + shared, + distincts, +}: ProcessDatasetSourceTypeContentArgs): ControlShared['content'] => { + const {elementType} = shared.source; + + if (distincts && elementType !== DashTabItemControlElementType.Date) { + return processDistinctsContent({shared, distincts}); + } + + if (elementType === DashTabItemControlElementType.Input) { + return processAcceptableValuesContent(shared); + } + + return processEmptyContent(); +}; diff --git a/src/server/modes/charts/plugins/control/js/helpers/dataset/process-fields.ts b/src/server/modes/charts/plugins/control/js/helpers/dataset/process-fields.ts new file mode 100644 index 0000000..be585f6 --- /dev/null +++ b/src/server/modes/charts/plugins/control/js/helpers/dataset/process-fields.ts @@ -0,0 +1,52 @@ +import type {IChartEditor} from '../../../../../../../../shared'; +import type {ControlDatasetFields} from '../../types'; + +const prepareDatasetFields = (fields: ControlDatasetFields) => { + const datasetFields: Record = {}; + const paramsFromDataset: Record = {}; + + fields.fields.forEach((field) => { + datasetFields[field.guid] = field.title; + paramsFromDataset[field.guid] = ''; + }); + + const datasetFieldsList = fields.fields.map((field) => { + const {guid} = field; + + return { + title: field.title, + guid, + dataType: field.data_type, + calc_mode: field.calc_mode, + fieldType: field.type, + }; + }); + + return { + datasetFieldsList, + paramsFromDataset, + datasetFields, + }; +}; + +export const processDatasetFields = ( + datasetId: string, + fields: ControlDatasetFields | undefined, + ChartEditor: IChartEditor, +) => { + if (!fields) { + return; + } + + const {datasetFieldsList, paramsFromDataset, datasetFields} = prepareDatasetFields(fields); + + ChartEditor.setExtra('datasets', [ + { + fields: datasetFields, + fieldsList: datasetFieldsList, + id: datasetId, + }, + ]); + + ChartEditor.updateParams(paramsFromDataset); +}; diff --git a/src/server/modes/charts/plugins/control/js/helpers/process-content.ts b/src/server/modes/charts/plugins/control/js/helpers/process-content.ts new file mode 100644 index 0000000..73e33ff --- /dev/null +++ b/src/server/modes/charts/plugins/control/js/helpers/process-content.ts @@ -0,0 +1,42 @@ +import { + DashTabItemControlSourceType, + type IChartEditor, + type StringParams, +} from '../../../../../../../shared'; +import {extractTypedQueryParams} from '../../../../../../../shared/modules/typed-query-api/helpers/parameters'; +import type {ControlShared} from '../../types'; +import type {SourceResponseData} from '../types'; + +import {processDatasetSourceTypeContent} from './dataset/process-dataset-content'; +import {processDatasetFields} from './dataset/process-fields'; +import {processTypedQueryContent} from './typed-query/process-typed-query-content'; +import {processTypedQueryParameters} from './typed-query/process-typed-query-parameters'; + +type ProcessContentArgs = { + data: SourceResponseData; + shared: ControlShared; + ChartEditor: IChartEditor; + params: StringParams; +}; +export const processContent = (args: ProcessContentArgs): ControlShared['content'] => { + const {data, shared, ChartEditor, params} = args; + const {sourceType, source, content, param} = shared; + + switch (sourceType) { + case DashTabItemControlSourceType.Dataset: { + processDatasetFields(source.datasetId, data.fields, ChartEditor); + + return processDatasetSourceTypeContent({shared, distincts: data.distincts}); + } + case DashTabItemControlSourceType.Connection: { + processTypedQueryParameters({ + ChartEditor, + parameters: extractTypedQueryParams(params, param), + }); + return processTypedQueryContent(data.connectionDistincts); + } + default: { + return content || []; + } + } +}; diff --git a/src/server/modes/charts/plugins/control/js/helpers/typed-query/process-typed-query-content.ts b/src/server/modes/charts/plugins/control/js/helpers/typed-query/process-typed-query-content.ts new file mode 100644 index 0000000..66f9c62 --- /dev/null +++ b/src/server/modes/charts/plugins/control/js/helpers/typed-query/process-typed-query-content.ts @@ -0,0 +1,11 @@ +import type {ConnectionTypedQueryApiResponse} from '../../../../../../../../shared'; +import {getControlDisticntsFromRows} from '../../../../../../../../shared/modules/control/typed-query-helpers'; +import type {ControlShared} from '../../../types'; + +export const processTypedQueryContent = ( + distincts: ConnectionTypedQueryApiResponse | undefined, +): ControlShared['content'] => { + const rows = distincts?.data?.rows || []; + + return getControlDisticntsFromRows(rows).map((v) => ({title: v, value: v})); +}; diff --git a/src/server/modes/charts/plugins/control/js/helpers/typed-query/process-typed-query-parameters.ts b/src/server/modes/charts/plugins/control/js/helpers/typed-query/process-typed-query-parameters.ts new file mode 100644 index 0000000..b2cf792 --- /dev/null +++ b/src/server/modes/charts/plugins/control/js/helpers/typed-query/process-typed-query-parameters.ts @@ -0,0 +1,17 @@ +import type {IChartEditor, StringParams} from '../../../../../../../../shared'; + +export type ProcessTypedQueryParametersArgs = { + parameters: StringParams; + ChartEditor: IChartEditor; +}; + +export const processTypedQueryParameters = (args: ProcessTypedQueryParametersArgs) => { + const {parameters, ChartEditor} = args; + + const params = Object.keys(parameters).reduce( + (acc, key) => Object.assign(acc, {[key]: ''}), + {} as StringParams, + ); + + ChartEditor.updateParams(params); +}; diff --git a/src/server/modes/charts/plugins/control/js/index.ts b/src/server/modes/charts/plugins/control/js/index.ts new file mode 100644 index 0000000..7b89047 --- /dev/null +++ b/src/server/modes/charts/plugins/control/js/index.ts @@ -0,0 +1,122 @@ +import {DashTabItemControlElementType} from '../../../../../../shared'; +import type {IChartEditor, StringParams} from '../../../../../../shared'; +import {formatIntervalRangeDate, formatRelativeRangeDate, getISOFromToday} from '../helpers/misc'; +import type {ControlShared, UIControl} from '../types'; + +import {processContent} from './helpers/process-content'; +import type {SourceResponseData} from './types'; + +// eslint-disable-next-line complexity +export const buildGraph = ({ + data, + shared, + params, + ChartEditor, +}: { + data: SourceResponseData; + shared: ControlShared; + params: StringParams; + ChartEditor: IChartEditor; +}) => { + shared.content = processContent({data, shared, ChartEditor, params}); + + const {source, param, content} = shared; + + const uiControl: UIControl = { + label: source.showTitle ? shared.title : '', + multiselect: source.multiselectable, + content, + param, + required: source.required, + disabled: shared.disabled, + }; + + switch (source.elementType) { + case DashTabItemControlElementType.Date: + if (source.isRange) { + uiControl.type = 'range-datepicker'; + + const {defaultValue} = source; + + if (typeof defaultValue === 'string') { + const resolvedInterval = ChartEditor.resolveInterval(defaultValue); + + if (resolvedInterval) { + const {from, to} = resolvedInterval; + params[param] = `__interval_${from || ''}_${to || ''}`; + } + } else if (content.length) { + params[param] = formatIntervalRangeDate({ + from: content[0].value, + to: content[content.length - 1].value, + }); + } else { + params[param] = formatRelativeRangeDate({from: 0, to: 0}); + } + } else { + uiControl.type = 'datepicker'; + + const {defaultValue} = source; + + if (typeof defaultValue === 'string') { + params[param] = ChartEditor.resolveRelative(defaultValue) || defaultValue; + } else if (content.length) { + params[param] = [content[content.length - 1].value]; + } else { + params[param] = [getISOFromToday()]; + } + } + + if (source.acceptableValues) { + const {from, to} = source.acceptableValues as { + from: string; + to: string; + }; + uiControl.minDate = from; + uiControl.maxDate = to; + } + + break; + + case DashTabItemControlElementType.Select: + uiControl.type = source.elementType; + + if (!params[param]) { + if (Array.isArray(source.defaultValue)) { + params[param] = source.defaultValue.length > 0 ? source.defaultValue : ['']; + } else { + params[param] = source.defaultValue || ['']; + } + } + + break; + + case DashTabItemControlElementType.Checkbox: + uiControl.type = source.elementType; + + if (!params[param]) { + if (source.defaultValue === undefined) { + params[param] = ['false']; + } else { + params[param] = [source.defaultValue]; + } + } + + break; + + default: + uiControl.type = source.elementType; + + if (!params[param]) { + if (source.defaultValue === undefined) { + params[param] = ['']; + } else { + params[param] = [source.defaultValue]; + } + } + } + + shared.uiControl = uiControl; + + ChartEditor.updateParams({[param]: params[param]}); +}; diff --git a/src/server/modes/charts/plugins/control/js/types.ts b/src/server/modes/charts/plugins/control/js/types.ts new file mode 100644 index 0000000..fac5a98 --- /dev/null +++ b/src/server/modes/charts/plugins/control/js/types.ts @@ -0,0 +1,11 @@ +import type {ApiV2ResultData, ConnectionTypedQueryApiResponse} from '../../../../../../shared'; +import type {PartialDatasetField} from '../../../../../../shared/schema'; + +export type ControlDatasetFields = { + fields: PartialDatasetField[]; +}; +export type SourceResponseData = { + distincts?: ApiV2ResultData; + fields?: ControlDatasetFields; + connectionDistincts?: ConnectionTypedQueryApiResponse; +}; diff --git a/src/server/modes/charts/plugins/control/types.ts b/src/server/modes/charts/plugins/control/types.ts new file mode 100644 index 0000000..352fae7 --- /dev/null +++ b/src/server/modes/charts/plugins/control/types.ts @@ -0,0 +1,61 @@ +import type { + ConnectionQueryContent, + ConnectionQueryTypeValues, + ControlType, + DashTabItemControlElementType, + DashTabItemControlSourceType, + DatasetFieldType, +} from '../../../../../shared'; + +export type ControlShared = { + title: string; + source: { + fieldName: string; + datasetId: string; + fieldType: string; + showTitle: boolean; + isRange: boolean; + multiselectable: boolean; + elementType: DashTabItemControlElementType; + datasetFieldId: string; + datasetFieldType: DatasetFieldType; + acceptableValues: + | { + value: string; + title: string; + }[] + | { + from: string; + to: string; + }; + defaultValue: any; + required?: boolean; + connectionId?: string; + connectionQueryType?: ConnectionQueryTypeValues; + connectionQueryContent?: ConnectionQueryContent; + }; + sourceType: DashTabItemControlSourceType; + type: ControlType.Dash; + param: string; + uiControl?: UIControl; + content: { + title: string; + value: string; + }[]; + disabled?: boolean; +}; + +export type UIControl = { + label: string; + multiselect: boolean; + content: { + title: string; + value: string; + }[]; + param: string; + type?: string; + minDate?: string; + maxDate?: string; + required?: boolean; + disabled?: boolean; +}; diff --git a/src/server/modes/charts/plugins/control/url/constants.ts b/src/server/modes/charts/plugins/control/url/constants.ts new file mode 100644 index 0000000..9195b5a --- /dev/null +++ b/src/server/modes/charts/plugins/control/url/constants.ts @@ -0,0 +1,8 @@ +export const DATASET_ID_PLACEHOLDER = '{datasetId}'; +export const CONNECTION_ID_PLACEHOLDER = '{connectionId}'; +export const DATASET_DISTINCTS_URL = '/_bi_datasets/{datasetId}/values/distinct'; +export const DATASET_FIELDS_URL = '/_bi_datasets/{datasetId}/fields'; +export const DATASET_RESULT_URL = '/_bi_datasets/{datasetId}/result'; +export const CONNECTIONS_TYPED_QUERY_URL = '/_bi_connections/{connectionId}/typed_query'; +export const CONNECTIONS_TYPED_QUERY_RAW_URL = '/_bi_connections/{connectionId}/typed_query_raw'; +export const CONNECTIONS_DASHSQL = '/_bi_connections/{connectionId}/dashsql'; diff --git a/src/server/modes/charts/plugins/control/url/distincts/build-distincts-body.ts b/src/server/modes/charts/plugins/control/url/distincts/build-distincts-body.ts new file mode 100644 index 0000000..6099936 --- /dev/null +++ b/src/server/modes/charts/plugins/control/url/distincts/build-distincts-body.ts @@ -0,0 +1,205 @@ +import type {AppContext} from '@gravity-ui/nodekit'; + +import type { + ApiV2Filter, + ApiV2Parameter, + ApiV2RequestBody, + FiltersOperationFromURL, + StringParams, +} from '../../../../../../../shared'; +import { + DatasetFieldType, + Operations, + resolveIntervalDate, + resolveOperation, + resolveRelativeDate, + splitParamsToParametersAndFilters, + transformParamsToUrlParams, + transformUrlParamsToParams, +} from '../../../../../../../shared'; +import type {PartialDatasetField} from '../../../../../../../shared/schema'; +import type {ControlShared} from '../../types'; + +function buildDistinctsBodyRequest({ + where, + fieldGuid, + parameters, + datasetFieldsMap, +}: { + where: { + column: string; + operation: Operations; + values: string[]; + }[]; + fieldGuid: string; + parameters: StringParams; + datasetFieldsMap: Record; +}): ApiV2RequestBody { + const finalFieldGuid = datasetFieldsMap[fieldGuid]?.guid || fieldGuid; + + const filters: ApiV2Filter[] = (where || []) + .filter((el) => datasetFieldsMap[el.column]?.fieldType !== DatasetFieldType.Measure) + .map((el) => { + return { + ref: {type: 'id', id: datasetFieldsMap[el.column]?.guid || el.column || ''}, + operation: el.operation, + values: el.values, + }; + }) + .filter((filter) => { + return filter.ref.type === 'id' && filter.ref.id !== finalFieldGuid; + }); + + const parameter_values = Object.keys(parameters) + .map((key) => { + const guid = datasetFieldsMap[key]?.guid || key; + const parameterValue = parameters[key]; + + return { + ref: {type: 'id', id: guid}, + // Param does not work with multiselect, so we pick first value from array + value: Array.isArray(parameterValue) ? parameterValue[0] : parameterValue, + }; + }) + .filter((item) => item.value !== ''); + + return { + ignore_nonexistent_filters: true, + fields: [ + { + ref: { + type: 'id', + id: finalFieldGuid, + }, + role_spec: { + role: 'distinct', + }, + }, + ], + filters, + parameter_values, + }; +} + +export const getDistinctsRequestBody = ({ + shared, + params, + datasetFields, + ctx, +}: { + shared: ControlShared; + params: StringParams; + datasetFields: PartialDatasetField[]; + ctx?: AppContext; +}): ApiV2RequestBody => { + ctx?.log('CONTROLS_START_PREPARING_DISTINCTS_BODY'); + const targetParam = shared.param; + + const where: { + column: string; + operation: Operations; + values: string[]; + }[] = []; + + ctx?.log('CONTROLS_START_MAPPING_DATASET_FIELDS'); + + const datasetFieldsMap = datasetFields.reduce( + (acc, field) => { + const fieldData = { + fieldType: field.type, + guid: field.guid, + }; + acc[field.guid] = fieldData; + acc[field.title] = fieldData; + + return acc; + }, + {} as Record, + ); + + ctx?.log('CONTROLS_END_MAPPING_DATASET_FIELDS'); + + ctx?.log?.('CONTROLS_START_TRANSFORMING_PARAMS'); + + const urlSearchParams = transformParamsToUrlParams(params); + + ctx?.log?.('CONTROLS_END_TRANSFORMING_PARAMS'); + + ctx?.log?.('CONTROLS_START_SPLIT_PARAMS'); + + const {filtersParams, parametersParams} = splitParamsToParametersAndFilters( + urlSearchParams, + datasetFields, + ); + + ctx?.log?.('CONTROLS_START_TRANSFORMING_PARAMS'); + + const transformedFilterParams = transformUrlParamsToParams(filtersParams); + const transformedParametersParams = transformUrlParamsToParams(parametersParams); + + ctx?.log?.('CONTROLS_END_TRANSFORMING_PARAMS'); + + ctx?.log('CONTROLS_START_PROCESSING_FILTERS'); + + Object.keys(transformedFilterParams).forEach((param) => { + if (param === targetParam) { + return; + } + + let values: string[] | string = []; + let operation; + const paramValue = params[param]; + if (Array.isArray(paramValue)) { + const valuesWithOperations = paramValue + .filter((value) => value) + .map((value) => resolveOperation(value)); + + values = valuesWithOperations.map( + (item: FiltersOperationFromURL | null) => item!.value, + ); + operation = valuesWithOperations.find((item) => item && item.operation)?.operation; + + if (values.length === 1 && String(values[0]).startsWith('__relative')) { + const resolvedRelative = resolveRelativeDate(values[0]); + + if (resolvedRelative) { + values[0] = resolvedRelative; + } + } + + if (values.length === 1 && String(values[0]).startsWith('__interval')) { + const resolvedInterval = resolveIntervalDate(values[0]); + + if (resolvedInterval) { + const {from, to} = resolvedInterval; + + values = [from, to]; + operation = Operations.BETWEEN; + } + } + } + + operation = operation || Operations.IN; + + if (values.length) { + where.push({ + column: param, + operation, + values, + }); + } + }); + + ctx?.log('CONTROLS_END_PROCESSING_FILTERS'); + + const apiV2RequestBody = buildDistinctsBodyRequest({ + where, + fieldGuid: targetParam, + parameters: transformedParametersParams, + datasetFieldsMap, + }); + + ctx?.log('CONTROLS_END_PREPARING_DISTINCTS_BODY'); + + return apiV2RequestBody; +}; diff --git a/src/server/modes/charts/plugins/control/url/distincts/index.ts b/src/server/modes/charts/plugins/control/url/distincts/index.ts new file mode 100644 index 0000000..7c59486 --- /dev/null +++ b/src/server/modes/charts/plugins/control/url/distincts/index.ts @@ -0,0 +1,21 @@ +import { + CONTROL_MIDDLEWARE_URL_TYPE, + REQUEST_WITH_DATASET_SOURCE_NAME, +} from '../../../constants/middleware-urls'; +import type {SourceControlArgs, SourceControlDistinctsRequestWithDataset} from '../types'; + +export const prepareDistinctsRequest = ( + sourceArgs: SourceControlArgs, +): SourceControlDistinctsRequestWithDataset => { + const datasetId = sourceArgs.shared.source.datasetId; + return { + sourceArgs, + method: 'POST', + path: 'values/distinct', + datasetId, + middlewareUrl: { + sourceName: REQUEST_WITH_DATASET_SOURCE_NAME, + middlewareType: CONTROL_MIDDLEWARE_URL_TYPE, + }, + }; +}; diff --git a/src/server/modes/charts/plugins/control/url/fields/index.ts b/src/server/modes/charts/plugins/control/url/fields/index.ts new file mode 100644 index 0000000..506bb71 --- /dev/null +++ b/src/server/modes/charts/plugins/control/url/fields/index.ts @@ -0,0 +1,12 @@ +import type {SourceControlFieldsRequest} from '../types'; + +export const prepareFieldsRequest = ({ + datasetId, +}: { + datasetId: string; +}): SourceControlFieldsRequest => { + return { + datasetId: datasetId, + path: 'fields', + }; +}; diff --git a/src/server/modes/charts/plugins/control/url/index.ts b/src/server/modes/charts/plugins/control/url/index.ts new file mode 100644 index 0000000..5788f99 --- /dev/null +++ b/src/server/modes/charts/plugins/control/url/index.ts @@ -0,0 +1,59 @@ +import { + DashTabItemControlElementType, + DashTabItemControlSourceType, + DatasetFieldType, +} from '../../../../../../shared'; +import type {ControlShared} from '../types'; + +import {prepareDistinctsRequest} from './distincts'; +import {prepareFieldsRequest} from './fields'; +import {prepareTypedQueryRequest} from './typed-query'; +import type {SourceControlArgs, SourceControlRequests} from './types'; + +const buildManualSelectorSources = (shared: ControlShared): Record => { + if (shared.source.elementType === DashTabItemControlElementType.Select) { + shared.content = shared.source.acceptableValues as { + value: string; + title: string; + }[]; + } + + shared.param = shared.source.fieldName; + + return {}; +}; + +export const buildSources = ({ + shared, + params, +}: SourceControlArgs): SourceControlRequests | Record => { + switch (shared.sourceType) { + case DashTabItemControlSourceType.Manual: + return buildManualSelectorSources(shared); + case DashTabItemControlSourceType.Connection: + return { + connectionDistincts: prepareTypedQueryRequest({shared, params}), + }; + case DashTabItemControlSourceType.Dataset: + default: { + const datasetId = shared.source.datasetId; + + shared.param = shared.source.datasetFieldId || shared.source.fieldName; + + const sources: SourceControlRequests = { + fields: prepareFieldsRequest({datasetId}), + }; + + if ( + shared.source.elementType !== DashTabItemControlElementType.Date && + shared.source.elementType !== DashTabItemControlElementType.Input && + shared.source.elementType !== DashTabItemControlElementType.Checkbox && + shared.source.datasetFieldType !== DatasetFieldType.Measure + ) { + sources.distincts = prepareDistinctsRequest({shared, params}); + } + + return sources; + } + } +}; diff --git a/src/server/modes/charts/plugins/control/url/typed-query/index.ts b/src/server/modes/charts/plugins/control/url/typed-query/index.ts new file mode 100644 index 0000000..17c1270 --- /dev/null +++ b/src/server/modes/charts/plugins/control/url/typed-query/index.ts @@ -0,0 +1,31 @@ +import {mapParametersRecordToTypedQueryApiParameters} from '../../../../../../../shared/modules/typed-query-api'; +import {extractTypedQueryParams} from '../../../../../../../shared/modules/typed-query-api/helpers/parameters'; +import {CONNECTIONS_TYPED_QUERY_URL, CONNECTION_ID_PLACEHOLDER} from '../constants'; +import type {SourceControlArgs, SourceControlTypedQueryRequest} from '../types'; + +export const prepareTypedQueryRequest = ( + args: SourceControlArgs, +): SourceControlTypedQueryRequest => { + const {shared, params} = args; + const {connectionId, connectionQueryType, connectionQueryContent} = shared.source; + + shared.param = shared.source.fieldName; + + if (!connectionId || !connectionQueryType || !connectionQueryContent) { + throw new Error('Missed required fields for TypedQueryApi request'); + } + + const parameters = mapParametersRecordToTypedQueryApiParameters( + extractTypedQueryParams(params, shared.source.fieldName), + ); + + return { + url: CONNECTIONS_TYPED_QUERY_URL.replace(CONNECTION_ID_PLACEHOLDER, connectionId), + method: 'POST', + data: { + query_type: connectionQueryType, + query_content: connectionQueryContent, + parameters, + }, + }; +}; diff --git a/src/server/modes/charts/plugins/control/url/types.ts b/src/server/modes/charts/plugins/control/url/types.ts new file mode 100644 index 0000000..e5fc30d --- /dev/null +++ b/src/server/modes/charts/plugins/control/url/types.ts @@ -0,0 +1,34 @@ +import type {Optional} from 'utility-types'; + +import type {ApiV2Request, ConnectionTypedQueryApiRequest} from '../../../../../../shared'; +import type {Source} from '../../../../../components/charts-engine/types'; +import type {MiddlewareUrl} from '../../types'; +import type {ControlShared} from '../types'; + +export type SourceControlArgs = { + shared: ControlShared; + params: Record; +}; + +export type SourceControlRequests = + | { + distincts?: SourceControlDistinctsRequestWithDataset | SourceControlDistinctsOldRequest; + fields: SourceControlFieldsRequest; + } + | {connectionDistincts: SourceControlTypedQueryRequest}; + +export type SourceControlDistinctsRequestWithDataset = { + sourceArgs: SourceControlArgs; + middlewareUrl: MiddlewareUrl; + datasetId: string; + path: string; +} & Omit; + +export type SourceControlDistinctsOldRequest = ApiV2Request; + +export type SourceControlFieldsRequest = { + path: string; + datasetId: string; +}; + +export type SourceControlTypedQueryRequest = Optional, 'ui'>; diff --git a/src/server/modes/charts/plugins/dash-api.ts b/src/server/modes/charts/plugins/dash-api.ts new file mode 100644 index 0000000..55c8e5a --- /dev/null +++ b/src/server/modes/charts/plugins/dash-api.ts @@ -0,0 +1,210 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {AppContext} from '@gravity-ui/nodekit'; +import {REQUEST_ID_PARAM_NAME, USER_ID_PARAM_NAME} from '@gravity-ui/nodekit'; +import {isObject} from 'lodash'; +import pick from 'lodash/pick'; + +import type {DashEntry, EntryReadParams} from '../../../../shared'; +import {DASH_API_BASE_URL, EntryScope, ErrorCode} from '../../../../shared'; +import {Utils} from '../../../components'; +import type {Plugin, PluginRoute} from '../../../components/charts-engine/types'; +import {Dash} from '../../../components/sdk'; +import {DASH_ENTRY_RELEVANT_FIELDS} from '../../../constants'; + +function purgeResult(result: DashEntry) { + return pick(result, DASH_ENTRY_RELEVANT_FIELDS); +} + +type ConfiguredDashApiPluginOptions = { + basePath?: string; + privatePath?: string; + /** + * @deprecated use validationConfig instead + */ + validate?: PluginRoute['validationConfig']; + validationConfig?: PluginRoute['validationConfig']; + routeParams?: Partial>; + privateRouteParams?: Partial>; +}; + +const getRoutes = (options?: ConfiguredDashApiPluginOptions): Plugin['routes'] => { + const { + routeParams, + privateRouteParams, + validate, + basePath = DASH_API_BASE_URL, + privatePath, + } = options || {}; + let {validationConfig} = options || {}; + + if (validate && !validationConfig) { + validationConfig = validate; + } + + let routes: PluginRoute[] = [ + { + method: 'POST', + path: basePath, + validationConfig, + handlerName: 'dashAPIcreate', + handler: async (req: Request, res: Response) => { + try { + const {body, ctx} = req; + const I18n = req.ctx.get('i18n'); + + const result = await Dash.create(body, Utils.pickHeaders(req), ctx, I18n); + + res.status(200).send(purgeResult(result)); + } catch (error) { + const errorCode = Utils.getErrorCode(error); + const errorStatus = errorCode === ErrorCode.ReadOnlyMode ? 451 : 500; + res.status(errorStatus).send({ + message: Utils.getErrorMessage(error), + details: Utils.getErrorDetails(error), + code: errorCode, + }); + sendStats(req.ctx, 'dashAPIcreate', errorStatus); + } + }, + ...(routeParams || {}), + }, + { + method: 'GET', + path: `${basePath}/:id`, + handlerName: 'dashAPIget', + handler: async (req: Request, res: Response) => { + try { + const { + params: {id}, + query, + ctx, + } = req; + + if (!id || id === 'null') { + res.status(404).send({message: 'Dash not found'}); + sendStats(req.ctx, 'dashAPIget', 404); + return; + } + const result = await Dash.read( + id, + query as unknown as EntryReadParams | null, + Utils.pickHeaders(req), + ctx, + ); + + if (result.scope !== EntryScope.Dash) { + res.status(404).send({message: 'No entry found'}); + sendStats(req.ctx, 'dashAPIget', 404); + return; + } + + res.status(200).send(purgeResult(result)); + } catch (error) { + const originalStatus = Utils.getErrorStatus(error); + + const errorStatus = + originalStatus && [400, 403, 404].includes(originalStatus) + ? originalStatus + : 500; + res.status(errorStatus).send({message: Utils.getErrorMessage(error)}); + sendStats(req.ctx, 'dashAPIget', errorStatus); + } + }, + ...(routeParams || {}), + }, + { + method: 'POST', + path: `${basePath}/:id`, + handlerName: 'dashAPIupdate', + validationConfig, + handler: async (req: Request, res: Response) => { + try { + const { + params: {id}, + body, + ctx, + } = req; + + const I18n = req.ctx.get('i18n'); + + const result = await Dash.update(id, body, Utils.pickHeaders(req), ctx, I18n); + + res.status(200).send(purgeResult(result)); + } catch (error) { + let errorStatus = 500; + if (Utils.getErrorStatus(error) === 403) { + errorStatus = 403; + } else if (Utils.getErrorCode(error) === ErrorCode.ReadOnlyMode) { + errorStatus = 451; + } + res.status(errorStatus).send({message: Utils.getErrorMessage(error)}); + sendStats(req.ctx, 'dashAPIupdate', errorStatus); + } + }, + ...(routeParams || {}), + }, + { + method: 'DELETE', + path: `${basePath}/:id`, + handlerName: 'dashAPIdelete', + handler: async (req: Request, res: Response) => { + try { + const { + params: {id}, + ctx, + } = req; + + const result = await Dash.delete(id, Utils.pickHeaders(req), ctx); + + res.status(200).send(purgeResult(result)); + } catch (error) { + let errorStatus = 500; + const originalStatus = Utils.getErrorStatus(error); + if (originalStatus === 403 || originalStatus === 423) { + errorStatus = originalStatus; + } else if (Utils.getErrorCode(error) === ErrorCode.ReadOnlyMode) { + errorStatus = 451; + } + res.status(errorStatus).send({message: Utils.getErrorMessage(error)}); + sendStats(req.ctx, 'dashAPIdelete', errorStatus); + } + }, + ...(routeParams || {}), + }, + ]; + + if (privatePath) { + routes = routes.concat( + routes.map((route) => { + return { + ...route, + path: route.path.replace(basePath, privatePath), + ...(privateRouteParams || {}), + }; + }), + ); + } + + return routes; +}; + +export function configuredDashApiPlugin(options?: ConfiguredDashApiPluginOptions): Plugin { + return { + routes: getRoutes(options), + }; +} + +function sendStats(ctx: AppContext, handlerName: string, status: number) { + let userId = ctx.get(USER_ID_PARAM_NAME) || '0'; + if (isObject(userId)) { + userId = 'value' in userId ? (userId as {value: string}).value : '0'; + } + ctx.stats('dashApiErrors', { + handlerName, + timestamp: Date.now(), + status, + requestId: ctx.get(REQUEST_ID_PARAM_NAME) || '', + userId, + traceId: ctx.getTraceId() || '', + }); +} diff --git a/src/server/modes/charts/plugins/data-api-json-schema.ts b/src/server/modes/charts/plugins/data-api-json-schema.ts new file mode 100644 index 0000000..0e1a548 --- /dev/null +++ b/src/server/modes/charts/plugins/data-api-json-schema.ts @@ -0,0 +1,639 @@ +import { + CONTROLS_PLACEMENT_MODE, + DASH_CURRENT_SCHEME_VERSION, + DashLoadPriority, + DashTabConnectionKind, + DashTabItemControlElementType, + DashTabItemControlSourceType, + DashTabItemTitleSizes, + DashTabItemType, +} from '../../../../shared'; +import {DASH_DEFAULT_NAMESPACE} from '../../../constants'; +import type {ValidationConfig} from '../../../lib/validation/types'; + +// TODO: add "additionalProperties: false" for all "type: object" +// in view of the existing incorrect schemes, we leave validation enabled only for internal installation +// (in particular, because there is a public API inside) +// dashboards in other installations are being run through purgeData while saving +export const dashApiValidation: ValidationConfig = { + body: { + definitions: { + text: { + type: 'object', + required: ['text'], + properties: { + text: {type: 'string'}, + }, + }, + title: { + type: 'object', + required: ['text', 'size'], + properties: { + text: {type: 'string'}, + size: { + enum: Object.values(DashTabItemTitleSizes), + }, + showInTOC: {type: 'boolean'}, + }, + }, + widget: { + type: 'object', + required: ['tabs'], + properties: { + hideTitle: {type: 'boolean'}, + tabs: { + type: 'array', + items: { + type: 'object', + required: ['id', 'title'], + properties: { + id: { + type: 'string', + minLength: 1, + }, + title: { + type: 'string', + minLength: 1, + }, + description: { + type: 'string', + }, + chartId: { + type: 'string', + minLength: 1, + }, + isDefault: { + type: 'boolean', + }, + params: { + type: 'object', + }, + autoHeight: { + type: 'boolean', + }, + }, + }, + }, + }, + }, + control: { + type: 'object', + required: ['title', 'sourceType', 'source'], + properties: { + title: { + type: 'string', + minLength: 1, + }, + sourceType: { + enum: Object.values(DashTabItemControlSourceType), + }, + }, + allOf: [ + // @deprecated use groupControl + { + if: { + properties: { + sourceType: { + const: DashTabItemControlSourceType.Dataset, + }, + }, + }, + then: { + properties: { + source: {$ref: '#/definitions/controlSourceDataset'}, + }, + }, + }, + // @deprecated use groupControl + { + if: { + properties: { + sourceType: { + const: DashTabItemControlSourceType.Manual, + }, + }, + }, + then: { + properties: { + source: {$ref: '#/definitions/controlSourceManual'}, + }, + }, + }, + { + if: { + properties: { + sourceType: { + const: DashTabItemControlSourceType.External, + }, + }, + }, + then: { + properties: { + source: {$ref: '#/definitions/controlSourceExternal'}, + }, + }, + }, + ], + }, + controlSourceDataset: { + allOf: [ + {$ref: '#/definitions/controlElementType'}, + { + type: 'object', + required: ['datasetId', 'datasetFieldId'], + properties: { + datasetId: { + type: 'string', + minLength: 1, + }, + datasetFieldId: { + type: 'string', + minLength: 1, + }, + }, + }, + ], + }, + controlSourceManual: { + allOf: [ + {$ref: '#/definitions/controlElementType'}, + { + type: 'object', + required: ['fieldName'], // 'acceptableValues' is required for select and date, but not for input + properties: { + fieldName: { + type: 'string', + minLength: 1, + }, + acceptableValues: { + oneOf: [ + // elementType: select + { + type: 'array', + items: { + type: 'object', + required: ['value', 'title'], + properties: { + value: {type: 'string'}, + title: {type: 'string'}, + }, + }, + }, + // elementType: date + { + type: 'object', + properties: { + from: {type: 'string'}, + to: {type: 'string'}, + }, + }, + ], + }, + }, + }, + ], + }, + controlSourceExternal: { + type: 'object', + required: ['chartId'], + properties: { + text: {type: 'string'}, + autoHeight: {type: 'boolean'}, + }, + }, + controlElementType: { + type: 'object', + required: ['elementType'], + properties: { + required: {type: 'boolean'}, + showHint: {type: 'boolean'}, + showTitle: {type: 'boolean'}, + elementType: { + enum: Object.values(DashTabItemControlElementType), + }, + }, + allOf: [ + { + if: { + properties: { + elementType: { + const: DashTabItemControlElementType.Select, + }, + }, + }, + then: { + properties: { + defaultValue: { + oneOf: [ + {type: 'string'}, + { + type: 'array', + items: {type: 'string'}, + }, + {type: 'null'}, + ], + }, + multiselectable: {type: 'boolean'}, + }, + }, + }, + { + if: { + properties: { + elementType: { + const: DashTabItemControlElementType.Date, + }, + }, + }, + then: { + properties: { + defaultValue: {type: 'string'}, + isRange: {type: 'boolean'}, + }, + }, + }, + { + if: { + properties: { + elementType: { + const: DashTabItemControlElementType.Input, + }, + }, + }, + then: { + properties: { + defaultValue: {type: 'string'}, + }, + }, + }, + { + if: { + properties: { + elementType: { + const: DashTabItemControlElementType.Checkbox, + }, + }, + }, + then: { + properties: { + defaultValue: {type: 'string'}, + }, + }, + }, + ], + }, + groupControlItems: { + allOf: [ + { + required: ['sourceType', 'source', 'namespace', 'id', 'title', 'defaults'], + properties: { + id: { + type: 'string', + minLength: 1, + }, + title: { + type: 'string', + minLength: 1, + }, + namespace: { + const: DASH_DEFAULT_NAMESPACE, + // uncomment when namespace is different from default + // type: 'string', + // minLength: 1, + }, + sourceType: { + enum: [ + DashTabItemControlSourceType.Dataset, + DashTabItemControlSourceType.Manual, + ], + }, + defaults: {type: 'object'}, + placementMode: { + enum: Object.values(CONTROLS_PLACEMENT_MODE), + }, + width: { + type: 'string', + }, + }, + }, + { + if: { + properties: { + sourceType: { + const: DashTabItemControlSourceType.Dataset, + }, + }, + }, + then: { + properties: { + source: {$ref: '#/definitions/controlSourceDataset'}, + }, + }, + }, + { + if: { + properties: { + sourceType: { + const: DashTabItemControlSourceType.Manual, + }, + }, + }, + then: { + properties: { + source: {$ref: '#/definitions/controlSourceManual'}, + }, + }, + }, + ], + }, + groupControl: { + type: 'object', + required: ['group'], + properties: { + group: { + type: 'array', + items: {$ref: '#/definitions/groupControlItems'}, + }, + autoHeight: { + type: 'boolean', + }, + buttonApply: { + type: 'boolean', + }, + buttonReset: { + type: 'boolean', + }, + showGroupName: { + type: 'boolean', + }, + updateControlsOnChange: { + type: 'boolean', + }, + }, + }, + }, + + type: 'object', + properties: { + key: { + type: 'string', + minLength: 1, + }, + data: { + type: 'object', + required: ['tabs'], + properties: { + counter: { + type: 'integer', + minimum: 1, + }, + salt: { + type: 'string', + minLength: 1, + }, + schemeVersion: { + type: 'integer', + minimum: 1, + maximum: DASH_CURRENT_SCHEME_VERSION, + }, + tabs: { + type: 'array', + items: { + type: 'object', + required: ['id', 'title', 'items', 'layout', 'connections', 'aliases'], + properties: { + id: { + type: 'string', + minLength: 1, + }, + title: { + type: 'string', + minLength: 1, + }, + items: { + type: 'array', + items: { + type: 'object', + required: ['id', 'namespace', 'type'], + properties: { + id: { + type: 'string', + minLength: 1, + }, + namespace: { + const: DASH_DEFAULT_NAMESPACE, + // uncomment when namespace is different from default + // type: 'string', + // minLength: 1, + }, + type: { + enum: Object.values(DashTabItemType), + }, + }, + allOf: [ + { + if: { + properties: { + type: { + const: DashTabItemType.Text, + }, + }, + }, + then: { + required: ['data'], + properties: { + data: { + $ref: '#/definitions/text', + }, + }, + }, + }, + { + if: { + properties: { + type: { + const: DashTabItemType.Title, + }, + }, + }, + then: { + required: ['data'], + properties: { + data: { + $ref: '#/definitions/title', + }, + }, + }, + }, + { + if: { + properties: { + type: { + const: DashTabItemType.Widget, + }, + }, + }, + then: { + required: ['data'], + properties: { + data: { + $ref: '#/definitions/widget', + }, + }, + }, + }, + { + if: { + properties: { + type: { + const: DashTabItemType.Control, + }, + }, + }, + then: { + required: ['data', 'defaults'], + properties: { + data: { + $ref: '#/definitions/control', + }, + defaults: {type: 'object'}, + }, + }, + }, + { + if: { + properties: { + type: { + const: DashTabItemType.GroupControl, + }, + }, + }, + then: { + required: ['data'], + properties: { + data: { + $ref: '#/definitions/groupControl', + }, + }, + }, + }, + ], + }, + }, + layout: { + type: 'array', + items: { + type: 'object', + required: ['i', 'h', 'w', 'x', 'y'], + properties: { + i: { + type: 'string', + minLength: 1, + }, + h: {type: 'number'}, + w: {type: 'number'}, + x: {type: 'number'}, + y: {type: 'number'}, + parent: {type: 'string'}, + }, + }, + }, + aliases: { + type: 'object', + properties: { + [DASH_DEFAULT_NAMESPACE]: { + type: 'array', + items: { + type: 'array', + items: { + type: 'string', + minLength: 1, + }, + minItems: 2, + uniqueItems: true, + }, + }, + }, + additionalProperties: false, + // uncomment when namespace is different from default + // patternProperties: { + // '^.*$': { + // type: 'array', + // items: { + // ... + // }, + // }, + // }, + }, + connections: { + type: 'array', + items: { + type: 'object', + required: ['from', 'to', 'kind'], + properties: { + from: { + type: 'string', + minLength: 1, + }, + to: { + type: 'string', + minLength: 1, + }, + kind: { + enum: Object.values(DashTabConnectionKind), + }, + }, + }, + }, + }, + }, + }, + settings: { + type: 'object', + properties: { + autoupdateInterval: { + // TODO: no need in string here, you need to save it correctly in the settings dialog + type: ['integer', 'string', 'null'], + minimum: 30, + }, + maxConcurrentRequests: { + type: ['integer', 'null'], + minimum: 1, + }, + loadPriority: { + enum: Object.values(DashLoadPriority), + }, + silentLoading: { + type: 'boolean', + }, + dependentSelectors: { + type: 'boolean', + }, + globalParams: { + type: 'object', + }, + hideTabs: { + type: 'boolean', + }, + hideDashTitle: { + type: 'boolean', + }, + expandTOC: { + type: 'boolean', + }, + }, + }, + }, + }, + meta: { + type: 'object', + }, + links: { + type: 'object', + }, + annotation: { + type: 'object', + }, + }, + }, +}; diff --git a/src/server/modes/charts/plugins/datalens/chart-editor-module.ts b/src/server/modes/charts/plugins/datalens/chart-editor-module.ts new file mode 100644 index 0000000..99a8ed5 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/chart-editor-module.ts @@ -0,0 +1,136 @@ +import type { + DashWidgetConfig, + FeatureConfig, + IChartEditor, + Palette, + ServerChartsConfig, + Shared, + StringParams, +} from '../../../../../shared'; + +import {buildChartsConfigPrivate} from './config'; +import type {BuildChartConfigArgs} from './config/types'; +import {buildHighchartsConfigPrivate} from './highcharts'; +import {buildGraphPrivate} from './js/js'; +import {fallbackJSFunctionPrivate} from './js/js-v1.5-private'; +import {buildSourcesPrivate} from './url/build-sources'; +import type {SourceRequests, SourcesArgs} from './url/types'; +import {setConsole} from './utils/misc-helpers'; + +type JSTabOptions = + | [{shared: Shared | ServerChartsConfig; ChartEditor: IChartEditor; data: any}] + | [any, Shared | ServerChartsConfig, IChartEditor]; + +declare const __features: FeatureConfig; +declare const __palettes: Record; +declare const __defaultColorPaletteId: string; + +const buildHighchartsConfig = (...options: [{shared: ServerChartsConfig} | ServerChartsConfig]) => { + let shared: ServerChartsConfig; + if ('shared' in options[0]) { + shared = options[0].shared; + } else { + shared = options[0]; + } + + return buildHighchartsConfigPrivate({shared, features: __features}); +}; + +const buildSources = (args: SourcesArgs): SourceRequests => { + return buildSourcesPrivate({...args, palettes: __palettes}); +}; + +const fallbackJSFunction = (...options: JSTabOptions) => { + return fallbackJSFunctionPrivate({ + options, + features: __features, + palettes: __palettes, + defaultColorPaletteId: __defaultColorPaletteId, + }); +}; + +export const buildGraph = (...options: JSTabOptions) => { + let data: any; + let shared: Shared | ServerChartsConfig; + let ChartEditor: IChartEditor; + let apiVersion; + + if ('shared' in options[0]) { + data = options[0].data; + shared = options[0].shared as Shared | ServerChartsConfig; + ChartEditor = + (options[0].ChartEditor as IChartEditor) || (options[0].Editor as IChartEditor); + apiVersion = options[0].apiVersion; + } else { + data = options[0]; + shared = options[1] as Shared | ServerChartsConfig; + ChartEditor = options[2] as IChartEditor; + } + + apiVersion = apiVersion || '1.5'; + if (apiVersion === '1.5') { + return fallbackJSFunction.apply(this, options); + } + + return buildGraphPrivate({ + shared, + ChartEditor, + data, + palettes: __palettes, + features: __features, + defaultColorPaletteId: __defaultColorPaletteId, + }); +}; + +export const buildChartsConfig = ( + args: BuildChartConfigArgs | ServerChartsConfig, + _params?: StringParams, +) => { + let shared; + let params: StringParams; + let widgetConfig: DashWidgetConfig['widgetConfig']; + + if ('shared' in args) { + shared = args.shared; + params = args.params as StringParams; + widgetConfig = args.widgetConfig; + } else { + shared = args; + params = _params as StringParams; + } + + return buildChartsConfigPrivate({ + shared, + params, + widgetConfig, + features: __features, + }); +}; + +export default { + buildHighchartsConfig, + buildSources, + buildGraph, + buildGravityChartsConfig: ({ + shared, + Editor, + data, + }: { + shared: Shared | ServerChartsConfig; + Editor: IChartEditor; + data: unknown; + }) => { + return buildGraphPrivate({ + shared, + ChartEditor: Editor, + data, + palettes: __palettes, + features: __features, + plugin: 'gravity-charts', + defaultColorPaletteId: __defaultColorPaletteId, + }); + }, + buildChartsConfig, + buildD3Config: () => {}, + setConsole, +}; diff --git a/src/server/modes/charts/plugins/datalens/config/constants.ts b/src/server/modes/charts/plugins/datalens/config/constants.ts new file mode 100644 index 0000000..520b05e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/config/constants.ts @@ -0,0 +1,5 @@ +export enum CommentsMatchType { + Full = 'full', + Contains = 'contains', + Intersection = 'intersection', +} diff --git a/src/server/modes/charts/plugins/datalens/config/index.ts b/src/server/modes/charts/plugins/datalens/config/index.ts new file mode 100644 index 0000000..c82ed15 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/config/index.ts @@ -0,0 +1,230 @@ +import set from 'lodash/set'; + +import type {FeatureConfig, ServerChartsConfig} from '../../../../../../shared'; +import { + ChartkitHandlers, + DEFAULT_CHART_LINES_LIMIT, + Feature, + PlaceholderId, + WizardVisualizationId, + getIsNavigatorEnabled, + isDimensionField, + isFieldHierarchy, + isTreeField, +} from '../../../../../../shared'; +import {mapChartsConfigToServerConfig} from '../utils/config-helpers'; +import {getAllPlaceholderItems, isNeedToCalcClosestPointManually, log} from '../utils/misc-helpers'; + +import {CommentsMatchType} from './constants'; +import type { + BuildChartConfigArgs, + Config, + ConfigWithActionParams, + MetricConfig, + TableConfig, +} from './types'; + +function getActionParamsEvents( + visualizationId?: WizardVisualizationId, +): ConfigWithActionParams['events'] { + switch (visualizationId) { + case WizardVisualizationId.FlatTable: { + return { + click: [{handler: {type: 'setActionParams'}, scope: 'row'}], + }; + } + case WizardVisualizationId.PivotTable: { + return { + click: [{handler: {type: 'setActionParams'}, scope: 'cell'}], + }; + } + case WizardVisualizationId.Line: + case WizardVisualizationId.Area: + case WizardVisualizationId.Column: + case WizardVisualizationId.Column100p: + case WizardVisualizationId.Bar: + case WizardVisualizationId.Bar100p: + case WizardVisualizationId.Scatter: + case WizardVisualizationId.Pie: + case WizardVisualizationId.Donut: + case WizardVisualizationId.CombinedChart: { + return { + click: [{handler: {type: 'setActionParams'}, scope: 'point'}], + }; + } + case WizardVisualizationId.Geolayer: { + return { + click: [{handler: {type: 'setActionParams'}, scope: 'point'}], + }; + } + } + + return undefined; +} + +function canUseActionParams(shared: ServerChartsConfig) { + const hasDrillDownEvents = Boolean(shared.sharedData?.drillDownData); + const tableVisualization = shared.visualization.id === WizardVisualizationId.FlatTable; + const hasTreeFields = + tableVisualization && + shared.visualization.placeholders.find( + (p) => p.id === PlaceholderId.FlatTableColumns && p.items.some(isTreeField), + ); + + return !hasDrillDownEvents && !hasTreeFields; +} + +// eslint-disable-next-line complexity +export const buildChartsConfigPrivate = ( + args: BuildChartConfigArgs & {features: FeatureConfig}, +) => { + const {shared: serverChartConfig, params, widgetConfig, features} = args; + const shared = mapChartsConfigToServerConfig(serverChartConfig); + const {visualization} = shared; + + let hideHolidaysBands = !features[Feature.HolidaysOnChart]; + if (!hideHolidaysBands) { + hideHolidaysBands = [visualization].concat(visualization.layers || []).some((layer) => { + return layer.placeholders?.some((placeholder) => { + return ( + placeholder.id === PlaceholderId.X && + (placeholder.settings?.holidays || 'off') === 'off' + ); + }); + }); + } + + const config: Partial = { + title: shared.title, + hideHolidaysBands, + linesLimit: DEFAULT_CHART_LINES_LIMIT, + tooltip: {pin: {altKey: true}, sort: {enabled: true}}, + preventDefaultForPointClick: false, + }; + + if (shared.extraSettings) { + if (shared.extraSettings.title && shared.extraSettings.titleMode === 'show') { + config.title = shared.extraSettings.title; + } + + const isPivotFallbackEnabled = shared.extraSettings?.pivotFallback === 'on'; + + if ( + visualization.id === WizardVisualizationId.FlatTable || + (visualization.id === WizardVisualizationId.PivotTable && !isPivotFallbackEnabled) + ) { + const tableExtraSettings = shared.extraSettings; + const items = getAllPlaceholderItems(shared.visualization.placeholders); + const hasDimensions = items.some( + (field) => isDimensionField(field) || isFieldHierarchy(field), + ); + + // No pagination if all columns are measures + (config as TableConfig).paginator = { + enabled: hasDimensions && tableExtraSettings?.pagination === 'on', + limit: tableExtraSettings?.limit && tableExtraSettings?.limit, + }; + } + + config.comments = { + matchType: CommentsMatchType.Intersection, + }; + + const matchedParams: string[] = []; + if (shared.datasetsPartialFields) { + shared.datasetsPartialFields.forEach((fields) => + fields.forEach((field) => params[field.title] && matchedParams.push(field.title)), + ); + + config.comments.matchedParams = matchedParams; + } + + if (shared.extraSettings.feed) { + config.comments.feeds = [ + { + feed: shared.extraSettings.feed, + matchedParams, + matchType: CommentsMatchType.Intersection, + }, + ]; + } + + if (getIsNavigatorEnabled(shared)) { + config.navigatorSettings = shared.extraSettings.navigatorSettings; + } + + config.enableGPTInsights = shared.extraSettings.enableGPTInsights; + } + + const visualizationId = shared.visualization.id; + if ( + visualizationId === 'line' || + visualizationId === 'area' || + visualizationId === 'area100p' || + visualizationId === 'column' || + visualizationId === 'column100p' || + visualizationId === 'bar' || + visualizationId === 'bar100p' + ) { + config.manageTooltipConfig = ChartkitHandlers.WizardManageTooltipConfig; + + const extraSettings = shared.extraSettings; + if (extraSettings) { + const {tooltipSum} = extraSettings; + + if (typeof tooltipSum === 'undefined' || tooltipSum === 'on') { + config.enableSum = true; + } + } + } else if (visualizationId === 'pie' || visualizationId === 'donut') { + config.showPercentInTooltip = true; + config.manageTooltipConfig = ChartkitHandlers.WizardManageTooltipConfig; + } else if (visualizationId === 'metric') { + (config as MetricConfig).metricVersion = 2; + } else if (visualizationId === 'pivotTable') { + (config as TableConfig).settings = { + externalSort: true, + }; + } else if (visualizationId === WizardVisualizationId.CombinedChart) { + config.manageTooltipConfig = ChartkitHandlers.WizardManageTooltipConfig; + } + + const isTableWidget = ( + [WizardVisualizationId.FlatTable, WizardVisualizationId.PivotTable] as string[] + ).includes(visualizationId); + + if (isTableWidget) { + const size = widgetConfig?.size ?? shared?.extraSettings?.size; + if (size) { + set(config, 'size', size); + } + + set(config, 'settings.width', 'max-content'); + + if (shared?.extraSettings?.preserveWhiteSpace) { + set(config, 'preserveWhiteSpace', true); + } + } + + const placeholders = shared.visualization.placeholders; + const colors = shared.colors; + + if (isNeedToCalcClosestPointManually(visualizationId, placeholders, colors)) { + // Highcharts can't calculate column sizes automatically for different series. + // Therefore, if we have a columns/bar visualization type and the colors contain a field, then we will calculate the fields ourselves + // Because the data format without colors is {series: [{data: [...]}]} + // And with colors {series: [{data: [...]}, {data: [...]}, {data: [...]}]; + config.calcClosestPointManually = true; + } + + if (widgetConfig?.actionParams?.enable && canUseActionParams(shared)) { + (config as ConfigWithActionParams).events = getActionParamsEvents( + visualizationId as WizardVisualizationId, + ); + } + + log('CONFIG:'); + log(config); + + return config; +}; diff --git a/src/server/modes/charts/plugins/datalens/config/types.ts b/src/server/modes/charts/plugins/datalens/config/types.ts new file mode 100644 index 0000000..b2304e9 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/config/types.ts @@ -0,0 +1,87 @@ +import type {HighchartsWidgetData} from '@gravity-ui/chartkit/highcharts'; + +import type { + ChartkitHandlers, + DashWidgetConfig, + GraphTooltipLine, + GraphWidgetEventScope, + ServerChartsConfig, + ServerCommonSharedExtraSettings, + StringParams, + TableWidgetEventScope, + WidgetEvent, +} from '../../../../../../shared'; + +import type {CommentsMatchType} from './constants'; + +interface Comments { + feeds?: Feed[]; + matchedParams?: string[]; + matchType?: CommentsMatchType; +} + +type Feed = { + feed: string; + matchedParams?: string[]; + matchType?: CommentsMatchType; +}; + +type BaseConfig = { + drillDown?: { + breadcrumbs: string[]; + level: number; + }; + enableJsAndHtml?: boolean; +}; + +type GraphConfig = BaseConfig & + Pick< + HighchartsWidgetData['config'], + | 'title' + | 'hideHolidaysBands' + | 'linesLimit' + | 'tooltip' + | 'withoutLineLimit' + | 'enableSum' + | 'showPercentInTooltip' + > & { + manageTooltipConfig?: + | ChartkitHandlers.WizardManageTooltipConfig + | ((config: {lines: GraphTooltipLine[]}) => void); + comments?: Comments; + navigatorSettings?: ServerCommonSharedExtraSettings['navigatorSettings']; + calcClosestPointManually?: boolean; + enableGPTInsights?: ServerCommonSharedExtraSettings['enableGPTInsights']; + events?: { + click?: WidgetEvent | WidgetEvent[]; + }; + preventDefaultForPointClick?: boolean; + }; + +export type TableConfig = BaseConfig & { + settings: { + externalSort?: boolean; + preserveWhiteSpace?: boolean; + }; + paginator?: { + enabled: boolean; + limit?: number; + }; + events?: { + click?: WidgetEvent | WidgetEvent[]; + }; +}; + +export type MetricConfig = BaseConfig & { + metricVersion?: number; +}; + +export type Config = GraphConfig | TableConfig | MetricConfig; + +export type ConfigWithActionParams = TableConfig | GraphConfig; + +export type BuildChartConfigArgs = { + shared: ServerChartsConfig; + params: StringParams; + widgetConfig?: DashWidgetConfig['widgetConfig']; +}; diff --git a/src/server/modes/charts/plugins/datalens/gravity-charts/utils/dataLabels.ts b/src/server/modes/charts/plugins/datalens/gravity-charts/utils/dataLabels.ts new file mode 100644 index 0000000..1a5f292 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/gravity-charts/utils/dataLabels.ts @@ -0,0 +1,18 @@ +import type {ServerField} from '../../../../../../../shared'; +import {getFormatOptions, isNumberField} from '../../../../../../../shared'; +import {chartKitFormatNumberWrapper} from '../../utils/misc-helpers'; + +export function getFormattedLabel(value?: string | number, labelField?: ServerField) { + if (!labelField || typeof value === 'undefined') { + return undefined; + } + + if (isNumberField(labelField)) { + return chartKitFormatNumberWrapper(value, { + lang: 'ru', + ...getFormatOptions(labelField), + }); + } + + return String(value); +} diff --git a/src/server/modes/charts/plugins/datalens/gravity-charts/utils/format.ts b/src/server/modes/charts/plugins/datalens/gravity-charts/utils/format.ts new file mode 100644 index 0000000..bc6e3b3 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/gravity-charts/utils/format.ts @@ -0,0 +1,28 @@ +import type {ValueFormat} from '@gravity-ui/chartkit/gravity-charts'; + +import type {ServerField} from '../../../../../../../shared'; +import {getFormatOptions, isDateField, isNumberField} from '../../../../../../../shared'; + +export function getFieldFormatOptions({field}: {field?: ServerField}): ValueFormat | undefined { + if (isNumberField(field)) { + const fieldFormatting = getFormatOptions(field); + + const labelFormat = + fieldFormatting?.labelMode === 'percent' ? 'percent' : fieldFormatting?.format; + + return { + type: 'number', + ...fieldFormatting, + format: labelFormat, + }; + } + + if (isDateField(field)) { + return { + type: 'date', + format: field.format, + }; + } + + return undefined; +} diff --git a/src/server/modes/charts/plugins/datalens/gravity-charts/utils/index.ts b/src/server/modes/charts/plugins/datalens/gravity-charts/utils/index.ts new file mode 100644 index 0000000..d0f800a --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/gravity-charts/utils/index.ts @@ -0,0 +1,231 @@ +import type { + ChartData, + ChartTitle, + ChartTooltip, + ChartYAxis, +} from '@gravity-ui/chartkit/gravity-charts'; + +import { + PlaceholderId, + WizardVisualizationId, + isDateField, + isTooltipSumEnabled, +} from '../../../../../../../shared'; +import type { + ServerChartsConfig, + ServerCommonSharedExtraSettings, + ServerPlaceholder, + ServerPlaceholderSettings, +} from '../../../../../../../shared'; +import {getAxisTitle, getTickPixelInterval, isGridEnabled} from '../../utils/axis-helpers'; + +import {getRangeSliderConfig} from './range-slider'; + +export {getTotalsPrecisionFromSeriesTooltips} from './tooltip'; + +export function getChartTitle(settings?: ServerCommonSharedExtraSettings): ChartTitle | undefined { + if (settings?.titleMode !== 'hide' && settings?.title) { + return { + text: settings.title, + }; + } + + return undefined; +} + +export function getAxisLabelsRotationAngle(placeholderSettings?: ServerPlaceholderSettings) { + switch (placeholderSettings?.labelsView) { + case 'horizontal': { + return 0; + } + case 'vertical': { + return 90; + } + case 'angle': { + return 45; + } + } + + return undefined; +} + +function getAxisMinMax( + placeholderSettings?: ServerPlaceholderSettings, +): [number | undefined, number | undefined] { + if (placeholderSettings?.scale === 'manual') { + if (!Array.isArray(placeholderSettings?.scaleValue)) { + return [undefined, undefined]; + } + + const min = Number(placeholderSettings.scaleValue[0]); + const max = Number(placeholderSettings.scaleValue[1]); + + return [Number.isNaN(min) ? undefined : min, Number.isNaN(max) ? undefined : max]; + } + + if (placeholderSettings?.scaleValue === '0-max') { + return [0, undefined]; + } + + return [undefined, undefined]; +} + +export function getYAxisBaseConfig({ + placeholder, +}: { + placeholder: ServerPlaceholder | undefined; +}): ChartYAxis { + const placeholderSettings = placeholder?.settings || {}; + const yItem = placeholder?.items[0]; + + const [yMin, yMax] = getAxisMinMax(placeholderSettings); + + return { + // todo: the axis type should depend on the type of field + type: isDateField(yItem) ? 'datetime' : 'linear', + visible: placeholderSettings?.axisVisibility !== 'hide', + labels: { + enabled: Boolean(yItem) && placeholder?.settings?.hideLabels !== 'yes', + rotation: getAxisLabelsRotationAngle(placeholder?.settings), + }, + title: { + text: getAxisTitle(placeholderSettings, yItem) || undefined, + maxRowCount: 3, + style: { + fontSize: '13px', + }, + }, + grid: { + enabled: Boolean(yItem) && isGridEnabled(placeholderSettings), + }, + ticks: { + pixelInterval: getTickPixelInterval(placeholderSettings) || 72, + }, + lineColor: 'var(--g-color-line-generic)', + min: yMin, + max: yMax, + }; +} + +export function getBaseChartConfig(args: { + shared: ServerChartsConfig; + visualization: {id: string; placeholders: ServerPlaceholder[]}; +}) { + const {shared, visualization} = args; + const extraSettings = shared.extraSettings; + + const xPlaceholder = visualization.placeholders.find((p) => p.id === PlaceholderId.X); + const xItem = xPlaceholder?.items[0]; + const xPlaceholderSettings = xPlaceholder?.settings || {}; + + const tooltip: ChartTooltip = { + enabled: extraSettings?.tooltip !== 'hide', + }; + + if (isTooltipSumEnabled({visualizationId: shared.visualization.id})) { + tooltip.totals = { + enabled: extraSettings?.tooltipSum !== 'off', + }; + } + + const legend: ChartData['legend'] = {}; + if (extraSettings?.legendMode === 'hide') { + legend.enabled = false; + } + + let chartWidgetData: Partial = { + title: getChartTitle(extraSettings), + tooltip, + legend, + series: { + data: [], + options: { + 'bar-x': { + barMaxWidth: 50, + barPadding: 0.05, + groupPadding: 0.4, + dataSorting: { + direction: 'desc', + }, + }, + line: { + lineWidth: 2, + }, + area: { + lineWidth: 2, + }, + }, + }, + chart: { + margin: { + top: 10, + left: 10, + right: 10, + bottom: 15, + }, + zoom: { + enabled: true, + resetButton: { + align: 'top-right', + offset: {x: 2, y: 30}, + relativeTo: 'plot-box', + }, + }, + }, + }; + + const visualizationId = visualization.id as WizardVisualizationId; + const visualizationWithoutAxis = [ + WizardVisualizationId.Pie, + WizardVisualizationId.Donut, + WizardVisualizationId.Treemap, + ]; + + const visualizationWithYMainAxis = [WizardVisualizationId.Bar, WizardVisualizationId.Bar100p]; + + if (!visualizationWithoutAxis.includes(visualizationId)) { + const yPlaceholder = visualization.placeholders.find((p) => p.id === PlaceholderId.Y); + const [xMin, xMax] = getAxisMinMax(xPlaceholderSettings); + + chartWidgetData = { + ...chartWidgetData, + xAxis: { + visible: xPlaceholderSettings?.axisVisibility !== 'hide', + labels: { + enabled: xPlaceholderSettings?.hideLabels !== 'yes', + rotation: getAxisLabelsRotationAngle(xPlaceholderSettings), + maxWidth: '20%', + }, + title: { + text: getAxisTitle(xPlaceholderSettings, xItem) || undefined, + maxRowCount: 3, + style: { + fontSize: '13px', + }, + }, + grid: { + enabled: isGridEnabled(xPlaceholderSettings), + }, + ticks: { + pixelInterval: getTickPixelInterval(xPlaceholderSettings), + }, + lineColor: 'var(--g-color-line-generic)', + min: xMin, + max: xMax, + rangeSlider: getRangeSliderConfig({extraSettings, shared}), + }, + yAxis: [getYAxisBaseConfig({placeholder: yPlaceholder})], + }; + + if (visualizationWithYMainAxis.includes(visualizationId)) { + chartWidgetData.xAxis = {...chartWidgetData.xAxis, lineColor: 'transparent'}; + } else { + chartWidgetData.yAxis = (chartWidgetData.yAxis || []).map((yAxis) => ({ + ...yAxis, + lineColor: 'transparent', + })); + } + } + + return chartWidgetData; +} diff --git a/src/server/modes/charts/plugins/datalens/gravity-charts/utils/range-slider.ts b/src/server/modes/charts/plugins/datalens/gravity-charts/utils/range-slider.ts new file mode 100644 index 0000000..15a73a2 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/gravity-charts/utils/range-slider.ts @@ -0,0 +1,59 @@ +import type { + ChartAxisRangeSlider, + ChartSeriesRangeSliderOptions, +} from '@gravity-ui/chartkit/gravity-charts'; + +import {NavigatorLinesMode, getIsNavigatorEnabled} from '../../../../../../../shared'; +import type { + ServerChartsConfig, + ServerCommonSharedExtraSettings, +} from '../../../../../../../shared'; + +export function getRangeSliderConfig(args: { + extraSettings?: ServerCommonSharedExtraSettings; + shared: ServerChartsConfig; +}): ChartAxisRangeSlider | undefined { + const {extraSettings, shared} = args; + const isNavigatorEnabled = getIsNavigatorEnabled(shared); + + if (!isNavigatorEnabled) { + return undefined; + } + + let defaultRange: ChartAxisRangeSlider['defaultRange'] | undefined; + + if (extraSettings?.navigatorSettings?.periodSettings) { + const {period, value} = extraSettings?.navigatorSettings?.periodSettings || {}; + const preparedValue = Number(value); + + if (!Number.isNaN(preparedValue)) { + defaultRange = { + size: {[period]: preparedValue}, + }; + } + } + + return { + defaultRange, + enabled: true, + height: 30, + }; +} + +export function getSeriesRangeSliderConfig(args: { + seriesName: string; + extraSettings?: ServerCommonSharedExtraSettings; +}): ChartSeriesRangeSliderOptions | undefined { + const {extraSettings, seriesName} = args; + const linesMode = extraSettings?.navigatorSettings?.linesMode; + let visible: boolean; + + if (linesMode === NavigatorLinesMode.Selected) { + const selectedLines = extraSettings?.navigatorSettings?.selectedLines || []; + visible = selectedLines.includes(seriesName); + } else { + visible = true; + } + + return {visible}; +} diff --git a/src/server/modes/charts/plugins/datalens/gravity-charts/utils/tooltip.ts b/src/server/modes/charts/plugins/datalens/gravity-charts/utils/tooltip.ts new file mode 100644 index 0000000..9e04d11 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/gravity-charts/utils/tooltip.ts @@ -0,0 +1,28 @@ +import type {BarYSeries, ValueFormat} from '@gravity-ui/chartkit/gravity-charts'; +import get from 'lodash/get'; + +import type {ExtendedAreaSeries} from '../../preparers/area/types'; +import type {ExtendedBarXSeries} from '../../preparers/bar-x/types'; +import type {ExtendedLineSeries} from '../../preparers/line/types'; + +type ValueFormatPrecision = Extract['precision']; + +export function getTotalsPrecisionFromSeriesTooltips( + series: BarYSeries[] | ExtendedAreaSeries[] | ExtendedBarXSeries[] | ExtendedLineSeries[], +): ValueFormatPrecision { + const precisionValues = series.reduce((acc, s) => { + const precision = get(s, 'tooltip.valueFormat.precision'); + + if (typeof precision === 'number') { + acc.push(precision); + } + + return acc; + }, []); + + if (precisionValues.length === 0) { + return 'auto'; + } + + return Math.max(0, ...precisionValues); +} diff --git a/src/server/modes/charts/plugins/datalens/highcharts.ts b/src/server/modes/charts/plugins/datalens/highcharts.ts new file mode 100644 index 0000000..ed53fc3 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/highcharts.ts @@ -0,0 +1,422 @@ +import set from 'lodash/set'; + +import type { + DATASET_FIELD_TYPES, + FeatureConfig, + ServerChartsConfig, + ServerPlaceholder, +} from '../../../../../shared'; +import { + AxisLabelFormatMode, + ChartkitHandlers, + LabelsPositions, + LegendDisplayMode, + VISUALIZATIONS_WITH_LABELS_POSITION, + WizardVisualizationId, + isDateField, +} from '../../../../../shared'; +import {PERCENT_VISUALIZATIONS} from '../../../../../shared/constants/visualization'; + +import type {IgnoreProps} from './utils/axis-helpers'; +import {applyPlaceholderSettingsToAxis} from './utils/axis-helpers'; +import {mapChartsConfigToServerConfig} from './utils/config-helpers'; +import {isNumericalDataType, log} from './utils/misc-helpers'; + +type ExtendedHighchartsLegendOptions = Omit & { + labelFormatter?: + | typeof ChartkitHandlers.WizardLabelFormatter + | Highcharts.LegendOptions['labelFormatter']; +}; + +type ExtendedHighchartsOptions = Omit & { + legend?: ExtendedHighchartsLegendOptions; +}; + +// eslint-disable-next-line complexity +export const buildHighchartsConfigPrivate = (args: { + shared: ServerChartsConfig; + features: FeatureConfig; +}) => { + const shared = mapChartsConfigToServerConfig(args.shared); + + if ( + ['geolayer', 'geopoint', 'geopolygon', 'heatmap', 'polyline'].includes( + shared.visualization.id, + ) + ) { + // center and zoom are specified as the default value if bounds does not arrive + // if bounds comes, then center and zoom are ignored + return { + state: { + center: [55.76, 37.64], + zoom: 8, + controls: ['zoomControl'], + behaviors: ['drag', 'scrollZoom', 'multiTouch'], + }, + options: {}, + }; + } + + const tooltip: Highcharts.Options['tooltip'] = {}; + let legend: ExtendedHighchartsLegendOptions = {}; + let colorAxis: Highcharts.Options['colorAxis']; + + let xAxis: Highcharts.Options['xAxis'] = { + endOnTick: false, + }; + + let yAxis: Highcharts.Options['yAxis'] = {}; + + const chart: Highcharts.ChartOptions = { + type: shared.visualization.highchartsId || shared.visualization.id, + zoomType: 'x', + }; + + if (shared.visualization.id === WizardVisualizationId.CombinedChart) { + chart.type = ''; + } + + const plotOptions: any = {}; + + const axisWithAppliedSettings = applyCommonAxisSettings({shared, xAxis, yAxis}); + xAxis = axisWithAppliedSettings.xAxis; + yAxis = axisWithAppliedSettings.yAxis; + + // We apply settings that are unique for each type of visualization + + const visualizationsIds = [shared.visualization.id].concat( + (shared.visualization.layers || []).map((layer) => layer.id), + ); + + visualizationsIds.forEach((visualizationId) => { + switch (visualizationId) { + case WizardVisualizationId.Line: { + chart.zoomType = 'xy'; + legend.symbolWidth = 38; + break; + } + case WizardVisualizationId.Column: + case WizardVisualizationId.Column100p: + case WizardVisualizationId.Bar: + case WizardVisualizationId.Bar100p: + case WizardVisualizationId.CombinedChart: { + chart.zoomType = 'xy'; + + legend.labelFormatter = ChartkitHandlers.WizardLabelFormatter; + break; + } + } + + extendPlotOptions({visualizationId: shared.visualization.id, plotOptions}); + + shared.visualization.layers?.forEach((layer) => { + extendPlotOptions({visualizationId: layer.id, plotOptions}); + }); + }); + + if (shared.visualization.id === WizardVisualizationId.Area) { + plotOptions.area = { + stacking: shared.extraSettings?.stacking !== 'off' ? 'normal' : undefined, + }; + } + + if (shared.visualization.id === WizardVisualizationId.Scatter) { + plotOptions.series = {turboThreshold: 100000}; + plotOptions.scatter = { + tooltip: { + formatter: ChartkitHandlers.WizardScatterTooltipFormatter, + }, + }; + + chart.zoomType = 'xy'; + + const xPlaceholder = shared.visualization.placeholders.find( + (placeholder) => placeholder.id === 'x', + ); + if (xPlaceholder && xPlaceholder.items.length) { + const xItem = xPlaceholder.items[0]; + + if (isDateField(xItem)) { + (xAxis as Highcharts.XAxisOptions).type = 'datetime'; + } + } + + if (!Array.isArray(yAxis)) { + yAxis.labels = {...(yAxis.labels || {})}; + + const yPlaceholder = shared.visualization.placeholders.find( + (placeholder) => placeholder.id === 'y', + ); + if (yPlaceholder && yPlaceholder.items.length) { + const yItem = yPlaceholder.items[0]; + + if (isDateField(yItem)) { + yAxis.type = 'datetime'; + } + + if ( + !isNumericalDataType(yItem.data_type as DATASET_FIELD_TYPES) && + !isDateField(yItem) && + yPlaceholder.settings?.axisFormatMode !== AxisLabelFormatMode.ByField + ) { + // A special formatter that returns text labels on the Y axis + // @ts-ignore + yAxis.labels.formatter = ChartkitHandlers.WizardScatterYAxisLabelFormatter; + } + } + } + } + + if (shared.visualization.id === WizardVisualizationId.Treemap) { + chart.zoomType = undefined; + plotOptions.treemap = { + tooltip: { + pointFormatter: ChartkitHandlers.WizardTreemapTooltipFormatter, + }, + }; + } + + if (shared.extraSettings?.legendMode === LegendDisplayMode.Hide) { + legend = { + enabled: false, + }; + } + + if (shared.extraSettings?.tooltip === 'hide') { + tooltip.enabled = false; + } + + if ( + shared.extraSettings?.labelsPosition && + visualizationsIds.some((id) => VISUALIZATIONS_WITH_LABELS_POSITION.has(id)) + ) { + plotOptions.series = { + ...plotOptions.series, + dataLabels: { + ...plotOptions.series?.dataLabels, + inside: shared.extraSettings?.labelsPosition !== LabelsPositions.Outside, + }, + }; + } + + const allowOverlap = shared.extraSettings?.overlap === 'on'; + + plotOptions.series = { + ...plotOptions.series, + dataGrouping: { + ...plotOptions.series?.dataGrouping, + enabled: false, + }, + dataLabels: { + ...plotOptions.series?.dataLabels, + allowOverlap, + }, + }; + + const navigator: Highcharts.Options['navigator'] = { + series: { + dataLabels: {color: 'transparent'}, + fillOpacity: 0.15, + }, + }; + + switch (shared.visualization.id) { + case WizardVisualizationId.Column: { + set(navigator, 'yAxis.softMin', 0); + break; + } + } + + const result: ExtendedHighchartsOptions = { + chart, + legend, + xAxis, + yAxis, + tooltip, + colorAxis, + plotOptions, + // https://api.highcharts.com/highstock/navigator.series + // Option navigator.series.dataLabels.enabled = false does not work (highcharts v8.2.2) + // The documentation says that the series between the chart and the navigator are fumbling, and apparently, + // because of this, there is a problem when trying to hide dataLabels, because they are marked in the series + navigator, + }; + + log('HIGHCHARTS:'); + log(result); + + return result; +}; + +type ApplyCommonAxisSettingsPayload = { + xAxis: NonNullable; + yAxis: NonNullable; + shared: ServerChartsConfig; +}; + +const applyCommonAxisSettings = ({ + shared, + xAxis, + yAxis, +}: ApplyCommonAxisSettingsPayload): { + xAxis: NonNullable; + yAxis: NonNullable; +} => { + const visualization = shared.visualization; + + const ignore: IgnoreProps = { + title: Boolean((shared.segments || []).length), + }; + + // Apply common settings for axes + if ( + visualization.id === WizardVisualizationId.Line || + visualization.id === WizardVisualizationId.Area || + visualization.id === WizardVisualizationId.Area100p || + visualization.id === WizardVisualizationId.Column || + visualization.id === WizardVisualizationId.Column100p || + visualization.id === WizardVisualizationId.Bar || + visualization.id === WizardVisualizationId.Bar100p || + visualization.id === WizardVisualizationId.Scatter || + visualization.id === WizardVisualizationId.CombinedChart + ) { + let x: ServerPlaceholder | undefined; + let y: ServerPlaceholder | undefined; + let y2: ServerPlaceholder | undefined; + + if (visualization.id === WizardVisualizationId.CombinedChart) { + visualization.layers?.forEach((layer) => { + const placeholders = layer.placeholders; + + x = x?.items.length ? x : placeholders[0]; + y = y?.items.length ? y : placeholders[1]; + y2 = y2?.items.length ? y2 : placeholders[2]; + }); + } else { + const placeholders = visualization.placeholders; + + x = placeholders[0]; + y = placeholders[1]; + y2 = placeholders[2]; + } + + let axes: (Highcharts.XAxisOptions | Highcharts.YAxisOptions)[] = [ + xAxis as Highcharts.XAxisOptions, + ]; + let axesData = [x]; + + if (y?.items.length && y2?.items.length) { + yAxis = [ + { + opposite: false, + labels: { + y: 3, + }, + }, + { + opposite: true, + labels: { + y: 3, + }, + }, + ]; + + axes = [...axes, yAxis[0], yAxis[1]]; + axesData = [...axesData, y, y2]; + } else { + (yAxis as Highcharts.XAxisOptions).opposite = Boolean( + !y?.items.length && y2?.items.length, + ); + (yAxis as Highcharts.XAxisOptions).labels = { + y: 3, + }; + + axes = [...axes, yAxis as Highcharts.XAxisOptions]; + const isY2HasItems = y2 && y2.items.length; + axesData = [...axesData, isY2HasItems ? y2 : y]; + } + + // eslint-disable-next-line complexity + axes.forEach((axis, i) => { + const axisData = axesData[i]; + + applyPlaceholderSettingsToAxis(axisData, axis, ignore); + }); + } + + // Due to fractional values (presumably) highcharts sometimes incorrectly calculates the maximum + // in this case, the chart is displayed correctly, but the maximum value on the y axis becomes more than 100 percent + if (PERCENT_VISUALIZATIONS.has(visualization.id) && !('max' in yAxis)) { + set(yAxis, 'max', 100); + } + + return {xAxis, yAxis}; +}; + +type ExtendPlotOptionsPayload = { + visualizationId: string; + plotOptions: NonNullable; +}; + +const extendPlotOptions = ({visualizationId, plotOptions}: ExtendPlotOptionsPayload) => { + switch (visualizationId) { + case WizardVisualizationId.Column: + plotOptions.column = plotOptions.column || {}; + plotOptions.column.stacking = 'normal'; + break; + + case WizardVisualizationId.Bar: + plotOptions.bar = plotOptions.bar || {}; + plotOptions.bar.stacking = 'normal'; + break; + + case WizardVisualizationId.Column100p: + plotOptions.column = plotOptions.column || {}; + plotOptions.column.stacking = 'percent'; + break; + + case 'area': + plotOptions.area = { + stacking: 'normal', + }; + break; + + case WizardVisualizationId.Bar100p: + plotOptions.bar = plotOptions.bar || {}; + plotOptions.bar.stacking = 'percent'; + break; + + case WizardVisualizationId.Area100p: + plotOptions.area = { + stacking: 'percent', + }; + break; + case WizardVisualizationId.Pie: + plotOptions.pie = { + allowPointSelect: false, + }; + break; + case WizardVisualizationId.Donut: + plotOptions.pie = { + allowPointSelect: false, + innerSize: '50%', + }; + break; + } + + switch (visualizationId) { + case WizardVisualizationId.Column: + case WizardVisualizationId.Column100p: + case WizardVisualizationId.Bar: + case WizardVisualizationId.Bar100p: { + plotOptions.column = plotOptions.column || {}; + plotOptions.column.dataGrouping = plotOptions.column.dataGrouping || {}; + // CHARTS-6460 + plotOptions.column.dataGrouping.enabled = false; + plotOptions.column.maxPointWidth = 50; + + plotOptions.bar = plotOptions.bar || {}; + break; + } + } +}; diff --git a/src/server/modes/charts/plugins/datalens/js/constants/errors.ts b/src/server/modes/charts/plugins/datalens/js/constants/errors.ts new file mode 100644 index 0000000..2537370 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/constants/errors.ts @@ -0,0 +1,6 @@ +export enum OversizeErrorType { + Default = 'default', + PivotTableCells = 'pivot-table_cells', + PivotTableColumns = 'pivot-table_columns', + SegmentsNumber = 'segments-number', +} diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/__tests__/combined-chart/mergeResultForCombinedCharts.test.ts b/src/server/modes/charts/plugins/datalens/js/helpers/__tests__/combined-chart/mergeResultForCombinedCharts.test.ts new file mode 100644 index 0000000..f0d5bb4 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/__tests__/combined-chart/mergeResultForCombinedCharts.test.ts @@ -0,0 +1,66 @@ +import {mergeResultForCombinedCharts} from '../../layer-chart'; + +describe('mergeResultForCombinedCharts', () => { + it('Should merge all results into one graphs array', () => { + const mockedResults = [ + {graphs: [{name: 'Test', data: [1, 2, 3, 4]}]}, + {graphs: [{name: 'Test 2', data: [{x: 1, y: 5}]}]}, + ]; + + const result = mergeResultForCombinedCharts(mockedResults); + + expect(result).toEqual({ + graphs: [ + {name: 'Test', data: [1, 2, 3, 4]}, + {name: 'Test 2', data: [{x: 1, y: 5}]}, + ], + }); + }); + + it('Should remove empty graph', () => { + const mockedResults = [ + {graphs: [{name: 'Test', data: [1, 2, 3, 4]}]}, + {graphs: [{name: 'Test 2', data: [{x: 1, y: 5}]}]}, + {graphs: [{name: 'Test 3', data: [null, null, null]}]}, + {graphs: [{name: 'Test 4', data: []}]}, + ]; + + const result = mergeResultForCombinedCharts(mockedResults); + + expect(result).toEqual({ + graphs: [ + {name: 'Test', data: [1, 2, 3, 4]}, + {name: 'Test 2', data: [{x: 1, y: 5}]}, + ], + }); + }); + + it('Should return first empty chart when all graphs without data', () => { + const mockedResults = [ + {graphs: [{name: 'Test 1', data: [null, null, null]}]}, + {graphs: [{name: 'Test 2', data: [null, null, null]}]}, + {graphs: [{name: 'Test 3', data: [null, null, null]}]}, + {graphs: [{name: 'Test 4', data: []}]}, + ]; + + const result = mergeResultForCombinedCharts(mockedResults); + + expect(result).toEqual({ + graphs: [{name: 'Test 1', data: [null, null, null]}], + }); + }); + + it('Should remove categories_ms when one chart with data exists', () => { + const mockedResults = [ + { + graphs: [{name: 'Test 1', data: [null, null, null]}], + categories_ms: [12321421, 124242151], + }, + {graphs: [{name: 'Test 2', data: [1, 2, 3, 4]}]}, + ]; + + const result = mergeResultForCombinedCharts(mockedResults); + + expect(result).toEqual({graphs: [{name: 'Test 2', data: [1, 2, 3, 4]}]}); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/__tests__/mocks/totals/getMergedTotals.mock.ts b/src/server/modes/charts/plugins/datalens/js/helpers/__tests__/mocks/totals/getMergedTotals.mock.ts new file mode 100644 index 0000000..89872c4 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/__tests__/mocks/totals/getMergedTotals.mock.ts @@ -0,0 +1,64 @@ +export const FIRST_DATASET_MERGED_ORDER = [ + {datasetId: 'lmsaoq5u6m2qd', title: 'Category'}, + {datasetId: 'lmsaoq5u6m2qd', title: 'Profit'}, +]; +export const SECOND_DATASET_MERGED_ORDER = [ + [ + { + datasetId: 'lmsaoq5u6m2qd', + title: 'Category', + }, + { + datasetId: 'oz7af2sj6mqih', + title: 'Type', + }, + ], + { + datasetId: 'lmsaoq5u6m2qd', + title: 'Profit', + }, + { + datasetId: 'oz7af2sj6mqih', + title: 'Voices count', + }, +]; + +export const FIRST_DATASET_LAST_RESULT_ROW = { + data: ['', '250'], + legend: [2, 3], +}; +export const SECOND_DATASET_LAST_RESULT_ROW = { + data: ['600', ''], + legend: [2, 3], +}; + +export const FIRST_DATASET_LOADED_DATA_ROWS = [ + { + data: ['Furniture', '10'], + legend: [0, 1], + }, + { + data: ['Office Supplies', '100'], + legend: [0, 1], + }, + { + data: ['Technology', '140'], + legend: [0, 1], + }, +]; +export const SECOND_DATASET_LOADED_DATA_ROWS = [ + { + data: ['550', 'film'], + legend: [0, 1], + }, + { + data: ['50', 'serial'], + legend: [0, 1], + }, +]; + +export const FIRST_DATASET_CURRENT_ORDER = []; +export const SECOND_DATASET_CURRENT_ORDER = [ + {dataType: 'integer', title: 'Voices count'}, + {dataType: 'string', title: 'Type'}, +]; diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/__tests__/totals/getMergedTotals.test.ts b/src/server/modes/charts/plugins/datalens/js/helpers/__tests__/totals/getMergedTotals.test.ts new file mode 100644 index 0000000..219b1ea --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/__tests__/totals/getMergedTotals.test.ts @@ -0,0 +1,54 @@ +import {getMergedTotals} from '../../totals'; +import { + FIRST_DATASET_CURRENT_ORDER, + FIRST_DATASET_LAST_RESULT_ROW, + FIRST_DATASET_LOADED_DATA_ROWS, + FIRST_DATASET_MERGED_ORDER, + SECOND_DATASET_CURRENT_ORDER, + SECOND_DATASET_LAST_RESULT_ROW, + SECOND_DATASET_LOADED_DATA_ROWS, + SECOND_DATASET_MERGED_ORDER, +} from '../mocks/totals/getMergedTotals.mock'; + +describe('getMergedTotals', () => { + it('merge all totals into one array if data came from only one dataset', () => { + const mergedTotals = getMergedTotals({ + mergedOrder: FIRST_DATASET_MERGED_ORDER, + lastResultRow: FIRST_DATASET_LAST_RESULT_ROW, + isFirstDataset: true, + totals: [], + currentOrder: FIRST_DATASET_CURRENT_ORDER, + resultDataRows: FIRST_DATASET_LOADED_DATA_ROWS, + }); + + expect(mergedTotals).toEqual(['', '250']); + }); + + it('Merge all totals depending on the mergedOrder array if multiple datasets', () => { + let mergedTotals: (string | null)[] = []; + + const mergedTotalsArgs = [ + // The first dataset + { + mergedOrder: FIRST_DATASET_MERGED_ORDER, + lastResultRow: FIRST_DATASET_LAST_RESULT_ROW, + isFirstDataset: true, + currentOrder: FIRST_DATASET_CURRENT_ORDER, + resultDataRows: FIRST_DATASET_LOADED_DATA_ROWS, + }, + { + mergedOrder: SECOND_DATASET_MERGED_ORDER, + lastResultRow: SECOND_DATASET_LAST_RESULT_ROW, + isFirstDataset: false, + currentOrder: SECOND_DATASET_CURRENT_ORDER, + resultDataRows: SECOND_DATASET_LOADED_DATA_ROWS, + }, + ]; + + mergedTotalsArgs.forEach((args) => { + mergedTotals = getMergedTotals({...args, totals: mergedTotals}); + }); + + expect(mergedTotals.slice(0, 3)).toEqual(['', '250', '600']); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/colors/index.ts b/src/server/modes/charts/plugins/datalens/js/helpers/colors/index.ts new file mode 100644 index 0000000..9813d72 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/colors/index.ts @@ -0,0 +1,54 @@ +import type {ColorPalette, Palette, ServerColorsConfig} from '../../../../../../../../shared'; +import { + GradientType, + TWO_POINT_DEFAULT_ID, + selectAvailableGradientsColors, +} from '../../../../../../../../shared'; +import {selectServerPalette} from '../../../../../../../constants'; +import type {ChartColorsConfig} from '../../../types'; + +type GetChartColorsArgs = { + colorsConfig?: ServerColorsConfig; + loadedColorPalettes: Record; + availablePalettes: Record; + defaultColorPaletteId: string; +}; + +export const getChartColorsConfig = ({ + colorsConfig = {}, + loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, +}: GetChartColorsArgs): ChartColorsConfig => { + const fallbackColors = selectServerPalette({ + palette: colorsConfig.palette, + availablePalettes, + customColorPalettes: loadedColorPalettes, + defaultColorPaletteId, + }); + + const fallbackGradientColors = selectAvailableGradientsColors( + (colorsConfig.gradientMode as GradientType | undefined) || GradientType.TWO_POINT, + colorsConfig.gradientPalette || TWO_POINT_DEFAULT_ID, + ); + + let colors: string[] = fallbackColors; + let gradientColors: string[] = fallbackGradientColors; + + if (colorsConfig.gradientPalette) { + gradientColors = + loadedColorPalettes[colorsConfig.gradientPalette]?.colors || fallbackGradientColors; + } + + if (colorsConfig.palette) { + colors = loadedColorPalettes[colorsConfig.palette]?.colors || fallbackColors; + } + + return { + ...colorsConfig, + colors, + gradientColors, + loadedColorPalettes, + availablePalettes, + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/index.ts b/src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/index.ts new file mode 100644 index 0000000..2deeeaa --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/index.ts @@ -0,0 +1,49 @@ +import {OversizeErrorType} from '../../../constants/errors'; + +import type {GetOversizeErrorArgs} from './types'; + +export function getOversizeError({type, limit, current}: GetOversizeErrorArgs) { + let code = ''; + let details: Record = {}; + switch (type) { + case OversizeErrorType.Default: { + code = 'ERR.CHARTS.ROWS_NUMBER_OVERSIZE'; + details = { + rowsLength: current, + rowsLimit: limit, + }; + break; + } + case OversizeErrorType.PivotTableCells: { + code = 'ERR.CHARTS.TABLE_OVERSIZE'; + details = { + type: 'cells', + cellsCount: current, + cellsLimit: limit, + }; + break; + } + case OversizeErrorType.PivotTableColumns: { + code = 'ERR.CHARTS.TABLE_OVERSIZE'; + details = { + type: 'columns', + columnsCount: current, + columnsLimit: limit, + }; + break; + } + case OversizeErrorType.SegmentsNumber: { + code = 'ERR.CHARTS.SEGMENTS_OVERSIZE'; + details = { + segmentsCount: current, + segmentsLimit: limit, + }; + break; + } + } + + return { + code, + details, + }; +} diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/types.ts b/src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/types.ts new file mode 100644 index 0000000..5a6b4c4 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/types.ts @@ -0,0 +1,16 @@ +import type {ServerField} from '../../../../../../../../../shared'; +import type {PrepareFunctionDataRow, ResultDataOrder} from '../../../../preparers/types'; +import type {OversizeErrorType} from '../../../constants/errors'; + +export type GetOversizeErrorArgs = { + type: OversizeErrorType; + limit: number; + current: number; +}; + +export type IsSegmentsOversizeError = { + segments: ServerField[]; + order: ResultDataOrder; + idToTitle: Record; + data: PrepareFunctionDataRow[]; +}; diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/utils.ts b/src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/utils.ts new file mode 100644 index 0000000..fc23348 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/errors/oversize-error/utils.ts @@ -0,0 +1,59 @@ +import {MAX_SEGMENTS_NUMBER} from '../../../../../../../../../shared'; +import {getSegmentsIndexInOrder, getSegmentsList} from '../../../../preparers/line/helpers'; + +import type {IsSegmentsOversizeError} from './types'; + +export const isDefaultOversizeError = ( + rowsLength: number | undefined, + rowsLimit: number | undefined, +) => { + return ( + typeof rowsLength !== 'undefined' && + typeof rowsLimit !== 'undefined' && + rowsLength > rowsLimit + ); +}; + +export const isBackendPivotCellsOversizeError = ( + cellsCount: number | undefined, + cellsLimit: number | undefined, +) => { + return ( + typeof cellsCount !== 'undefined' && + typeof cellsLimit !== 'undefined' && + cellsCount > cellsLimit + ); +}; + +export const isBackendPivotColumnsOversizeError = ( + columnsCount: number | undefined, + columnsLimit: number | undefined, +) => { + return ( + typeof columnsCount !== 'undefined' && + typeof columnsLimit !== 'undefined' && + columnsCount > columnsLimit + ); +}; + +export const isSegmentsOversizeError = ( + args: IsSegmentsOversizeError, +): {segmentsOversize: boolean; segmentsNumber: number} => { + const {order, segments, idToTitle, data} = args; + + if (!segments.length) { + return { + segmentsOversize: false, + segmentsNumber: 0, + }; + } + + const segmentIndexInOrder = getSegmentsIndexInOrder(order, segments[0], idToTitle); + + const segmentsNames = getSegmentsList({data, segmentIndexInOrder, segmentField: segments[0]}); + + return { + segmentsOversize: segmentsNames.length > MAX_SEGMENTS_NUMBER, + segmentsNumber: segmentsNames.length, + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/layer-chart/index.ts b/src/server/modes/charts/plugins/datalens/js/helpers/layer-chart/index.ts new file mode 100644 index 0000000..7144218 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/layer-chart/index.ts @@ -0,0 +1,144 @@ +import type {ChartData} from '@gravity-ui/chartkit/gravity-charts'; +import merge from 'lodash/merge'; + +import type {LayerChartMeta} from '../../../preparers/types'; + +import type {ExtendCombinedChartGraphsArgs} from './types'; + +export const extendCombinedChartGraphs = (args: ExtendCombinedChartGraphsArgs) => { + const {graphs, layer, layers, legendValues} = args; + const charType = layer.id; + const layerSettingsLayerId = layer.layerSettings.id; + + graphs.forEach((graph) => { + graph.type = charType; + graph.id = `${graph.id || graph.title}__${layerSettingsLayerId}`; + + if (graph.colorGuid) { + const legendColorValueId = `${graph.colorGuid}__${graph.legendTitle}__${graph.shapeGuid}`; + if (layers.length > 1) { + graph.title = `${graph.measureFieldTitle}: ${graph.title}`; + } + if (legendValues[legendColorValueId]) { + graph.id = legendValues[legendColorValueId]; + graph.showInLegend = false; + } else { + legendValues[legendColorValueId] = graph.id; + } + } + + switch (charType) { + case 'line': + { + const placeholders = layer.placeholders; + const hasItemsInYPlaceholder = placeholders[1].items.length; + const hasItemsInY2Placeholder = placeholders[2].items.length; + + if (!hasItemsInYPlaceholder && hasItemsInY2Placeholder && layers.length > 1) { + graph.yAxis = 1; + } + } + break; + case 'column': + graph.stack = `${layerSettingsLayerId}__${graph.stack || ''}`; + break; + case 'area': + graph.stack = layerSettingsLayerId; + break; + default: + break; + } + }); +}; + +export function combineLayersIntoSingleChart({layers}: {layers: Partial[]}) { + if (layers.length === 0) { + return {}; + } + + if (layers.length === 1) { + return layers[0]; + } + + // Deep merge all layer configs except series (title, chart, tooltip, legend, xAxis, yAxis settings) + // Note: merge works by index for arrays, which is correct for yAxis (axis 0 merges with axis 0, etc.) + const mergedConfig = layers.reduce((acc, layerData) => { + const {series: _, ...rest} = layerData; + return merge({}, acc, rest); + }, {} as Partial); + + // Build series separately: options are merged, data is concatenated + const series: ChartData['series'] = { + options: layers.reduce( + (acc, layerData) => ({...acc, ...layerData.series?.options}), + {} as ChartData['series']['options'], + ), + data: layers.reduce( + (acc, layerData) => [...acc, ...(layerData.series?.data ?? [])], + [] as ChartData['series']['data'], + ), + }; + + return { + ...mergedConfig, + series, + }; +} + +const isChartWithoutData = (graph: {data: any[]}) => { + const uniqueData = new Set(graph.data || []); + + const isNullChart = uniqueData.size === 1 && uniqueData.has(null); + const isEmptyChart = uniqueData.size === 0; + return isNullChart || isEmptyChart; +}; + +export const mergeResultForCombinedCharts = ( + result: {graphs: any[]; categories_ms?: number[]; categories?: number[]}[], +) => { + if (result.length === 0) { + return []; + } + + const [firstChart, ...restData] = result; + + const combinedChartResult = firstChart; + + restData.forEach((item) => { + combinedChartResult.graphs.push(...item.graphs); + }); + + if (!combinedChartResult.graphs.length) { + return combinedChartResult; + } + + const hasOneGraphWithData = combinedChartResult.graphs.some((graph) => { + return !isChartWithoutData(graph); + }); + + if (hasOneGraphWithData && isChartWithoutData(combinedChartResult.graphs[0])) { + delete combinedChartResult.categories_ms; + } + + combinedChartResult.graphs = combinedChartResult.graphs.filter( + (graph: {data: any[]}, index) => { + if (hasOneGraphWithData) { + return !isChartWithoutData(graph); + } + + return !(isChartWithoutData(graph) && index !== 0); + }, + ); + + return combinedChartResult; +}; + +export const getLayerChartMeta = ({isComboChart}: {isComboChart: boolean}): LayerChartMeta => { + const layerChartsMeta: LayerChartMeta = {}; + + if (isComboChart) { + layerChartsMeta.isCategoriesSortAvailable = true; + } + + return layerChartsMeta; +}; diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/layer-chart/types.ts b/src/server/modes/charts/plugins/datalens/js/helpers/layer-chart/types.ts new file mode 100644 index 0000000..5934d1f --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/layer-chart/types.ts @@ -0,0 +1,8 @@ +import type {ServerVisualizationLayer} from '../../../../../../../../shared'; + +export interface ExtendCombinedChartGraphsArgs { + graphs: any[]; + layer: ServerVisualizationLayer; + layers: ServerVisualizationLayer[]; + legendValues: Record; +} diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/misc/prepare-single-result.ts b/src/server/modes/charts/plugins/datalens/js/helpers/misc/prepare-single-result.ts new file mode 100644 index 0000000..3a15009 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/misc/prepare-single-result.ts @@ -0,0 +1,352 @@ +import type { + ColorPalette, + DATASET_FIELD_TYPES, + FeatureConfig, + IChartEditor, + Palette, + ServerChartsConfig, + ServerVisualization, + ServerVisualizationLayer, +} from '../../../../../../../../shared'; +import {WizardVisualizationId, isMonitoringOrPrometheusChart} from '../../../../../../../../shared'; +import {prepareGravityChartArea} from '../../../preparers/area'; +import prepareBackendPivotTableData from '../../../preparers/backend-pivot-table'; +import type {PivotData} from '../../../preparers/backend-pivot-table/types'; +import {prepareGravityChartBarX, prepareHighchartsBarX} from '../../../preparers/bar-x'; +import {prepareGravityChartsBarY, prepareHighchartsBarY} from '../../../preparers/bar-y'; +import prepareFlatTableData from '../../../preparers/flat-table'; +import prepareGeopointData from '../../../preparers/geopoint'; +import prepareGeopolygonData from '../../../preparers/geopolygon'; +import prepareHeatmapData from '../../../preparers/heatmap'; +import {prepareHighchartsLine} from '../../../preparers/line'; +import {prepareGravityChartLine} from '../../../preparers/line/gravity-charts'; +import prepareLineTime from '../../../preparers/line-time'; +import prepareMetricData from '../../../preparers/metric'; +import preparePivotTableData from '../../../preparers/old-pivot-table/old-pivot-table'; +import {prepareD3Pie, prepareHighchartsPie} from '../../../preparers/pie'; +import preparePolylineData from '../../../preparers/polyline'; +import {prepareGravityChartsScatter, prepareHighchartsScatter} from '../../../preparers/scatter'; +import {prepareD3Treemap, prepareHighchartsTreemap} from '../../../preparers/treemap'; +import type { + PrepareFunction, + PrepareFunctionArgs, + PrepareFunctionResultData, +} from '../../../preparers/types'; +import type {ChartPlugin} from '../../../types'; +import {getServerDateFormat} from '../../../utils/misc-helpers'; +import {OversizeErrorType} from '../../constants/errors'; +import {getChartColorsConfig} from '../colors'; +import {getOversizeError} from '../errors/oversize-error'; +import { + isBackendPivotCellsOversizeError, + isBackendPivotColumnsOversizeError, + isDefaultOversizeError, +} from '../errors/oversize-error/utils'; + +export type PrepareSingleResultArgs = { + resultData: PrepareFunctionResultData; + visualization: ServerVisualization; + shared: ServerChartsConfig; + idToTitle: Record; + idToDataType: Record; + ChartEditor: IChartEditor; + datasetsIds: string[]; + palettes: Record; + loadedColorPalettes?: Record; + disableDefaultSorting?: boolean; + features: FeatureConfig; + plugin?: ChartPlugin; + defaultColorPaletteId: string; +}; + +// eslint-disable-next-line complexity +export default ({ + resultData, + visualization, + shared, + idToTitle, + idToDataType, + ChartEditor, + datasetsIds, + loadedColorPalettes = {}, + disableDefaultSorting = false, + palettes, + features, + plugin, + defaultColorPaletteId, +}: PrepareSingleResultArgs) => { + const { + sharedData: {drillDownData}, + chartType, + } = shared; + + let rowsLength: undefined | number; + + let cellsCount: number | undefined; + let columnsCount: number | undefined; + + if ((resultData as any)?.pivot_data) { + const pivotData = (resultData as any).pivot_data as PivotData; + + const rows = pivotData.rows || []; + const columns = pivotData.columns || []; + + const rowsValues = rows[0]?.values || []; + + cellsCount = rows.length * rowsValues.length; + columnsCount = columns.length; + } else { + rowsLength = resultData.data && resultData.data.length; + } + + if (drillDownData) { + const currentDrillDownField = drillDownData.fields[drillDownData.level]; + ChartEditor.updateConfig({ + drillDown: { + breadcrumbs: drillDownData.breadcrumbs, + dateFormat: getServerDateFormat(currentDrillDownField?.data_type), + }, + }); + + ChartEditor.updateParams({ + drillDownLevel: drillDownData.level, + drillDownFilters: drillDownData.filters, + isColorDrillDown: drillDownData.isColorDrillDown, + }); + } + + if (rowsLength === 0) { + return {}; + } + + let prepare; + + let rowsLimit: number | undefined; + let cellsLimit: number | undefined; + let columnsLimit: number | undefined; + + const segments = shared.segments || []; + + switch (visualization.id) { + case WizardVisualizationId.Line: { + rowsLimit = 75000; + if (isMonitoringOrPrometheusChart(chartType)) { + prepare = prepareLineTime; + } else if (plugin === 'gravity-charts') { + prepare = prepareGravityChartLine; + } else { + prepare = prepareHighchartsLine; + } + break; + } + case WizardVisualizationId.Area: + case WizardVisualizationId.Area100p: { + rowsLimit = 75000; + if (isMonitoringOrPrometheusChart(chartType)) { + prepare = prepareLineTime; + } else if (plugin === 'gravity-charts') { + prepare = prepareGravityChartArea; + } else { + prepare = prepareHighchartsLine; + } + break; + } + + case WizardVisualizationId.Column: + case WizardVisualizationId.Column100p: { + rowsLimit = 75000; + if (isMonitoringOrPrometheusChart(chartType)) { + prepare = prepareLineTime; + } else if (plugin === 'gravity-charts') { + prepare = prepareGravityChartBarX; + } else { + prepare = prepareHighchartsBarX; + } + break; + } + + case WizardVisualizationId.Bar: + case WizardVisualizationId.Bar100p: { + if (plugin === 'gravity-charts') { + prepare = prepareGravityChartsBarY; + } else { + prepare = prepareHighchartsBarY; + } + rowsLimit = 75000; + break; + } + + case WizardVisualizationId.Scatter: + if (plugin === 'gravity-charts') { + prepare = prepareGravityChartsScatter; + } else { + prepare = prepareHighchartsScatter; + } + rowsLimit = 75000; + break; + + case WizardVisualizationId.Pie: + case WizardVisualizationId.Donut: + if (plugin === 'gravity-charts') { + prepare = prepareD3Pie; + } else { + prepare = prepareHighchartsPie; + } + rowsLimit = 1000; + break; + + case WizardVisualizationId.Metric: + prepare = prepareMetricData; + rowsLimit = 1000; + break; + + case WizardVisualizationId.Treemap: + if (plugin === 'gravity-charts') { + prepare = prepareD3Treemap; + } else { + prepare = prepareHighchartsTreemap; + } + rowsLimit = 800; + break; + + case WizardVisualizationId.FlatTable: + prepare = prepareFlatTableData; + rowsLimit = 100000; + break; + + case WizardVisualizationId.PivotTable: { + const pivotFallbackEnabled = shared.extraSettings?.pivotFallback === 'on'; + + if (pivotFallbackEnabled) { + prepare = preparePivotTableData; + rowsLimit = 40000; + } else { + prepare = prepareBackendPivotTableData; + cellsLimit = 100000; + columnsLimit = 800; + } + break; + } + case 'geopoint': + prepare = prepareGeopointData; + rowsLimit = 40000; + break; + + case 'geopolygon': + prepare = prepareGeopolygonData; + rowsLimit = 40000; + break; + + case 'heatmap': + prepare = prepareHeatmapData; + rowsLimit = 40000; + break; + + case 'polyline': + prepare = preparePolylineData; + rowsLimit = 40000; + break; + } + + const oversize = isDefaultOversizeError(rowsLength, rowsLimit); + const backendPivotCellsOversize = isBackendPivotCellsOversizeError(cellsCount, cellsLimit); + const backendPivotColumnsOversize = isBackendPivotColumnsOversizeError( + columnsCount, + columnsLimit, + ); + + const isChartOversizeError = + oversize || backendPivotCellsOversize || backendPivotColumnsOversize; + + if (isChartOversizeError) { + let errorType; + let limit; + let current; + if (backendPivotColumnsOversize) { + errorType = OversizeErrorType.PivotTableColumns; + limit = columnsLimit!; + current = columnsCount!; + } else if (backendPivotCellsOversize) { + errorType = OversizeErrorType.PivotTableCells; + limit = cellsLimit!; + current = cellsCount; + } else { + errorType = OversizeErrorType.Default; + limit = rowsLimit!; + current = rowsLength!; + } + + const oversizeError = getOversizeError({ + type: errorType, + limit, + current: current!, + }); + + ChartEditor._setError(oversizeError); + + return {}; + } + + let { + shapes = [], + shapesConfig = {}, + colors = [], + colorsConfig, + labels = [], + tooltips = [], + tooltipConfig, + geopointsConfig, + sort = [], + } = shared; + + if ((visualization as ServerVisualizationLayer).layerSettings) { + ({ + geopointsConfig, + colors, + colorsConfig = {}, + labels, + tooltips, + sort, + shapes = [], + shapesConfig = {}, + tooltipConfig = {}, + } = (visualization as ServerVisualizationLayer).commonPlaceholders); + } + + const chartColorsConfig = getChartColorsConfig({ + loadedColorPalettes, + colorsConfig, + availablePalettes: palettes, + defaultColorPaletteId, + }); + + const prepareFunctionArgs: PrepareFunctionArgs = { + placeholders: visualization.placeholders, + fields: [], + colors, + colorsConfig: chartColorsConfig, + geopointsConfig, + sort, + visualizationId: visualization.id, + layerSettings: (visualization as ServerVisualizationLayer).layerSettings, + labels, + tooltips, + tooltipConfig, + datasets: datasetsIds, + + resultData, + idToTitle, + idToDataType, + shared, + ChartEditor, + shapes, + shapesConfig, + segments, + + disableDefaultSorting, + features, + defaultColorPaletteId, + }; + + return (prepare as PrepareFunction)(prepareFunctionArgs); +}; diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/notifications/index.ts b/src/server/modes/charts/plugins/datalens/js/helpers/notifications/index.ts new file mode 100644 index 0000000..6f9d642 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/notifications/index.ts @@ -0,0 +1,59 @@ +import type { + ChartsInsight, + ServerField, + ServerPlaceholder, + ServerVisualization, +} from '../../../../../../../../shared'; +import {ChartsInsightLocator, getFakeTitleOrTitle} from '../../../../../../../../shared'; + +export const prepareNotifications = ( + notifications: ChartsInsight[], + visualization: ServerVisualization, +) => { + const fieldDict: Record = visualization.placeholders.reduce( + (acc: Record, placeholder: ServerPlaceholder) => { + placeholder.items.forEach((placeholderItem) => { + acc[placeholderItem.guid] = placeholderItem; + acc[placeholderItem.title] = placeholderItem; + }); + + return acc; + }, + {}, + ); + + return notifications.map((notification) => { + if (notification.locator.startsWith(ChartsInsightLocator.UsingDeprecatedDatetimeFields)) { + return prepareUsingDeprecatedDatetimeFieldsNotification(notification, fieldDict); + } + + return notification; + }); +}; + +const getTitleWithGuidRegexp = () => + /title-[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i; + +const prepareUsingDeprecatedDatetimeFieldsNotification = ( + notification: ChartsInsight, + fieldDict: Record, +) => { + const match = notification.message.match(getTitleWithGuidRegexp()); + + if (!match) { + return notification; + } + + const guid = match[0].replace('title-', ''); + + const field = fieldDict[guid]; + + if (!field) { + return notification; + } + + return { + ...notification, + message: notification.message.replace(getTitleWithGuidRegexp(), getFakeTitleOrTitle(field)), + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/totals/index.ts b/src/server/modes/charts/plugins/datalens/js/helpers/totals/index.ts new file mode 100644 index 0000000..833aefd --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/totals/index.ts @@ -0,0 +1,29 @@ +import type {GetMergedTotalsArgs} from './types'; + +export const getMergedTotals = (args: GetMergedTotalsArgs): (string | null)[] => { + const {mergedOrder, isFirstDataset, lastResultRow, totals, currentOrder, resultDataRows} = args; + + const mergedTotals = [...totals]; + + if (isFirstDataset) { + return [...mergedTotals, ...(lastResultRow.data || [])]; + } + + if (resultDataRows.length) { + currentOrder.forEach(({title}, index) => { + const indexInMergedOrder = mergedOrder.findIndex((orderItem) => { + if (Array.isArray(orderItem)) { + return orderItem.some((item) => item.title === title); + } + + return orderItem.title === title; + }); + + if (indexInMergedOrder >= 0) { + mergedTotals[indexInMergedOrder] = lastResultRow.data[index]; + } + }); + } + + return mergedTotals; +}; diff --git a/src/server/modes/charts/plugins/datalens/js/helpers/totals/types.ts b/src/server/modes/charts/plugins/datalens/js/helpers/totals/types.ts new file mode 100644 index 0000000..f5e6815 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/helpers/totals/types.ts @@ -0,0 +1,11 @@ +import type {ApiV2ResultDataRow} from '../../../../../../../../shared'; +import type {PrepareFunctionResultData} from '../../../preparers/types'; + +export type GetMergedTotalsArgs = { + isFirstDataset: boolean; + totals: (string | null)[]; + lastResultRow: ApiV2ResultDataRow; + currentOrder: {title: string; dataType: string}[]; + mergedOrder: PrepareFunctionResultData['order']; + resultDataRows: ApiV2ResultDataRow[]; +}; diff --git a/src/server/modes/charts/plugins/datalens/js/js-v1.5-private.ts b/src/server/modes/charts/plugins/datalens/js/js-v1.5-private.ts new file mode 100644 index 0000000..a2148ba --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/js-v1.5-private.ts @@ -0,0 +1,507 @@ +import type { + DATASET_FIELD_TYPES, + FeatureConfig, + IChartEditor, + Palette, + ServerChartsConfig, + ServerLink, + ServerVisualization, + Shared, +} from '../../../../../../shared'; +import {extractColorPalettesFromData} from '../../helpers/color-palettes'; +import {getDatasetIdAndLayerIdFromKey} from '../../helpers/misc'; +import type {PrepareFunctionDataRow, PrepareFunctionResultData} from '../preparers/types'; +import {mapChartsConfigToServerConfig} from '../utils/config-helpers'; +import {LAT, LONG} from '../utils/constants'; +import {log} from '../utils/misc-helpers'; + +import prepareSingleResult from './helpers/misc/prepare-single-result'; + +type MergedData = { + result: PrepareFunctionResultData; + fields: any[]; + fieldsByDataset: Record; +}; + +type MergeDataArgs = { + data: any; + links: ServerLink[]; +}; + +function mergeData({data, links}: MergeDataArgs) { + const mergedData: MergedData = { + result: { + data: [], + order: [], + totals: [], + }, + fields: [], + fieldsByDataset: {}, + }; + + let masterDatasetId: string; + + const mergedOrder: PrepareFunctionResultData['order'] = []; + + Object.keys(data).forEach((key) => { + // We get the key of the current dataset + const [currentDatasetId] = getDatasetIdAndLayerIdFromKey(key); + + if (data[key].pivot_data) { + mergedData.result = data[key]; + mergedData.fields = data[key].fields; + mergedData.fieldsByDataset[currentDatasetId] = data[key].fields; + + return; + } + + mergedData.fields = mergedData.fields.concat(data[key].result.fields); + mergedData.fieldsByDataset[currentDatasetId] = data[key].result.fields; + mergedData.result.totals = mergedData.result.totals.concat(data[key].result.totals || []); + + const mergedRows = mergedData.result.data; + + // If this is the first dataset, then this is the master dataset + if (mergedRows.length === 0) { + masterDatasetId = currentDatasetId; + + if (data[key].result.data.Data.length) { + let targetRow; + + // Merge all values + data[key].result.data.Data.forEach((row: any[], i: number) => { + targetRow = mergedRows[i] = []; + + row.forEach((value) => { + targetRow.push(value); + }); + }); + + // Merge all types + data[key].result.data.Type[1][1].forEach((type: [string]) => { + mergedOrder.push({ + datasetId: currentDatasetId, + title: type[0], + }); + }); + } + } else if (data[key].result.data.Data.length) { + // If this is not the first dataset, then we will merge the values by connections + // We record the order of the fields in the current dataset + const currentOrder: string[][] = []; + + // First we write down the original types + data[key].result.data.Type[1][1].forEach((type: string[]) => { + currentOrder.push(type); + }); + + // We find the necessary connections between the current dataset and the master + const neededLinks = links.filter((link) => { + return link.fields[currentDatasetId] && link.fields[masterDatasetId]; + }); + + // We write out the order of the fields from the wizard and the current dataset + const fieldsInMaster = mergedOrder.map((entry) => + Array.isArray(entry) ? entry[0].title : entry.title, + ); + const fieldsInCurrent = currentOrder.map((entry) => entry[0]); + + const indexInMasterByIndexInCurrent: Record = {}; + const indexInCurrentByLinkIndex: Record = {}; + + // Merge the order + neededLinks.forEach((link, linkIndex) => { + const current = link.fields[currentDatasetId].field; + const master = link.fields[masterDatasetId].field; + + const masterFieldData = + mergedData.fields.find((field) => field.guid === master.guid) || master; + const currentFieldData = + mergedData.fields.find((field) => field.guid === current.guid) || current; + + const indexInMaster = fieldsInMaster.findIndex( + (title) => title === masterFieldData.title, + ); + const indexInCurrent = fieldsInCurrent.findIndex( + (title) => title === currentFieldData.title, + ); + + indexInCurrentByLinkIndex[linkIndex] = indexInCurrent; + indexInMasterByIndexInCurrent[indexInCurrent] = indexInMaster; + }); + + fieldsInCurrent.forEach((_title, i) => { + const indexInMaster = indexInMasterByIndexInCurrent[i]; + + if (typeof indexInMaster === 'undefined') { + mergedOrder.push({ + datasetId: currentDatasetId, + title: currentOrder[i][0], + }); + } else { + const currentOrderItem = mergedOrder[indexInMaster]; + + const newOrderItem = { + datasetId: currentDatasetId, + title: currentOrder[i][0], + }; + + if (Array.isArray(currentOrderItem)) { + currentOrderItem.push(newOrderItem); + } else { + mergedOrder[indexInMaster] = [currentOrderItem, newOrderItem]; + } + } + }); + + const newMergedRows: PrepareFunctionDataRow[] = []; + + // Merge values + const rows: PrepareFunctionDataRow[] = data[key].result.data.Data; + + rows.forEach((row: PrepareFunctionDataRow) => { + let possibleTargetRows: PrepareFunctionDataRow[]; + const possibleTargetRow: PrepareFunctionDataRow = []; + + neededLinks.forEach((_link, linkIndex) => { + const indexInCurrent = indexInCurrentByLinkIndex[linkIndex]; + const indexInMaster = indexInMasterByIndexInCurrent[indexInCurrent]; + + const targetValue = row[indexInCurrent]; + + possibleTargetRows = (possibleTargetRows || mergedRows).filter( + (someRow: PrepareFunctionDataRow) => { + return someRow[indexInMaster] === targetValue; + }, + ); + + possibleTargetRow[indexInMaster] = targetValue; + }); + + const unlinkedFields: PrepareFunctionDataRow = []; + + // Merge unlinked indicators + // TODO here it will still be necessary to check that this is an indicator when there is a type in fields + row.forEach((value, i) => { + // If the field is not merged yet + if (typeof indexInMasterByIndexInCurrent[i] === 'undefined') { + unlinkedFields.push(value); + } + }); + + let targetRow = possibleTargetRows![0]; + + if (!targetRow || targetRow.length + unlinkedFields.length > mergedOrder.length) { + targetRow = possibleTargetRow; + + newMergedRows.push(targetRow); + } + + while (mergedOrder.length > targetRow.length + unlinkedFields.length) { + targetRow.push(null); + } + + unlinkedFields.forEach((field) => { + targetRow.push(field); + }); + }); + + mergedData.result.data = [...mergedRows, ...newMergedRows]; + } + }); + + mergedData.result.order = mergedOrder; + + return mergedData; +} + +type V1ServerResponse = any; + +export type JSTabOptions = + | [{shared: Shared | ServerChartsConfig; ChartEditor: IChartEditor; data: any}] + | [any, Shared | ServerChartsConfig, IChartEditor]; + +export const fallbackJSFunctionPrivate = ({ + options, + features, + palettes, + defaultColorPaletteId, +}: { + options: JSTabOptions; + features: FeatureConfig; + palettes: Record; + defaultColorPaletteId: string; +}) => { + let data: any; + let shared: Shared | ServerChartsConfig; + let ChartEditor: IChartEditor; + + if ('shared' in options[0]) { + data = options[0].data; + shared = options[0].shared as Shared | ServerChartsConfig; + ChartEditor = options[0].ChartEditor as IChartEditor; + } else { + data = options[0]; + shared = options[1] as Shared | ServerChartsConfig; + ChartEditor = options[2] as IChartEditor; + } + + shared = mapChartsConfigToServerConfig(shared); + + log('LOADED DATA:'); + log(data); + + const {colorPalettes: loadedColorPalettes, loadedData} = extractColorPalettesFromData(data); + + log('LINKS:'); + log(shared.links); + + Object.entries(loadedData).forEach(([key, value]: [string, V1ServerResponse]) => { + if (value.result && value.result.query) { + // For the inspector in ChartKit, we report the original request for data + ChartEditor.setDataSourceInfo(key, {query: value.result.query}); + } + + if (value.result && value.result.data_export_forbidden) { + // Hiding the data export button in the ChartKit menu + ChartEditor.setExtra?.('dataExportForbidden', true); + } + }); + + const newParams: Record = {}; + const idToTitle: Record = {}; + const idToDataType: Record = {}; + + const layers = (shared as ServerChartsConfig).visualization.layers; + let datasetsMeta: ( + | { + id: string; + fields: Record; + fieldsList: { + title: string; + guid: string; + dataType: string; + }[]; + } + | [] + )[] = []; + let mergedData: MergedData[]; + const datasetsSchemaFields = shared.datasetsPartialFields; + const datasetsIds = shared.datasetsIds; + + // If we have layers, then for each layer we get 1 set of confused data + // If we don 't have layers , then we get 1 set of confused data according to the classics + if (layers) { + mergedData = []; + + layers.forEach((layer) => { + const layerData: Record = {}; + Object.keys(loadedData).forEach((key) => { + const [_datasetId, layerId] = getDatasetIdAndLayerIdFromKey(key); + if (layer.layerSettings?.id === layerId) { + layerData[key.replace(layerId, '')] = loadedData[key]; + } + }); + + const layerMergedData = mergeData({ + data: layerData, + links: shared.links, + }); + + const datasetFields = layerMergedData.fields; + + datasetFields.forEach((field) => { + const fieldId = field.guid || field.id; + + newParams[fieldId] = []; + idToTitle[fieldId] = field.title; + idToDataType[fieldId] = field.data_type; + }); + + mergedData.push(layerMergedData); + + datasetsMeta = datasetsIds.map((id, datasetIndex) => { + if (datasetsMeta[datasetIndex]) { + return datasetsMeta[datasetIndex]; + } + + if (!layerMergedData.fieldsByDataset[id]) { + return []; + } + + const fields: Record = {}; + + const schema = datasetsSchemaFields[datasetIndex]; + + schema.forEach((item) => { + fields[item.guid] = idToTitle[item.guid]; + }); + + const fieldsList = layerMergedData.fieldsByDataset[id].map( + (item: {guid: string; id: string}) => { + const fieldId = item.guid || item.id; + + return { + title: idToTitle[fieldId], + guid: fieldId, + dataType: idToDataType[fieldId], + }; + }, + ); + + return { + id, + fields, + fieldsList, + }; + }); + }); + } else { + mergedData = [ + mergeData({ + data: loadedData, + links: shared.links, + }), + ]; + + const datasetFields = mergedData[0].fields; + + datasetFields.forEach((field) => { + const fieldId = field.guid || field.id; + + newParams[fieldId] = []; + idToTitle[fieldId] = field.title; + idToDataType[fieldId] = field.data_type; + }); + + datasetsMeta = datasetsIds.map((id, datasetIndex) => { + if (!mergedData[0].fieldsByDataset[id]) { + return []; + } + + const fields: Record = {}; + + const schema = datasetsSchemaFields[datasetIndex]; + + schema.forEach((item) => { + fields[item.guid] = idToTitle[item.guid]; + }); + + const fieldsList = mergedData[0].fieldsByDataset[id].map( + (item: {guid: string; id: string}) => { + const fieldId = item.guid || item.id; + + return { + title: idToTitle[fieldId], + guid: fieldId, + dataType: idToDataType[fieldId], + }; + }, + ); + + return { + id, + fields, + fieldsList, + }; + }); + } + + ChartEditor.updateParams(newParams); + ChartEditor.setExtra('datasets', datasetsMeta); + + log('MERGED DATA:'); + log(mergedData); + + let result: any = []; + + if (layers) { + let bounds: null | any = null; + + layers.forEach((layer, layerIndex) => { + const resultData = mergedData[layerIndex].result; + + const localResult = prepareSingleResult({ + resultData, + shared: shared as ServerChartsConfig, + visualization: layer, + idToTitle, + idToDataType, + ChartEditor, + datasetsIds, + loadedColorPalettes, + palettes, + features, + defaultColorPaletteId, + }); + + if (localResult && localResult[0] && localResult[0].bounds) { + const {bounds: localBounds} = localResult[0]; + + const boundExists = Boolean(bounds && bounds[0] && bounds[1]); + const localBoundExists = Boolean(localBounds && localBounds[0] && localBounds[1]); + + if (boundExists && localBoundExists) { + if (localBounds[0][LAT] < bounds[0][LAT]) { + bounds[0][LAT] = localBounds[0][LAT]; + } + + if (localBounds[0][LONG] < bounds[0][LONG]) { + bounds[0][LONG] = localBounds[0][LONG]; + } + + if (localBounds[1][LAT] > bounds[1][LAT]) { + bounds[1][LAT] = localBounds[1][LAT]; + } + + if (localBounds[1][LONG] > bounds[1][LONG]) { + bounds[1][LONG] = localBounds[1][LONG]; + } + } else if (localBoundExists) { + bounds = [...localBounds]; + } + } + + if (Array.isArray(localResult)) { + result = [...result, ...localResult]; + } + }); + + if (ChartEditor && bounds) { + ChartEditor.updateHighchartsConfig({ + state: { + bounds, + }, + }); + } + } else { + const resultData = mergedData[0].result; + + result = prepareSingleResult({ + resultData, + shared: shared as ServerChartsConfig, + visualization: shared.visualization as ServerVisualization, + idToTitle, + idToDataType, + ChartEditor, + datasetsIds, + loadedColorPalettes, + palettes, + features, + defaultColorPaletteId, + }); + + if (result && result[0] && result[0].bounds) { + ChartEditor.updateHighchartsConfig({ + state: { + bounds: result[0].bounds, + }, + }); + } + } + + log('RESULT:'); + log(result); + + return result; +}; diff --git a/src/server/modes/charts/plugins/datalens/js/js.ts b/src/server/modes/charts/plugins/datalens/js/js.ts new file mode 100644 index 0000000..a2083ad --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/js/js.ts @@ -0,0 +1,1073 @@ +import cloneDeep from 'lodash/cloneDeep'; +import moment from 'moment'; + +import type { + ApiV2RequestField, + ApiV2ResultDataRow, + ApiV2ResultField, + ChartsInsight, + ColorPalette, + FeatureConfig, + IChartEditor, + Palette, + ServerChartsConfig, + ServerField, + ServerLink, + ServerShape, + ServerVisualization, + ServerVisualizationLayer, + Shared, +} from '../../../../../../shared'; +import { + DATASET_FIELD_TYPES, + MAX_SEGMENTS_NUMBER, + WizardVisualizationId, + isDateType, + isMarkupField, + isMarkupItem, + markupToRawString, +} from '../../../../../../shared'; +import {extractColorPalettesFromData} from '../../helpers/color-palettes'; +import {getDatasetIdAndLayerIdFromKey, getFieldList} from '../../helpers/misc'; +import type {ResolvedSource} from '../../types'; +import {prepareGravityChartArea} from '../preparers/area'; +import prepareBackendPivotTableData from '../preparers/backend-pivot-table'; +import type {PivotData} from '../preparers/backend-pivot-table/types'; +import {prepareGravityChartBarX, prepareHighchartsBarX} from '../preparers/bar-x'; +import {prepareGravityChartsBarY, prepareHighchartsBarY} from '../preparers/bar-y'; +import prepareFlatTableData from '../preparers/flat-table'; +import {prepareFunnel} from '../preparers/funnel'; +import prepareGeopointData from '../preparers/geopoint'; +import prepareGeopointWithClusterData from '../preparers/geopoint-with-cluster'; +import prepareGeopolygonData from '../preparers/geopolygon'; +import prepareHeatmapData from '../preparers/heatmap'; +import {prepareGravityChartLine, prepareHighchartsLine} from '../preparers/line'; +import prepareMetricData from '../preparers/metric'; +import preparePivotTableData from '../preparers/old-pivot-table/old-pivot-table'; +import {prepareD3Pie, prepareHighchartsPie} from '../preparers/pie'; +import preparePolylineData from '../preparers/polyline'; +import {prepareGravityChartsScatter, prepareHighchartsScatter} from '../preparers/scatter'; +import {prepareD3Treemap, prepareHighchartsTreemap} from '../preparers/treemap'; +import type { + LayerChartMeta, + PrepareFunction, + PrepareFunctionArgs, + PrepareFunctionDataRow, + PrepareFunctionResultData, + ResultDataOrderItem, +} from '../preparers/types'; +import type {ChartPlugin, DatasetMeta} from '../types'; +import {mapChartsConfigToServerConfig} from '../utils/config-helpers'; +import {LAT, LONG} from '../utils/constants'; +import {preprocessHierarchies} from '../utils/hierarchy-helpers'; +import {getServerDateFormat, log} from '../utils/misc-helpers'; + +import {OversizeErrorType} from './constants/errors'; +import {getChartColorsConfig} from './helpers/colors'; +import {getOversizeError} from './helpers/errors/oversize-error'; +import { + isBackendPivotCellsOversizeError, + isBackendPivotColumnsOversizeError, + isDefaultOversizeError, + isSegmentsOversizeError, +} from './helpers/errors/oversize-error/utils'; +import { + combineLayersIntoSingleChart, + extendCombinedChartGraphs, + getLayerChartMeta, + mergeResultForCombinedCharts, +} from './helpers/layer-chart'; +import {prepareNotifications} from './helpers/notifications'; +import {getMergedTotals} from './helpers/totals'; + +type MergedData = { + result: PrepareFunctionResultData; + fields: any[]; + fieldsByDataset: Record; + notifications: ChartsInsight[]; +}; + +type MergeDataArgs = { + data: any; + links: ServerLink[]; +}; + +function getValueForCompare( + value: string | null, + field: {dataType?: string} | undefined, + otherField: {dataType?: string} | undefined, +) { + if ( + field?.dataType === DATASET_FIELD_TYPES.DATETIMETZ || + (field?.dataType && isDateType(field.dataType) && field.dataType !== otherField?.dataType) + ) { + return moment.utc(value).valueOf(); + } + + if (isMarkupField({data_type: String(field?.dataType)}) && isMarkupItem(value)) { + return markupToRawString(value); + } + + return value; +} + +function mergeData({data, links}: MergeDataArgs) { + const mergedData: MergedData = { + result: { + data: [], + legend: [], + order: [], + totals: [], + }, + fields: [], + notifications: [], + fieldsByDataset: {}, + }; + + let masterDatasetId: string; + + const mergedOrder: PrepareFunctionResultData['order'] = []; + + Object.keys(data).forEach((key) => { + // We get the key of the current dataset + const [currentDatasetId] = getDatasetIdAndLayerIdFromKey(key); + + const mappedFields = data[key].fields.map((item: any) => ({...item, guid: item.id})); + + if (data[key].pivot_data) { + mergedData.result = data[key]; + mergedData.fields = mappedFields; + mergedData.notifications = mergedData.notifications.concat( + data[key].notifications || [], + ); + mergedData.fieldsByDataset[currentDatasetId] = mappedFields; + + return; + } + + mergedData.fields = mergedData.fields.concat(mappedFields); + mergedData.fieldsByDataset[currentDatasetId] = mappedFields; + mergedData.notifications = mergedData.notifications.concat(data[key].notifications || []); + + const mergedRows = mergedData.result.data; + const mergedLegends = mergedData.result.legend || ([] as number[][]); + + const resultDataRows = data[key].result_data[0].rows as ApiV2ResultDataRow[]; + if (!resultDataRows.length) { + return; + } + + const legends = resultDataRows.map((el) => el.legend); + const resultFields = data[key].fields as ApiV2ResultField[]; + + const lastResultRow = resultDataRows[resultDataRows.length - 1]; + + const resultContainsTotals = lastResultRow?.legend.some((legendItemId: number) => { + const field = resultFields.find((field: any) => field.legend_item_id === legendItemId); + + return field?.role_spec?.role === 'total'; + }); + + let isEmptyPlaceholder = false; + const currentOrder: {guid: string; title: string; dataType: string}[] = []; + + if (lastResultRow?.legend.length === 1) { + const field = resultFields.find( + (field) => field.legend_item_id === lastResultRow?.legend[0], + ); + + // A case where multi-datasets are used and the only thing that lies in the last row with data is an empty row + // Occurs when only a Dimension is used in one of the datasets and the role: template is specified for it in the request + // This placeholder then breaks the visualization if you don't get rid of it. + // Therefore, we are looking for a field and if it is a template, we delete it from the array of strings. + + isEmptyPlaceholder = field?.role_spec?.role === 'template'; + } + + if (resultContainsTotals || isEmptyPlaceholder) { + resultDataRows.pop(); + } + + // If this is the first dataset, then this is the master dataset + const isFirstDataset = mergedRows.length === 0; + + if (isFirstDataset) { + masterDatasetId = currentDatasetId; + + // Merge all values + resultDataRows.forEach((row: {data: any[]; legend: number[]}, i: number) => { + mergedRows[i] = [...row.data]; + mergedLegends[i] = [...row.legend]; + }); + + const rowLegendIds = resultDataRows[0]?.legend || []; + rowLegendIds.forEach((legendId) => { + const field = resultFields.find((f) => f.legend_item_id === legendId); + if (field) { + mergedOrder.push({ + datasetId: currentDatasetId, + title: field.title, + dataType: field.data_type, + }); + } + }); + } else { + // If this is not the first dataset, then we will merge the values by connections + // We record the order of the fields in the current dataset + // First we write down the original types + + const rowLegendIds = resultDataRows[0]?.legend || []; + rowLegendIds.forEach((legendId: number) => { + const field = resultFields.find((field) => field.legend_item_id === legendId); + if (field) { + currentOrder.push({ + guid: field.id, + title: field.title, + dataType: field.data_type, + }); + } + }); + + // We write out the order of the fields from the wizard and the current dataset + const fieldsInMaster = mergedOrder.map((entries) => { + const entry = Array.isArray(entries) ? entries[0] : entries; + return { + title: entry.title, + dataType: entry.dataType, + }; + }); + + // We find the necessary connections between the current dataset and the master + const neededLinks = links.filter((link) => { + return link.fields[currentDatasetId] && link.fields[masterDatasetId]; + }); + const indexInMasterByIndexInCurrent: Record = {}; + const indexInCurrentByLinkIndex: Record = {}; + neededLinks.forEach((link, linkIndex) => { + const current = link.fields[currentDatasetId].field; + const currentFieldData = + mergedData.fields.find((field) => field.guid === current.guid) || current; + + const master = link.fields[masterDatasetId].field; + const masterFieldData = + mergedData.fields.find((field) => field.guid === master.guid) || master; + + const indexInMaster = fieldsInMaster.findIndex( + ({title}) => title === masterFieldData.title, + ); + const indexInCurrent = currentOrder.findIndex( + ({title}) => title === currentFieldData.title, + ); + + indexInCurrentByLinkIndex[linkIndex] = indexInCurrent; + indexInMasterByIndexInCurrent[indexInCurrent] = indexInMaster; + }); + currentOrder.forEach((_title, i) => { + const indexInMaster = indexInMasterByIndexInCurrent[i]; + const resultOrderItem = { + datasetId: currentDatasetId, + title: currentOrder[i].title, + dataType: currentOrder[i].dataType, + }; + + if (typeof indexInMaster === 'undefined') { + mergedOrder.push(resultOrderItem); + } else { + const currentOrderItem = mergedOrder[indexInMaster]; + if (!Array.isArray(currentOrderItem)) { + mergedOrder[indexInMaster] = [currentOrderItem]; + } + + (mergedOrder[indexInMaster] as ResultDataOrderItem[]).push(resultOrderItem); + } + }); + + const linkOrder = neededLinks.map((link) => { + const left = mergedOrder.findIndex((item) => { + const orderItem = Array.isArray(item) ? item[0] : item; + return orderItem.title === link.fields[masterDatasetId]?.field.title; + }); + const right = currentOrder.findIndex((item) => { + return item.title === link.fields[currentDatasetId]?.field.title; + }); + + return [left, right]; + }); + + const sourceDataMap: Record = {}; + mergedRows.forEach((row) => { + const joinBy = linkOrder + .map(([left, right]) => { + const orderItems = mergedOrder[left]; + const field = Array.isArray(orderItems) ? orderItems[0] : orderItems; + + return getValueForCompare(row[left], field, currentOrder[right]); + }) + .join(); + + sourceDataMap[joinBy] = row; + }); + + const newMergedRows: PrepareFunctionDataRow[] = []; + const rows: ApiV2ResultDataRow[] = data[key].result_data[0].rows; + rows.forEach((row: ApiV2ResultDataRow) => { + const joinBy = linkOrder + .map(([left, right]) => { + const orderItem = mergedOrder[left]; + const otherField = Array.isArray(orderItem) ? orderItem[0] : orderItem; + + return getValueForCompare(row.data[right], currentOrder[right], otherField); + }) + .join(); + let targetRow = sourceDataMap[joinBy]; + + const possibleTargetRow: PrepareFunctionDataRow = []; + linkOrder.forEach(([left, right]) => { + possibleTargetRow[left] = row.data[right]; + }); + + const unlinkedFields: PrepareFunctionDataRow = []; + + // Merge unlinked indicators + // TODO here it will still be necessary to check that this is an indicator when there is a type in fields + row.data.forEach((value, i) => { + // If the field is not merged yet + if (typeof indexInMasterByIndexInCurrent[i] === 'undefined') { + unlinkedFields.push(value); + } + }); + + if (!targetRow || targetRow.length + unlinkedFields.length > mergedOrder.length) { + targetRow = possibleTargetRow; + + newMergedRows.push(targetRow); + } + + while (mergedOrder.length > targetRow.length + unlinkedFields.length) { + targetRow.push(null); + } + + unlinkedFields.forEach((field) => { + targetRow.push(field); + }); + }); + + mergedData.result.data = [...mergedRows, ...newMergedRows]; + mergedData.result.legend = [...mergedLegends, ...legends]; + } + + if (resultContainsTotals) { + mergedData.result.totals = getMergedTotals({ + isFirstDataset, + mergedOrder, + lastResultRow, + totals: mergedData.result.totals, + currentOrder, + resultDataRows, + }) as string[]; + } + }); + + mergedData.result.order = mergedOrder; + + return mergedData; +} + +type PrepareSingleResultArgs = { + resultData: PrepareFunctionResultData; + fields: ApiV2RequestField[]; + notifications: ChartsInsight[]; + visualization: ServerVisualization; + shared: ServerChartsConfig; + idToTitle: Record; + idToDataType: Record; + ChartEditor: IChartEditor; + datasetsIds: string[]; + palettes: Record; + loadedColorPalettes: Record; + layerChartMeta?: LayerChartMeta; + usedColors?: (string | undefined)[]; + features: FeatureConfig; + plugin?: ChartPlugin; + defaultColorPaletteId: string; +}; + +// eslint-disable-next-line complexity +function prepareSingleResult({ + resultData, + fields, + notifications, + visualization, + shared, + idToTitle, + idToDataType, + ChartEditor, + datasetsIds, + loadedColorPalettes, + layerChartMeta, + usedColors, + palettes, + features, + plugin, + defaultColorPaletteId, +}: PrepareSingleResultArgs) { + const isVisualizationWithLayers = Boolean( + (visualization as ServerVisualizationLayer).layerSettings, + ); + const commonPlaceholders = (visualization as ServerVisualizationLayer).commonPlaceholders; + + preprocessHierarchies({ + visualizationId: visualization.id, + placeholders: visualization.placeholders, + params: ChartEditor.getParams(), + sharedData: shared.sharedData, + colors: isVisualizationWithLayers ? commonPlaceholders.colors : shared.colors, + shapes: (isVisualizationWithLayers ? commonPlaceholders.shapes : shared.shapes) || [], + segments: shared.segments || [], + }); + + const { + sharedData: {drillDownData}, + } = shared; + + let rowsLength: undefined | number; + + let cellsCount: number | undefined; + let columnsCount: number | undefined; + + if ((resultData as any)?.pivot_data) { + const pivotData = (resultData as any).pivot_data as PivotData; + + const rows = pivotData.rows || []; + const columns = pivotData.columns || []; + + const rowsValues = rows[0]?.values || []; + + cellsCount = rows.length * rowsValues.length; + columnsCount = columns.length; + } else { + rowsLength = resultData.data && resultData.data.length; + } + + if (notifications.length) { + notifications = prepareNotifications(notifications, visualization); + + ChartEditor.setChartsInsights(notifications); + } + + if (drillDownData) { + const currentDrillDownField = drillDownData.fields[drillDownData.level]; + ChartEditor.updateConfig({ + drillDown: { + breadcrumbs: drillDownData.breadcrumbs, + dateFormat: getServerDateFormat(currentDrillDownField?.data_type || ''), + }, + }); + + ChartEditor.updateParams({ + drillDownLevel: drillDownData.level, + drillDownFilters: drillDownData.filters, + isColorDrillDown: drillDownData.isColorDrillDown, + }); + } + + if (rowsLength === 0) { + return {}; + } + + let prepare; + + let rowsLimit: number | undefined; + let cellsLimit: number | undefined; + let columnsLimit: number | undefined; + + let shapes: ServerShape[] = []; + let shapesConfig; + + const segments: ServerField[] = shared.segments || []; + + switch (visualization.id) { + case WizardVisualizationId.Line: { + shapes = shared.shapes || []; + shapesConfig = shared.shapesConfig; + + if (plugin === 'gravity-charts') { + prepare = prepareGravityChartLine; + } else { + prepare = prepareHighchartsLine; + } + rowsLimit = 75000; + break; + } + + case WizardVisualizationId.Area: + case WizardVisualizationId.Area100p: { + if (plugin === 'gravity-charts') { + prepare = prepareGravityChartArea; + } else { + prepare = prepareHighchartsLine; + } + rowsLimit = 75000; + break; + } + + case WizardVisualizationId.Bar: + case WizardVisualizationId.Bar100p: { + if (plugin === 'gravity-charts') { + prepare = prepareGravityChartsBarY; + } else { + prepare = prepareHighchartsBarY; + } + rowsLimit = 75000; + break; + } + + case WizardVisualizationId.Column: + case WizardVisualizationId.Column100p: { + if (plugin === 'gravity-charts') { + prepare = prepareGravityChartBarX; + } else { + prepare = prepareHighchartsBarX; + } + rowsLimit = 75000; + break; + } + + case 'bar-x-d3': { + prepare = prepareGravityChartBarX; + rowsLimit = 75000; + break; + } + + case WizardVisualizationId.Scatter: { + if (plugin === 'gravity-charts') { + prepare = prepareGravityChartsScatter; + } else { + prepare = prepareHighchartsScatter; + } + shapes = shared.shapes || []; + shapesConfig = shared.shapesConfig; + rowsLimit = 75000; + break; + } + + case 'scatter-d3': + shapes = shared.shapes || []; + shapesConfig = shared.shapesConfig; + prepare = prepareGravityChartsScatter; + rowsLimit = 75000; + break; + + case 'pie': + case 'donut': + if (plugin === 'gravity-charts') { + prepare = prepareD3Pie; + } else { + prepare = prepareHighchartsPie; + } + + rowsLimit = 1000; + break; + + case 'metric': + prepare = prepareMetricData; + rowsLimit = 1000; + break; + + case 'treemap': + if (plugin === 'gravity-charts') { + prepare = prepareD3Treemap; + } else { + prepare = prepareHighchartsTreemap; + } + rowsLimit = 800; + break; + + case WizardVisualizationId.Funnel: + prepare = prepareFunnel; + rowsLimit = 800; + break; + + case 'flatTable': + prepare = prepareFlatTableData; + rowsLimit = 100000; + break; + + case 'pivotTable': { + const pivotFallbackEnabled = shared.extraSettings?.pivotFallback === 'on'; + + if (pivotFallbackEnabled) { + prepare = preparePivotTableData; + rowsLimit = 40000; + } else { + prepare = prepareBackendPivotTableData; + cellsLimit = 100000; + columnsLimit = 800; + } + break; + } + case 'geopoint': + prepare = prepareGeopointData; + rowsLimit = 40000; + break; + case 'geopoint-with-cluster': + prepare = prepareGeopointWithClusterData; + rowsLimit = 40000; + break; + case 'geopolygon': + prepare = prepareGeopolygonData; + rowsLimit = 40000; + break; + + case 'heatmap': + prepare = prepareHeatmapData; + rowsLimit = 40000; + break; + + case 'polyline': + prepare = preparePolylineData; + rowsLimit = 40000; + break; + } + + const oversize = isDefaultOversizeError(rowsLength, rowsLimit); + const backendPivotCellsOversize = isBackendPivotCellsOversizeError(cellsCount, cellsLimit); + const backendPivotColumnsOversize = isBackendPivotColumnsOversizeError( + columnsCount, + columnsLimit, + ); + const {segmentsOversize, segmentsNumber} = isSegmentsOversizeError({ + segments, + idToTitle, + order: resultData.order, + data: resultData.data, + }); + + const isChartOversizeError = + oversize || backendPivotCellsOversize || backendPivotColumnsOversize || segmentsOversize; + + if (isChartOversizeError) { + let errorType; + let limit; + let current; + if (backendPivotColumnsOversize) { + errorType = OversizeErrorType.PivotTableColumns; + limit = columnsLimit!; + current = columnsCount!; + } else if (backendPivotCellsOversize) { + errorType = OversizeErrorType.PivotTableCells; + limit = cellsLimit!; + current = cellsCount; + } else if (segmentsOversize) { + errorType = OversizeErrorType.SegmentsNumber; + limit = MAX_SEGMENTS_NUMBER; + current = segmentsNumber; + } else { + errorType = OversizeErrorType.Default; + limit = rowsLimit!; + current = rowsLength!; + } + + const oversizeError = getOversizeError({ + type: errorType, + limit, + current: current!, + }); + + ChartEditor._setError(oversizeError); + + return {}; + } + + let { + colors = [], + colorsConfig, + labels = [], + tooltips = [], + tooltipConfig, + geopointsConfig, + sort = [], + } = shared; + + if ((visualization as ServerVisualizationLayer).layerSettings) { + ({ + geopointsConfig, + colors, + colorsConfig = {}, + labels, + tooltips, + sort, + shapes = [], + shapesConfig = {}, + tooltipConfig, + } = (visualization as ServerVisualizationLayer).commonPlaceholders); + } + + const chartColorsConfig = getChartColorsConfig({ + loadedColorPalettes, + colorsConfig, + availablePalettes: palettes, + defaultColorPaletteId, + }); + + const prepareFunctionArgs: PrepareFunctionArgs = { + placeholders: visualization.placeholders, + colors, + colorsConfig: chartColorsConfig, + geopointsConfig, + sort, + visualizationId: visualization.id, + layerSettings: (visualization as ServerVisualizationLayer).layerSettings, + labels, + tooltips, + tooltipConfig, + datasets: datasetsIds, + + resultData, + fields, + idToTitle, + idToDataType, + shared, + ChartEditor, + shapes, + shapesConfig, + segments, + layerChartMeta, + usedColors, + features, + defaultColorPaletteId, + }; + + return (prepare as PrepareFunction)(prepareFunctionArgs); +} + +function updateItemsFromDatasetFields( + item: unknown, + datasetFields: Record, +) { + if (item && typeof item === 'object') { + if ('guid' in item && 'datasetId' in item) { + const field = item as ServerField; + const datasetField = datasetFields?.[String(field.datasetId)]?.datasetFields?.find( + (d) => d.guid === String(field.guid), + ); + if (!datasetField) { + return; + } + + if ('ui_settings' in field || 'ui_settings' in datasetField) { + field['ui_settings'] = datasetField['ui_settings']; + } + } else { + Object.values(item).forEach((val) => updateItemsFromDatasetFields(val, datasetFields)); + } + } +} + +type V1ServerResponse = any; + +export const buildGraphPrivate = (args: { + shared: Shared | ServerChartsConfig; + ChartEditor: IChartEditor; + data: any; + palettes: Record; + features: FeatureConfig; + plugin?: ChartPlugin; + defaultColorPaletteId: string; + sources?: Record; +}) => { + const { + shared: chartSharedConfig, + ChartEditor, + data, + palettes, + features, + plugin, + defaultColorPaletteId, + sources = {}, + } = args; + + log('LOADED DATA:'); + log(data); + + const sharedServerConfig = mapChartsConfigToServerConfig(chartSharedConfig); + + const shared = cloneDeep(sharedServerConfig); + updateItemsFromDatasetFields(shared, sources); + + const {colorPalettes: loadedColorPalettes, loadedData} = extractColorPalettesFromData(data); + + log('LINKS:'); + log(shared.links); + + Object.entries(loadedData).forEach(([key, value]: [string, V1ServerResponse]) => { + const query = (value.blocks || []) + .map((block: {query: string}) => block.query) + .filter(Boolean) + .join('\n\n'); + + if (query) { + // For the inspector in ChartKit, we report the original request for data + ChartEditor.setDataSourceInfo(key, {query}); + } + + if (value.data_export_forbidden) { + // Hiding the data export button in the ChartKit menu + ChartEditor.setExtra?.('dataExportForbidden', true); + } + }); + + const newParams: Record = {}; + const idToTitle: Record = {}; + const idToDataType: Record = {}; + + const layers = (shared as ServerChartsConfig).visualization.layers; + let datasetsMeta: DatasetMeta[] = []; + let mergedData: MergedData[]; + const datasetsSchemaFields = shared.datasetsPartialFields; + const datasetsIds = shared.datasetsIds; + + // If we have layers, then for each layer we get 1 set of merged data + // If we don 't have layers , then we get 1 set of merged data according to the classics + if (layers) { + mergedData = []; + + layers.forEach((layer) => { + const layerData: Record = {}; + Object.keys(loadedData).forEach((key) => { + const [_datasetId, layerId] = getDatasetIdAndLayerIdFromKey(key); + if (layer.layerSettings?.id === layerId) { + layerData[key.replace(layerId, '')] = loadedData[key]; + } + }); + + const layerMergedData = mergeData({ + data: layerData, + links: shared.links, + }); + + const datasetFields = layerMergedData.fields; + + datasetFields.forEach((field) => { + const fieldId = field.guid || field.id; + + newParams[fieldId] = []; + idToTitle[fieldId] = field.title; + idToDataType[fieldId] = field.data_type; + }); + + mergedData.push(layerMergedData); + + datasetsMeta = datasetsIds.reduce((acc, id, datasetIndex) => { + if (datasetsMeta[datasetIndex]) { + return acc; + } + + if (!layerMergedData.fieldsByDataset[id]) { + return acc; + } + + const fields: Record = {}; + + const schema = datasetsSchemaFields[datasetIndex] ?? []; + + schema.forEach((item) => { + fields[item.guid] = idToTitle[item.guid]; + }); + + acc.push({ + id, + fields, + fieldsList: getFieldList( + layerMergedData.fieldsByDataset[id], + layer.placeholders, + ), + }); + return acc; + }, [] as DatasetMeta[]); + }); + } else { + mergedData = [ + mergeData({ + data: loadedData, + links: shared.links, + }), + ]; + + const datasetFields = mergedData[0].fields; + + datasetFields.forEach((field) => { + const fieldId = field.guid || field.id; + + newParams[fieldId] = []; + idToTitle[fieldId] = field.title; + idToDataType[fieldId] = field.data_type; + }); + + datasetsMeta = datasetsIds.reduce((acc, id, datasetIndex) => { + if (!mergedData[0].fieldsByDataset[id]) { + return acc; + } + + const fields: Record = {}; + + const schema = datasetsSchemaFields[datasetIndex] ?? []; + + schema.forEach((item) => { + fields[item.guid] = idToTitle[item.guid]; + }); + + acc.push({ + id, + fields, + fieldsList: getFieldList( + mergedData[0].fieldsByDataset[id], + shared.visualization.placeholders, + ), + }); + return acc; + }, [] as DatasetMeta[]); + } + + ChartEditor.updateParams(newParams); + ChartEditor.setExtra('datasets', datasetsMeta); + + log('MERGED DATA:'); + log(mergedData); + + let result: any = []; + let bounds: null | any = null; + + if (layers) { + const legendValues: Record = {}; + const layerChartMeta = getLayerChartMeta({ + isComboChart: shared.visualization.id === 'combined-chart', + }); + const usedColors: (string | undefined)[] = []; + layers.forEach((layer, layerIndex) => { + const resultData = mergedData[layerIndex].result; + const fields = mergedData[layerIndex].fields; + const notifications = mergedData[layerIndex].notifications; + + const localResult = prepareSingleResult({ + resultData, + fields, + notifications, + shared: shared as ServerChartsConfig, + visualization: layer, + idToTitle, + idToDataType, + ChartEditor, + datasetsIds, + loadedColorPalettes, + layerChartMeta, + usedColors, + palettes, + features, + plugin, + defaultColorPaletteId, + }); + + if (localResult && localResult[0] && localResult[0].bounds) { + const {bounds: localBounds} = localResult[0]; + + const boundExists = Boolean(bounds && bounds[0] && bounds[1]); + const localBoundExists = Boolean(localBounds && localBounds[0] && localBounds[1]); + + if (boundExists && localBoundExists) { + if (localBounds[0][LAT] < bounds[0][LAT]) { + bounds[0][LAT] = localBounds[0][LAT]; + } + + if (localBounds[0][LONG] < bounds[0][LONG]) { + bounds[0][LONG] = localBounds[0][LONG]; + } + + if (localBounds[1][LAT] > bounds[1][LAT]) { + bounds[1][LAT] = localBounds[1][LAT]; + } + + if (localBounds[1][LONG] > bounds[1][LONG]) { + bounds[1][LONG] = localBounds[1][LONG]; + } + } else if (localBoundExists) { + bounds = [...localBounds]; + } + } + + if (shared.visualization.id === WizardVisualizationId.CombinedChart) { + if (plugin === 'gravity-charts') { + result.push(localResult); + } else if (localResult?.graphs) { + extendCombinedChartGraphs({ + graphs: localResult.graphs, + layer, + layers, + legendValues, + }); + result = result.concat(localResult); + } + } else if (Array.isArray(localResult)) { + result = [...result, ...localResult]; + } + }); + + if (shared.visualization.id === WizardVisualizationId.CombinedChart) { + if (plugin === 'gravity-charts') { + result = combineLayersIntoSingleChart({layers: result}); + } else { + result = mergeResultForCombinedCharts(result); + } + } + } else { + const resultData = mergedData[0].result; + const fields = mergedData[0].fields; + const notifications = mergedData[0].notifications; + + result = prepareSingleResult({ + resultData, + fields, + notifications, + shared: shared as ServerChartsConfig, + visualization: shared.visualization as ServerVisualization, + idToTitle, + idToDataType, + ChartEditor, + datasetsIds, + loadedColorPalettes, + palettes, + features, + plugin, + defaultColorPaletteId, + }); + + if (result?.[0]?.bounds) { + bounds = result[0].bounds; + } + } + + if (bounds) { + ChartEditor.updateHighchartsConfig({ + ...(Boolean(bounds) && {state: {bounds}}), + }); + } + + const isTableChart = [ + WizardVisualizationId.FlatTable, + WizardVisualizationId.PivotTable, + ].includes(shared.visualization.id as WizardVisualizationId); + + if (isTableChart) { + const page = ChartEditor.getCurrentPage(); + const limit = shared.extraSettings?.limit; + const shouldDisablePaginator = page === 1 && limit && limit > (result.rows?.length ?? 0); + if (shouldDisablePaginator) { + ChartEditor.updateConfig({paginator: {enabled: false}}); + } + } + + log('RESULT:'); + log(result); + + return result; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/__tests__/common.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/__tests__/common.mock.ts new file mode 100644 index 0000000..77c679b --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/__tests__/common.mock.ts @@ -0,0 +1,41 @@ +import type {IChartEditor, ServerField} from '../../../../../../../shared'; +import {COMMON_PALETTE_ID, DATASET_FIELD_TYPES} from '../../../../../../../shared'; +import classic20 from '../../../../../../../shared/constants/colors/common/classic-20'; +import type {PrepareFunctionArgs} from '../types'; + +export const EmptyPrepapreArgs: PrepareFunctionArgs = { + ChartEditor: { + getWidgetConfig: () => {}, + } as IChartEditor, + // @ts-ignore + shared: {}, + sort: [], + colors: [], + colorsConfig: { + loadedColorPalettes: {}, + colors: ['blue', 'red', 'orange'], + gradientColors: [], + availablePalettes: {[COMMON_PALETTE_ID.CLASSIC_20]: classic20}, + }, + shapes: [], + segments: [], + idToTitle: {}, + idToDataType: {}, + features: {}, +}; + +const datasetId = 'datasetId'; + +export const IntegerField = { + datasetId, + title: 'IntegerField_title', + guid: 'IntegerField_guid', + data_type: DATASET_FIELD_TYPES.INTEGER, +} as ServerField; + +export const DateTimeField = { + datasetId, + title: 'DateTimeField_title', + guid: 'DateTimeField_guid', + data_type: DATASET_FIELD_TYPES.GENERICDATETIME, +} as ServerField; diff --git a/src/server/modes/charts/plugins/datalens/preparers/area/index.ts b/src/server/modes/charts/plugins/datalens/preparers/area/index.ts new file mode 100644 index 0000000..2cab311 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/area/index.ts @@ -0,0 +1,360 @@ +import type { + AreaSeries, + AreaSeriesData, + ChartData, + ChartSeries, +} from '@gravity-ui/chartkit/gravity-charts'; +import merge from 'lodash/merge'; +import sortBy from 'lodash/sortBy'; + +import type {SeriesExportSettings, ServerField} from '../../../../../../../shared'; +import { + AxisMode, + PlaceholderId, + WizardVisualizationId, + getFakeTitleOrTitle, + getXAxisMode, + isDateField, + isHtmlField, + isMarkdownField, + isMarkupField, + isNumberField, +} from '../../../../../../../shared'; +import type {ExtendedChartData} from '../../../../../../../shared/types/chartkit'; +import {wrapHtml} from '../../../../../../../shared/utils/ui-sandbox'; +import { + getBaseChartConfig, + getTotalsPrecisionFromSeriesTooltips, + getYAxisBaseConfig, +} from '../../gravity-charts/utils'; +import {getFieldFormatOptions} from '../../gravity-charts/utils/format'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import {getExportColumnSettings} from '../../utils/export-helpers'; +import {SEGMENT_TITLE_BASE_CONFIG} from '../constants'; +import {getAxisFormatting, getAxisType} from '../helpers/axis'; +import {DATA_LABEL_DEFAULT_PADDING} from '../helpers/axis/data-labels'; +import {concatStringValues} from '../helpers/common'; +import {getSegmentMap} from '../helpers/segments'; +import {prepareLineData} from '../line/prepare-line-data'; +import type {PrepareFunctionArgs} from '../types'; +import { + mapChartkitFormatSettingsToGravityChartValueFormat, + mapToGravityChartValueFormat, +} from '../utils'; + +import type {ExtendedAreaSeries, ExtendedAreaSeriesData} from './types'; + +type PreparedAreaPoint = { + x?: AreaSeriesData['x']; + y?: number | null; + label?: AreaSeriesData['label']; + custom?: AreaSeriesData['custom']; +}; + +// eslint-disable-next-line complexity +export function prepareGravityChartArea(args: PrepareFunctionArgs) { + const { + visualizationId, + labels, + placeholders, + disableDefaultSorting = false, + shared, + idToDataType, + colors, + segments: split, + layerSettings, + } = args; + const baseConfig = getBaseChartConfig({ + shared, + visualization: {placeholders, id: visualizationId}, + }); + const xPlaceholder = placeholders.find((p) => p.id === PlaceholderId.X); + const xField: ServerField | undefined = xPlaceholder?.items?.[0]; + const yPlaceholder = placeholders.find((p) => p.id === PlaceholderId.Y); + const yFields = yPlaceholder?.items || []; + const labelField = labels?.[0]; + const isDataLabelsEnabled = Boolean(labelField); + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + const xAxisMode = getXAxisMode({config: chartConfig}) ?? AxisMode.Discrete; + const isCategoriesXAxis = + !xField || + getAxisType({ + field: xField, + settings: xPlaceholder?.settings, + axisMode: xAxisMode, + }) === 'category' || + disableDefaultSorting; + + if (!xField || !yFields.length) { + return { + series: { + data: [], + }, + }; + } + + const segmentField = split?.[0]; + const segmentsMap = getSegmentMap(args); + const segments = sortBy(Object.values(segmentsMap), (s) => s.index); + const isSplitEnabled = Boolean(segmentField); + const isSplitWithHtmlValues = isHtmlField(segmentField); + + const preparedData = prepareLineData(args); + const xCategories = preparedData.categories; + + const colorItem = colors[0]; + const xValueExportSetting = getExportColumnSettings({ + path: isCategoriesXAxis ? 'category' : 'x', + field: xField, + }); + const yValueExportSetting = getExportColumnSettings({path: 'y', field: yFields?.[0]}); + + const shouldUseHtmlForLabels = + isMarkupField(labelField) || isHtmlField(labelField) || isMarkdownField(labelField); + const shouldUsePercentStacking = visualizationId === WizardVisualizationId.Area100p; + + let seriesTooltip: AreaSeries['tooltip']; + if (!yFields.length) { + seriesTooltip = {enabled: false}; + } + + const seriesData: ExtendedAreaSeries[] = preparedData.graphs.map((graph) => { + const labelFormatting = graph.dataLabels + ? mapToGravityChartValueFormat({field: labelField, formatSettings: graph.dataLabels}) + : undefined; + const shouldUsePercentageAsLabel = + labelFormatting && + 'labelMode' in labelFormatting && + labelFormatting?.labelMode === 'percent'; + let seriesName = graph.title; + + if (graph.custom?.segmentTitle) { + seriesName = concatStringValues(graph.custom.segmentTitle, ': ', seriesName); + } + + let stacking: AreaSeries['stacking']; + if (shouldUsePercentStacking) { + stacking = 'percent'; + } else if (shared.extraSettings?.stacking !== 'off') { + stacking = 'normal'; + } + + const tooltip = graph.tooltip?.chartKitFormatting + ? { + ...seriesTooltip, + valueFormat: mapChartkitFormatSettingsToGravityChartValueFormat({ + chartkitFormatSettings: graph.tooltip, + }), + } + : seriesTooltip; + const stackId = [layerSettings?.id, graph.stack].filter(Boolean).join('__') || undefined; + const exportSettings: SeriesExportSettings = { + columns: [ + xValueExportSetting, + { + ...yValueExportSetting, + id: seriesName, + name: seriesName, + }, + ], + }; + + return { + name: seriesName, + type: 'area', + stackId, + stacking, + color: graph.color, + nullMode: graph.connectNulls ? 'connect' : 'skip', + tooltip, + data: graph.data.reduce( + (acc: ExtendedAreaSeriesData[], item: PreparedAreaPoint, index: number) => { + const pointX = item?.x; + const total = + preparedData.graphs.reduce( + (sum, currentGraph) => + sum + + (currentGraph.data.find( + (point: PreparedAreaPoint) => point?.x === pointX, + )?.y ?? 0), + 0, + ) ?? 0; + const ratio = total === 0 ? 0 : Number(item?.y ?? 0) / total; + const percentage = ratio * 100; + const labelValue = shouldUsePercentageAsLabel ? percentage : item?.label; + const dataItem: ExtendedAreaSeriesData = { + y: item?.y ?? null, + custom: item.custom, + }; + + if (isDataLabelsEnabled) { + if (item?.y === null) { + dataItem.label = ''; + } else { + dataItem.label = labelValue; + } + } + + if (isCategoriesXAxis) { + dataItem.x = index; + } else if (!item && xCategories) { + dataItem.x = xCategories[index]; + } else { + dataItem.x = item?.x; + } + + acc.push(dataItem); + + return acc; + }, + [], + ), + legend: { + groupId: graph.id, + itemText: graph.legendTitle, + }, + dataLabels: { + enabled: isDataLabelsEnabled, + html: shouldUseHtmlForLabels, + format: labelFormatting, + padding: DATA_LABEL_DEFAULT_PADDING, + }, + custom: { + ...graph.custom, + exportSettings, + colorValue: graph.colorValue, + shapeValue: graph.shapeValue, + drillDownFilterValue: graph.drillDownFilterValue, + }, + yAxis: graph.yAxis, + }; + }); + + const shouldUseHtmlForLegend = isHtmlField(colorItem); + const legend: ChartData['legend'] = {html: shouldUseHtmlForLegend}; + const nonEmptyLegendGroups = Array.from( + new Set(seriesData.map((s) => s.legend?.groupId).filter(Boolean)), + ); + if (isSplitEnabled && nonEmptyLegendGroups.length <= 1) { + legend.enabled = false; + } + + let xAxis: ChartData['xAxis'] = { + startOnTick: false, + endOnTick: false, + }; + + if (isCategoriesXAxis) { + xAxis = { + type: 'category', + // @ts-ignore There may be a type mismatch due to the wrapper over html, markup and markdown + categories: xCategories, + labels: { + html: isHtmlField(xField) || isMarkdownField(xField) || isMarkupField(xField), + }, + }; + } else { + if (isDateField(xField)) { + xAxis.type = 'datetime'; + } + + if (isNumberField(xField)) { + xAxis.type = xPlaceholder?.settings?.type === 'logarithmic' ? 'logarithmic' : 'linear'; + + if (xAxis.type === 'logarithmic') { + xAxis.startOnTick = true; + xAxis.endOnTick = true; + } + } + + xAxis.tickMarks = {enabled: Boolean(baseConfig.xAxis?.labels?.enabled)}; + } + + const config: ExtendedChartData = { + series: { + data: seriesData as ChartSeries[], + }, + xAxis, + legend, + custom: { + tooltip: { + headerLabel: + isDateField(xField) && !isCategoriesXAxis + ? undefined + : getFakeTitleOrTitle(xField), + }, + }, + }; + + const axisLabelNumberFormat = yPlaceholder + ? getAxisFormatting({ + placeholder: yPlaceholder, + visualizationId, + }) + : undefined; + + const yAxisBaseConfig = merge( + getYAxisBaseConfig({ + placeholder: yPlaceholder, + }), + { + labels: { + numberFormat: axisLabelNumberFormat ?? undefined, + }, + startOnTick: true, + endOnTick: true, + maxPadding: 0.001, + }, + ); + + if (isSplitEnabled) { + config.yAxis = segments.map((d) => { + let titleText: string = d.title; + if (isSplitWithHtmlValues) { + // @ts-ignore There may be a type mismatch due to the wrapper over html, markup and markdown + titleText = wrapHtml(d.title); + } + + const axisTitle = { + ...SEGMENT_TITLE_BASE_CONFIG, + text: titleText, + html: isSplitWithHtmlValues, + }; + + return merge({}, yAxisBaseConfig, { + plotIndex: d.index, + title: axisTitle, + }); + }); + } else { + config.yAxis = [{...yAxisBaseConfig, lineColor: 'transparent'}]; + } + + if (isSplitEnabled) { + config.split = { + enable: true, + gap: '40px', + plots: segments.map(() => { + return {}; + }), + }; + } + + if (yFields[0]) { + const valueFormat = getFieldFormatOptions({field: yFields[0]}); + config.tooltip = { + valueFormat, + totals: { + valueFormat: + valueFormat?.type === 'number' && valueFormat.format + ? valueFormat + : { + type: 'number', + precision: getTotalsPrecisionFromSeriesTooltips(seriesData), + }, + }, + }; + } + + return merge(baseConfig, config); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/area/types.ts b/src/server/modes/charts/plugins/datalens/preparers/area/types.ts new file mode 100644 index 0000000..e1cc12b --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/area/types.ts @@ -0,0 +1,14 @@ +import type {AreaSeries, AreaSeriesData} from '@gravity-ui/chartkit/gravity-charts'; + +import type {SeriesExportSettings, WrappedHTML, WrappedMarkdown} from '../../../../../../../shared'; + +export type ExtendedAreaSeriesData = Omit & { + x?: AreaSeriesData['x'] | WrappedHTML | WrappedMarkdown; +}; + +export type ExtendedAreaSeries = Omit & { + custom?: { + exportSettings?: SeriesExportSettings; + }; + data: ExtendedAreaSeriesData[]; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/__tests__/table-head-generator.test.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/__tests__/table-head-generator.test.ts new file mode 100644 index 0000000..48c0851 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/__tests__/table-head-generator.test.ts @@ -0,0 +1,186 @@ +import {mapColumnsToHeadCellData} from '../table-head-generator'; +import type {PivotDataColumn} from '../types'; + +const input1: PivotDataColumn[] = [ + [[['Furniture', 90, 90]]], + [[['Office Supplies', 90, 90]]], + [[['Technology', 90, 90]]], +]; + +const expectedOutput1 = [ + { + item: [['Furniture', 90, 90]], + parents: {}, + }, + { + item: [['Office Supplies', 90, 90]], + parents: {}, + }, + { + item: [['Technology', 90, 90]], + parents: {}, + }, +]; + +const input2: PivotDataColumn[] = [ + [[['Furniture', 24, 24]], [['Bookcases', 25, 25]]], + [[['Furniture', 24, 24]], [['Chairs', 25, 25]]], + [[['Office Supplies', 24, 24]], [['Envelopes', 25, 25]]], + [[['Office Supplies', 24, 24]], [['Paper', 25, 25]]], + [[['Technology', 24, 24]], [['Machines', 25, 25]]], + [[['Technology', 24, 24]], [['Phones', 25, 25]]], +]; + +const expectedOutput2 = [ + { + item: [['Furniture', 24, 24]], + sub: [ + { + item: [['Bookcases', 25, 25]], + parents: {Furniture: true}, + }, + { + item: [['Chairs', 25, 25]], + parents: {Furniture: true}, + }, + ], + parents: {}, + }, + { + item: [['Office Supplies', 24, 24]], + parents: {}, + sub: [ + { + item: [['Envelopes', 25, 25]], + parents: {'Office Supplies': true}, + }, + { + item: [['Paper', 25, 25]], + parents: {'Office Supplies': true}, + }, + ], + }, + { + item: [['Technology', 24, 24]], + parents: {}, + sub: [ + { + item: [['Machines', 25, 25]], + parents: {Technology: true}, + }, + { + item: [['Phones', 25, 25]], + parents: {Technology: true}, + }, + ], + }, +]; + +const input3: PivotDataColumn[] = [ + [[['10', 0, 0]]], + [[['1', 0, 0]]], + [[['6', 0, 0]]], + [[['3', 0, 0]]], +]; + +const expectedOutput3 = [ + { + item: [['10', 0, 0]], + parents: {}, + }, + { + item: [['1', 0, 0]], + parents: {}, + }, + { + item: [['6', 0, 0]], + parents: {}, + }, + { + item: [['3', 0, 0]], + parents: {}, + }, +]; + +const input4: PivotDataColumn[] = [ + [[['Furniture', 24, 24]], [['Bookcases', 25, 25]]], + [[['Office Supplies', 24, 24]], [['Envelopes', 25, 25]]], + [[['Furniture', 24, 24]], [['Chairs', 25, 25]]], + [[['Furniture', 24, 24]], [['Tables', 25, 25]]], + [[['Technology', 24, 24]], [['Machines', 25, 25]]], + [[['Technology', 24, 24]], [['Phones', 25, 25]]], +]; + +const expectedOutput4 = [ + { + item: [['Furniture', 24, 24]], + sub: [ + { + item: [['Bookcases', 25, 25]], + parents: {Furniture: true}, + }, + ], + parents: {}, + }, + { + item: [['Office Supplies', 24, 24]], + parents: {}, + sub: [ + { + item: [['Envelopes', 25, 25]], + parents: {'Office Supplies': true}, + }, + ], + }, + { + item: [['Furniture', 24, 24]], + parents: {}, + sub: [ + { + item: [['Chairs', 25, 25]], + parents: {Furniture: true}, + }, + { + item: [['Tables', 25, 25]], + parents: {Furniture: true}, + }, + ], + }, + { + item: [['Technology', 24, 24]], + parents: {}, + sub: [ + { + item: [['Machines', 25, 25]], + parents: {Technology: true}, + }, + { + item: [['Phones', 25, 25]], + parents: {Technology: true}, + }, + ], + }, +]; + +describe('mapColumnsToHeadCellData function', () => { + test('should map head cell data to correct flat structure', () => { + const output = mapColumnsToHeadCellData(input1, {}); + expect(expectedOutput1).toEqual(output); + }); + + test('should map head cell data to correct nested structure', () => { + const output = mapColumnsToHeadCellData(input2, {}); + expect(expectedOutput2).toEqual(output); + }); + + test('should save order when map columns to head cell data', () => { + const output = mapColumnsToHeadCellData(input3, {}); + expect(expectedOutput3).toEqual(output); + }); + + test('should only group columns when they follow each other', () => { + const result = mapColumnsToHeadCellData(input4, {}); + + expect(result).toEqual(expectedOutput4); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/constants/misc.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/constants/misc.ts new file mode 100644 index 0000000..e4bb8d1 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/constants/misc.ts @@ -0,0 +1 @@ +export const MEASURE_NAME_PSEUDO_ID = '__@measure_name__'; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/__tests__/color.test.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/__tests__/color.test.ts new file mode 100644 index 0000000..1ee4d81 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/__tests__/color.test.ts @@ -0,0 +1,216 @@ +import cloneDeep from 'lodash/cloneDeep'; + +import {colorizePivotTableByColorField, getColorSettings, getCurrentRowColorValues} from '../color'; + +import { + MOCKED_COLORS_FIELDS, + MOCKED_COLORS_FIELDS_MEASURE_NAME, + MOCKED_PIVOT_ANNOTATIONS_MAP, + MOCKED_PIVOT_COLORS_CONFIG, + MOCKED_PIVOT_DATA_ROWS, + MOCKED_PIVOT_DATA_ROWS_WITH_EMPTY_CELL, + MOCKED_PIVOT_DATA_ROWS_WITH_FALSY_VALUES, + MOCKED_PIVOT_DATA_ROWS_WITH_INVALID_VALUES, + MOCKED_PIVOT_ROWS, +} from './mocks/color.mock'; + +describe('getCurrentRowColorValues', () => { + it('Should return null|number array for currentRow', () => { + const result = getCurrentRowColorValues( + MOCKED_PIVOT_DATA_ROWS[0], + MOCKED_PIVOT_ANNOTATIONS_MAP, + ); + + expect(result).toEqual([90402, 2120777]); + }); + + it('Should return null if cell is falsy value', () => { + const result = getCurrentRowColorValues( + MOCKED_PIVOT_DATA_ROWS_WITH_EMPTY_CELL[0], + MOCKED_PIVOT_ANNOTATIONS_MAP, + ); + + expect(result).toEqual([425.23, null]); + }); + + it('Should return null when annotation is not found', () => { + const result = getCurrentRowColorValues(MOCKED_PIVOT_DATA_ROWS[0], {}); + + expect(result).toEqual([null, null]); + }); + + it('Should return null when colorValue is invalid', () => { + const result = getCurrentRowColorValues( + MOCKED_PIVOT_DATA_ROWS_WITH_INVALID_VALUES[0], + MOCKED_PIVOT_ANNOTATIONS_MAP, + ); + + expect(result).toEqual([4205.45, null, null, null, null, null]); + }); +}); + +describe('getColorSettings', () => { + it('Should return colorValues null|number array with min and max values from pivot rows', () => { + const result = getColorSettings({ + rows: MOCKED_PIVOT_DATA_ROWS, + annotationsMap: MOCKED_PIVOT_ANNOTATIONS_MAP, + }); + + const expectedColorValues = [ + [90402, 2120777], + [5555, 142023], + [156, 4748], + ]; + + expect(result!.colorValues).toEqual(expectedColorValues); + expect(result!.min).toEqual(156); + expect(result!.max).toEqual(2120777); + }); + + it('Should return undefined when rows are empty', () => { + const result = getColorSettings({ + rows: [], + annotationsMap: MOCKED_PIVOT_ANNOTATIONS_MAP, + }); + + expect(result).toBeUndefined(); + }); + + it('Should return null in row array if data is not valid: [undefined, null, ""]', () => { + const result = getColorSettings({ + rows: MOCKED_PIVOT_DATA_ROWS_WITH_FALSY_VALUES, + annotationsMap: MOCKED_PIVOT_ANNOTATIONS_MAP, + }); + + expect(result!.colorValues).toEqual([[null, null, null]]); + }); + + it('Should return 0 as min and max when all data is not valid: [undefined, null, ""]', () => { + const result = getColorSettings({ + rows: MOCKED_PIVOT_DATA_ROWS_WITH_FALSY_VALUES, + annotationsMap: MOCKED_PIVOT_ANNOTATIONS_MAP, + }); + + expect(result!.min).toEqual(0); + expect(result!.max).toEqual(0); + }); +}); + +describe('colorizePivotTableByColorField', () => { + let PIVOT_ROWS: any[] = []; + beforeEach(() => { + // cloning the object for each test because colorizePivotTableByColorField mutate the rows + PIVOT_ROWS = cloneDeep(MOCKED_PIVOT_ROWS); + }); + + it('Should return cells with css property', () => { + colorizePivotTableByColorField({ + rows: PIVOT_ROWS, + colorsConfig: MOCKED_PIVOT_COLORS_CONFIG, + colors: MOCKED_COLORS_FIELDS, + rowHeaderLength: 1, + rowsData: MOCKED_PIVOT_DATA_ROWS, + annotationsMap: MOCKED_PIVOT_ANNOTATIONS_MAP, + }); + + expect(PIVOT_ROWS).toStrictEqual([ + { + cells: [ + { + id: 0, + isTotalCell: false, + value: 'Australia', + }, + { + css: { + backgroundColor: 'rgb(5, 73, 166)', + color: '#FFF', + value: 90402, + }, + formattedValue: '657,06', + id: 1, + value: 657.058, + }, + { + css: { + backgroundColor: 'rgb(140, 203, 255)', + color: '#FFF', + value: 2120777, + }, + formattedValue: '2 901,74', + id: 2, + value: 2901.739000000002, + }, + ], + }, + { + cells: [ + { + id: 3, + isTotalCell: false, + value: 'Austria', + }, + { + css: { + backgroundColor: 'rgb(0, 68, 163)', + color: '#FFF', + value: 5555, + }, + formattedValue: '7,89', + id: 4, + value: 7.891, + }, + { + css: { + backgroundColor: 'rgb(9, 77, 169)', + color: '#FFF', + value: 142023, + }, + formattedValue: '661,35', + id: 5, + value: 661.3510000000002, + }, + ], + }, + ]); + }); + + it('Should not mutate rows when colors is empty', () => { + colorizePivotTableByColorField({ + rows: PIVOT_ROWS, + colorsConfig: MOCKED_PIVOT_COLORS_CONFIG, + colors: [], + rowHeaderLength: 1, + rowsData: MOCKED_PIVOT_DATA_ROWS, + annotationsMap: MOCKED_PIVOT_ANNOTATIONS_MAP, + }); + + expect(PIVOT_ROWS).toEqual(MOCKED_PIVOT_ROWS); + }); + + it('Should not mutate rows when colors are Measure Names', () => { + colorizePivotTableByColorField({ + rows: PIVOT_ROWS, + colorsConfig: MOCKED_PIVOT_COLORS_CONFIG, + colors: MOCKED_COLORS_FIELDS_MEASURE_NAME, + rowHeaderLength: 1, + rowsData: MOCKED_PIVOT_DATA_ROWS, + annotationsMap: MOCKED_PIVOT_ANNOTATIONS_MAP, + }); + + expect(PIVOT_ROWS).toEqual(MOCKED_PIVOT_ROWS); + }); + + it('Should not mutate rows when colorsSettings is undefined', () => { + colorizePivotTableByColorField({ + rows: PIVOT_ROWS, + colorsConfig: MOCKED_PIVOT_COLORS_CONFIG, + colors: [], + rowHeaderLength: 1, + rowsData: MOCKED_PIVOT_DATA_ROWS, + annotationsMap: {}, + }); + + expect(PIVOT_ROWS).toEqual(MOCKED_PIVOT_ROWS); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/__tests__/mocks/color.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/__tests__/mocks/color.mock.ts new file mode 100644 index 0000000..c71fb3e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/__tests__/mocks/color.mock.ts @@ -0,0 +1,174 @@ +import type {ServerField} from '../../../../../../../../../../shared'; +import type {ChartColorsConfig} from '../../../../../types'; +import type {AnnotationsMap, PivotDataRows} from '../../../types'; + +export const MOCKED_PIVOT_DATA_ROWS = [ + { + header: [[['Australia', 1, 1]]], + values: [ + [ + ['657.058', 2, 2], + ['90402', 3, 3], + ], + [ + ['2901.739000000002', 2, 2], + ['2120777', 3, 3], + ], + ], + }, + { + header: [[['Austria', 1, 1]]], + values: [ + [ + ['7.891', 2, 2], + ['5555', 3, 3], + ], + [ + ['661.3510000000002', 2, 2], + ['142023', 3, 3], + ], + ], + }, + { + header: [[['Azerbaijan', 1, 1]]], + values: [ + [ + ['12.363', 2, 2], + ['156', 3, 3], + ], + [ + ['113.76400000000002', 2, 2], + ['4748', 3, 3], + ], + ], + }, +] as PivotDataRows[]; + +export const MOCKED_PIVOT_DATA_ROWS_WITH_FALSY_VALUES = [ + { + header: [[['Australia', 1, 1]]], + values: [ + [ + ['657.058', 2, 2], + [undefined, 3, 3], + ], + [ + ['2901.739000000002', 2, 2], + [null, 3, 3], + ], + [ + ['23141.739000000002', 2, 2], + ['', 3, 3], + ], + ], + }, +] as PivotDataRows[]; + +export const MOCKED_PIVOT_DATA_ROWS_WITH_EMPTY_CELL = [ + { + header: [[['Australia', 1, 1]]], + values: [ + [ + ['657.058', 2, 2], + ['425.23', 3, 3], + ], + null, + ], + }, +] as PivotDataRows[]; + +export const MOCKED_PIVOT_DATA_ROWS_WITH_INVALID_VALUES = [ + { + header: [[['Australia', 1, 1]]], + values: [ + [ + ['147.99', 2, 2], + ['4205.45', 3, 3], + ], + [ + ['657.058', 2, 2], + [null, 3, 3], + ], + [ + ['123.128', 2, 2], + [undefined, 3, 3], + ], + [ + ['8472.08', 2, 2], + ['', 3, 3], + ], + [ + ['55', 2, 2], + [NaN, 3, 3], + ], + [ + ['265.2', 2, 2], + ['abc', 3, 3], + ], + ], + }, +] as PivotDataRows[]; + +export const MOCKED_PIVOT_ANNOTATIONS_MAP = { + [3]: 'color', +} as AnnotationsMap; + +export const MOCKED_PIVOT_COLORS_CONFIG = { + gradientColors: ['#0044A3', '#8CCBFF'], +} as ChartColorsConfig; + +export const MOCKED_COLORS_FIELDS = [ + { + type: 'MEASURE', + guid: '5b58a23b-68a2-4979-bfb3-9ee13f16d24d', + }, +] as ServerField[]; + +export const MOCKED_COLORS_FIELDS_MEASURE_NAME = [ + { + type: 'PSEUDO', + title: 'Measure Names', + guid: 'Measure Names', + }, +] as ServerField[]; + +export const MOCKED_PIVOT_ROWS = [ + { + cells: [ + { + id: 0, + value: 'Australia', + isTotalCell: false, + }, + { + id: 1, + value: 657.058, + formattedValue: '657,06', + }, + { + id: 2, + value: 2901.739000000002, + formattedValue: '2 901,74', + }, + ], + }, + { + cells: [ + { + id: 3, + value: 'Austria', + isTotalCell: false, + }, + { + id: 4, + value: 7.891, + formattedValue: '7,89', + }, + { + id: 5, + value: 661.3510000000002, + formattedValue: '661,35', + }, + ], + }, +]; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts new file mode 100644 index 0000000..f863952 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts @@ -0,0 +1,386 @@ +import type { + ColorPalette, + FieldGuid, + Palette, + ServerField, + TableFieldBackgroundSettings, +} from '../../../../../../../../shared'; +import { + ApiV2Annotations, + GradientNullModes, + PseudoFieldTitle, + getFakeTitleOrTitle, +} from '../../../../../../../../shared'; +import {selectServerPalette} from '../../../../../../../constants'; +import type {ChartColorsConfig} from '../../../types'; +import {colorizePivotTableCell} from '../../../utils/color-helpers'; +import {getMountedColor} from '../../../utils/constants'; +import {getCurrentBackgroundGradient} from '../../helpers/backgroundSettings/misc'; +import type { + AnnotationsMap, + CellValue, + ChartkitCell, + ChartkitHeadCell, + PivotDataRows, + PivotField, + PivotTableFieldSettings, +} from '../types'; + +import {getAnnotation, getDatasetFieldFromPivotTableValue, parsePivotTableCellId} from './misc'; + +const getContinuousColorValue = (colorValue: undefined | null | string | number): number | null => { + if ( + colorValue === undefined || + colorValue === null || + colorValue === '' || + isNaN(Number(colorValue)) + ) { + return null; + } + + return Number(colorValue); +}; + +const getDiscreteColorValue = (args: { + colorValue: undefined | null | string; + settings: TableFieldBackgroundSettings['settings']; + customColorPalettes: Record; + availablePalettes: Record; + defaultColorPaletteId: string; +}): string | null => { + const {colorValue, settings, customColorPalettes, availablePalettes, defaultColorPaletteId} = + args; + const mountedColors = settings.paletteState.mountedColors; + const palette = settings.paletteState.palette; + + if (!mountedColors || !colorValue) { + return null; + } + + return getMountedColor({ + mountedColors, + colors: selectServerPalette({ + palette, + customColorPalettes, + availablePalettes, + defaultColorPaletteId, + }), + value: colorValue, + }); +}; + +type ColorizePivotTableHeaderByBackgroundSettings = { + cell: ChartkitCell | ChartkitHeadCell; + backgroundSettings: TableFieldBackgroundSettings | undefined; + cellValue: string; + parents: Record; + isTotal: boolean; + loadedColorPalettes: Record; + availablePalettes: Record; + defaultColorPaletteId: string; +}; +export const colorizePivotTableHeaderByBackgroundSettings = ({ + backgroundSettings, + cell, + cellValue, + parents, + isTotal, + loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, +}: ColorizePivotTableHeaderByBackgroundSettings) => { + if (isTotal) { + return {}; + } + if ( + backgroundSettings && + backgroundSettings.enabled && + backgroundSettings.settings.paletteState + ) { + let colorValue = getDiscreteColorValue({ + colorValue: cellValue, + settings: backgroundSettings.settings, + customColorPalettes: loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, + }); + + if (!colorValue) { + const parentNames = Object.keys(parents); + parentNames.forEach((parentName) => { + if (colorValue) { + return; + } + + colorValue = getDiscreteColorValue({ + colorValue: parentName, + settings: backgroundSettings.settings, + customColorPalettes: loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, + }); + }); + } + + if (colorValue) { + return { + ...cell.css, + backgroundColor: colorValue, + color: '#FFF', + }; + } + } + + return {}; +}; + +type PrepareBackgroundColorSettings = { + rowsData: PivotDataRows[]; + annotationsMap: AnnotationsMap; + fieldsItemIdMap: Record; + fieldDict: Record; + settingsByField: Record; + loadedColorPalettes: Record; + availablePalettes: Record; + defaultColorPaletteId: string; +}; + +export const prepareBackgroundColorSettings = (args: PrepareBackgroundColorSettings) => { + const { + annotationsMap, + rowsData, + fieldsItemIdMap, + fieldDict, + settingsByField, + availablePalettes, + defaultColorPaletteId, + } = args; + + if (!rowsData.length) { + return {discreteColorsByField: {}, continuousColorsByField: {}}; + } + + const colorValuesByField: Record> = {}; + const discreteColorsByField: Record> = {}; + const continuousColorsByField: Record> = {}; + + rowsData.forEach((row) => { + row.values.forEach((cellValues) => { + if (!cellValues) { + return; + } + + const datasetField = getDatasetFieldFromPivotTableValue( + cellValues, + fieldsItemIdMap, + fieldDict, + ); + + const backgroundSettings = + settingsByField[datasetField?.guid || '']?.backgroundSettings; + + if (!datasetField || !backgroundSettings) { + return; + } + + const backgroundColorAnnotation = getAnnotation( + cellValues, + annotationsMap, + ApiV2Annotations.BackgroundColor, + ); + + if (!backgroundColorAnnotation) { + return; + } + + const [colorValue] = backgroundColorAnnotation; + const {settings, colorFieldGuid} = backgroundSettings; + + if (settings.isContinuous) { + if (!colorValuesByField[colorFieldGuid]) { + colorValuesByField[colorFieldGuid] = new Set(); + } + + colorValuesByField[colorFieldGuid].add(colorValue); + return; + } + + if (!discreteColorsByField[datasetField.guid]) { + discreteColorsByField[datasetField.guid] = {}; + } + + discreteColorsByField[datasetField.guid][colorValue] = getDiscreteColorValue({ + colorValue, + settings, + customColorPalettes: args.loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, + }); + }); + }); + + const fieldSettings = Object.values(settingsByField); + Array.from(fieldSettings).forEach((fieldSetting) => { + const backgroundSettings = fieldSetting.backgroundSettings; + if (!backgroundSettings?.settings.isContinuous) { + return; + } + + const guid = backgroundSettings.colorFieldGuid; + + const colorValues = colorValuesByField[guid]; + + if (!colorValues) { + return; + } + + const fieldColorValues = Array.from(colorValues); + + continuousColorsByField[guid] = {}; + const nilValue = + backgroundSettings.settings.gradientState.nullMode === GradientNullModes.AsZero + ? 0 + : null; + + const colorValuesWithoutNull = fieldColorValues.reduce((acc, cv) => { + const colorValue = cv === null ? nilValue : cv; + if (colorValue !== null) { + acc.push(Number(colorValue)); + } + + return acc; + }, []); + + const min = Math.min(...colorValuesWithoutNull); + const max = Math.max(...colorValuesWithoutNull); + + const chartColorsConfig: ChartColorsConfig = { + ...backgroundSettings.settings.gradientState, + colors: [], + loadedColorPalettes: args.loadedColorPalettes, + gradientColors: getCurrentBackgroundGradient( + backgroundSettings.settings.gradientState, + args.loadedColorPalettes, + ).colors, + availablePalettes, + }; + + fieldColorValues.forEach((value) => { + const colorValue = getContinuousColorValue(value); + if ( + colorValue === null && + backgroundSettings.settings.gradientState.nullMode !== GradientNullModes.AsZero + ) { + return; + } + + const color = colorizePivotTableCell(colorValue, chartColorsConfig, [min, max]); + continuousColorsByField[guid][String(value)] = color?.backgroundColor || null; + }); + }); + + return {continuousColorsByField, discreteColorsByField}; +}; + +type ColorizePivotTableByFieldBackgroundSettings = { + rows: any[]; + annotationsMap: AnnotationsMap; + settingsByField: Record; + rowHeaderLength: number; + + rowsData: PivotDataRows[]; + fieldsItemIdMap: Record; + fieldDict: Record; + loadedColorPalettes: Record; + availablePalettes: Record; + defaultColorPaletteId: string; +}; + +export const colorizePivotTableByFieldBackgroundSettings = ( + args: ColorizePivotTableByFieldBackgroundSettings, +) => { + const { + settingsByField, + rows, + annotationsMap, + rowHeaderLength, + rowsData, + fieldDict, + fieldsItemIdMap, + loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, + } = args; + + const {discreteColorsByField, continuousColorsByField} = prepareBackgroundColorSettings({ + rowsData, + fieldDict, + fieldsItemIdMap, + annotationsMap, + settingsByField, + loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, + }); + + rows.forEach((row) => { + for (let i = rowHeaderLength; i < row.cells.length; i++) { + const cell: ChartkitCell = row.cells[i]; + if (!cell.id || cell.isTotalCell) { + continue; + } + + const fieldGuid = parsePivotTableCellId(cell.id).guid; + const backgroundColorSettings = settingsByField[fieldGuid]?.backgroundSettings; + + if (!backgroundColorSettings) { + continue; + } + + const {settings, colorFieldGuid} = backgroundColorSettings; + const colorKey = cell.colorKey; + + if ( + !colorKey && + backgroundColorSettings.settings.gradientState.nullMode !== GradientNullModes.AsZero + ) { + continue; + } + + const datasetField = fieldDict[fieldGuid]; + + let backgroundColor: string | undefined | null; + if (settings.isContinuous && continuousColorsByField[colorFieldGuid]) { + const continuousColorsByCellValue = continuousColorsByField[colorFieldGuid]; + backgroundColor = continuousColorsByCellValue[String(colorKey)]; + } else if (settings.paletteState && discreteColorsByField[fieldGuid]) { + const discreteColorsByCellValue = discreteColorsByField[fieldGuid]; + backgroundColor = discreteColorsByCellValue[String(colorKey)]; + } else if ( + // TODO: CHARTS-7124 + // Now the backend fail with 500 when the helmet is in the measure names annotations. + // Therefore, we made the coloring on our own. After backend corrects, + // you will need to switch to a general approach and remove if here + backgroundColorSettings.colorFieldGuid === PseudoFieldTitle.MeasureNames && + datasetField + ) { + const measureName = getFakeTitleOrTitle(datasetField); + backgroundColor = getDiscreteColorValue({ + colorValue: measureName, + settings, + customColorPalettes: loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, + }); + } + + if (backgroundColor) { + cell.css = { + ...cell.css, + color: '#FFF', + backgroundColor, + }; + } + } + }); +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts new file mode 100644 index 0000000..38d632e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts @@ -0,0 +1,133 @@ +import type {ServerColor} from '../../../../../../../../shared'; +import {ApiV2Annotations, GradientNullModes, isMeasureName} from '../../../../../../../../shared'; +import type {ChartColorsConfig} from '../../../types'; +import {colorizePivotTableCell} from '../../../utils/color-helpers'; +import type {AnnotationsMap, PivotDataCellValue, PivotDataRows} from '../types'; + +import {getAnnotation} from './misc'; + +export const getCurrentRowColorValues = ( + row: PivotDataRows, + annotationsMap: AnnotationsMap, +): (number | null)[] => { + return row.values.reduce( + (acc, cell: PivotDataCellValue[] | null) => { + if (!cell) { + acc.push(null); + return acc; + } + + const colorAnnotation = getAnnotation(cell, annotationsMap, ApiV2Annotations.Color); + + if (!colorAnnotation) { + acc.push(null); + return acc; + } + + const [colorValue] = colorAnnotation; + + const isInvalidColorValue = + colorValue === undefined || + colorValue === null || + colorValue === '' || + isNaN(Number(colorValue)); + + if (isInvalidColorValue) { + acc.push(null); + return acc; + } + + acc.push(Number(colorValue)); + return acc; + }, + [] as (null | number)[], + ); +}; + +type GetPivotColorSettingsArgs = { + rows: PivotDataRows[]; + annotationsMap: AnnotationsMap; +}; + +type GetColorSettings = { + min: number; + max: number; + colorValues: (number | null)[][]; +}; +export const getColorSettings = (args: GetPivotColorSettingsArgs): GetColorSettings | undefined => { + const {rows, annotationsMap} = args; + + if (!rows.length) { + return undefined; + } + + const flatColorValues: (number | null)[] = []; + + const colorValuesByRow = rows.reduce( + (colorValues, row) => { + const currentRowColorValues = getCurrentRowColorValues(row, annotationsMap); + + flatColorValues.push(...currentRowColorValues); + colorValues.push(currentRowColorValues); + + return colorValues; + }, + [] as (null | number)[][], + ); + + const valuesWithoutNull = flatColorValues.filter((n): n is number => n !== null); + + const min = valuesWithoutNull.length ? Math.min(...valuesWithoutNull) : 0; + const max = valuesWithoutNull.length ? Math.max(...valuesWithoutNull) : 0; + + return { + colorValues: colorValuesByRow, + min, + max, + }; +}; + +type ColorizeByColorFieldArgs = { + rows: any[]; + rowHeaderLength: number; + colors: ServerColor[]; + colorsConfig: ChartColorsConfig; + rowsData: PivotDataRows[]; + annotationsMap: AnnotationsMap; +}; +export const colorizePivotTableByColorField = (args: ColorizeByColorFieldArgs) => { + const {colors, colorsConfig, rows, rowHeaderLength, rowsData, annotationsMap} = args; + + const filteredColors = colors.filter((el) => !isMeasureName(el)); + if (!filteredColors.length) { + return; + } + + const colorSettings = getColorSettings({rows: rowsData, annotationsMap}); + + if (!colorSettings) { + return; + } + + const {colorValues, min, max} = colorSettings; + const nilValue = colorsConfig.nullMode === GradientNullModes.AsZero ? 0 : null; + + rows.forEach((row, rowIndex) => { + for (let i = rowHeaderLength; i < row.cells.length; i++) { + const cell = row.cells[i]; + const rawColorValue = colorValues[rowIndex][i - rowHeaderLength]; + const colorValue = rawColorValue === null ? nilValue : rawColorValue; + + const isInvalidColorValue = colorValue === null; + + if (isInvalidColorValue || (cell.css && cell.css.backgroundColor)) { + continue; + } + + cell.css = { + ...cell.css, + ...colorizePivotTableCell(colorValue, colorsConfig, [min, max]), + }; + } + }); +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/misc.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/misc.ts new file mode 100644 index 0000000..cb3cb97 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/misc.ts @@ -0,0 +1,211 @@ +import type { + ApiV2Annotations, + MarkupItem, + ServerField, + ServerPlaceholder, +} from '../../../../../../../../shared'; +import { + PlaceholderId, + isMarkupField, + isMarkupItem, + markupToRawString, +} from '../../../../../../../../shared'; +import { + isColumnSettingsWidthEnabled, + isTableBarsSettingsEnabled, + isTableFieldBackgroundSettingsEnabled, +} from '../../../utils/misc-helpers'; +import {getBarSettingsViewOptions} from '../../helpers/barsSettings'; +import {MEASURE_NAME_PSEUDO_ID} from '../constants/misc'; +import type { + AnnotationsMap, + PivotDataCellValue, + PivotDataRows, + PivotDataStructure, + PivotField, + PivotTableFieldSettings, +} from '../types'; + +export const getActualizedPlaceholderItem = ( + placeholderItem: ServerField, + options: {idToDataType: Record; idToTitle: Record}, +): ServerField => { + const {idToDataType, idToTitle} = options; + + const actualDataType = idToDataType[placeholderItem.guid] || placeholderItem.data_type; + const actualTitle = idToTitle[placeholderItem.guid] || placeholderItem.title; + + return { + ...placeholderItem, + data_type: actualDataType, + title: actualTitle, + }; +}; + +export const getValuesByField = (args: { + rows: PivotDataRows[]; + fieldsItemIdMap: Record; + placeholders: ServerPlaceholder[]; +}) => { + const {rows, fieldsItemIdMap, placeholders} = args; + + const measuresPlaceholder = placeholders.find( + (placeholder) => placeholder.id === PlaceholderId.Measures, + ); + + const measures = measuresPlaceholder?.items || []; + + const isBarInTotalsEnabledMap = measures.reduce( + (acc, field) => { + if (isTableBarsSettingsEnabled(field)) { + acc[field.guid] = field.barsSettings.showBarsInTotals; + } + return acc; + }, + {} as Record, + ); + + return rows.reduce( + (acc, row) => { + row.values.forEach((rowValue) => { + if (rowValue) { + const [value, legendItemId] = rowValue[0]; + const pivotField: PivotField = fieldsItemIdMap[legendItemId]; + + const isTotalValue = pivotField.role_spec.role === 'total'; + + if (isTotalValue && !isBarInTotalsEnabledMap[pivotField.id]) { + return; + } + + if (acc[pivotField.id]) { + acc[pivotField.id].push(value); + } else { + acc[pivotField.id] = [value]; + } + } + }); + + return acc; + }, + {} as Record, + ); +}; + +export const getPivotTableSettingsFromField = ( + field: ServerField, + prevSettings: PivotTableFieldSettings, + placeholderId: PlaceholderId, + valueByField: Record, +): PivotTableFieldSettings => { + const settings: PivotTableFieldSettings = {}; + + if (isColumnSettingsWidthEnabled(field)) { + settings.columnSettings = prevSettings.columnSettings || {}; + switch (placeholderId) { + case PlaceholderId.PivotTableColumns: + settings.columnSettings.column = field.columnSettings; + break; + case PlaceholderId.PivotTableRows: + settings.columnSettings.row = field.columnSettings; + break; + default: + break; + } + } + + if (isTableBarsSettingsEnabled(field)) { + const columnValues = valueByField[field.guid]; + + if (columnValues) { + settings.barsSettings = { + options: getBarSettingsViewOptions({ + barsSettings: field.barsSettings, + columnValues, + }), + columnValues, + }; + } + } + + if (isTableFieldBackgroundSettingsEnabled(field)) { + settings.backgroundSettings = field.backgroundSettings; + } + + return settings; +}; + +export const getAnnotation = ( + cellData: PivotDataCellValue[], + annotationsMap: AnnotationsMap, + annotation: ApiV2Annotations, +) => { + return cellData.find( + ([_value, _legendItemId, pivotItemId]) => annotationsMap[pivotItemId] === annotation, + ); +}; +export const getAnnotationsMap = (structure: PivotDataStructure[]): AnnotationsMap => { + const annotationsMap: AnnotationsMap = {}; + + structure.forEach((structEl) => { + if ( + structEl?.role_spec?.role === 'pivot_annotation' && + structEl.role_spec.annotation_type + ) { + annotationsMap[structEl.pivot_item_id] = structEl.role_spec.annotation_type; + } + }); + + return annotationsMap; +}; + +export const getPivotTableCellId = (guid: string | undefined, id: {current: number}): string => { + return `fieldId=${guid || ''}__index=${id.current++}`; +}; + +export const parsePivotTableCellId = (id: string) => { + const [templateFieldId, templateIndex] = id.split('__'); + if (!templateFieldId || !templateIndex) { + return {guid: id}; + } + + const fieldId = templateFieldId.replace('fieldId=', ''); + const index = templateIndex.replace('index=', ''); + + return { + guid: fieldId, + index, + }; +}; + +export const getDatasetFieldFromPivotTableValue = ( + pivotDataRowValue: PivotDataCellValue[], + fieldsItemIdMap: Record, + fieldDict: Record, +): ServerField | undefined => { + const pivotDataCellValue = pivotDataRowValue[0]; + + const [value, legendItemId] = pivotDataCellValue; + const field = fieldsItemIdMap[legendItemId]; + const isMeasure = field.item_type === 'measure_name'; + const fieldGuid = isMeasure ? value.replace('title-', '') : field.id; + return fieldDict[fieldGuid]; +}; + +export const getCellValueForHeader = ( + cellValue: string | MarkupItem | null, + options: {pivotField?: PivotField; datasetField?: ServerField} = {}, +): string => { + const {pivotField, datasetField} = options; + const isMeasureName = pivotField?.id === MEASURE_NAME_PSEUDO_ID; + + if (cellValue && (isMarkupField(datasetField) || isMarkupItem(cellValue))) { + return markupToRawString(cellValue as MarkupItem); + } + + if (isMeasureName) { + return datasetField?.fakeTitle || (cellValue as string); + } + + return cellValue as string; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/sort.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/sort.ts new file mode 100644 index 0000000..d818e76 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/sort.ts @@ -0,0 +1,69 @@ +import {PseudoFieldTitle} from '../../../../../../../../shared'; +import type {BackendPivotTableCellCustom} from '../../../types'; +import type {HeaderInfo, PivotDataStructure, PivotDataTotals} from '../types'; + +export const getSortDirection = ( + currentDirection: HeaderInfo['sorting_direction'], +): 'asc' | 'desc' | undefined => { + switch (currentDirection) { + case 'desc': { + return 'asc'; + } + case 'asc': { + return undefined; + } + case null: { + return 'desc'; + } + default: { + return undefined; + } + } +}; +export const isSortByRoleAllowed = ( + pivotStructure: PivotDataStructure[], + pivotTotals: PivotDataTotals | null, + role: 'pivot_column' | 'pivot_row', +) => { + const hasRowSubtotals = pivotTotals?.rows.some((item) => item.level > 0); + const hasColumnSubtotals = pivotTotals?.columns.some((item) => item.level > 0); + + if ( + (hasRowSubtotals && role === 'pivot_column') || + (hasColumnSubtotals && role === 'pivot_row') + ) { + return false; + } + + const measures = pivotStructure.filter((item) => item.role_spec.role === 'pivot_measure'); + + if (measures.length > 1) { + const measureName = pivotStructure.find((s) => s.title === PseudoFieldTitle.MeasureNames); + return measureName?.role_spec.role === role; + } + + return true; +}; + +export const getSortMeta = ({ + meta, + path, + measureGuid, + fieldOrder, +}: { + meta: HeaderInfo; + path: string[]; + measureGuid: string | undefined; + fieldOrder: string[]; +}): BackendPivotTableCellCustom => { + const prevDirection = meta.sorting_direction; + + return { + role: meta.role_spec.role, + currentSortDirection: prevDirection, + nextSortDirection: getSortDirection(prevDirection), + path, + measureGuid, + fieldOrder, + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/totals.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/totals.ts new file mode 100644 index 0000000..587d5d1 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/totals.ts @@ -0,0 +1,108 @@ +import type {IChartEditor, MarkupItem} from '../../../../../../../../shared'; +import {isMarkupItem, markupToRawString} from '../../../../../../../../shared'; +import type {CharkitTableHead, ChartkitTableRows, PivotDataRowsHeader, PivotField} from '../types'; + +export const isRowWithTotals = ( + headers: PivotDataRowsHeader[], + fieldsItemIdMap: Record, +) => { + return headers.some((header) => { + if (!header) { + return false; + } + const [_value, legendItemId] = header[0]; + + return fieldsItemIdMap[legendItemId]?.item_type === 'placeholder'; + }); +}; + +export const setTotalsHeadersToRows = (rows: ChartkitTableRows, i18n: (key: string) => string) => { + rows.forEach((row: {cells: any[]}) => { + let isTotalHeaderSet = false; + + row.cells.forEach((cell, index) => { + if (cell.isTotalCell && cell.value === '' && !isTotalHeaderSet) { + isTotalHeaderSet = true; + + const totalTitle = i18n('label_total'); + + const prevCell = row.cells[index - 1]; + + if (prevCell) { + if (isMarkupItem(prevCell.value)) { + const markupValue: MarkupItem = { + type: 'concat', + children: [{type: 'text', content: `${totalTitle} `}, prevCell.value], + }; + cell.value = markupValue; + } else { + cell.value = `${totalTitle} ${prevCell.value}`; + cell.type = 'text'; + } + } else { + cell.value = totalTitle; + cell.type = 'text'; + } + } + }); + }); +}; + +export const setTotalsHeadersToHead = ( + head: CharkitTableHead, + i18n: (key: string) => string, + parentName?: string | null | MarkupItem, +) => { + for (let i = 0; i < head.length; i++) { + const headItem = head[i]; + if (headItem.isTotalCell) { + const totalTitle = i18n('label_total'); + if (parentName) { + const isParentMarkup = typeof parentName === 'object' && 'content' in parentName; + + const parentCellValue = isParentMarkup ? markupToRawString(parentName) : parentName; + + headItem.name = `${totalTitle} ${parentCellValue}`; + } else { + headItem.name = totalTitle; + } + break; + } + + const children = headItem.sub; + + if (children && children.length) { + setTotalsHeadersToHead(children, i18n, headItem.name); + } + } +}; + +export const setTotalsHeaders = ( + {rows, head}: {rows: ChartkitTableRows; head: CharkitTableHead}, + ChartEditor: IChartEditor, +) => { + const i18n = (key: string) => ChartEditor.getTranslation('wizard.prepares', key); + + setTotalsHeadersToHead(head, i18n); + setTotalsHeadersToRows(rows, i18n); +}; + +export const getGrandTotalsRowIndex = (rows: ChartkitTableRows) => { + let totalRowIndex = -1; + // Since totals are always at the end of lines. + // Find the first non-total header, and then save the previous index as the first index of the total rows + for (let i = rows.length - 1; i >= 0; i--) { + const prevIndex = i + 1; + const row = rows[i]; + const prevRow = rows[prevIndex]; + const firstCell = row.cells[0]; + const prevFirstCell = prevRow?.cells[0]; + + if (prevFirstCell && prevFirstCell.isTotalCell && !firstCell.isTotalCell) { + totalRowIndex = prevIndex; + break; + } + } + + return totalRowIndex; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/types.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/types.ts new file mode 100644 index 0000000..c680efa --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/types.ts @@ -0,0 +1,6 @@ +export type PivotTableColorSettings = { + min: number; + max: number; + colorValues: (number | null)[][]; + flatColorValues: (number | null)[]; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/index.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/index.ts new file mode 100644 index 0000000..27db4ce --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/index.ts @@ -0,0 +1,256 @@ +import type {ServerPlaceholder, StringParams} from '../../../../../../../shared'; +import {PlaceholderId} from '../../../../../../../shared'; +import {getPivotTableSubTotals} from '../../utils/pivot-table/totals'; +import {addActionParamValue} from '../helpers/action-params'; +import type {PrepareFunctionArgs} from '../types'; + +import { + getActualizedPlaceholderItem, + getPivotTableSettingsFromField, + getValuesByField, +} from './helpers/misc'; +import {isSortByRoleAllowed} from './helpers/sort'; +import {getGrandTotalsRowIndex, setTotalsHeaders} from './helpers/totals'; +import {generateTableHead} from './table-head-generator'; +import {generateTableRows} from './table-rows-generator'; +import type { + CharkitTableHead, + ChartkitTableRows, + PivotData, + PivotDataColumn, + PivotDataRows, + PivotDataStructure, + PivotDataWithInfo, + PivotField, + PivotTableFieldDict, + PivotTableFieldSettings, + PivotTableHeaderSortMeta, + PivotTableSortSettings, +} from './types'; + +type BackendPivotTablePreparerResult = { + head: CharkitTableHead; + rows: ChartkitTableRows; + footer: ChartkitTableRows; +}; + +const backendPivotTablePreparer = (args: PrepareFunctionArgs): BackendPivotTablePreparerResult => { + const {shared, defaultColorPaletteId} = args; + const rawPivotData: PivotData | PivotDataWithInfo = (args.resultData as any).pivot_data as + | PivotData + | PivotDataWithInfo; + + const pinnedColumns = shared.extraSettings?.pinnedColumns || 0; + let pivotData: PivotData; + + const backendSortMeta: PivotTableHeaderSortMeta = { + columnsMeta: {}, + rowsMeta: {}, + }; + + const fieldsItemIdMap: Record = (args.resultData as any).fields.reduce( + (acc: Record, item: PivotField) => { + acc[item.legend_item_id] = item; + return acc; + }, + {}, + ); + + const headerTotalsIndexMap: Record = {}; + + if ('columns_with_info' in rawPivotData) { + pivotData = { + columns: rawPivotData.columns_with_info.reduce((acc, data, cellIndex) => { + backendSortMeta.columnsMeta[cellIndex] = data.header_info; + + const cells = data.cells; + const hasTotals = cells.find((cell) => { + const [_, legendItemId] = cell[0]; + + const field = fieldsItemIdMap[legendItemId]; + return field.item_type === 'placeholder'; + }); + + if (hasTotals) { + headerTotalsIndexMap[cellIndex] = true; + } + + acc.push([...data.cells]); + return acc; + }, [] as PivotDataColumn[]), + order: rawPivotData.order, + row_dimension_headers: rawPivotData.row_dimension_headers, + rows: rawPivotData.rows.reduce((acc, data, cellIndex) => { + backendSortMeta.rowsMeta[cellIndex] = data.header_with_info.header_info; + acc.push({header: [...data.header_with_info.cells], values: data.values}); + return acc; + }, [] as PivotDataRows[]), + }; + } else { + pivotData = rawPivotData; + } + + if (pivotData.rows.length === 0 && pivotData.columns.length === 0) { + return { + rows: [], + head: [], + footer: [], + }; + } + + const valuesByField = getValuesByField({ + rows: pivotData.rows, + placeholders: args.placeholders, + fieldsItemIdMap, + }); + + const {fieldDict, settingsByField} = args.placeholders.reduce( + (acc: PivotTableFieldDict, placeholder: ServerPlaceholder) => { + placeholder.items.forEach((placeholderItem) => { + const actualizedPlaceholderItem = getActualizedPlaceholderItem(placeholderItem, { + idToDataType: args.idToDataType, + idToTitle: args.idToTitle, + }); + + acc.fieldDict[actualizedPlaceholderItem.guid] = actualizedPlaceholderItem; + acc.fieldDict[actualizedPlaceholderItem.title] = actualizedPlaceholderItem; + + const prevSettings: PivotTableFieldSettings = + acc.settingsByField[actualizedPlaceholderItem.guid] || + acc.settingsByField[actualizedPlaceholderItem.title] || + {}; + + const settings = getPivotTableSettingsFromField( + actualizedPlaceholderItem, + prevSettings, + placeholder.id as PlaceholderId, + valuesByField, + ); + acc.settingsByField[actualizedPlaceholderItem.guid] = settings; + acc.settingsByField[actualizedPlaceholderItem.title] = settings; + }); + + return acc; + }, + {fieldDict: {}, settingsByField: {}} as PivotTableFieldDict, + ); + + const pivotStructure: PivotDataStructure[] = (args.resultData as any).pivot.structure || []; + + const rowsFields = + args.placeholders.find((placeholder) => placeholder.id === PlaceholderId.PivotTableRows) + ?.items || []; + const columnsFields = + args.placeholders.find((placeholder) => placeholder.id === PlaceholderId.PivotTableColumns) + ?.items || []; + const measures = args.placeholders[2].items; + const isTotalsEnabled = Object.values(fieldDict).some((field) => { + return field.subTotalsSettings?.enabled; + }); + + const isPaginatorEnabled = args.shared.extraSettings?.pagination === 'on'; + const isInlineSortEnabled = !(args.shared.extraSettings?.pivotInlineSort === 'off'); + + const pivotTotals = getPivotTableSubTotals({rowsFields, columnsFields}); + const sortSettings: PivotTableSortSettings = { + isSortByRowAllowed: + isSortByRoleAllowed(pivotStructure, pivotTotals, 'pivot_row') && isInlineSortEnabled, + isSortByColumnAllowed: isSortByRoleAllowed(pivotStructure, pivotTotals, 'pivot_column'), + ...backendSortMeta, + }; + + const rows = generateTableRows({ + settingsByField, + pivotData, + fieldsItemIdMap, + fieldDict, + colorsConfig: args.colorsConfig, + colors: args.colors, + isTotalsEnabled, + pivotStructure, + sortSettings, + headerTotalsIndexMap, + ChartEditor: args.ChartEditor, + defaultColorPaletteId, + }); + + const columns = pivotData.columns; + + let lineHeaderLength: number; + + const tableRows = pivotData.rows[0]; + const rowHeaders = tableRows?.header || []; + const rowHeadersLength = rowHeaders.length; + + if (columns.length === 1 && columns[0].length === 0) { + const rowValues = tableRows?.values || []; + const rowValuesLength = rowValues.length; + lineHeaderLength = rowHeadersLength + rowValuesLength; + } else { + lineHeaderLength = rowHeadersLength; + } + + const head = generateTableHead({ + pivotData, + pivotStructure, + fieldsItemIdMap, + lineHeaderLength, + fieldDict, + idToDataType: args.idToDataType, + measures, + isTotalsEnabled, + settingsByField, + isPaginatorEnabled, + loadedColorPalettes: args.colorsConfig.loadedColorPalettes, + availablePalettes: args.colorsConfig.availablePalettes, + sortSettings, + pinnedColumns, + defaultColorPaletteId, + }); + + let footer = []; + + if (isTotalsEnabled) { + setTotalsHeaders({rows, head}, args.ChartEditor); + + const totalRowIndex = getGrandTotalsRowIndex(rows); + + if (totalRowIndex !== -1) { + const rowsToRemove = rows.length - totalRowIndex; + footer = rows.splice(totalRowIndex, rowsToRemove); + } + } + + const widgetConfig = args.ChartEditor.getWidgetConfig(); + const isActionParamsEnable = widgetConfig?.actionParams?.enable; + if (isActionParamsEnable) { + const headParams = rawPivotData.columns.map((items) => { + const actionParams = {}; + items.forEach((cell) => { + const [[value, legend_item_id]] = cell; + const pivotField = fieldsItemIdMap[legend_item_id]; + addActionParamValue(actionParams, fieldDict[pivotField?.id], value); + }); + + return actionParams; + }); + + rows.forEach((row) => { + const rowActionParams = {}; + row.cells.forEach((cell: any, cellIndex: number) => { + if (cellIndex < rowHeaders.length) { + const [[, legend_item_id]] = rowHeaders[cellIndex] || []; + const pivotField = fieldsItemIdMap[legend_item_id]; + addActionParamValue(rowActionParams, fieldDict[pivotField?.id], cell.value); + } else { + const headCellParams = headParams[cellIndex - rowHeaders.length]; + cell.custom = {actionParams: {...rowActionParams, ...headCellParams}}; + } + }); + }); + } + + return {head, rows, footer}; +}; + +export default backendPivotTablePreparer; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/table-head-generator.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/table-head-generator.ts new file mode 100644 index 0000000..eac123f --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/table-head-generator.ts @@ -0,0 +1,597 @@ +import type { + ColorPalette, + DATASET_FIELD_TYPES, + MarkupItem, + Palette, + ServerField, +} from '../../../../../../../shared'; +import { + getFakeTitleOrTitle, + getFormatOptions, + isDateField, + isMarkupField, + isMeasureField, + isMeasureName, + markupToRawString, +} from '../../../../../../../shared'; +import {TABLE_TOTALS_STYLES} from '../../../constants/misc'; +import { + chartKitFormatNumberWrapper, + formatDate, + getDefaultDateFormat, + isNumericalDataType, +} from '../../utils/misc-helpers'; +import {getColumnWidthValue} from '../helpers/columnSettings'; + +import {MEASURE_NAME_PSEUDO_ID} from './constants/misc'; +import {colorizePivotTableHeaderByBackgroundSettings} from './helpers/backgroundColor'; +import {getCellValueForHeader, getPivotTableCellId} from './helpers/misc'; +import {getSortMeta} from './helpers/sort'; +import type { + CharkitTableHead, + ChartkitCell, + ChartkitHeadCell, + HeaderInfo, + PivotData, + PivotDataCellValue, + PivotDataCellValues, + PivotDataColumn, + PivotDataStructure, + PivotField, + PivotTableFieldSettings, + PivotTableSortSettings, +} from './types'; + +type GenerateTableHeadArgs = { + pivotData: PivotData; + pivotStructure: PivotDataStructure[]; + settingsByField: Record; + fieldsItemIdMap: Record; + lineHeaderLength: number; + idToDataType: Record; + fieldDict: Record; + measures: ServerField[]; + isTotalsEnabled: boolean; + isPaginatorEnabled: boolean; + loadedColorPalettes: Record; + availablePalettes: Record; + sortSettings: PivotTableSortSettings; + pinnedColumns: number; + defaultColorPaletteId: string; +}; + +type GetHeaderCellMetadataArgs = { + settingsByField: Record; + fieldDict: Record; + fieldsItemIdMap: Record; + pivotDataCellValue: PivotDataCellValue; + measures: ServerField[]; + parents: Record; + isTotalHeader?: boolean; + isPaginatorEnabled?: boolean; + loadedColorPalettes: Record; + availablePalettes: Record; + defaultColorPaletteId: string; +}; + +type HeadSortMetaData = { + measureGuid: {guid: string}; + isSortByRowAllowed: boolean; + isSortByColumnAllowed: boolean; + fieldOrder: string[]; +}; + +export const getRowHeaderCellMetadata = (args: GetHeaderCellMetadataArgs): ChartkitCell => { + const {defaultColorPaletteId} = args; + const [value, legendItemId] = args.pivotDataCellValue; + const pivotField = args.fieldsItemIdMap[legendItemId]; + const isMeasureName = pivotField.id === MEASURE_NAME_PSEUDO_ID; + const fieldGuidOrTitle = isMeasureName ? value.replace('title-', '') : pivotField.id; + const field = args.fieldDict[fieldGuidOrTitle]; + + const cell: ChartkitCell = { + value, + css: args.isTotalHeader ? TABLE_TOTALS_STYLES : undefined, + isTotalCell: args.isTotalHeader, + }; + + if (!field) { + return cell; + } + + const columnSettingsFieldId = isMeasureName ? pivotField.title : fieldGuidOrTitle; + const backgroundSettings = args.settingsByField[columnSettingsFieldId]?.backgroundSettings; + + const cellValue: string = getCellValueForHeader(value, { + pivotField, + datasetField: field, + }); + + cell.css = colorizePivotTableHeaderByBackgroundSettings({ + backgroundSettings, + cell, + parents: args.parents, + cellValue, + isTotal: Boolean(args.isTotalHeader), + loadedColorPalettes: args.loadedColorPalettes, + availablePalettes: args.availablePalettes, + defaultColorPaletteId, + }); + + if (isMeasureName) { + cell.value = field.fakeTitle || cell.value; + + return cell; + } + + // Formatting a column cell + if (isNumericalDataType(field.data_type)) { + cell.type = 'number'; + cell.fieldId = field.guid; + + if (value !== null) { + cell.value = isNaN(Number(value)) ? value : Number(value); + cell.formattedValue = chartKitFormatNumberWrapper(cell.value, { + lang: 'ru', + ...getFormatOptions(field), + }); + } + } else if (isDateField(field) && value) { + cell.formattedValue = formatDate({ + valueType: field.data_type, + value, + format: field.format, + }); + } + + return cell; +}; + +function getCellType(field: ServerField) { + if (!field) { + return undefined; + } + + if (isNumericalDataType(field.data_type)) { + return 'number'; + } + + if (isDateField(field)) { + return 'date'; + } + + return undefined; +} + +export const getHeaderCellMetadata = ( + args: GetHeaderCellMetadataArgs & {sortMetaData: HeadSortMetaData}, +): ChartkitHeadCell => { + const {defaultColorPaletteId} = args; + const [name, legendItemId] = args.pivotDataCellValue; + const pivotField = args.fieldsItemIdMap[legendItemId]; + const isMeasureName = pivotField.id === MEASURE_NAME_PSEUDO_ID; + const fieldGuidOrTitle = isMeasureName ? name.replace('title-', '') : pivotField.id; + const field = args.fieldDict[fieldGuidOrTitle]; + + const pseudoFieldCellValue = field?.fakeTitle || name; + + const cell: ChartkitHeadCell = { + name, + css: args.isTotalHeader ? TABLE_TOTALS_STYLES : undefined, + isTotalCell: args.isTotalHeader, + }; + + const measureItem = args.measures[0]; + const isSortable = args.sortMetaData.isSortByColumnAllowed; + + // We fill in the column-specific data so that sorting works. + cell.type = getCellType(measureItem); + if (measureItem && (isNumericalDataType(measureItem.data_type) || isDateField(measureItem))) { + cell.sortable = isSortable; + } + + if (!field) { + return cell; + } + + const columnSettingsFieldId = isMeasureName ? pivotField.title : fieldGuidOrTitle; + const columnSettings = args.settingsByField[columnSettingsFieldId]?.columnSettings?.column; + const widthSettings = columnSettings?.width; + + cell.width = getColumnWidthValue(widthSettings); + + const backgroundSettings = args.settingsByField[columnSettingsFieldId]?.backgroundSettings; + cell.css = colorizePivotTableHeaderByBackgroundSettings({ + backgroundSettings, + cell, + parents: args.parents, + cellValue: getCellValueForHeader(name, {pivotField, datasetField: field}), + isTotal: Boolean(args.isTotalHeader), + loadedColorPalettes: args.loadedColorPalettes, + availablePalettes: args.availablePalettes, + defaultColorPaletteId, + }); + + if (isMeasureName) { + cell.name = pseudoFieldCellValue; + return cell; + } + + // Formatting a column cell + if (cell.name === null) { + cell.formattedName = String(cell.name); + cell.sortable = false; + } else if (isNumericalDataType(field.data_type)) { + const formatting = getFormatOptions(field); + + cell.formattedName = chartKitFormatNumberWrapper(Number(name), { + lang: 'ru', + ...formatting, + }); + } else if (isDateField(field)) { + cell.name = formatDate({ + valueType: field.data_type, + value: name, + format: field.format, + }); + } else if (isMarkupField(field)) { + const markupItem = name as unknown as MarkupItem; + cell.name = markupToRawString(markupItem); + cell.markup = markupItem; + } + + return cell; +}; + +export const generateTableHead = ({ + pivotData, + lineHeaderLength, + fieldsItemIdMap, + fieldDict, + measures, + isTotalsEnabled, + settingsByField, + isPaginatorEnabled, + loadedColorPalettes, + availablePalettes, + sortSettings, + pivotStructure, + pinnedColumns, + defaultColorPaletteId, +}: GenerateTableHeadArgs): CharkitTableHead => { + const {columnsMeta, isSortByRowAllowed, isSortByColumnAllowed} = sortSettings; + const mappedHeadCellData = mapColumnsToHeadCellData(pivotData.columns, columnsMeta); + const cellId = { + current: 0, + }; + + const head = mapHeadCellDataToHead({ + data: mappedHeadCellData, + lineHeaderLength, + isTotalsEnabled, + headerMetadataSource: { + fieldDict, + fieldsItemIdMap, + settingsByField, + measures, + isPaginatorEnabled, + loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, + }, + cellId, + sortMetaData: { + measureGuid: {guid: ''}, + isSortByRowAllowed, + isSortByColumnAllowed, + fieldOrder: [], + }, + pivotStructure, + }); + + pivotData.row_dimension_headers?.forEach((item, index) => { + if (!head[index]) { + return; + } + + let fieldName = item[0][0]; + + fieldName = fieldName.startsWith('title-') ? fieldName.replace('title-', '') : fieldName; + + const field = fieldDict[fieldName]; + + if (field) { + fieldName = getFakeTitleOrTitle(field); + + const columnSettings = (settingsByField[field.guid] || settingsByField[field.title]) + ?.columnSettings?.row; + const widthSettings = columnSettings?.width; + + if (isMeasureName(field)) { + head[index].sortable = false; + } + + const cellType = getCellType(field); + + head[index].width = getColumnWidthValue(widthSettings); + head[index].id = getPivotTableCellId(field.guid, cellId); + head[index].type = cellType; + head[index].fieldId = field.guid; + + if (cellType === 'date') { + head[index].format = field.format || getDefaultDateFormat(field.data_type); + } + + if (field.hintSettings?.enabled) { + head[index].hint = field.hintSettings?.text; + } + } + + head[index].name = fieldName; + head[index].header = true; + + if (index < pinnedColumns) { + head[index].pinned = true; + } + }); + + return head; +}; + +type MapSubObjectToArrayArgs = { + [x: string]: { + item: PivotDataCellValues; + sub?: Record; + parents: Record; + columnSortMeta: HeaderInfo; + }; +}; + +function mapSubObjectToArray(map: MapSubObjectToArrayArgs): MappedHeadCellData[] { + const arr = Object.values(map); + + if (!arr.length) { + return []; + } + + return arr.map((item) => { + const sub = mapSubObjectToArray(item.sub as unknown as MapSubObjectToArrayArgs); + + return { + ...item, + sub: sub.length ? sub : undefined, + }; + }); +} + +type MappedHeadCellData = { + item: PivotDataCellValues; + sub?: MappedHeadCellData[]; + + parents: Record; + columnSortMeta: HeaderInfo; +}; + +export function mapColumnsToHeadCellData( + columns: PivotDataColumn[], + columnsSortMeta: Record, +): MappedHeadCellData[] { + const result = {}; + + const columnsWithInfo: Array<{column: PivotDataColumn; meta: HeaderInfo}> = columns.map( + (column, index) => { + return { + column, + meta: columnsSortMeta[index], + }; + }, + ); + + _mapColumnsToHeadCellData(columnsWithInfo, result, 0, undefined, columnsSortMeta); + + return mapSubObjectToArray(result); +} + +function _mapColumnsToHeadCellData( + columns: Array<{column: PivotDataColumn; meta: HeaderInfo}>, + map: any, + index: number, + parents: Record = {}, + columnsSortMeta: Record, +) { + const uniqueHeaders = new Set([]); + const temp: Record< + string, + {cellData: any; meta: HeaderInfo; indexes: Record} + > = {}; + + columns.forEach(({column, meta}, rowIndex) => { + if (!column[index]) { + return; + } + + const cellData = column[index]; + const cellValue = getCellValueForHeader(cellData[0][0]); + + const previousCellData = columns[rowIndex - 1]?.column?.[index]; + const previousCellValue = previousCellData + ? getCellValueForHeader(previousCellData[0][0]) + : undefined; + + let cellValueKey = `_${cellValue}__${index}`; + + if (temp[cellValueKey] && (column.length === 1 || previousCellValue !== cellValue)) { + cellValueKey += `_${rowIndex}`; + } else if (temp[cellValueKey] && previousCellValue === cellValue) { + cellValueKey = Array.from(uniqueHeaders)[uniqueHeaders.size - 1]; + } + + if (temp[cellValueKey]) { + temp[cellValueKey].indexes[rowIndex] = true; + } else { + temp[cellValueKey] = {cellData, meta, indexes: {[rowIndex]: true}}; + } + + uniqueHeaders.add(cellValueKey); + }); + + Array.from(uniqueHeaders).forEach((key) => { + const cell = temp[key]; + const {cellData, meta, indexes} = cell; + const cellValue = cellData[0][0]; + + const nextParent = { + ...parents, + }; + + map[key] = { + item: cellData, + sub: {}, + parents, + columnSortMeta: meta, + }; + + nextParent[getCellValueForHeader(cellValue)] = true; + + const subColumns = columns.filter((_, colIndex) => indexes[colIndex]); + + _mapColumnsToHeadCellData(subColumns, map[key].sub, index + 1, nextParent, columnsSortMeta); + }); +} + +type MapHeadCellDataToHead = { + data: MappedHeadCellData[]; + lineHeaderLength: number; + isTotalsEnabled: boolean; + headerMetadataSource: Omit; + cellId: {current: number}; + sortMetaData: HeadSortMetaData; + isTotalSubHeader?: boolean; + path?: string[]; + pivotStructure: PivotDataStructure[]; +}; + +function mapHeadCellDataToHead(options: MapHeadCellDataToHead): CharkitTableHead { + const { + data, + lineHeaderLength, + isTotalsEnabled, + headerMetadataSource, + cellId, + sortMetaData, + isTotalSubHeader = false, + path = [], + pivotStructure, + } = options; + const head = data.map((item, index) => { + const row: PivotDataCellValues = item.item; + const pivotDataCellValue: PivotDataCellValue = row[0]; + + const [value, legendItemId, pivotItemId] = pivotDataCellValue; + const field = headerMetadataSource.fieldsItemIdMap[legendItemId]; + const isMeasure = field.item_type === 'measure_name'; + const fieldGuid = isMeasure ? value.replace('title-', '') : field.id; + let datasetField = headerMetadataSource.fieldDict[fieldGuid]; + + const isTotalHeader = + isTotalSubHeader || (isTotalsEnabled && field.item_type === 'placeholder'); + + if (isTotalHeader && !datasetField) { + const pivotItem = pivotStructure.find((item) => item.pivot_item_id === pivotItemId); + pivotItem?.legend_item_ids.forEach((id) => { + const guid = headerMetadataSource.fieldsItemIdMap[id]?.id; + + if (headerMetadataSource.fieldDict[guid]) { + datasetField = headerMetadataSource.fieldDict[guid]; + } + }); + } + + let result: ChartkitHeadCell = { + id: getPivotTableCellId(datasetField?.guid, cellId), + sortable: true, + name: value, + allowGroupSort: true, + }; + + const measures = isMeasure + ? headerMetadataSource.measures.slice(index) + : headerMetadataSource.measures; + + const datasetFieldGuid = datasetField?.guid || ''; + let fieldOrderItem; + + if (isMeasureField(datasetField)) { + sortMetaData.measureGuid.guid = datasetField.guid; + fieldOrderItem = 'measure_name'; + } else { + fieldOrderItem = datasetFieldGuid; + } + + if (item.sub) { + result.sub = mapHeadCellDataToHead({ + data: item.sub, + lineHeaderLength: 0, + isTotalsEnabled, + headerMetadataSource: {...headerMetadataSource, measures}, + cellId, + sortMetaData: { + ...sortMetaData, + fieldOrder: [...sortMetaData.fieldOrder, fieldOrderItem], + }, + isTotalSubHeader: isTotalHeader, + path: [...path, value], + pivotStructure, + }); + } else if (item.columnSortMeta && sortMetaData.isSortByColumnAllowed) { + result.custom = getSortMeta({ + meta: item.columnSortMeta, + path: [...path, value], + measureGuid: sortMetaData.measureGuid.guid, + fieldOrder: [...sortMetaData.fieldOrder, fieldOrderItem], + }); + } + + result = { + ...result, + ...getHeaderCellMetadata({ + ...headerMetadataSource, + pivotDataCellValue, + measures, + isTotalHeader, + parents: item.parents, + sortMetaData, + }), + }; + + return result; + }); + + const useGroup = lineHeaderLength > 1; + const emptyHead = head.length === 0; + const isLastColumnMeasure = headerMetadataSource.measures.length; + + const headCell: ChartkitHeadCell = { + autogroup: false, + group: useGroup, + name: '', + sortable: true, + allowGroupSort: useGroup, + }; + + if (emptyHead && isLastColumnMeasure) { + headCell.type = 'number'; + } + + let iterator = lineHeaderLength; + + while (iterator--) { + // Next, the HeadCell object is mutated to put column headers with rows + // Therefore, we make a copy + head.unshift({ + ...headCell, + }); + } + + return head; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/table-rows-generator.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/table-rows-generator.ts new file mode 100644 index 0000000..f2d6f77 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/table-rows-generator.ts @@ -0,0 +1,364 @@ +import type { + BarViewOptions, + ColorPalette, + IChartEditor, + Palette, + ServerColor, + ServerField, +} from '../../../../../../../shared'; +import { + ApiV2Annotations, + getFormatOptions, + isDateField, + isMarkupItem, + isMeasureField, +} from '../../../../../../../shared'; +import {TABLE_TOTALS_STYLES} from '../../../constants/misc'; +import type {ChartColorsConfig} from '../../types'; +import { + chartKitFormatNumberWrapper, + formatDate, + isNumericalDataType, + isTableBarsSettingsEnabled, +} from '../../utils/misc-helpers'; +import {getBarSettingsValue} from '../helpers/barsSettings'; +import type {BarValueOptions} from '../helpers/barsSettings/types'; + +import {colorizePivotTableByFieldBackgroundSettings} from './helpers/backgroundColor'; +import {colorizePivotTableByColorField} from './helpers/color'; +import { + getAnnotation, + getAnnotationsMap, + getCellValueForHeader, + getDatasetFieldFromPivotTableValue, + getPivotTableCellId, +} from './helpers/misc'; +import {getSortMeta} from './helpers/sort'; +import {isRowWithTotals} from './helpers/totals'; +import {getRowHeaderCellMetadata} from './table-head-generator'; +import type { + AnnotationsMap, + ChartkitCell, + ChartkitTableRows, + PivotData, + PivotDataCellValue, + PivotDataRows, + PivotDataRowsHeader, + PivotDataRowsValue, + PivotDataStructure, + PivotField, + PivotTableFieldSettings, + PivotTableSortSettings, +} from './types'; + +type GetRowCellMetadataArgs = { + pivotDataCellValue: PivotDataCellValue[]; + fieldsItemIdMap: Record; + fieldDict: Record; + settingsByField: Record; + loadedColorPalettes: Record; + availablePalettes: Record; + defaultColorPaletteId: string; + annotationsMap: AnnotationsMap; +}; + +function getRowCellMetadata(args: GetRowCellMetadataArgs): ChartkitCell { + const {loadedColorPalettes, availablePalettes, annotationsMap, defaultColorPaletteId} = args; + const [value, legendItemId] = args.pivotDataCellValue[0]; + + const pivotField = args.fieldsItemIdMap[legendItemId]; + + const fieldGuid = pivotField.id; + const field = args.fieldDict[fieldGuid]; + + const isTotalsRowValue = pivotField.role_spec.role === 'total'; + + let colorKey = value; + const backgroundColorAnnotation = getAnnotation( + args.pivotDataCellValue, + annotationsMap, + ApiV2Annotations.BackgroundColor, + ); + + if (backgroundColorAnnotation) { + const [colorValue] = backgroundColorAnnotation; + colorKey = colorValue; + } + + const cell: ChartkitCell = { + value, + colorKey, + isTotalCell: isTotalsRowValue, + css: { + ...(isTotalsRowValue ? TABLE_TOTALS_STYLES : {}), + }, + }; + + if (value === null) { + return cell; + } + + if (field.data_type === 'markup') { + cell.type = 'markup'; + } else if (isNumericalDataType(field.data_type)) { + cell.type = 'number'; + + const barSettings = args.settingsByField[field.guid]?.barsSettings; + if (isTableBarsSettingsEnabled(field) && barSettings) { + const {columnValues, options} = barSettings; + + const barValueOptions = getBarSettingsValue({ + field, + rowValue: value, + columnValues, + isTotalCell: isTotalsRowValue, + availablePalettes, + loadedColorPalettes, + defaultColorPaletteId, + }); + + const fullFilledBarSettings: BarViewOptions & BarValueOptions = { + ...barValueOptions, + ...options, + }; + + Object.assign(cell, fullFilledBarSettings); + } else { + const formatting = getFormatOptions(field); + cell.value = Number(value); + cell.formattedValue = chartKitFormatNumberWrapper(cell.value, { + lang: 'ru', + ...formatting, + }); + } + } else if (isDateField(field)) { + cell.type = 'date'; + cell.formattedValue = formatDate({ + valueType: field.data_type, + value, + format: field.format, + }); + } + + return cell; +} + +type GenerateTableRowsArgs = { + pivotData: PivotData; + settingsByField: Record; + fieldsItemIdMap: Record; + fieldDict: Record; + colorsConfig: ChartColorsConfig; + colors: ServerColor[]; + isTotalsEnabled: boolean; + pivotStructure: PivotDataStructure[]; + sortSettings: PivotTableSortSettings; + headerTotalsIndexMap: Record; + ChartEditor: IChartEditor; + defaultColorPaletteId: string; +}; + +export const generateTableRows = ({ + pivotData, + fieldsItemIdMap, + colorsConfig, + colors, + fieldDict, + isTotalsEnabled, + settingsByField, + pivotStructure, + sortSettings, + headerTotalsIndexMap, + ChartEditor, + defaultColorPaletteId, +}: GenerateTableRowsArgs): ChartkitTableRows => { + const {rowsMeta, isSortByRowAllowed} = sortSettings; + const cellId = {current: 0}; + const annotationsMap = getAnnotationsMap(pivotStructure); + + const rows: any[] = pivotData.rows.reduce( + (acc: any[], pivotDataRow: PivotDataRows, rowIndex) => { + const row: {cells: ChartkitCell[]} = { + cells: [], + }; + + const headerParentByIndex: Record = {}; + + const isTotalsRow = + isTotalsEnabled && isRowWithTotals(pivotDataRow.header, fieldsItemIdMap); + let measureGuid = ''; + const fieldOrder: string[] = []; + let isHeaderContainsMarkup = false; + + pivotDataRow.header.forEach((pivotDataRowValue: PivotDataRowsHeader, headerIndex) => { + if (!pivotDataRowValue) { + const emptyCell: ChartkitCell = { + value: '', + }; + + row.cells.push(emptyCell); + + return; + } + + const datasetField = getDatasetFieldFromPivotTableValue( + pivotDataRowValue, + fieldsItemIdMap, + fieldDict, + ); + + const isMeasure = isMeasureField(datasetField); + + if (datasetField) { + if (!measureGuid) { + measureGuid = isMeasure ? datasetField.guid : ''; + } + fieldOrder.push(isMeasure ? 'measure_name' : datasetField.guid); + } + + const path = Object.values(headerParentByIndex); + + const parents: Record = path.reduce( + (parentsResult, headerValue) => { + parentsResult[headerValue] = true; + return parentsResult; + }, + {} as Record, + ); + + const [value] = pivotDataRowValue[0]; + + const cell: ChartkitCell = { + id: getPivotTableCellId(datasetField?.guid, cellId), + ...getRowHeaderCellMetadata({ + pivotDataCellValue: pivotDataRowValue[0], + fieldsItemIdMap, + fieldDict, + measures: [], + isTotalHeader: isTotalsRow && value === '', + settingsByField, + parents, + loadedColorPalettes: colorsConfig.loadedColorPalettes, + availablePalettes: colorsConfig.availablePalettes, + defaultColorPaletteId, + }), + }; + + headerParentByIndex[headerIndex] = getCellValueForHeader(value, {datasetField}); + isHeaderContainsMarkup = isHeaderContainsMarkup || isMarkupItem(value); + + const isLastHeader = headerIndex === pivotDataRow.header.length - 1; + const isSortingAllowed = + isSortByRowAllowed && + rowsMeta[rowIndex] && + datasetField && + isLastHeader && + !isHeaderContainsMarkup; + + if (isSortingAllowed) { + if (cell.value === null) { + // DLFR-1767 sorting for null values is not supported on the backend yet + cell.onClick = { + action: 'showMsg', + args: { + message: ChartEditor.getTranslation( + 'wizard.prepares', + 'label_null-sorting-disabled-info', + ), + }, + }; + } else { + const sortMeta = getSortMeta({ + meta: rowsMeta[rowIndex], + path: [...path, value], + measureGuid, + fieldOrder, + }); + cell.sortDirection = sortMeta.currentSortDirection; + cell.onClick = { + action: 'setParams', + args: { + _sortRowMeta: JSON.stringify(sortMeta), + }, + }; + } + + cell.css = { + ...(cell.css || {}), + cursor: 'pointer', + }; + } + + row.cells.push(cell); + }); + + pivotDataRow.values.forEach((pivotDataRowValue: PivotDataRowsValue, valueIndex) => { + if (!pivotDataRowValue) { + const isTotalCell = isTotalsRow || headerTotalsIndexMap[valueIndex]; + const emptyCell: ChartkitCell = { + value: '', + css: isTotalCell ? TABLE_TOTALS_STYLES : undefined, + }; + + row.cells.push(emptyCell); + + return; + } + + const datasetField = getDatasetFieldFromPivotTableValue( + pivotDataRowValue, + fieldsItemIdMap, + fieldDict, + ); + + const cell: ChartkitCell = { + id: getPivotTableCellId(datasetField?.guid, cellId), + fieldId: datasetField?.guid, + ...getRowCellMetadata({ + pivotDataCellValue: pivotDataRowValue, + fieldsItemIdMap, + fieldDict, + settingsByField, + loadedColorPalettes: colorsConfig.loadedColorPalettes, + availablePalettes: colorsConfig.availablePalettes, + annotationsMap, + defaultColorPaletteId, + }), + }; + + row.cells.push(cell); + }); + + acc.push(row); + + return acc; + }, + [], + ); + + const rowHeaderLength = pivotData.rows[0]?.header?.length || 0; + + colorizePivotTableByFieldBackgroundSettings({ + annotationsMap, + rows, + rowHeaderLength, + rowsData: pivotData.rows, + settingsByField, + fieldDict, + fieldsItemIdMap, + loadedColorPalettes: colorsConfig.loadedColorPalettes, + availablePalettes: colorsConfig.availablePalettes, + defaultColorPaletteId, + }); + + colorizePivotTableByColorField({ + rows, + colors, + rowHeaderLength, + rowsData: pivotData.rows, + annotationsMap, + colorsConfig, + }); + + return rows; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/types.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/types.ts new file mode 100644 index 0000000..d11f7c4 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/types.ts @@ -0,0 +1,157 @@ +import type { + ApiV2Annotations, + ApiV2RequestPivotRoleSpec, + BarTableCell, + BarViewOptions, + ColumnSettings, + CommonTableColumn, + DATASET_FIELD_TYPES, + MarkupItem, + ServerField, + TableFieldBackgroundSettings, +} from '../../../../../../../shared'; +import type {BackendPivotTableCellCustom} from '../../types'; + +export type PivotDataWithInfo = { + columns: PivotDataColumn[]; + columns_with_info: Array<{cells: PivotDataColumn; header_info: HeaderInfo}>; + rows: PivotDataRowsWithInfo[]; + row_dimension_headers?: PivotRowDimensionHeaders[]; + order: any[]; +}; + +export type PivotData = { + columns: PivotDataColumn[]; + rows: PivotDataRows[]; + row_dimension_headers?: PivotRowDimensionHeaders[]; + order: any[]; +}; + +export type PivotField = { + data_type: DATASET_FIELD_TYPES; + field_type: 'DIMENSION' | 'MEASURE'; + id: string; + item_type: string; + legend_item_id: number; + role_spec: {role: string}; + title: string; +}; + +export type PivotDataStructure = { + title: string; + legend_item_ids: number[]; + pivot_item_id: number; + role_spec: ApiV2RequestPivotRoleSpec; +}; + +export type PivotDataTotal = { + level: number; +}; + +export type PivotDataTotals = { + columns: PivotDataTotal[]; + rows: PivotDataTotal[]; +}; + +export type PivotDataColumn = PivotDataCellValues[]; + +export type PivotDataCellValues = PivotDataCellValue[]; + +export type PivotRowDimensionHeaders = PivotDataCellValue[]; + +export type CellValue = string; + +export type LegendItemId = number; + +export type PivotItemId = number; + +export type PivotDataCellValue = [CellValue, LegendItemId, PivotItemId]; + +export type HeaderInfo = { + role_spec: { + role: 'data' | 'total'; + }; + sorting_direction: 'asc' | 'desc' | null; +}; + +export type PivotDataRowsWithInfo = PivotDataRows & { + header_with_info: {cells: PivotDataRowsHeader[]; header_info: HeaderInfo}; +}; + +export type PivotDataRows = { + header: PivotDataRowsHeader[]; + values: PivotDataRowsValue[]; +}; + +export type PivotDataRowsHeader = PivotDataCellValue[] | null; + +export type PivotDataRowsValue = PivotDataCellValue[] | null; + +export type CharkitTableHead = any[]; +export type ChartkitTableRows = any[]; + +export type ChartkitHeadCell = { + id?: number | string; + name?: string; + formattedName?: string; + autogroup?: boolean; + group?: boolean; + type?: 'number' | 'markup' | 'date'; + sortable?: boolean; + sub?: ChartkitHeadCell[]; + markup?: MarkupItem; + css?: CommonTableColumn['css']; + width?: CommonTableColumn['width']; + allowGroupSort?: boolean; + custom?: BackendPivotTableCellCustom; + isTotalCell?: boolean; +}; + +export type ChartkitCell = { + id?: string; + // Used to sort the table on the client. + value?: number | string | null; + colorKey?: string; + barColor?: string; + // Used for line headers. + formattedValue?: string; + css?: + | ({ + fontSize: string; + minHeight: string; + lineHeight: string; + } & CommonTableColumn['css']) + | CommonTableColumn['css']; + isTotalCell?: boolean; + header?: boolean; + type?: CommonTableColumn['type']; +} & Omit, 'type'>; + +export type PivotTableColumnSettingsDict = { + column?: ColumnSettings; + row?: ColumnSettings; +}; + +export type PivotTableFieldSettings = { + columnSettings?: PivotTableColumnSettingsDict; + barsSettings?: {options: BarViewOptions; columnValues: (string | null)[]}; + + backgroundSettings?: TableFieldBackgroundSettings; +}; + +export type PivotTableFieldDict = { + fieldDict: Record; + settingsByField: Record; +}; + +export type PivotTableHeaderSortMeta = { + columnsMeta: Record; + rowsMeta: Record; +}; + +export type PivotTableSortSettings = { + isSortByRowAllowed: boolean; + isSortByColumnAllowed: boolean; +} & PivotTableHeaderSortMeta; + +export type AnnotationsMap = Record; diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/mocks/prepareBarX.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/mocks/prepareBarX.mock.ts new file mode 100644 index 0000000..74aa6dd --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/mocks/prepareBarX.mock.ts @@ -0,0 +1,101 @@ +import cloneDeep from 'lodash/cloneDeep'; +import merge from 'lodash/merge'; + +import type { + IChartEditor, + ServerColor, + ServerField, + ServerShape, +} from '../../../../../../../../../shared'; +import {DATASET_FIELD_TYPES} from '../../../../../../../../../shared'; +import type {PrepareFunctionArgs} from '../../../types'; + +const ChartEditor = { + getWidgetConfig: () => {}, +} as IChartEditor; + +const datasetId = 'someDatasetId'; + +export const x1Field = { + datasetId, + title: 'X1Field', + guid: 'guidX1', + data_type: DATASET_FIELD_TYPES.DATE, +} as ServerField; + +export const x2Field = { + datasetId, + title: 'X2Field', + guid: 'guidX2', + data_type: DATASET_FIELD_TYPES.STRING, +} as ServerField; + +export const yField = { + datasetId, + title: 'YField', + guid: 'a6b94410-e219-11e9-a279-0b30c0a74ab7', + data_type: DATASET_FIELD_TYPES.FLOAT, +} as ServerField; + +export const colorField = { + datasetId, + title: 'ColorField', + guid: '38e0b1f4-d46b-48a0-8905-6a6d1e61900d', + data_type: DATASET_FIELD_TYPES.STRING, +} as ServerColor; + +export const shapeField = { + datasetId, + title: 'ShapeField', + guid: '45bdfbb6-1dd9-41bd-9871-b011efd8ec6b', + data_type: DATASET_FIELD_TYPES.STRING, +} as ServerShape; + +export const prepareBarXArgs = { + ChartEditor, + colors: [], + colorsConfig: {loadedColorPalettes: {}, colors: ['blue', 'red', 'orange'], gradientColors: []}, + datasets: [], + fields: [], + shapes: [], + sort: [], + segments: [], + visualizationId: 'column', + shared: { + visualization: {id: 'column'}, + }, + idToDataType: { + [x1Field.guid]: x1Field.data_type, + [x2Field.guid]: x2Field.data_type, + [yField.guid]: yField.data_type, + [colorField.guid]: colorField.data_type, + [shapeField.guid]: shapeField.data_type, + } as Record, + idToTitle: { + [x1Field.guid]: x1Field.title, + [x2Field.guid]: x2Field.title, + [yField.guid]: yField.title, + [colorField.guid]: colorField.title, + [shapeField.guid]: shapeField.title, + }, + placeholders: [ + { + id: 'x', + items: [x1Field], + }, + { + id: 'y', + items: [yField], + }, + ], + resultData: { + data: [], + order: [x1Field, yField], + totals: [], + }, + features: {}, +}; + +export function getPrepareFunctionArgs(options: Partial = {}) { + return merge(cloneDeep(prepareBarXArgs), options) as unknown as PrepareFunctionArgs; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/mocks/prepareHighchartsBarX.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/mocks/prepareHighchartsBarX.mock.ts new file mode 100644 index 0000000..38b5595 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/mocks/prepareHighchartsBarX.mock.ts @@ -0,0 +1,768 @@ +import {AxisMode, COMMON_PALETTE_ID} from '../../../../../../../../../shared'; + +const chartEditorMock = { + getLang: () => { + return 'en'; + }, + updateHighchartsConfig: () => {}, + updateConfig: () => {}, + getWidgetConfig: () => {}, +}; + +export const barXColoringBaseArgs = { + placeholders: [ + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'x', + type: 'x', + title: 'section_x', + iconProps: {}, + items: [ + { + lock_aggregation: null, + title: 'Month', + hidden: false, + guid: 'e83fb454-ca9a-4083-95be-1994e9957655', + managed_by: 'user', + cast: 'integer', + data_type: 'integer', + aggregation_locked: null, + has_auto_aggregation: null, + virtual: false, + type: 'DIMENSION', + initial_data_type: 'integer', + autoaggregated: false, + valid: true, + aggregation: 'none', + description: '', + source: 'Month', + avatar_id: '7a1555b0-33c8-4643-a8ad-d41d76569b69', + calc_mode: 'direct', + formula: '', + guid_formula: '', + datasetId: 'y55xt5d3p9csw', + id: 'dimension-7', + datasetName: 'Example', + default_value: null, + value_constraint: null, + }, + ], + required: false, + capacity: 2, + settings: { + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + holidays: 'off', + axisFormatMode: 'auto', + axisModeMap: {'e83fb454-ca9a-4083-95be-1994e9957655': AxisMode.Continuous}, + }, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'y', + type: 'y', + title: 'section_y', + iconProps: {}, + items: [ + { + lock_aggregation: false, + title: 'Profit', + hidden: false, + guid: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + managed_by: 'user', + cast: 'integer', + data_type: 'integer', + aggregation_locked: false, + has_auto_aggregation: false, + virtual: false, + type: 'MEASURE', + initial_data_type: 'float', + autoaggregated: false, + valid: true, + aggregation: 'sum', + description: '', + source: 'Profit', + avatar_id: '7a1555b0-33c8-4643-a8ad-d41d76569b69', + calc_mode: 'direct', + formula: '', + guid_formula: '', + datasetId: 'y55xt5d3p9csw', + id: 'measure-12', + datasetName: 'Example', + default_value: null, + value_constraint: null, + }, + ], + settings: { + scale: 'auto', + scaleValue: 'min-max', + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + nulls: 'ignore', + axisFormatMode: 'auto', + }, + }, + ], + colors: [ + { + lock_aggregation: null, + title: 'Year', + hidden: false, + guid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + managed_by: 'user', + cast: 'integer', + data_type: 'integer', + aggregation_locked: null, + has_auto_aggregation: null, + virtual: false, + type: 'DIMENSION', + initial_data_type: 'integer', + autoaggregated: false, + valid: true, + aggregation: 'none', + description: '', + source: 'Year', + avatar_id: '7a1555b0-33c8-4643-a8ad-d41d76569b69', + calc_mode: 'direct', + formula: '', + guid_formula: '', + datasetId: 'y55xt5d3p9csw', + id: 'dimension-8', + datasetName: 'Example', + default_value: null, + value_constraint: null, + }, + ], + colorsConfig: {}, + geopointsConfig: {}, + sort: [], + visualizationId: 'column', + labels: [], + tooltips: [], + datasets: ['y55xt5d3p9csw'], + resultData: { + data: [ + ['2', '2789', '2015'], + ['1', '7074', '2017'], + ['3', '9667', '2015'], + ['2', '844', '2014'], + ['1', '2785', '2016'], + ['3', '429', '2014'], + ['3', '14646', '2017'], + ['2', '1564', '2017'], + ['1', '-3308', '2015'], + ['3', '3535', '2016'], + ['2', '4969', '2016'], + ['1', '2419', '2014'], + ], + legend: [ + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + ], + order: [ + {datasetId: 'y55xt5d3p9csw', title: 'Month', dataType: 'integer'}, + {datasetId: 'y55xt5d3p9csw', title: 'Profit', dataType: 'integer'}, + {datasetId: 'y55xt5d3p9csw', title: 'Year', dataType: 'integer'}, + ], + totals: [], + }, + fields: [ + { + title: 'Month', + role_spec: {role: 'row', visibility: 'visible'}, + data_type: 'integer', + id: 'e83fb454-ca9a-4083-95be-1994e9957655', + field_type: 'DIMENSION', + item_type: 'field', + legend_item_id: 0, + guid: 'e83fb454-ca9a-4083-95be-1994e9957655', + }, + { + title: 'Profit', + role_spec: {role: 'measure'}, + data_type: 'integer', + id: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + field_type: 'MEASURE', + item_type: 'field', + legend_item_id: 1, + guid: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + }, + { + title: 'Year', + role_spec: {role: 'row', visibility: 'visible'}, + data_type: 'integer', + id: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + field_type: 'DIMENSION', + item_type: 'field', + legend_item_id: 2, + guid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + }, + ], + idToTitle: { + 'e83fb454-ca9a-4083-95be-1994e9957655': 'Month', + '4b6463d1-30e1-425e-b7d2-77205bc75de6': 'Profit', + 'd0ab8220-16e9-4320-b6da-1ca12163d956': 'Year', + }, + idToDataType: { + 'e83fb454-ca9a-4083-95be-1994e9957655': 'integer', + '4b6463d1-30e1-425e-b7d2-77205bc75de6': 'integer', + 'd0ab8220-16e9-4320-b6da-1ca12163d956': 'integer', + }, + shared: { + sharedData: {}, + }, + ChartEditor: chartEditorMock, + shapes: [], + segments: [], +}; + +export const barXColoringDimensionByGradientArgs = { + colorsConfig: { + gradientMode: '2-point', + gradientPalette: 'red-blue', + polygonBorders: 'show', + reversed: false, + thresholdsMode: 'auto', + colorMode: 'gradient', + colors: [ + '#4DA2F1', + '#FF3D64', + '#8AD554', + '#FFC636', + '#FFB9DD', + '#84D1EE', + '#FF91A1', + '#54A520', + '#DB9100', + '#BA74B3', + '#1F68A9', + '#ED65A9', + '#0FA08D', + '#FF7E00', + '#E8B0A4', + '#52A6C5', + '#BE2443', + '#70C1AF', + '#FFB46C', + '#DCA3D7', + ], + gradientColors: ['#BA2534', '#1B6DD1'], + loadedColorPalettes: {}, + }, +}; + +export const barXColoringDimensionByPaletteArgs = { + colorsConfig: { + colorMode: 'palette', + coloredByMeasure: false, + colors: [ + '#4DA2F1', + '#FF3D64', + '#8AD554', + '#FFC636', + '#FFB9DD', + '#84D1EE', + '#FF91A1', + '#54A520', + '#DB9100', + '#BA74B3', + '#1F68A9', + '#ED65A9', + '#0FA08D', + '#FF7E00', + '#E8B0A4', + '#52A6C5', + '#BE2443', + '#70C1AF', + '#FFB46C', + '#DCA3D7', + ], + fieldGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + gradientColors: ['#0044A3', '#8CCBFF'], + loadedColorPalettes: {}, + mountedColors: { + '2014': '12', + '2015': '13', + '2016': '14', + '2017': '15', + }, + palette: COMMON_PALETTE_ID.CLASSIC_20, + polygonBorders: 'show', + }, +}; + +export const barXColoringDimensionByGradientResult = { + graphs: [ + { + id: '2014', + title: '2014', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + { + y: 2419, + colorValue: 2014, + dataLabels: {enabled: false}, + x: 1, + label: '', + color: 'rgb(186, 37, 52)', + }, + { + y: 844, + colorValue: 2014, + dataLabels: {enabled: false}, + x: 2, + label: '', + color: 'rgb(186, 37, 52)', + }, + { + y: 429, + colorValue: 2014, + dataLabels: {enabled: false}, + x: 3, + label: '', + color: 'rgb(186, 37, 52)', + }, + ], + legendTitle: '2014', + drillDownFilterValue: '2014', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: 2014, + shapeValue: 'Profit', + custom: {}, + }, + { + id: '2015', + title: '2015', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + { + y: -3308, + colorValue: 2015, + dataLabels: {enabled: false}, + x: 1, + label: '', + color: 'rgb(133, 61, 104)', + }, + { + y: 2789, + colorValue: 2015, + dataLabels: {enabled: false}, + x: 2, + label: '', + color: 'rgb(133, 61, 104)', + }, + { + y: 9667, + colorValue: 2015, + dataLabels: {enabled: false}, + x: 3, + label: '', + color: 'rgb(133, 61, 104)', + }, + ], + legendTitle: '2015', + drillDownFilterValue: '2015', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: 2015, + shapeValue: 'Profit', + custom: {}, + }, + { + id: '2016', + title: '2016', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + { + y: 2785, + colorValue: 2016, + dataLabels: {enabled: false}, + x: 1, + label: '', + color: 'rgb(80, 85, 156)', + }, + { + y: 4969, + colorValue: 2016, + dataLabels: {enabled: false}, + x: 2, + label: '', + color: 'rgb(80, 85, 156)', + }, + { + y: 3535, + colorValue: 2016, + dataLabels: {enabled: false}, + x: 3, + label: '', + color: 'rgb(80, 85, 156)', + }, + ], + legendTitle: '2016', + drillDownFilterValue: '2016', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: 2016, + shapeValue: 'Profit', + custom: {}, + }, + { + id: '2017', + title: '2017', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + { + y: 7074, + colorValue: 2017, + dataLabels: {enabled: false}, + x: 1, + label: '', + color: 'rgb(27, 109, 209)', + }, + { + y: 1564, + colorValue: 2017, + dataLabels: {enabled: false}, + x: 2, + label: '', + color: 'rgb(27, 109, 209)', + }, + { + y: 14646, + colorValue: 2017, + dataLabels: {enabled: false}, + x: 3, + label: '', + color: 'rgb(27, 109, 209)', + }, + ], + legendTitle: '2017', + drillDownFilterValue: '2017', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: 2017, + shapeValue: 'Profit', + custom: {}, + }, + ], +}; + +export const barXColoringDimensionByPaletteResult = { + graphs: [ + { + id: '2014', + title: '2014', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + {y: 2419, dataLabels: {enabled: false}, x: 1, label: ''}, + {y: 844, dataLabels: {enabled: false}, x: 2, label: ''}, + {y: 429, dataLabels: {enabled: false}, x: 3, label: ''}, + ], + legendTitle: '2014', + drillDownFilterValue: '2014', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: '2014', + shapeValue: 'Profit', + custom: {}, + color: '#0FA08D', + }, + { + id: '2015', + title: '2015', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + {y: -3308, dataLabels: {enabled: false}, x: 1, label: ''}, + {y: 2789, dataLabels: {enabled: false}, x: 2, label: ''}, + {y: 9667, dataLabels: {enabled: false}, x: 3, label: ''}, + ], + legendTitle: '2015', + drillDownFilterValue: '2015', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: '2015', + shapeValue: 'Profit', + custom: {}, + color: '#FF7E00', + }, + { + id: '2016', + title: '2016', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + {y: 2785, dataLabels: {enabled: false}, x: 1, label: ''}, + {y: 4969, dataLabels: {enabled: false}, x: 2, label: ''}, + {y: 3535, dataLabels: {enabled: false}, x: 3, label: ''}, + ], + legendTitle: '2016', + drillDownFilterValue: '2016', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: '2016', + shapeValue: 'Profit', + custom: {}, + color: '#E8B0A4', + }, + { + id: '2017', + title: '2017', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + {y: 7074, dataLabels: {enabled: false}, x: 1, label: ''}, + {y: 1564, dataLabels: {enabled: false}, x: 2, label: ''}, + {y: 14646, dataLabels: {enabled: false}, x: 3, label: ''}, + ], + legendTitle: '2017', + drillDownFilterValue: '2017', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: '2017', + shapeValue: 'Profit', + custom: {}, + color: '#52A6C5', + }, + ], +}; + +export const barXPrepareForQLArgs = { + placeholders: [ + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'x', + type: 'x', + title: 'section_x', + iconProps: {}, + items: [ + { + guid: 'built_year-0', + title: 'built_year', + datasetId: 'ql-mocked-dataset', + data_type: 'string', + cast: 'string', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + required: false, + capacity: 2, + settings: { + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + holidays: 'off', + axisFormatMode: 'auto', + axisModeMap: {'built_year-0': AxisMode.Discrete}, + disableAxisMode: false, + }, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'y', + type: 'y', + title: 'section_y', + iconProps: {}, + items: [ + { + guid: 'iznos-1', + title: 'iznos', + datasetId: 'ql-mocked-dataset', + data_type: 'float', + cast: 'float', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + settings: { + scale: 'auto', + scaleValue: 'min-max', + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + nulls: 'connect', + axisFormatMode: 'auto', + }, + }, + ], + fields: [], + colors: [], + colorsConfig: { + colors: [ + '#4DA2F1', + '#FF3D64', + '#8AD554', + '#FFC636', + '#FFB9DD', + '#84D1EE', + '#FF91A1', + '#54A520', + '#DB9100', + '#BA74B3', + '#1F68A9', + '#ED65A9', + '#0FA08D', + '#FF7E00', + '#E8B0A4', + '#52A6C5', + '#BE2443', + '#70C1AF', + '#FFB46C', + '#DCA3D7', + ], + gradientColors: ['#0044A3', '#8CCBFF'], + loadedColorPalettes: {}, + }, + sort: [], + visualizationId: 'column', + labels: [], + tooltips: [], + datasets: [], + resultData: { + data: [ + ['1976', 100], + ['1970', 70], + ['1950', 78], + ['1949', 67], + ['1958', 72], + ], + order: [ + {datasetId: 'ql-mocked-dataset', title: 'built_year'}, + {datasetId: 'ql-mocked-dataset', title: 'iznos'}, + {datasetId: 'ql-mocked-dataset', title: 'Column Names'}, + ], + totals: [], + }, + idToTitle: {'built_year-0': 'built_year', 'iznos-1': 'iznos', '': 'Column Names'}, + idToDataType: {'built_year-0': 'string', 'iznos-1': 'float', '': 'string'}, + shared: { + sharedData: {}, + }, + ChartEditor: chartEditorMock, + shapes: [], + shapesConfig: {}, + segments: [], + disableDefaultSorting: false, +}; + +export const barXPrepareForQLResult = { + graphs: [ + { + id: 'iznos', + title: 'iznos', + tooltip: {chartKitFormatting: true, chartKitPrecision: 2}, + data: [ + {x: 0, y: 67, dataLabels: {enabled: false}, label: ''}, + {x: 1, y: 78, dataLabels: {enabled: false}, label: ''}, + {x: 2, y: 72, dataLabels: {enabled: false}, label: ''}, + {x: 3, y: 70, dataLabels: {enabled: false}, label: ''}, + {x: 4, y: 100, dataLabels: {enabled: false}, label: ''}, + ], + legendTitle: 'iznos', + colorKey: 'iznos', + colorGuid: null, + shapeGuid: null, + connectNulls: true, + measureFieldTitle: 'iznos', + yAxis: 0, + custom: {}, + color: '#4DA2F1', + }, + ], + categories: ['1949', '1950', '1958', '1970', '1976'], +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/prepareBarX.test.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/prepareBarX.test.ts new file mode 100644 index 0000000..126c6cb --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/prepareBarX.test.ts @@ -0,0 +1,99 @@ +import uniq from 'lodash/uniq'; + +import {prepareBarX} from '../prepare-bar-x'; + +import { + colorField, + getPrepareFunctionArgs, + x1Field, + x2Field, + yField, +} from './mocks/prepareBarX.mock'; + +describe('prepareBarX', () => { + test('two fields in X section -> legendTitle should be constructed as "[X second field name]: [X second field value]"', () => { + const options = getPrepareFunctionArgs({ + placeholders: [ + { + id: 'x', + items: [x1Field, x2Field], + }, + { + id: 'y', + items: [yField], + }, + ], + resultData: { + data: [ + ['2023-05-11T00:00:00', 'A', '10'], + ['2023-05-11T00:00:00', 'B', '11'], + ['2023-05-11T00:00:00', 'A', '15'], + ['2023-05-11T00:00:00', 'B', '100'], + ], + order: [x1Field, x2Field, yField], + totals: [], + }, + }); + const result = prepareBarX(options); + const items = uniq(result.graphs.map((item) => item.legendTitle)); + + expect(items).toEqual(['X2Field: A', 'X2Field: B']); + }); + + test('two fields in X section, second has date type -> legendTitle should be constructed with formatted date value"', () => { + const options = getPrepareFunctionArgs({ + placeholders: [ + { + id: 'x', + items: [x2Field, x1Field], + }, + { + id: 'y', + items: [yField], + }, + ], + resultData: { + data: [['A', '2023-05-10T00:00:00', '10']], + order: [x2Field, x1Field, yField], + totals: [], + }, + }); + const result = prepareBarX(options); + const items = uniq(result.graphs.map((item) => item.legendTitle)); + + expect(items).toEqual(['X1Field: 10.05.2023']); + }); + + test('two fields in X section and filed in Colors section -> legendTitle should be constructed as "[Color field value]", series id must match the legend title', () => { + const options = getPrepareFunctionArgs({ + placeholders: [ + { + id: 'x', + items: [x1Field, x2Field], + }, + { + id: 'y', + items: [yField], + }, + ], + colors: [colorField], + resultData: { + data: [ + ['2023-05-11T00:00:00', 'A', '10', 'Color-1'], + ['2023-05-11T00:00:00', 'B', '11', 'Color-1'], + ['2023-05-11T00:00:00', 'A', '15', 'Color-2'], + ['2023-05-11T00:00:00', 'B', '100', 'Color-2'], + ], + order: [x1Field, x2Field, yField, colorField], + totals: [], + }, + }); + + const result = prepareBarX(options); + const legendTitles = uniq(result.graphs.map((item) => item.legendTitle)); + const seriesIds = uniq(result.graphs.map((item) => item.legendTitle)); + + expect(legendTitles).toEqual(['Color-1', 'Color-2']); + expect(seriesIds).toEqual(legendTitles); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/prepareHighchartsBarX.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/prepareHighchartsBarX.ts new file mode 100644 index 0000000..65fc967 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-x/__tests__/prepareHighchartsBarX.ts @@ -0,0 +1,41 @@ +import {prepareHighchartsBarX} from '../highcharts'; + +const { + barXColoringBaseArgs, + barXColoringDimensionByGradientArgs, + barXColoringDimensionByGradientResult, + barXColoringDimensionByPaletteArgs, + barXColoringDimensionByPaletteResult, + barXPrepareForQLArgs, + barXPrepareForQLResult, +} = require('./mocks/prepareHighchartsBarX.mock'); + +describe('prepareHighchartsBarX', () => { + describe('wizard', () => { + describe('colors', () => { + test('should prepare bar-x with coloring by gradient with dimension', () => { + const result = prepareHighchartsBarX({ + ...barXColoringBaseArgs, + ...barXColoringDimensionByGradientArgs, + }); + expect(result).toEqual(barXColoringDimensionByGradientResult); + }); + + test('should prepare bar-x with coloring by palette with dimension', () => { + const result = prepareHighchartsBarX({ + ...barXColoringBaseArgs, + ...barXColoringDimensionByPaletteArgs, + }); + expect(result).toEqual(barXColoringDimensionByPaletteResult); + }); + }); + }); + + describe('ql', () => { + test('should render simple bar-x correctly', () => { + const result = prepareHighchartsBarX(barXPrepareForQLArgs); + + expect(result).toEqual(barXPrepareForQLResult); + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-x/gravity-charts.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-x/gravity-charts.ts new file mode 100644 index 0000000..d20261e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-x/gravity-charts.ts @@ -0,0 +1,446 @@ +import type {BarXSeries, ChartData, ChartSeries} from '@gravity-ui/chartkit/gravity-charts'; +import merge from 'lodash/merge'; +import sortBy from 'lodash/sortBy'; + +import type { + SeriesExportSettings, + ServerField, + WizardVisualizationId, +} from '../../../../../../../shared'; +import { + AxisMode, + GradientType, + LabelsPositions, + PERCENT_VISUALIZATIONS, + PlaceholderId, + getFakeTitleOrTitle, + getIsNavigatorEnabled, + getRgbColorValue, + getXAxisMode, + isDateField, + isHtmlField, + isMarkdownField, + isMarkupField, + isNumberField, + transformHexToRgb, +} from '../../../../../../../shared'; +import type {ExtendedChartData} from '../../../../../../../shared/types/chartkit'; +import { + getBaseChartConfig, + getTotalsPrecisionFromSeriesTooltips, + getYAxisBaseConfig, +} from '../../gravity-charts/utils'; +import {getFieldFormatOptions} from '../../gravity-charts/utils/format'; +import {getSeriesRangeSliderConfig} from '../../gravity-charts/utils/range-slider'; +import {getRgbColors} from '../../utils/color-helpers'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import {getExportColumnSettings} from '../../utils/export-helpers'; +import {isGradientMode} from '../../utils/misc-helpers'; +import {SEGMENT_TITLE_BASE_CONFIG} from '../constants'; +import {getAxisFormatting, getAxisType, getYAxisPlaceholders} from '../helpers/axis'; +import {DATA_LABEL_DEFAULT_PADDING} from '../helpers/axis/data-labels'; +import {concatStringValues} from '../helpers/common'; +import {isXAxisReversed} from '../helpers/highcharts'; +import {getLegendColorScale, shouldUseGradientLegend} from '../helpers/legend'; +import {getSegmentMap} from '../helpers/segments'; +import type {PrepareFunctionArgs} from '../types'; +import { + mapChartkitFormatSettingsToGravityChartValueFormat, + mapToGravityChartValueFormat, +} from '../utils'; + +import {prepareBarX} from './prepare-bar-x'; +import type {ExtendedBarXSeries, ExtendedBarXSeriesData} from './types'; + +type OldBarXDataItem = { + y: number; + x?: number; + label?: string | number; + custom?: any; + color?: string; + colorValue?: number; +} | null; + +// eslint-disable-next-line complexity +export function prepareGravityChartBarX(args: PrepareFunctionArgs) { + const { + shared, + labels, + placeholders, + disableDefaultSorting = false, + idToDataType, + colors, + colorsConfig, + sort, + visualizationId, + segments: split, + layerSettings, + } = args; + const baseConfig = getBaseChartConfig({ + shared, + visualization: {placeholders, id: visualizationId}, + }); + const xPlaceholder = placeholders.find((p) => p.id === PlaceholderId.X); + const xField: ServerField | undefined = xPlaceholder?.items?.[0]; + const yPlaceholder = placeholders.find((p) => p.id === PlaceholderId.Y); + const yField: ServerField | undefined = yPlaceholder?.items?.[0]; + const labelField = labels?.[0]; + const isDataLabelsEnabled = Boolean(labelField); + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + const xAxisMode = getXAxisMode({config: chartConfig}) ?? AxisMode.Discrete; + const isCategoriesXAxis = + !xField || + getAxisType({ + field: xField, + settings: xPlaceholder?.settings, + axisMode: xAxisMode, + }) === 'category' || + disableDefaultSorting; + + if (!xField && !yField) { + return { + series: { + data: [], + }, + }; + } + + const preparedData = prepareBarX(args); + const preparedCategories = preparedData.categories ?? preparedData.categories_ms; + const xCategories = xField ? preparedCategories : yField ? [getFakeTitleOrTitle(yField)] : []; + const colorItem = colors[0]; + + const isGradient = + Boolean(colorItem) && + isGradientMode({ + colorField: colorItem, + colorFieldDataType: colorItem.data_type, + colorsConfig, + }); + + const shouldUseHtmlForLabels = + isMarkupField(labelField) || isHtmlField(labelField) || isMarkdownField(labelField); + const shouldUsePercentStacking = PERCENT_VISUALIZATIONS.has(visualizationId); + const isNavigatorEnabled = getIsNavigatorEnabled(shared); + + let seriesTooltip: BarXSeries['tooltip']; + if (!yField) { + seriesTooltip = {enabled: false}; + } + + const xValueExportSetting = getExportColumnSettings({ + path: isCategoriesXAxis ? 'category' : 'x', + field: xField, + }); + const yValueExportSetting = getExportColumnSettings({path: 'y', field: yField}); + + const seriesData = preparedData.graphs.map((graph) => { + const rangeSlider = isNavigatorEnabled + ? getSeriesRangeSliderConfig({ + extraSettings: shared.extraSettings, + seriesName: graph.title, + }) + : undefined; + const seriesName = graph.custom?.segmentTitle + ? concatStringValues(graph.custom.segmentTitle, ': ', graph.title) + : graph.title; + + const labelFormatting = graph.dataLabels + ? mapToGravityChartValueFormat({field: labelField, formatSettings: graph.dataLabels}) + : undefined; + const shouldUsePercentageAsLabel = + labelFormatting && + 'labelMode' in labelFormatting && + labelFormatting?.labelMode === 'percent'; + + let seriesColor = graph.color; + if (!seriesColor && isGradient) { + const points = preparedData.graphs + .map((currentGraph) => + (currentGraph.data ?? []).map((d: OldBarXDataItem) => ({ + colorValue: d?.colorValue, + })), + ) + .flat(2); + const colorScale = getLegendColorScale({ + colorsConfig, + points, + }); + const gradientColors = getRgbColors(colorScale.colors.map(transformHexToRgb), false); + seriesColor = getRgbColorValue(0.5, GradientType.TWO_POINT, 0.5, [ + gradientColors[0], + gradientColors[gradientColors.length - 1], + ]); + } + const tooltip = graph.tooltip?.chartKitFormatting + ? { + ...seriesTooltip, + valueFormat: mapChartkitFormatSettingsToGravityChartValueFormat({ + chartkitFormatSettings: graph.tooltip, + }), + } + : seriesTooltip; + + const stackId = [layerSettings?.id, graph.stack].filter(Boolean).join('__') || undefined; + const exportSettings: SeriesExportSettings = { + columns: [ + xValueExportSetting, + { + ...yValueExportSetting, + id: seriesName, + name: seriesName, + }, + ], + }; + + return { + name: seriesName, + type: 'bar-x', + color: seriesColor, + stackId, + stacking: shouldUsePercentStacking ? 'percent' : 'normal', + tooltip, + data: graph.data.reduce( + (acc: ExtendedBarXSeriesData[], item: OldBarXDataItem, index: number) => { + const pointX = item?.x; + const total = + preparedData.graphs.reduce( + (sum, currentGraph) => + sum + + (currentGraph.data.find( + (point: OldBarXDataItem) => point?.x === pointX, + )?.y ?? 0), + 0, + ) ?? 0; + const percentage = ((item?.y ?? 0) / total) * 100; + const label = shouldUsePercentageAsLabel ? percentage : item?.label; + const dataItem: ExtendedBarXSeriesData = { + y: item?.y ?? 0, + custom: item?.custom, + color: item?.color, + }; + + if (isDataLabelsEnabled) { + if (item?.y === null) { + dataItem.label = ''; + } else { + dataItem.label = label; + } + } + + if (isCategoriesXAxis) { + dataItem.x = index; + } else if (!item && xCategories) { + dataItem.x = xCategories[index]; + } else { + dataItem.x = item?.x; + } + + acc.push(dataItem); + + return acc; + }, + [], + ), + legend: { + groupId: graph.id, + itemText: graph.legendTitle, + enabled: yField ? undefined : false, + }, + custom: { + ...graph.custom, + colorValue: graph.colorValue, + drillDownFilterValue: graph.drillDownFilterValue, + exportSettings, + }, + dataLabels: { + enabled: isDataLabelsEnabled, + inside: shared.extraSettings?.labelsPosition !== LabelsPositions.Outside, + html: shouldUseHtmlForLabels, + format: labelFormatting, + padding: DATA_LABEL_DEFAULT_PADDING, + }, + yAxis: graph.yAxis, + rangeSlider, + }; + }); + + const segmentField = split?.[0]; + const segmentsMap = getSegmentMap(args); + const segments = sortBy(Object.values(segmentsMap), (s) => s.index); + const isSplitEnabled = Boolean(segmentField); + const isSplitWithHtmlValues = isHtmlField(segmentField); + + let legend: ChartData['legend']; + if (seriesData.length && shouldUseGradientLegend(colorItem, colorsConfig, shared)) { + const points = preparedData.graphs + .map((graph) => + (graph.data ?? []).map((d: OldBarXDataItem) => ({colorValue: d?.colorValue})), + ) + .flat(2); + + const colorScale = getLegendColorScale({ + colorsConfig, + points, + }); + + legend = { + enabled: shared.extraSettings?.legendMode !== 'hide', + type: 'continuous', + title: {text: getFakeTitleOrTitle(colorItem), style: {fontWeight: '500'}}, + colorScale, + }; + } else { + const shouldUseHtmlForLegend = isHtmlField(colorItem); + legend = {html: shouldUseHtmlForLegend}; + + const nonEmptyLegendGroups = Array.from( + new Set(seriesData.map((s) => s.legend?.groupId).filter(Boolean)), + ); + if (isSplitEnabled && nonEmptyLegendGroups.length <= 1) { + legend.enabled = false; + } + } + + let xAxis: ChartData['xAxis'] = { + startOnTick: false, + endOnTick: false, + }; + + if (isCategoriesXAxis && xCategories?.length) { + xAxis = { + type: 'category', + // @ts-ignore There may be a type mismatch due to the wrapper over html, markup and markdown + categories: xCategories.map((category) => { + if (typeof category === 'number') { + return String(category); + } + + return category; + }), + labels: { + html: isHtmlField(xField) || isMarkdownField(xField) || isMarkupField(xField), + }, + }; + } else { + if (isDateField(xField)) { + xAxis.type = 'datetime'; + } + + if (isNumberField(xField)) { + xAxis.type = xPlaceholder?.settings?.type === 'logarithmic' ? 'logarithmic' : 'linear'; + + if (xAxis.type === 'logarithmic') { + xAxis.startOnTick = true; + xAxis.endOnTick = true; + } + } + + const xAxisLabelNumberFormat = xPlaceholder + ? getAxisFormatting({ + placeholder: xPlaceholder, + visualizationId, + }) + : undefined; + + if (xAxisLabelNumberFormat) { + xAxis.labels = {numberFormat: xAxisLabelNumberFormat}; + } + + xAxis.tickMarks = {enabled: Boolean(baseConfig.xAxis?.labels?.enabled)}; + } + + if (xAxis && isXAxisReversed(xField, sort, visualizationId as WizardVisualizationId)) { + xAxis.order = 'reverse'; + } + + const axisLabelNumberFormat = yPlaceholder + ? getAxisFormatting({ + placeholder: yPlaceholder, + visualizationId, + }) + : undefined; + const config: ExtendedChartData = { + series: { + data: seriesData as ChartSeries[], + }, + legend, + xAxis, + custom: { + tooltip: { + headerLabel: + isDateField(xField) && !isCategoriesXAxis + ? undefined + : getFakeTitleOrTitle(xField), + }, + }, + }; + + const chartYAxisItems = getYAxisPlaceholders({placeholders, shared}); + const chartYAxisPlaceholder = chartYAxisItems.find((p) => p.id === PlaceholderId.Y); + + const yAxisBaseConfig = merge( + getYAxisBaseConfig({ + placeholder: chartYAxisPlaceholder, + }), + { + labels: { + numberFormat: axisLabelNumberFormat ?? undefined, + }, + startOnTick: true, + endOnTick: true, + maxPadding: 0.001, + }, + ); + + if (isSplitEnabled) { + config.yAxis = segments.map((d) => { + let titleText: string = d.title; + if (isSplitWithHtmlValues) { + // @ts-ignore There may be a type mismatch due to the wrapper over html, markup and markdown + titleText = wrapHtml(d.title); + } + + const axisTitle = { + ...SEGMENT_TITLE_BASE_CONFIG, + text: titleText, + html: isSplitWithHtmlValues, + }; + + return merge({}, yAxisBaseConfig, { + plotIndex: d.index, + title: axisTitle, + }); + }); + } else { + config.yAxis = [{...yAxisBaseConfig, lineColor: 'transparent'}]; + } + + if (isSplitEnabled) { + config.split = { + enable: true, + gap: '40px', + plots: segments.map(() => { + return {}; + }), + }; + } + + if (yField) { + const valueFormat = getFieldFormatOptions({field: yField}); + config.tooltip = { + valueFormat, + totals: { + valueFormat: + valueFormat?.type === 'number' && valueFormat.format + ? valueFormat + : { + type: 'number', + precision: getTotalsPrecisionFromSeriesTooltips(seriesData), + }, + }, + }; + } + + return merge(baseConfig, config); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-x/highcharts.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-x/highcharts.ts new file mode 100644 index 0000000..c73b889 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-x/highcharts.ts @@ -0,0 +1,245 @@ +import _isEmpty from 'lodash/isEmpty'; +import set from 'lodash/set'; + +import type {ServerPlaceholder, WizardVisualizationId} from '../../../../../../../shared'; +import { + AxisLabelFormatMode, + AxisMode, + ChartkitHandlers, + MINIMUM_FRACTION_DIGITS, + PlaceholderId, + getFakeTitleOrTitle, + getIsNavigatorEnabled, + getXAxisMode, + isDateField, + isHtmlField, + isMeasureNameOrValue, +} from '../../../../../../../shared'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import {getFieldExportingOptions} from '../../utils/export-helpers'; +import {isLegendEnabled, isNumericalDataType} from '../../utils/misc-helpers'; +import {addAxisFormatter, addAxisFormatting} from '../helpers/axis'; +import {getAxisChartkitFormatting} from '../helpers/axis/get-axis-formatting'; +import {getHighchartsColorAxis, isXAxisReversed} from '../helpers/highcharts'; +import {getYPlaceholders} from '../helpers/layers'; +import {shouldUseGradientLegend} from '../helpers/legend'; +import {getSegmentMap} from '../helpers/segments'; +import {getSegmentsYAxis} from '../line/helpers'; +import type {PrepareFunctionArgs} from '../types'; + +import {prepareBarX} from './prepare-bar-x'; + +// eslint-disable-next-line complexity +export function prepareHighchartsBarX(args: PrepareFunctionArgs) { + const { + ChartEditor, + placeholders, + colors, + colorsConfig, + sort, + idToDataType, + visualizationId, + shared, + segments, + } = args; + const preparedData = prepareBarX(args); + const {graphs} = preparedData; + + const xPlaceholder = placeholders.find((p) => p.id === PlaceholderId.X); + const x = xPlaceholder?.items[0]; + const xDataType = x ? idToDataType[x.guid] : null; + const xIsNumber = Boolean(xDataType && isNumericalDataType(xDataType)); + const xIsFloat = x ? xDataType === 'float' : null; + const xIsDate = Boolean(xDataType && isDateField({data_type: xDataType})); + const xPlaceholderSettings = xPlaceholder?.settings; + let xAxisMode = AxisMode.Discrete; + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + + if (x && xDataType) { + xAxisMode = getXAxisMode({config: chartConfig}) ?? AxisMode.Discrete; + } + + const isXDiscrete = xAxisMode === AxisMode.Discrete; + const x2 = placeholders[0].items[1]; + const yPlaceholder = placeholders.find((p) => p.id === PlaceholderId.Y); + const yFields = yPlaceholder?.items || []; + const colorItem = colors[0]; + const segmentsMap = getSegmentMap(args); + const isSegmentsExists = !_isEmpty(segmentsMap); + + // Here we manage the highcharts settings depending on the parameters + if (ChartEditor) { + const customConfig: any = {xAxis: {}, yAxis: {}}; + + if (yFields.length) { + const isYSectionHasFloatItem = yFields.some((y) => y.data_type === 'float'); + if (isYSectionHasFloatItem) { + ChartEditor.updateConfig({ + precision: MINIMUM_FRACTION_DIGITS, + }); + } + + Object.assign(customConfig, { + xAxis: {}, + plotOptions: {}, + axesFormatting: {xAxis: [], yAxis: []}, + exporting: { + csv: { + custom: { + categoryHeader: getFieldExportingOptions(x), + }, + columnHeaderFormatter: ChartkitHandlers.WizardExportColumnNamesFormatter, + }, + }, + }); + + addAxisFormatting(customConfig.axesFormatting.xAxis, visualizationId, xPlaceholder); + + const [layerYPlaceholder] = getYPlaceholders(args); + + const formatMode = layerYPlaceholder?.settings?.axisFormatMode; + if (formatMode && formatMode !== AxisLabelFormatMode.Auto && !isSegmentsExists) { + const formatting = getAxisChartkitFormatting(layerYPlaceholder, visualizationId); + if (formatting) { + customConfig.axesFormatting.yAxis.push(formatting); + } + } + + addAxisFormatter({ + axisConfig: customConfig.yAxis, + placeholder: layerYPlaceholder, + }); + + if (shouldUseGradientLegend(colorItem, colorsConfig, shared)) { + customConfig.colorAxis = getHighchartsColorAxis(graphs, colorsConfig); + customConfig.legend = { + title: { + text: getFakeTitleOrTitle(colorItem), + }, + enabled: isLegendEnabled(shared.extraSettings), + symbolWidth: null, + }; + customConfig.plotOptions = { + column: { + borderWidth: 1, + }, + }; + } + + if (xIsDate || xIsNumber) { + customConfig.xAxis.reversed = isXAxisReversed( + x, + sort, + visualizationId as WizardVisualizationId, + ); + + const isXDiscreteAndNotLogarithmic = + isXDiscrete && + xPlaceholderSettings && + xPlaceholderSettings.type !== 'logarithmic'; + const wizardXAxisFormatter = + isXDiscreteAndNotLogarithmic && xIsDate + ? ChartkitHandlers.WizardXAxisFormatter + : undefined; + + if (isXDiscreteAndNotLogarithmic) { + customConfig.xAxis.type = 'category'; + } else if (xIsDate) { + customConfig.xAxis.type = 'datetime'; + } + + addAxisFormatter({ + axisConfig: customConfig.xAxis, + placeholder: xPlaceholder, + otherwiseFormatter: wizardXAxisFormatter, + chartConfig, + }); + + if (x && getIsNavigatorEnabled(shared)) { + ChartEditor.updateConfig({ + // For old charts. In the new charts, we put the navigator settings in navigatorSettings and + // adding to the config in config.ts + highstock: { + base_series_name: shared.extraSettings?.navigatorSeriesName, + }, + }); + } + } + + if (getIsNavigatorEnabled(shared)) { + customConfig.xAxis.ordinal = isXDiscrete; + } + + if (shared.extraSettings) { + const {tooltipSum} = shared.extraSettings; + + if (typeof tooltipSum === 'undefined' || tooltipSum === 'on') { + customConfig.enableSum = true; + } + } + + if (x && !isMeasureNameOrValue(x)) { + const fieldTitle = getFakeTitleOrTitle(x); + customConfig.tooltipHeaderFormatter = fieldTitle; + } + + if (isSegmentsExists) { + customConfig.legend = { + enabled: + Boolean(colorItem || x2 || yFields.length > 1) && + isLegendEnabled(shared.extraSettings), + }; + + const {yAxisFormattings, yAxisSettings} = getSegmentsYAxis({ + segment: segments[0], + segmentsMap, + placeholders: { + y: yPlaceholder as ServerPlaceholder, + y2: undefined as unknown as ServerPlaceholder, + }, + visualizationId, + }); + customConfig.yAxis = yAxisSettings; + customConfig.axesFormatting.yAxis = yAxisFormattings; + } + } else if (xIsDate || xIsNumber) { + customConfig.xAxis.reversed = isXAxisReversed( + x, + sort, + visualizationId as WizardVisualizationId, + ); + + const wizardXAxisFormatter = + isXDiscrete && xIsDate ? ChartkitHandlers.WizardXAxisFormatter : undefined; + + if (xIsDate) { + if (!isXDiscrete) { + customConfig.xAxis.type = 'datetime'; + } + } else if (!xIsFloat) { + customConfig.xAxis.type = 'category'; + } + + addAxisFormatter({ + axisConfig: customConfig.xAxis, + placeholder: xPlaceholder, + otherwiseFormatter: wizardXAxisFormatter, + chartConfig, + }); + } + + ChartEditor.updateHighchartsConfig(customConfig); + + const shouldUseHtmlForLegend = isHtmlField(colorItem); + if (shouldUseHtmlForLegend) { + set(customConfig, 'legend.useHTML', true); + } + + const shouldUseHtmlForCategory = isHtmlField(x); + if (shouldUseHtmlForCategory) { + set(customConfig, 'xAxis.labels.useHTML', true); + } + } + + return preparedData; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-x/index.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-x/index.ts new file mode 100644 index 0000000..35f5127 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-x/index.ts @@ -0,0 +1,2 @@ +export * from './gravity-charts'; +export * from './highcharts'; diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-x/prepare-bar-x.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-x/prepare-bar-x.ts new file mode 100644 index 0000000..ced8e90 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-x/prepare-bar-x.ts @@ -0,0 +1,586 @@ +import isEmpty from 'lodash/isEmpty'; + +import type { + HighchartsSeriesCustomObject, + ServerField, + ServerPlaceholder, + WizardVisualizationId, + WrappedHTML, +} from '../../../../../../../shared'; +import { + AxisMode, + AxisNullsMode, + PlaceholderId, + getFakeTitleOrTitle, + getFormatOptions, + getXAxisMode, + isDateField, + isDimensionField, + isHtmlField, + isMarkdownField, + isMarkupField, + isMeasureField, + isMeasureValue, + isPercentVisualization, +} from '../../../../../../../shared'; +import {wrapHtml} from '../../../../../../../shared/utils/ui-sandbox'; +import {mapAndColorizeGraphsByPalette} from '../../utils/color-helpers'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import {PSEUDO} from '../../utils/constants'; +import { + chartKitFormatNumberWrapper, + collator, + getCategoryFormatter, + getLabelValue, + getSeriesTitleFormatter, + getTimezoneOffsettedTime, + isGradientMode, + isNumericalDataType, + numericCollator, +} from '../../utils/misc-helpers'; +import {addActionParamValue} from '../helpers/action-params'; +import {getSegmentMap} from '../helpers/segments'; +import { + getSegmentsIndexInOrder, + getSortedCategories, + getXAxisValue, + prepareLines, +} from '../line/helpers'; +import {colorizeByGradient} from '../line/helpers/color-helpers/colorizeByGradient'; +import {getSortedLineKeys} from '../line/helpers/getSortedLineKeys'; +import type {LineTemplate, LinesRecord, MergedYSectionItems} from '../line/types'; +import type {PrepareFunctionArgs} from '../types'; + +// eslint-disable-next-line complexity +export function prepareBarX(args: PrepareFunctionArgs) { + const { + placeholders, + resultData, + colors, + colorsConfig, + sort, + labels, + idToTitle, + idToDataType, + visualizationId, + datasets = [], + shared, + shapesConfig, + segments, + layerChartMeta, + usedColors, + ChartEditor, + disableDefaultSorting = false, + defaultColorPaletteId, + } = args; + const {data, order} = resultData; + const widgetConfig = ChartEditor.getWidgetConfig(); + const isActionParamsEnable = widgetConfig?.actionParams?.enable; + + const xPlaceholder = placeholders.find((p) => p.id === PlaceholderId.X); + const x: ServerField | undefined = xPlaceholder?.items[0]; + const xDataType = x ? idToDataType[x.guid] : null; + const xIsNumber = Boolean(xDataType && isNumericalDataType(xDataType)); + const xIsPseudo = Boolean(x && x.type === 'PSEUDO'); + const xIsDate = Boolean(xDataType && isDateField({data_type: xDataType})); + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + const xAxisMode = getXAxisMode({config: chartConfig}); + + const x2 = placeholders[0].items[1]; + const x2DataType = x2 ? idToDataType[x2.guid] : null; + const x2IsNumber = Boolean(x2DataType && isNumericalDataType(x2DataType)); + const x2IsDate = Boolean(x2DataType && isDateField({data_type: x2DataType})); + const x2IsPseudo = Boolean(x2 && x2.type === PSEUDO); + const isHtmlX = isHtmlField(x); + + const yPlaceholder: ServerPlaceholder = placeholders[1]; + + const ySectionItems = yPlaceholder.items; + const mergedYSections = [...ySectionItems]; + + const sortItems = sort; + const sortItem = sortItems?.[0]; + const isSortItemExists = Boolean(sort && sort.length); + const sortXItem = sort.find((s) => x && s.guid === x.guid); + const isSortingXAxis = Boolean(isSortItemExists && sortXItem); + const isSortingYAxis = + isSortItemExists && ySectionItems.find((item) => item?.guid === sort[0].guid); + const isSortCategoriesAvailable = layerChartMeta + ? Boolean(layerChartMeta.isCategoriesSortAvailable) + : true; + + const colorItem = colors[0]; + const colorFieldDataType = colorItem ? idToDataType[colorItem.guid] : null; + const isHtmlColor = isHtmlField(colorItem); + + const gradientMode = + colorItem && isGradientMode({colorField: colorItem, colorFieldDataType, colorsConfig}); + + const labelItem = labels?.[0]; + const labelsLength = labels && labels.length; + const isMarkdownLabel = isMarkdownField(labelItem); + const isMarkupLabel = isMarkupField(labelItem); + const isHtmlLabel = isHtmlField(labelItem); + + const segmentField = segments[0]; + const segmentIndexInOrder = getSegmentsIndexInOrder(order, segmentField, idToTitle); + const segmentsMap = getSegmentMap(args); + const isSegmentsExists = !isEmpty(segmentsMap); + const isHtmlSegment = isHtmlField(segmentField); + const segmentTitleFormatter = getSeriesTitleFormatter({fields: [segmentField]}); + + const isShapeItemExist = false; + const isColorItemExist = Boolean(colorItem && colorItem.type !== 'PSEUDO'); + + const isColorizeByMeasure = isMeasureField(colorItem); + const colorMode = colorsConfig.colorMode; + const isColorizeByMeasureValue = isMeasureValue(colorItem); + + /* + Specially reduced LinesRecord object. It has only one key that is equal to the colorItem header. + Needed at the moment when the user sorts the graph according to the same indicator by which it is colored. + For this sort, you need to get values from orders and match them to categories. + */ + const measureColorSortLine: Record> = {}; + const isSortByMeasureColor = sortItem && colorItem && sortItem.guid === colorItem.guid; + + if (isSortByMeasureColor) { + measureColorSortLine[getFakeTitleOrTitle(colorItem)] = {data: {}}; + } + + const nullsY1 = placeholders?.[1]?.settings?.nulls; + + const categoriesMap = new Map(); + const seriesNameFormatter = getSeriesTitleFormatter({fields: [colorItem]}); + + if (mergedYSections.length) { + let categories: (string | number)[] = []; + const categories2: (string | number)[] = []; + + const lines1: LinesRecord = {}; + const lines2: LinesRecord = {}; + + const labelsValues: Record> = {}; + + const mergedYSectionItems: MergedYSectionItems[] = ySectionItems.map( + (field): MergedYSectionItems => { + return { + field, + lines: lines1, + nullsSetting: nullsY1, + isFirstSection: true, + labelsValues, + }; + }, + ); + + data.forEach((values) => { + let xValue: ReturnType; + if (x) { + xValue = getXAxisValue({ + x, + ys1: ySectionItems, + order, + values, + idToTitle, + categories, + xIsDate, + xIsNumber, + xDataType: xDataType!, + xIsPseudo: Boolean(xIsPseudo), + categoriesMap, + }); + if ((xValue === null || xValue === undefined) && !xIsPseudo) { + return; + } + } else if (ySectionItems.length > 1) { + const value = (xValue = 'Measure Names'); + + if (!categoriesMap.has(value)) { + categoriesMap.set(value, true); + categories.push(value); + } + } else { + const ySectionItem = ySectionItems[0]; + const value = (xValue = ySectionItem.fakeTitle || idToTitle[ySectionItem.guid]); + + if (!categoriesMap.has(value)) { + categoriesMap.set(value, true); + categories.push(value); + } + } + + let x2Value: ReturnType; + if (x2) { + x2Value = getXAxisValue({ + x: x2, + ys1: ySectionItems, + categories: categories2, + idToTitle, + order, + values, + xIsNumber: x2IsNumber, + xDataType: x2DataType!, + xIsDate: x2IsDate, + xIsPseudo: x2IsPseudo, + }); + if ((x2Value === null || x2Value === undefined) && !x2IsPseudo) { + return; + } + } + + prepareLines({ + ySectionItems: mergedYSectionItems, + idToTitle, + idToDataType, + order, + values, + isMultiDatasets: datasets.length > 1, + isColorizeByMeasureValue, + isColorizeByMeasure, + isSegmentsExists, + x2Field: x2, + colorItem, + rawXValue: xValue, + rawX2Value: x2Value, + x2IsDate, + isSortByMeasureColor, + measureColorSortLine, + isShapeItemExist, + isColorItemExist, + isMultiAxis: false, + xField: x, + shapesConfig, + labelItem, + segmentIndexInOrder, + layers: shared.visualization?.layers, + colorMode, + convertMarkupToString: false, + }); + }); + + let lineKeys1 = Object.keys(lines1); + let lineKeys2 = []; + + if (xIsDate && !disableDefaultSorting) { + (categories as number[]).sort(numericCollator); + } + + const lines = [lines1, lines2]; + const isSortBySegments = Boolean( + isSortItemExists && segmentField && sortItem.guid === segmentField.guid, + ); + const isSortableXAxis = !isPercentVisualization(visualizationId); + + if (!disableDefaultSorting) { + categories = getSortedCategories({ + lines, + colorItem, + categories, + ySectionItems, + isSortWithYSectionItem: Boolean(ySectionItems.length && isSortableXAxis), + sortItem: sortItems[0], + isSortAvailable: isSortItemExists && isSortCategoriesAvailable, + isXNumber: xIsNumber, + measureColorSortLine, + isSegmentsExists, + isSortBySegments, + }); + } + + const sortedLineKeys = getSortedLineKeys({ + colorItem, + lines, + isSortAvailable: isSortItemExists, + isSortBySegments, + sortItem, + yField: ySectionItems[0], + visualizationId: visualizationId as WizardVisualizationId, + categories, + }); + + lineKeys1 = sortedLineKeys[0] || lineKeys1; + lineKeys2 = sortedLineKeys[1] || []; + + const graphs: any[] = []; + const uniqueTitles: string[] = []; + + const isXDiscrete = xAxisMode === AxisMode.Discrete; + const isSortNumberTypeXAxisByMeasure = + isSortCategoriesAvailable && + isSortItemExists && + xIsNumber && + !isSortingXAxis && + (isSortingYAxis || isSortByMeasureColor); + + const isXCategoryAxis = + isXDiscrete || + xDataType === 'string' || + xIsPseudo || + isSortNumberTypeXAxisByMeasure || + disableDefaultSorting; + + const orderedLineKeys = [lineKeys1, lineKeys2]; + + orderedLineKeys.forEach((lineKeys, lineKeysIndex) => { + lineKeys.forEach((lineKey) => { + let line: LineTemplate; + let nulls: any; + if (lineKeysIndex === 0) { + line = lines1[lineKey]; + nulls = nullsY1; + } else { + line = lines2[lineKey]; + nulls = undefined; + } + + const innerLabels = labelsValues[lineKey]; + + const customSeriesData: HighchartsSeriesCustomObject = {}; + + const graph: any = { + id: line.id, + title: seriesNameFormatter(line.title || 'Null'), + tooltip: line.tooltip, + dataLabels: { + ...line.dataLabels, + useHTML: isMarkdownLabel || isMarkupLabel || isHtmlLabel, + }, + data: categories + .map((category, i) => { + const lineData = line.data[category]; + const colorValue = lineData?.colorValue; + let value = lineData?.value; + + if (typeof value === 'undefined' && nulls === AxisNullsMode.AsZero) { + value = 0; + } + + // We can skip a point only if we put x in each point instead of categories + if (!isXCategoryAxis && typeof value === 'undefined') { + return null; + } + + const y = typeof value === 'number' ? Number(value) : null; + + const dataLabels = { + enabled: Boolean(labelsLength && labelItem), + }; + + const point: any = { + x: i, + y, + colorValue, + dataLabels, + }; + + if (line.segmentNameKey) { + const currentSegment = segmentsMap[line.segmentNameKey]; + let tooltipPointName: string | WrappedHTML = + `${currentSegment.title}: ${line.title}`; + if (isHtmlSegment) { + tooltipPointName = wrapHtml(tooltipPointName); + } + point.custom = { + tooltipPointName, + }; + } + + if (!isXCategoryAxis) { + const pointX = category; + + point.x = pointX; + + if (x && isNumericalDataType(x.data_type)) { + const formatting = getFormatOptions(x); + if (!isEmpty(formatting)) { + point.xFormatted = chartKitFormatNumberWrapper( + Number(pointX), + { + lang: 'ru', + ...formatting, + }, + ); + } + } + } + + point.label = getLabelValue(innerLabels?.[category], { + isMarkdownLabel, + isMarkupLabel, + isHtmlLabel, + }); + + if (isActionParamsEnable) { + const actionParams: Record = {}; + addActionParamValue(actionParams, x, category); + + point.custom = { + ...point.custom, + actionParams, + }; + } + + return point; + }) + .filter((point) => point !== null), + legendTitle: line.legendTitle || line.title || 'Null', + formattedName: colorItem ? undefined : line.formattedName, + drillDownFilterValue: line.drillDownFilterValue, + colorKey: line.colorKey, + colorGuid: colorItem?.guid || null, + connectNulls: nulls === AxisNullsMode.Connect, + measureFieldTitle: line.fieldTitle, + }; + + if (line.pointConflict) { + graph.pointConflict = true; + } + + if (line.segmentNameKey) { + const currentSegment = segmentsMap[line.segmentNameKey]; + graph.yAxis = currentSegment.index; + + customSeriesData.segmentTitle = segmentTitleFormatter(currentSegment.title); + } else if (lineKeysIndex === 0 || ySectionItems.length === 0) { + graph.yAxis = 0; + } else { + graph.yAxis = 1; + } + + if (uniqueTitles.indexOf(graph.legendTitle) === -1) { + uniqueTitles.push(graph.legendTitle); + } else { + graph.showInLegend = false; + } + + if (x2) { + graph.stack = line.stack; + } + + graph.colorValue = line.colorValue; + graph.shapeValue = line.shapeValue; + graph.colorShapeValue = line.colorShapeValue; + + graph.custom = customSeriesData; + + if (isActionParamsEnable) { + const actionParams: Record = {}; + + if (x2) { + actionParams[x2.guid] = line.stack; + } + + if (isDimensionField(colorItem)) { + actionParams[colorItem.guid] = line.colorValue; + } + + graph.custom = { + ...graph.custom, + actionParams, + }; + } + + graphs.push(graph); + }); + }); + + if (gradientMode) { + colorizeByGradient(visualizationId as WizardVisualizationId, { + graphs, + colorsConfig, + }); + } else { + mapAndColorizeGraphsByPalette({ + graphs, + colorsConfig, + isShapesItemExists: isShapeItemExist, + isColorsItemExists: isColorItemExist, + isSegmentsExists: isSegmentsExists, + usedColors, + colorField: colorItem, + defaultColorPaletteId, + }); + } + + if (isMarkdownLabel) { + ChartEditor.updateConfig({useMarkdown: true}); + } + + if (isMarkupLabel) { + ChartEditor.updateConfig({useMarkup: true}); + } + + if (isHtmlLabel || isHtmlX || isHtmlColor || isHtmlSegment) { + ChartEditor.updateConfig({useHtml: true}); + } + + if (x && isXCategoryAxis) { + const categoriesFormatter = getCategoryFormatter({ + field: {...x, data_type: xDataType ?? x.data_type}, + }); + + return { + graphs, + categories: categories.map(categoriesFormatter), + }; + } else { + return {graphs}; + } + } else { + // If no fields are selected for the Y axis, we draw only the X axis. + + const categories: (string | number)[] = []; + + data.forEach((values) => { + const value = values[0]; + + if (value === null) { + return; + } + + let xValue; + if (xIsDate) { + const time = new Date(value); + + if (xDataType === 'genericdatetime') { + time.setTime(getTimezoneOffsettedTime(time)); + } + + xValue = time.getTime(); + } else if (xIsNumber) { + xValue = Number(value); + } else { + xValue = value; + } + + if (!categoriesMap.has(xValue)) { + categoriesMap.set(xValue, true); + categories.push(xValue); + } + }); + + // Default sorting + if ((!isSortItemExists || !isSortCategoriesAvailable) && !disableDefaultSorting) { + if (xIsNumber) { + (categories as number[]).sort(numericCollator); + } else { + (categories as string[]).sort(collator.compare); + } + } + + // Generating data + const graphs = [ + { + data: categories.map(() => null), + }, + ]; + + // If there are dates on the X axis, then we pass them as dates + if (xIsDate && xAxisMode !== AxisMode.Discrete) { + return {graphs, categories_ms: categories}; + } else { + return {graphs, categories}; + } + } +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-x/types.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-x/types.ts new file mode 100644 index 0000000..8dd8e9e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-x/types.ts @@ -0,0 +1,16 @@ +import type {BarXSeries, BarXSeriesData} from '@gravity-ui/chartkit/gravity-charts'; + +import type {SeriesExportSettings, WrappedMarkdown} from '../../../../../../../shared'; +import type {WrappedHTML} from '../../../../../../../shared/types/charts'; + +export type ExtendedBarXSeriesData = Omit & { + x?: BarXSeriesData['x'] | WrappedHTML | WrappedMarkdown; +}; + +export type ExtendedBarXSeries = Omit & { + custom?: { + exportSettings?: SeriesExportSettings; + colorValue?: string; + }; + data: ExtendedBarXSeriesData[]; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-y/__tests__/bar-y.test.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-y/__tests__/bar-y.test.ts new file mode 100644 index 0000000..29709c2 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-y/__tests__/bar-y.test.ts @@ -0,0 +1,124 @@ +import merge from 'lodash/merge'; +import pick from 'lodash/pick'; + +import type {DATASET_FIELD_TYPES} from '../../../../../../../../shared'; +import {AxisMode, WizardVisualizationId} from '../../../../../../../../shared'; +import {DateTimeField, EmptyPrepapreArgs, IntegerField} from '../../__tests__/common.mock'; +import type {PrepareFunctionArgs} from '../../types'; +import {prepareBarYData} from '../prepare-bar-y-data'; + +const DimensionField = {...IntegerField, type: 'DIMENSION', guid: 'DimensionField_guid'}; + +describe('prepareBarYData', () => { + jest.spyOn(Date.prototype, 'getTimezoneOffset').mockImplementation(() => 0); + + const args: PrepareFunctionArgs = { + ...EmptyPrepapreArgs, + // @ts-ignore + shared: {visualization: {id: WizardVisualizationId.Bar}}, + idToTitle: { + [IntegerField.guid]: IntegerField.title, + [DateTimeField.guid]: DateTimeField.title, + [DimensionField.guid]: DimensionField.title, + }, + idToDataType: { + [IntegerField.guid]: IntegerField.data_type, + [DateTimeField.guid]: DateTimeField.data_type, + [DimensionField.guid]: DimensionField.data_type, + } as Record, + }; + + test('X is empty, Y has integer field -> categories contain values from the Y field', () => { + const result = prepareBarYData({ + ...args, + placeholders: [ + { + id: 'y', + items: [IntegerField], + }, + { + id: 'x', + items: [], + }, + ], + resultData: { + data: [['11'], ['222']], + order: [IntegerField], + totals: [], + }, + }); + expect(result.categories).toEqual(['11', '222']); + }); + + test('X is empty, Y has datetime field -> categories_ms contains values from the Y field', () => { + const placeholders = [ + { + id: 'y', + items: [DateTimeField], + settings: { + axisModeMap: { + [DateTimeField.guid]: AxisMode.Continuous, + }, + }, + }, + { + id: 'x', + items: [], + }, + ]; + const result = prepareBarYData({ + ...args, + placeholders, + shared: merge(args.shared, { + visualization: { + placeholders, + }, + }), + resultData: { + data: [['2023-09-17T00:00:00.000Z'], ['2023-09-18T00:00:00.000Z']], + order: [DateTimeField], + totals: [], + }, + }); + expect(result.categories_ms).toEqual([1694908800000, 1694995200000]); + }); + + test('X is not empty, Y has datetime field -> categories and categories_ms are undefined, result contain one graph', () => { + const result = prepareBarYData({ + ...args, + placeholders: [ + { + id: 'y', + items: [DateTimeField], + settings: { + axisModeMap: { + [DateTimeField.guid]: AxisMode.Continuous, + }, + }, + }, + { + id: 'x', + items: [DimensionField], + }, + ], + resultData: { + data: [ + ['2023-09-17T00:00:00.000Z', '100'], + ['2023-09-18T00:00:00.000Z', '200'], + ], + order: [DateTimeField, DimensionField], + totals: [], + }, + }); + expect(result.categories).not.toBeDefined(); + expect(result.categories_ms).not.toBeDefined(); + expect(result.graphs.map((g) => pick(g, 'data'))).toEqual([ + { + data: [ + {y: 100, x: 1694908800000, label: ''}, + {y: 200, x: 1694995200000, label: ''}, + ], + }, + ]); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-y/gravity-charts.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-y/gravity-charts.ts new file mode 100644 index 0000000..7783a73 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-y/gravity-charts.ts @@ -0,0 +1,281 @@ +import type {BarYSeries, BarYSeriesData, ChartData} from '@gravity-ui/chartkit/gravity-charts'; +import merge from 'lodash/merge'; + +import type {SeriesExportSettings, ServerField} from '../../../../../../../shared'; +import { + LabelsPositions, + PERCENT_VISUALIZATIONS, + PlaceholderId, + getFakeTitleOrTitle, + isDateField, + isHtmlField, + isMarkdownField, + isMarkupField, +} from '../../../../../../../shared'; +import type {ExtendedChartData} from '../../../../../../../shared/types/chartkit'; +import {getBaseChartConfig, getTotalsPrecisionFromSeriesTooltips} from '../../gravity-charts/utils'; +import {getFieldFormatOptions} from '../../gravity-charts/utils/format'; +import {colorizeByColorValues} from '../../utils/color-helpers'; +import {getExportColumnSettings} from '../../utils/export-helpers'; +import {getAxisFormatting} from '../helpers/axis'; +import {getLegendColorScale, shouldUseGradientLegend} from '../helpers/legend'; +import type {PrepareFunctionArgs} from '../types'; +import { + mapChartkitFormatSettingsToGravityChartValueFormat, + mapToGravityChartValueFormat, +} from '../utils'; + +import {prepareBarYData} from './prepare-bar-y-data'; + +type BarYPoint = {x: number; y: number} & Record; + +// eslint-disable-next-line complexity +export function prepareGravityChartsBarY(args: PrepareFunctionArgs): ChartData { + const {shared, visualizationId, colors, colorsConfig, labels, placeholders} = args; + const {graphs, categories} = prepareBarYData(args); + const hasCategories = Boolean(categories?.length); + const xPlaceholder = placeholders.find((p) => p.id === PlaceholderId.X); + const xField: ServerField | undefined = xPlaceholder?.items?.[0]; + const isLogXAxis = xPlaceholder?.settings?.type === 'logarithmic'; + const yPlaceholder = placeholders.find((p) => p.id === PlaceholderId.Y); + const yField: ServerField | undefined = yPlaceholder?.items?.[0]; + const labelField = labels?.[0]; + + const exportSettings: SeriesExportSettings = { + columns: [ + getExportColumnSettings({path: hasCategories ? 'category' : 'y', field: yField}), + getExportColumnSettings({path: 'x', field: xField}), + ], + }; + + const colorItem = colors[0]; + if (colorItem) { + exportSettings.columns.push( + getExportColumnSettings({path: 'series.custom.colorValue', field: colorItem}), + ); + } + + const shouldUseHtmlForLabels = + isMarkupField(labelField) || isHtmlField(labelField) || isMarkdownField(labelField); + + const shouldUsePercentStacking = PERCENT_VISUALIZATIONS.has(visualizationId); + const dataLabelsInside = + shouldUsePercentStacking || + shared.extraSettings?.labelsPosition !== LabelsPositions.Outside; + + let gradientColorMap: Record = {}; + const shouldSetColorByValues = graphs.some((s) => + s.data.some((d: any) => !d.color && d.colorValue), + ); + if (shouldSetColorByValues) { + const colorValues: number[] = []; + graphs.forEach((s) => { + s.data.forEach((d: any) => { + const colorValue = Number(d.colorValue); + if (Number.isFinite(colorValue)) { + colorValues.push(colorValue); + } + }); + }); + + const gradientColors = colorizeByColorValues({colorsConfig, colorValues}); + + gradientColorMap = gradientColors.reduce( + (acc, color, index) => { + acc[String(colorValues[index])] = color; + + return acc; + }, + {} as Record, + ); + } + + const series = graphs.reduce((items, graph) => { + const labelFormatting = graph.dataLabels + ? mapToGravityChartValueFormat({field: labelField, formatSettings: graph.dataLabels}) + : undefined; + const shouldUsePercentageAsLabel = + labelFormatting && + 'labelMode' in labelFormatting && + labelFormatting?.labelMode === 'percent'; + + const seriesData = graph.data.reduce((acc: BarYSeriesData[], d: BarYPoint) => { + const {x, y, label: originalLabel, ...other} = d; + const xValue: number | null = y; + + if (isLogXAxis && xValue <= 0) { + return acc; + } + + const total = + graphs.reduce( + (sum, g) => sum + (g.data.find((point: BarYPoint) => point.x === x)?.y ?? 0), + 0, + ) ?? 0; + const percentage = (d.y / total) * 100; + const label = shouldUsePercentageAsLabel ? percentage : (originalLabel as string); + + let color = d.color; + if (!color && typeof d.colorValue === 'number') { + color = gradientColorMap[String(d.colorValue)] ?? undefined; + } + + acc.push({ + ...other, + y: x, + x: xValue, + label, + total, + percentage, + color, + } as BarYSeriesData); + return acc; + }, []); + + if (!seriesData.length) { + return items; + } + + items.push({ + ...graph, + type: 'bar-y', + stackId: graph.stack, + stacking: shouldUsePercentStacking ? 'percent' : 'normal', + name: graph.title, + data: seriesData, + dataLabels: { + enabled: graph.dataLabels?.enabled, + inside: dataLabelsInside, + html: shouldUseHtmlForLabels, + format: labelFormatting, + }, + tooltip: graph.tooltip?.chartKitFormatting + ? { + valueFormat: mapChartkitFormatSettingsToGravityChartValueFormat({ + chartkitFormatSettings: graph.tooltip, + }), + } + : undefined, + custom: { + ...graph.custom, + colorValue: graph.colorValue, + drillDownFilterValue: graph.drillDownFilterValue, + exportSettings, + }, + } as BarYSeries); + + return items; + }, [] as BarYSeries[]); + + const xAxisLabelNumberFormat = xPlaceholder + ? getAxisFormatting({ + placeholder: xPlaceholder, + visualizationId, + }) + : undefined; + const xAxisType = isLogXAxis ? 'logarithmic' : 'linear'; + + const config: ExtendedChartData = { + series: { + data: series, + options: { + 'bar-y': { + stackGap: 0, + borderWidth: 1, + }, + }, + }, + xAxis: { + type: xAxisType, + labels: { + numberFormat: xAxisLabelNumberFormat ?? undefined, + }, + }, + custom: { + tooltip: { + headerLabel: + isDateField(yField) && !hasCategories ? undefined : getFakeTitleOrTitle(yField), + }, + }, + }; + + if (config.series.data.length && shouldUseGradientLegend(colorItem, colorsConfig, shared)) { + const points = graphs + .map((graph) => (graph.data ?? []).map((d: BarYPoint) => ({colorValue: d.colorValue}))) + .flat(2); + + const colorScale = getLegendColorScale({ + colorsConfig, + points, + }); + + config.legend = { + enabled: shared.extraSettings?.legendMode !== 'hide', + type: 'continuous', + title: {text: getFakeTitleOrTitle(colorItem), style: {fontWeight: '500'}}, + colorScale, + }; + } else { + const shouldUseHtmlForLegend = isHtmlField(colorItem); + config.legend = {html: shouldUseHtmlForLegend}; + + if (graphs.length <= 1) { + config.legend.enabled = false; + } + } + + if (xField) { + const valueFormat = getFieldFormatOptions({field: xField}); + config.tooltip = { + valueFormat, + totals: { + valueFormat: + valueFormat?.type === 'number' && valueFormat.format + ? valueFormat + : { + type: 'number', + precision: getTotalsPrecisionFromSeriesTooltips(series), + }, + }, + }; + } + + if (hasCategories) { + config.yAxis = [ + { + type: 'category', + categories: categories as string[], + order: 'reverse', + labels: { + enabled: yPlaceholder?.settings?.hideLabels !== 'yes', + html: isHtmlField(yField) || isMarkdownField(yField) || isMarkupField(yField), + maxWidth: '33%', + padding: 0, + }, + }, + ]; + } else { + const axisLabelNumberFormat = yPlaceholder + ? getAxisFormatting({ + placeholder: yPlaceholder, + visualizationId, + }) + : undefined; + + config.yAxis = [ + { + labels: { + enabled: yPlaceholder?.settings?.hideLabels !== 'yes', + numberFormat: axisLabelNumberFormat ?? undefined, + }, + maxPadding: 0, + order: 'reverse', + }, + ]; + } + + return merge( + getBaseChartConfig({shared, visualization: {placeholders, id: visualizationId}}), + config, + ); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-y/highcharts.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-y/highcharts.ts new file mode 100644 index 0000000..83b5fa4 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-y/highcharts.ts @@ -0,0 +1,160 @@ +import set from 'lodash/set'; + +import type {ServerField, WizardVisualizationId} from '../../../../../../../shared'; +import { + AxisMode, + ChartkitHandlers, + MINIMUM_FRACTION_DIGITS, + PlaceholderId, + getAxisMode, + getFakeTitleOrTitle, + getXAxisMode, + isDateField, + isFloatField, + isHtmlField, + isMeasureNameOrValue, +} from '../../../../../../../shared'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import {getFieldExportingOptions} from '../../utils/export-helpers'; +import {isLegendEnabled} from '../../utils/misc-helpers'; +import {addAxisFormatter, addAxisFormatting, getAxisType} from '../helpers/axis'; +import {getHighchartsColorAxis, isXAxisReversed} from '../helpers/highcharts'; +import {getYPlaceholders} from '../helpers/layers'; +import {shouldUseGradientLegend} from '../helpers/legend'; +import type {PrepareFunctionArgs} from '../types'; + +import {prepareBarYData} from './prepare-bar-y-data'; + +// eslint-disable-next-line complexity +function getHighchartsConfig(args: PrepareFunctionArgs & {graphs: any[]}) { + const { + placeholders, + colors, + colorsConfig, + sort, + visualizationId, + shared, + graphs, + idToDataType, + } = args; + // for some reason, the vertical axis for the horizontal bar is considered the X axis + const xPlaceholder = placeholders.find((p) => p.id === PlaceholderId.Y); + const yPlaceholder = placeholders.find((p) => p.id === PlaceholderId.X); + const xPlaceholderSettings = xPlaceholder?.settings; + const x: ServerField | undefined = xPlaceholder?.items[0]; + const isXDiscrete = getAxisMode(xPlaceholderSettings, x?.guid) === AxisMode.Discrete; + + const ySectionItems = yPlaceholder?.items || []; + const colorItem = colors[0]; + + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + const xAxisMode = getXAxisMode({config: chartConfig}) ?? AxisMode.Discrete; + + const wizardXAxisFormatter = + isDateField(x) && isXDiscrete ? ChartkitHandlers.WizardXAxisFormatter : undefined; + + const customConfig: any = { + xAxis: { + type: getAxisType({ + field: x, + settings: xPlaceholder?.settings, + axisMode: xAxisMode, + }), + reversed: isXAxisReversed(x, sort, visualizationId as WizardVisualizationId), + labels: { + useHTML: isHtmlField(x), + }, + }, + yAxis: {}, + axesFormatting: { + yAxis: [], + xAxis: [], + }, + exporting: { + csv: { + custom: { + categoryHeader: getFieldExportingOptions(x), + }, + columnHeaderFormatter: ChartkitHandlers.WizardExportColumnNamesFormatter, + }, + }, + }; + + if (ySectionItems.length) { + if (shouldUseGradientLegend(colorItem, colorsConfig, shared)) { + customConfig.colorAxis = getHighchartsColorAxis(graphs, colorsConfig); + customConfig.legend = { + title: { + text: getFakeTitleOrTitle(colorItem), + }, + enabled: isLegendEnabled(shared.extraSettings), + symbolWidth: null, + }; + } + + if (shared.extraSettings) { + const {tooltipSum} = shared.extraSettings; + + if (typeof tooltipSum === 'undefined' || tooltipSum === 'on') { + customConfig.enableSum = true; + } + } + + if (x && !isMeasureNameOrValue(x)) { + customConfig.tooltipHeaderFormatter = getFakeTitleOrTitle(x); + } + + const [layerYPlaceholder] = getYPlaceholders(args); + + addAxisFormatting(customConfig.axesFormatting.yAxis, visualizationId, yPlaceholder); + addAxisFormatting(customConfig.axesFormatting.xAxis, visualizationId, layerYPlaceholder); + addAxisFormatter({ + axisConfig: customConfig.xAxis, + placeholder: layerYPlaceholder, + otherwiseFormatter: wizardXAxisFormatter, + chartConfig, + }); + addAxisFormatter({ + axisConfig: customConfig.yAxis, + placeholder: yPlaceholder, + }); + } + + const shouldUseHtmlForLegend = isHtmlField(colorItem); + if (shouldUseHtmlForLegend) { + set(customConfig, 'legend.useHTML', true); + } + + return customConfig; +} + +function getConfig(args: PrepareFunctionArgs) { + const {placeholders} = args; + const config: any = {}; + + const fields = placeholders.find((p) => p.id === PlaceholderId.X)?.items || []; + if (fields.some(isFloatField)) { + config.precision = MINIMUM_FRACTION_DIGITS; + } + + return config; +} + +export function prepareHighchartsBarY(args: PrepareFunctionArgs) { + const {ChartEditor} = args; + + const preparedData = prepareBarYData(args); + + if (ChartEditor) { + const highchartsConfig = getHighchartsConfig({ + ...args, + graphs: preparedData.graphs, + }); + ChartEditor.updateHighchartsConfig(highchartsConfig); + + const config = getConfig(args); + ChartEditor.updateConfig(config); + } + + return preparedData; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-y/index.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-y/index.ts new file mode 100644 index 0000000..f1065e7 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-y/index.ts @@ -0,0 +1,2 @@ +export {prepareHighchartsBarY} from './highcharts'; +export * from './gravity-charts'; diff --git a/src/server/modes/charts/plugins/datalens/preparers/bar-y/prepare-bar-y-data.ts b/src/server/modes/charts/plugins/datalens/preparers/bar-y/prepare-bar-y-data.ts new file mode 100644 index 0000000..4329605 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/bar-y/prepare-bar-y-data.ts @@ -0,0 +1,543 @@ +import isEmpty from 'lodash/isEmpty'; + +import type { + HighchartsSeriesCustomObject, + ServerField, + ServerPlaceholder, + WizardVisualizationId, +} from '../../../../../../../shared'; +import { + AxisMode, + AxisNullsMode, + getFakeTitleOrTitle, + getFormatOptions, + getXAxisMode, + isDateField, + isHtmlField, + isMarkdownField, + isMarkupField, + isMeasureField, + isMeasureValue, + isNumberField, + isPercentVisualization, + isPseudoField, + isVisualizationWithSeveralFieldsXPlaceholder, +} from '../../../../../../../shared'; +import {mapAndColorizeGraphsByPalette} from '../../utils/color-helpers'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import { + chartKitFormatNumberWrapper, + collator, + getCategoryFormatter, + getLabelValue, + getSeriesTitleFormatter, + getTimezoneOffsettedTime, + isGradientMode, + numericCollator, +} from '../../utils/misc-helpers'; +import {addActionParamValue} from '../helpers/action-params'; +import {getSortedCategories, getXAxisValue, prepareLines} from '../line/helpers'; +import {colorizeByGradient} from '../line/helpers/color-helpers/colorizeByGradient'; +import {getSortedLineKeys} from '../line/helpers/getSortedLineKeys'; +import type {LineTemplate, LinesRecord, MergedYSectionItems} from '../line/types'; +import type {PrepareFunctionArgs} from '../types'; + +// eslint-disable-next-line complexity +export function prepareBarYData({ + ChartEditor, + placeholders, + resultData, + colors, + colorsConfig, + sort, + labels, + idToTitle, + idToDataType, + visualizationId, + datasets = [], + shared, + shapesConfig, + layerChartMeta, + usedColors, + disableDefaultSorting = false, + defaultColorPaletteId, +}: PrepareFunctionArgs) { + const {data, order} = resultData; + const widgetConfig = ChartEditor.getWidgetConfig(); + const isActionParamsEnable = widgetConfig?.actionParams?.enable; + + const x: ServerField | undefined = placeholders[0].items[0]; + const xDataType = x ? idToDataType[x.guid] : null; + const xIsNumber = isNumberField(x); + const xIsPseudo = isPseudoField(x); + const xIsDate = isDateField(x); + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + const xAxisMode = getXAxisMode({config: chartConfig}); + + const x2 = isVisualizationWithSeveralFieldsXPlaceholder(visualizationId) + ? placeholders[0].items[1] + : undefined; + const x2DataType = x2 ? idToDataType[x2.guid] : null; + const x2IsNumber = isNumberField(x2); + const x2IsDate = isDateField(x2); + const x2IsPseudo = isPseudoField(x2); + const yPlaceholder: ServerPlaceholder = placeholders[1]; + const ySectionItems = yPlaceholder.items; + + const sortItems = sort; + const sortItem = sortItems?.[0]; + const isSortItemExists = Boolean(sort && sort.length); + const sortXItem = sort.find((s) => x && s.guid === x.guid); + const isSortingXAxis = Boolean(isSortItemExists && sortXItem); + const isSortingYAxis = + isSortItemExists && ySectionItems.find((item) => item?.guid === sort[0].guid); + const isSortCategoriesAvailable = layerChartMeta + ? Boolean(layerChartMeta.isCategoriesSortAvailable) + : true; + + const colorItem = colors[0]; + const colorFieldDataType = colorItem ? idToDataType[colorItem.guid] : null; + const gradientMode = + colorItem && + colorFieldDataType && + isGradientMode({colorField: colorItem, colorFieldDataType, colorsConfig}); + + const labelItem = labels?.[0]; + const isMarkdownLabel = isMarkdownField(labelItem); + const isMarkupLabel = isMarkupField(labelItem); + const isHtmlLabel = isHtmlField(labelItem); + + const isColorItemExist = Boolean(colorItem && colorItem.type !== 'PSEUDO'); + const isColorizeByMeasure = isMeasureField(colorItem); + const colorMode = colorsConfig.colorMode; + const isColorizeByMeasureValue = isMeasureValue(colorItem); + + /* + Specially reduced LinesRecord object. It has only one key that is equal to the colorItem header. + Needed at the moment when the user sorts the graph according to the same indicator by which it is colored. + For this sort, you need to get values from orders and match them to categories. + */ + const measureColorSortLine: Record> = {}; + const isSortByMeasureColor = sortItem && colorItem && sortItem.guid === colorItem.guid; + + if (isSortByMeasureColor) { + measureColorSortLine[getFakeTitleOrTitle(colorItem)] = {data: {}}; + } + + const nullsY1 = placeholders?.[1]?.settings?.nulls; + + const categoriesMap = new Map(); + const categoryField = x ? {...x, data_type: xDataType ?? x?.data_type} : undefined; + const categoriesFormatter = getCategoryFormatter({ + field: categoryField, + }); + + const seriesNameFormatter = getSeriesTitleFormatter({fields: [colorItem]}); + + if (ySectionItems.length) { + let categories: (string | number)[] = []; + const categories2: (string | number)[] = []; + + const lines1: LinesRecord = {}; + const lines2: LinesRecord = {}; + + const labelsValues: Record> = {}; + + const mergedYSectionItems: MergedYSectionItems[] = ySectionItems.map( + (field) => { + return { + field, + lines: lines1, + nullsSetting: nullsY1, + isFirstSection: true, + labelsValues, + }; + }, + ); + + data.forEach((values) => { + let xValue: ReturnType; + if (x) { + xValue = getXAxisValue({ + x, + ys1: ySectionItems, + order, + values, + idToTitle, + categories, + xIsDate, + xIsNumber, + xDataType: xDataType!, + xIsPseudo: Boolean(xIsPseudo), + categoriesMap, + }); + if ((xValue === null || xValue === undefined) && !xIsPseudo) { + return; + } + } else if (ySectionItems.length > 1) { + const value = (xValue = 'Measure Names'); + + if (!categoriesMap.has(value)) { + categoriesMap.set(value, true); + categories.push(value); + } + } else { + const ySectionItem = ySectionItems[0]; + const value = (xValue = ySectionItem.fakeTitle || idToTitle[ySectionItem.guid]); + + if (!categoriesMap.has(value)) { + categoriesMap.set(value, true); + categories.push(value); + } + } + + let x2Value: ReturnType; + if (x2) { + x2Value = getXAxisValue({ + x: x2, + ys1: ySectionItems, + categories: categories2, + idToTitle, + order, + values, + xIsNumber: x2IsNumber, + xDataType: x2DataType!, + xIsDate: x2IsDate, + xIsPseudo: x2IsPseudo, + categoriesMap, + }); + if ((x2Value === null || x2Value === undefined) && !x2IsPseudo) { + return; + } + } + + prepareLines({ + ySectionItems: mergedYSectionItems, + idToTitle, + idToDataType, + order, + values, + isMultiDatasets: datasets.length > 1, + isColorizeByMeasureValue, + isColorizeByMeasure, + isSegmentsExists: false, + x2Field: x2, + colorItem, + rawXValue: xValue, + rawX2Value: x2Value, + x2IsDate, + isSortByMeasureColor, + measureColorSortLine, + isShapeItemExist: false, + isColorItemExist, + isMultiAxis: false, + shapeItem: undefined, + xField: x, + shapesConfig, + labelItem, + segmentIndexInOrder: -1, + layers: shared.visualization?.layers, + colorMode, + convertMarkupToString: false, + }); + }); + + let lineKeys1 = Object.keys(lines1); + let lineKeys2 = Object.keys(lines2); + + if (xIsDate && !disableDefaultSorting) { + (categories as number[]).sort(numericCollator); + } + + const lines = [lines1, lines2]; + const isSortableXAxis = !isPercentVisualization(visualizationId); + + if (!disableDefaultSorting) { + categories = getSortedCategories({ + lines, + colorItem, + categories, + ySectionItems, + isSortWithYSectionItem: Boolean(ySectionItems.length && isSortableXAxis), + sortItem: sortItems[0], + isSortAvailable: isSortItemExists && isSortCategoriesAvailable, + isXNumber: xIsNumber, + measureColorSortLine, + isSegmentsExists: false, + isSortBySegments: false, + }); + } + + const sortedLineKeys = getSortedLineKeys({ + colorItem, + lines, + isSortAvailable: isSortItemExists, + isSortBySegments: false, + sortItem, + yField: ySectionItems[0], + visualizationId: visualizationId as WizardVisualizationId, + categories, + }); + + lineKeys1 = sortedLineKeys[0] || lineKeys1; + lineKeys2 = sortedLineKeys[1] || lineKeys2; + + const graphs: any[] = []; + const uniqueTitles: string[] = []; + + const isXDiscrete = xAxisMode === AxisMode.Discrete; + const isSortNumberTypeXAxisByMeasure = + isSortCategoriesAvailable && + isSortItemExists && + xIsNumber && + !isSortingXAxis && + (isSortingYAxis || isSortByMeasureColor); + + const isXCategoryAxis = + isXDiscrete || + xDataType === 'string' || + xIsPseudo || + isSortNumberTypeXAxisByMeasure || + disableDefaultSorting; + + const orderedLineKeys = [lineKeys1, lineKeys2]; + + orderedLineKeys.forEach((lineKeys, lineKeysIndex) => { + lineKeys.forEach((lineKey) => { + let line: LineTemplate; + let nulls: any; + if (lineKeysIndex === 0) { + line = lines1[lineKey]; + nulls = nullsY1; + } else { + line = lines2[lineKey]; + nulls = undefined; + } + + const innerLabels = labelsValues[lineKey]; + + const customSeriesData: HighchartsSeriesCustomObject = {}; + + const graph: any = { + id: line.id, + title: seriesNameFormatter(line.title || 'Null'), + tooltip: line.tooltip, + dataLabels: { + enabled: Boolean(labelItem), + ...line.dataLabels, + useHTML: isMarkdownLabel || isMarkupLabel || isHtmlLabel, + }, + data: categories + .map((category, i) => { + const lineData = line.data[category]; + const colorValue = lineData?.colorValue; + let value = lineData?.value; + + if (typeof value === 'undefined' && nulls === AxisNullsMode.AsZero) { + value = 0; + } + + // We can skip a point only if we put x in each point instead of categories + if (!isXCategoryAxis && typeof value === 'undefined') { + return null; + } + + const y = typeof value === 'number' ? Number(value) : null; + + const point: any = { + x: i, + y, + colorValue, + }; + + if (!isXCategoryAxis) { + const pointX = category; + + point.x = pointX; + + if (isNumberField(x)) { + const formatting = getFormatOptions(x); + if (!isEmpty(formatting)) { + point.xFormatted = chartKitFormatNumberWrapper( + Number(pointX), + { + lang: 'ru', + ...formatting, + }, + ); + } + } + } + + point.label = getLabelValue(innerLabels?.[category], { + isMarkdownLabel, + isMarkupLabel, + isHtmlLabel, + }); + + if (isActionParamsEnable) { + const [yField] = ySectionItems || []; + const actionParams: Record = {}; + addActionParamValue(actionParams, x, category); + addActionParamValue(actionParams, yField, point.y); + + point.custom = { + ...point.custom, + actionParams, + }; + } + + return point; + }) + .filter((point) => point !== null), + legendTitle: line.legendTitle || line.title || 'Null', + formattedName: colorItem ? undefined : line.formattedName, + drillDownFilterValue: line.drillDownFilterValue, + colorKey: line.colorKey, + colorGuid: colorItem?.guid || null, + shapeGuid: null, + connectNulls: nulls === AxisNullsMode.Connect, + measureFieldTitle: line.fieldTitle, + }; + + if (line.pointConflict) { + graph.pointConflict = true; + } + + if (lineKeysIndex === 0 || ySectionItems.length === 0) { + graph.yAxis = 0; + } else { + graph.yAxis = 1; + } + + if (uniqueTitles.indexOf(graph.legendTitle) === -1) { + uniqueTitles.push(graph.legendTitle); + } else { + graph.showInLegend = false; + } + + if (x2) { + graph.stack = line.stack; + } + + graph.colorValue = line.colorValue; + graph.shapeValue = line.shapeValue; + graph.colorShapeValue = line.colorShapeValue; + + graph.custom = customSeriesData; + + if (isActionParamsEnable) { + const actionParams: Record = {}; + + const [, yField2] = ySectionItems || []; + addActionParamValue(actionParams, yField2, line.stack); + + addActionParamValue(actionParams, colorItem, line.colorValue); + + graph.custom = { + ...graph.custom, + actionParams, + }; + } + + graphs.push(graph); + }); + }); + + if (gradientMode) { + colorizeByGradient(visualizationId as WizardVisualizationId, { + graphs, + colorsConfig, + }); + } else { + mapAndColorizeGraphsByPalette({ + graphs, + colorsConfig, + isShapesItemExists: false, + isColorsItemExists: isColorItemExist, + isSegmentsExists: false, + usedColors, + colorField: colorItem, + defaultColorPaletteId, + }); + } + + if (isMarkdownLabel) { + ChartEditor.updateConfig({useMarkdown: true}); + } + + if (isMarkupLabel) { + ChartEditor.updateConfig({useMarkup: true}); + } + + if (isHtmlLabel || isHtmlField(x) || isHtmlField(colorItem)) { + ChartEditor.updateConfig({useHtml: true}); + } + + if (isXCategoryAxis) { + return { + graphs, + categories: categories.map(categoriesFormatter), + }; + } else { + return {graphs}; + } + } else { + // If no fields are selected for the Y axis, we draw only the X axis. + const categories: (string | number)[] = []; + + data.forEach((values) => { + const value = values[0]; + + if (value === null) { + return; + } + + let xValue; + if (xIsDate) { + const time = new Date(value); + + if (xDataType === 'genericdatetime') { + time.setTime(getTimezoneOffsettedTime(time)); + } + + xValue = time.getTime(); + } else if (xIsNumber) { + xValue = Number(value); + } else { + xValue = value; + } + + if (!categoriesMap.has(xValue)) { + categoriesMap.set(xValue, true); + categories.push(xValue); + } + }); + + // Default sorting + if ((!isSortItemExists || !isSortCategoriesAvailable) && !disableDefaultSorting) { + if (xIsNumber) { + (categories as number[]).sort(numericCollator); + } else { + (categories as string[]).sort(collator.compare); + } + } + + // Generating data + const graphs = [ + { + data: categories.map((_, index) => ({y: null, x: index})), + }, + ]; + + // If there are dates on the X axis, then we pass them as dates + if (xIsDate && xAxisMode !== AxisMode.Discrete) { + return {graphs, categories_ms: categories}; + } else { + return { + graphs, + categories: categories.map(categoriesFormatter), + }; + } + } +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/constants.ts b/src/server/modes/charts/plugins/datalens/preparers/constants.ts new file mode 100644 index 0000000..ab8537a --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/constants.ts @@ -0,0 +1,11 @@ +import type {ChartYAxisTitle} from '@gravity-ui/chartkit/gravity-charts'; + +export const SEGMENT_TITLE_BASE_CONFIG: Partial = { + rotation: 0, + maxWidth: '25%', + margin: 24, + style: { + fontWeight: 500, + fontSize: '14px', + }, +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/__tests__/flat-table.test.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/__tests__/flat-table.test.ts new file mode 100644 index 0000000..c3afbc7 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/__tests__/flat-table.test.ts @@ -0,0 +1,87 @@ +import prepareFlatTable from '../index'; + +const { + flatTableFormattingIntPrecisionArgs, + flatTableFormattingFloatPrecisionArgs, + flatTablePrepareWithTotalsArgs, +} = require('./mocks/flat-table.mock'); + +describe('prepareFlatTable', () => { + describe('common', () => { + test('should not use precision for integer cell', () => { + const result = prepareFlatTable(flatTableFormattingIntPrecisionArgs) as any; + const precision = result.head[0]?.formatter?.precision; + expect(precision).toEqual(undefined); + }); + + test('should use provided precision for float cell', () => { + const result = prepareFlatTable(flatTableFormattingFloatPrecisionArgs) as any; + const precision = result.head[0]?.formatter?.precision; + expect(precision).toEqual(1); + }); + + test('should ignore totals when empty string', () => { + const result = prepareFlatTable(flatTablePrepareWithTotalsArgs); + const totals = result.footer; + + const expectedTotals = [ + { + cells: [ + { + value: 'Total', + css: { + 'background-color': 'var(--g-color-base-generic)', + 'font-weight': 500, + }, + type: 'text', + }, + { + value: '', + css: { + 'background-color': 'var(--g-color-base-generic)', + 'font-weight': 500, + }, + }, + { + value: '', + css: { + 'background-color': 'var(--g-color-base-generic)', + 'font-weight': 500, + }, + }, + { + value: 4500, + css: { + 'background-color': 'var(--g-color-base-generic)', + 'font-weight': 500, + }, + }, + { + value: 4017.8571428571427, + css: { + 'background-color': 'var(--g-color-base-generic)', + 'font-weight': 500, + }, + }, + { + value: '', + css: { + 'background-color': 'var(--g-color-base-generic)', + 'font-weight': 500, + }, + }, + { + value: '', + css: { + 'background-color': 'var(--g-color-base-generic)', + 'font-weight': 500, + }, + }, + ], + }, + ]; + + expect(totals).toEqual(expectedTotals); + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/__tests__/mocks/flat-table.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/__tests__/mocks/flat-table.mock.ts new file mode 100644 index 0000000..fd0b5eb --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/__tests__/mocks/flat-table.mock.ts @@ -0,0 +1,249 @@ +const i18nMock = { + I18n: { + keyset: (_str: string) => { + return function (key: string, params: Record) { + if (key === 'label_total-value') { + return `Total: ${params.value}`; + } + + return `Total`; + }; + }, + }, + I18N: { + setLang: jest.fn(), + }, +}; + +const getTranslation = (keyset: string, key: string, params: Record) => + i18nMock.I18n.keyset(keyset)(key, params); + +const chartEditorMock = { + getParam: () => null, + getParams: () => ({}), + getCurrentPage() {}, + getLang: () => 'en', + getTranslation, + getWidgetConfig: () => undefined, +}; + +const baseArgs = { + features: {}, + colors: [], + colorsConfig: {}, + ChartEditor: chartEditorMock, + shared: { + sharedData: {}, + }, +}; + +export const flatTableFormattingIntPrecisionArgs = { + ...baseArgs, + idToDataType: {'fdddd3b0-5639-11eb-9c8e-41e84ec800f0': 'integer'}, + idToTitle: {'fdddd3b0-5639-11eb-9c8e-41e84ec800f0': 'Sales'}, + placeholders: [ + { + items: [ + { + guid: 'fdddd3b0-5639-11eb-9c8e-41e84ec800f0', + data_type: 'integer', + title: 'Sales', + formatting: { + format: 'number', + postfix: '', + precision: 2, + prefix: '', + showRankDelimiter: false, + units: null, + }, + }, + ], + }, + ], + resultData: { + data: [['2297241.5023116767']], + order: [{title: 'Sales'}], + totals: [], + }, +}; + +export const flatTableFormattingFloatPrecisionArgs = { + ...baseArgs, + idToDataType: {'fdddd3b0-5639-11eb-9c8e-41e84ec800f0': 'float'}, + idToTitle: {'fdddd3b0-5639-11eb-9c8e-41e84ec800f0': 'Sales'}, + placeholders: [ + { + items: [ + { + guid: 'fdddd3b0-5639-11eb-9c8e-41e84ec800f0', + data_type: 'float', + title: 'Sales', + formatting: { + format: 'number', + postfix: '', + precision: 1, + prefix: '', + showRankDelimiter: false, + units: null, + }, + }, + ], + }, + ], + resultData: { + data: [['2297241.5023116767']], + order: [{title: 'Sales'}], + totals: [], + }, +}; + +export const flatTablePrepareWithTotalsArgs = { + ...baseArgs, + placeholders: [ + { + items: [ + { + title: 'Creation date', + data_type: 'datetime', + datasetId: 'eqw2uiu7yoe86', + guid: 'created_date_wo4m', + }, + { + data_type: 'datetime', + datasetId: 'eqw2uiu7yoe86', + title: 'title-5e6c2e70-b65b-11ec-af88-4394e371e35d', + guid: '5e6c2e70-b65b-11ec-af88-4394e371e35d', + }, + { + data_type: 'string', + datasetId: 'eqw2uiu7yoe86', + title: 'title-90d94500-b65b-11ec-af88-4394e371e35d', + guid: '90d94500-b65b-11ec-af88-4394e371e35d', + }, + { + data_type: 'float', + datasetId: 'eqw2uiu7yoe86', + title: 'title-9cb5ebd0-b65b-11ec-af88-4394e371e35d', + guid: '9cb5ebd0-b65b-11ec-af88-4394e371e35d', + }, + { + guid: '3fad4e10-c24f-11ec-b4e2-bf6f817dc38a', + data_type: 'float', + datasetId: 'eqw2uiu7yoe86', + title: 'title-3fad4e10-c24f-11ec-b4e2-bf6f817dc38a', + }, + { + title: 'title-61c43c90-cd24-11ec-9505-b3c24ed01959', + guid: '61c43c90-cd24-11ec-9505-b3c24ed01959', + data_type: 'integer', + datasetId: 'eqw2uiu7yoe86', + }, + { + guid: 'cd7888d0-cd27-11ec-b241-fd02653f897f', + title: 'title-cd7888d0-cd27-11ec-b241-fd02653f897f', + data_type: 'float', + datasetId: 'eqw2uiu7yoe86', + }, + ], + }, + ], + datasets: ['eqw2uiu7yoe86'], + resultData: { + data: [ + [ + '2022-03-05T06:36:17', + '2022-03-05T17:55:12', + 'KZT', + '2550.0', + '2276.785714285714', + '22', + '1.2941176470588236', + ], + [ + '2022-03-05T07:01:28', + '2022-03-05T17:58:09', + 'KZT', + '1950.0', + '1741.0714285714284', + '16', + '1.2307692307692308', + ], + ], + legend: [ + [0, 1, 2, 3, 4, 5, 6], + [0, 1, 2, 3, 4, 5, 6], + ], + order: [ + { + datasetId: 'eqw2uiu7yoe86', + title: 'Creation date', + }, + { + datasetId: 'eqw2uiu7yoe86', + title: 'title-5e6c2e70-b65b-11ec-af88-4394e371e35d', + }, + { + datasetId: 'eqw2uiu7yoe86', + title: 'title-90d94500-b65b-11ec-af88-4394e371e35d', + }, + { + datasetId: 'eqw2uiu7yoe86', + title: 'title-9cb5ebd0-b65b-11ec-af88-4394e371e35d', + }, + { + datasetId: 'eqw2uiu7yoe86', + title: 'title-3fad4e10-c24f-11ec-b4e2-bf6f817dc38a', + }, + { + datasetId: 'eqw2uiu7yoe86', + title: 'title-61c43c90-cd24-11ec-9505-b3c24ed01959', + }, + { + datasetId: 'eqw2uiu7yoe86', + title: 'title-cd7888d0-cd27-11ec-b241-fd02653f897f', + }, + ], + totals: ['', '', '', '4500.0', '4017.8571428571427', '', ''], + }, + fields: [ + { + legend_item_id: 0, + }, + { + legend_item_id: 1, + }, + { + legend_item_id: 2, + }, + { + legend_item_id: 3, + }, + { + legend_item_id: 4, + }, + { + legend_item_id: 5, + }, + { + legend_item_id: 6, + }, + ], + idToTitle: { + created_date_wo4m: 'Creation date', + '5e6c2e70-b65b-11ec-af88-4394e371e35d': 'title-5e6c2e70-b65b-11ec-af88-4394e371e35d', + '90d94500-b65b-11ec-af88-4394e371e35d': 'title-90d94500-b65b-11ec-af88-4394e371e35d', + '9cb5ebd0-b65b-11ec-af88-4394e371e35d': 'title-9cb5ebd0-b65b-11ec-af88-4394e371e35d', + '3fad4e10-c24f-11ec-b4e2-bf6f817dc38a': 'title-3fad4e10-c24f-11ec-b4e2-bf6f817dc38a', + '61c43c90-cd24-11ec-9505-b3c24ed01959': 'title-61c43c90-cd24-11ec-9505-b3c24ed01959', + 'cd7888d0-cd27-11ec-b241-fd02653f897f': 'title-cd7888d0-cd27-11ec-b241-fd02653f897f', + }, + idToDataType: { + created_date_wo4m: 'datetime', + '5e6c2e70-b65b-11ec-af88-4394e371e35d': 'datetime', + '90d94500-b65b-11ec-af88-4394e371e35d': 'string', + '9cb5ebd0-b65b-11ec-af88-4394e371e35d': 'float', + '3fad4e10-c24f-11ec-b4e2-bf6f817dc38a': 'float', + '61c43c90-cd24-11ec-9505-b3c24ed01959': 'integer', + 'cd7888d0-cd27-11ec-b241-fd02653f897f': 'float', + }, +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/background-settings/get-background-colors-map-by-continuous-column.test.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/background-settings/get-background-colors-map-by-continuous-column.test.ts new file mode 100644 index 0000000..7a541c8 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/background-settings/get-background-colors-map-by-continuous-column.test.ts @@ -0,0 +1,93 @@ +import {GradientType} from '../../../../../../../../../../shared'; +import type {ChartColorsConfig} from '../../../../../types'; +import {colorizeFlatTableColumn} from '../../background-settings'; + +const getBlueGradientColors = () => ['#0044A3', '#8CCBFF']; + +const getRedOrangeGreenGradientColors = () => ['#FF3D64', '#FFC636', '#54A520']; + +const removeLineBreaksAndSpace = (arr: (string | null)[]) => + arr.map((v) => (v ? v.replace(/[\r\n\s]/gm, '') : v)); + +describe('colorizeFlatTableColumn', () => { + it('Should return array of rgb colors', () => { + const data = [['0'], ['1'], ['2'], ['3']]; + const index = 0; + const colorsConfig: ChartColorsConfig = { + gradientMode: GradientType.TWO_POINT, + gradientPalette: 'blue', + reversed: false, + colors: [], + gradientColors: getBlueGradientColors(), + loadedColorPalettes: {}, + availablePalettes: {}, + }; + + const result = colorizeFlatTableColumn({data, colorsConfig, index}); + + expect(removeLineBreaksAndSpace(result)).toEqual([ + 'rgb(0,68,163)', + 'rgb(47,113,194)', + 'rgb(93,158,224)', + 'rgb(140,203,255)', + ]); + }); + + it('Should return array with one rgb color as max value for 2-point gradient', () => { + const data = [['5']]; + const index = 0; + const colorsConfig: ChartColorsConfig = { + gradientMode: GradientType.TWO_POINT, + gradientPalette: 'blue', + reversed: false, + colors: [], + gradientColors: getBlueGradientColors(), + loadedColorPalettes: {}, + availablePalettes: {}, + }; + + const result = colorizeFlatTableColumn({data, colorsConfig, index}); + + expect(removeLineBreaksAndSpace(result)).toEqual(['rgb(140,203,255)']); + }); + + it('Should return array with one rgb color as max value for 3-point gradient', () => { + const data = [['5']]; + const index = 0; + const colorsConfig: ChartColorsConfig = { + gradientMode: GradientType.THREE_POINT, + gradientPalette: 'red-orange-green', + reversed: false, + colors: [], + gradientColors: getRedOrangeGreenGradientColors(), + loadedColorPalettes: {}, + availablePalettes: {}, + }; + + const result = colorizeFlatTableColumn({data, colorsConfig, index}); + + expect(removeLineBreaksAndSpace(result)).toEqual(['rgb(84,165,32)']); + }); + + it('Some rows do not have data for the column -> calculate the gradient only from non-empty values', () => { + const data = [[], ['0.5'], ['1.5']]; + const index = 0; + const colorsConfig: ChartColorsConfig = { + gradientMode: GradientType.TWO_POINT, + gradientPalette: 'blue', + reversed: false, + colors: [], + gradientColors: getBlueGradientColors(), + loadedColorPalettes: {}, + availablePalettes: {}, + }; + + const result = colorizeFlatTableColumn({data, colorsConfig, index}); + + expect(removeLineBreaksAndSpace(result)).toEqual([ + null, + 'rgb(0,68,163)', // min possible value + 'rgb(140,203,255)', // max possible value + ]); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/background-settings/get-flat-table-background-styles.test.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/background-settings/get-flat-table-background-styles.test.ts new file mode 100644 index 0000000..90a5aa2 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/background-settings/get-flat-table-background-styles.test.ts @@ -0,0 +1,34 @@ +import {getFlatTableBackgroundStyles} from '../../background-settings'; +import type {GetFlatTableCellBackgroundSettingsStylesArgs} from '../../background-settings/types'; + +describe('getFlatTableBackgroundStyles', () => { + it('The background color should be applied for null values', () => { + const field = {guid: '_guid', title: 'abc', data_type: 'integer', datasetId: '_datasetId'}; + const args = { + column: { + ...field, + backgroundSettings: { + settings: { + paletteState: { + palette: '_palette', + mountedColors: { + Null: 1, + }, + }, + }, + colorFieldGuid: field.guid, + }, + }, + values: [null], + idToTitle: {[field.guid]: field.title}, + order: [[{...field}]], + idToDataType: {[field.guid]: field.data_type}, + loadedColorPalettes: {_palette: {colors: ['color1', 'color2']}}, + } as unknown as GetFlatTableCellBackgroundSettingsStylesArgs; + + expect(getFlatTableBackgroundStyles(args)).toEqual({ + backgroundColor: 'color2', + color: '#FFF', + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/footer/getFooter.test.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/footer/getFooter.test.ts new file mode 100644 index 0000000..ec59a46 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/footer/getFooter.test.ts @@ -0,0 +1,148 @@ +import {PALETTE_ID} from '../../../../../../../../../../shared'; +import {getFooter} from '../../footer'; +import * as footerHelpersModule from '../../footer'; +import { + COLUMNS, + COLUMNS_WITH_DUPLICATES, + ID_TO_DATA_TYPE, + ID_TO_TITLE, + ORDER, + TOTALS, +} from '../mocks/getFooter.mock'; + +const i18nMock = { + I18n: { + keyset: (_str: string) => { + return function (key: string, params: Record) { + if (key === 'label_total-value') { + return `Total: ${params.value}`; + } + + return `Total`; + }; + }, + }, + I18N: { + getLang: () => { + return 'en'; + }, + setLang: jest.fn(), + }, +}; + +jest.mock('../../../../../../../../../../i18n', () => i18nMock); + +describe('getFooter', () => { + const ChartEditorMock = { + getLang: () => 'en', + getTranslation: (keyset: string, key: string, params: Record) => { + return i18nMock.I18n.keyset(keyset)(key, params); + }, + }; + const colorsConfig = { + loadedColorPalettes: {}, + colors: [], + gradientColors: [], + availablePalettes: {}, + }; + + it('Returns the footer: in the first place is always "Total"', () => { + const footer = getFooter({ + columns: COLUMNS, + idToDataType: ID_TO_DATA_TYPE, + idToTitle: ID_TO_TITLE, + totals: TOTALS, + order: ORDER, + columnValuesByColumn: {}, + ChartEditor: ChartEditorMock, + colorsConfig, + defaultColorPaletteId: PALETTE_ID.CLASSIC_20, + }); + + expect(footer[0].cells).toHaveLength(2); + + expect(footer[0].cells[0].value).toEqual('Total'); + expect(footer[0].cells[1].value).toEqual(282070); + }); + + it('All footer cells have the same style', () => { + const expectedStyles = { + 'background-color': 'var(--g-color-base-generic)', + 'font-weight': 500, + }; + + const footer = getFooter({ + columns: COLUMNS, + idToDataType: ID_TO_DATA_TYPE, + idToTitle: ID_TO_TITLE, + totals: TOTALS, + order: ORDER, + columnValuesByColumn: {}, + ChartEditor: ChartEditorMock, + colorsConfig, + defaultColorPaletteId: PALETTE_ID.CLASSIC_20, + }); + + footer[0].cells.forEach((cell) => { + expect(cell.css).toEqual(expectedStyles); + }); + }); + + it('If there is an measure in the first column, then the function returns "Total" with the measure value', () => { + const footer = getFooter({ + columns: [...COLUMNS].reverse(), + idToDataType: ID_TO_DATA_TYPE, + idToTitle: ID_TO_TITLE, + totals: [...TOTALS].reverse(), + order: [...ORDER].reverse(), + columnValuesByColumn: {}, + ChartEditor: ChartEditorMock, + colorsConfig, + defaultColorPaletteId: PALETTE_ID.CLASSIC_20, + }); + + expect(footer[0].cells).toHaveLength(2); + + expect(footer[0].cells[0].value).toEqual('Total: 282070'); + expect(footer[0].cells[1].value).toEqual(''); + }); + + it('The total is set correctly, even if the field is duplicated in columns', () => { + const footer = getFooter({ + columns: COLUMNS_WITH_DUPLICATES, + idToTitle: ID_TO_TITLE, + idToDataType: ID_TO_DATA_TYPE, + totals: TOTALS, + order: ORDER, + columnValuesByColumn: {}, + ChartEditor: ChartEditorMock, + colorsConfig, + defaultColorPaletteId: PALETTE_ID.CLASSIC_20, + }); + + expect(footer[0].cells).toHaveLength(4); + + expect(footer[0].cells[0].value).toEqual('Total'); + expect(footer[0].cells[1].value).toEqual(282070); + expect(footer[0].cells[2].value).toEqual(282070); + expect(footer[0].cells[3].value).toEqual(''); + }); + + it('The title setting function is called 1 time during the entire getFooter operation', () => { + const getTotalTitleFake = jest.spyOn(footerHelpersModule, 'getTotalTitle'); + const footer = getFooter({ + columns: COLUMNS_WITH_DUPLICATES, + idToDataType: ID_TO_DATA_TYPE, + idToTitle: ID_TO_TITLE, + totals: TOTALS, + order: ORDER, + columnValuesByColumn: {}, + ChartEditor: ChartEditorMock, + colorsConfig, + defaultColorPaletteId: PALETTE_ID.CLASSIC_20, + }); + + expect(footer[0].cells).toHaveLength(4); + expect(getTotalTitleFake).toBeCalledTimes(1); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/mocks/getFooter.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/mocks/getFooter.mock.ts new file mode 100644 index 0000000..24a998c --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/__tests__/mocks/getFooter.mock.ts @@ -0,0 +1,78 @@ +import type {ServerField} from '../../../../../../../../../../shared'; + +export const COLUMNS = [ + { + type: 'DIMENSION', + data_type: 'string', + guid: 'ac8dd226-3372-4212-bde7-17afc792a728', + title: 'Category', + calc_mode: 'direct', + datasetId: 'lmsaoq5u6m2qd', + }, + { + type: 'MEASURE', + data_type: 'integer', + guid: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + title: 'Profit', + calc_mode: 'direct', + datasetId: 'lmsaoq5u6m2qd', + }, +] as ServerField[]; + +export const COLUMNS_WITH_DUPLICATES = [ + { + type: 'DIMENSION', + data_type: 'string', + guid: 'ac8dd226-3372-4212-bde7-17afc792a728', + title: 'Category', + calc_mode: 'direct', + datasetId: 'lmsaoq5u6m2qd', + }, + { + type: 'MEASURE', + data_type: 'integer', + guid: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + title: 'Profit', + calc_mode: 'direct', + datasetId: 'lmsaoq5u6m2qd', + }, + { + type: 'MEASURE', + data_type: 'integer', + guid: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + title: 'Profit', + calc_mode: 'direct', + datasetId: 'lmsaoq5u6m2qd', + }, + { + type: 'DIMENSION', + data_type: 'string', + guid: 'ac8dd226-3372-4212-bde7-17afc792a728', + title: 'Category', + calc_mode: 'direct', + datasetId: 'lmsaoq5u6m2qd', + }, +] as ServerField[]; + +export const ID_TO_TITLE = { + 'ac8dd226-3372-4212-bde7-17afc792a728': 'Category', + '4b6463d1-30e1-425e-b7d2-77205bc75de6': 'Profit', +}; + +export const ID_TO_DATA_TYPE = { + 'ac8dd226-3372-4212-bde7-17afc792a728': 'string', + '4b6463d1-30e1-425e-b7d2-77205bc75de6': 'integer', +}; + +export const TOTALS = ['', '282070']; + +export const ORDER = [ + { + datasetId: 'lmsaoq5u6m2qd', + title: 'Category', + }, + { + datasetId: 'lmsaoq5u6m2qd', + title: 'Profit', + }, +]; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts new file mode 100644 index 0000000..d43a5a3 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts @@ -0,0 +1,86 @@ +import isNil from 'lodash/isNil'; + +import { + GradientNullModes, + type ServerField, + type TableFieldBackgroundSettings, +} from '../../../../../../../../../shared'; +import type {ChartColorsConfig} from '../../../../types'; +import {colorizeByColorValues} from '../../../../utils/color-helpers'; +import { + findIndexInOrder, + isTableFieldBackgroundSettingsEnabled, +} from '../../../../utils/misc-helpers'; +import {getCurrentBackgroundGradient} from '../../../helpers/backgroundSettings/misc'; +import type {PrepareFunctionDataRow} from '../../../types'; + +import type {GetBackgroundColorsMapByContinuousColumn} from './types'; + +export function colorizeFlatTableColumn({ + data, + colorsConfig, + index, +}: { + data: PrepareFunctionDataRow[]; + index: number; + colorsConfig: ChartColorsConfig; +}) { + const nilValue = colorsConfig.nullMode === GradientNullModes.AsZero ? 0 : null; + const colorValues = data.reduce<(number | null)[]>((acc, row) => { + const rowValue = row[index]; + const parsedRowValue = isNil(rowValue) ? nilValue : parseFloat(rowValue); + acc.push(parsedRowValue); + return acc; + }, []); + + return colorizeByColorValues({colorValues, colorsConfig}); +} + +export const getBackgroundColorsMapByContinuousColumn = ( + args: GetBackgroundColorsMapByContinuousColumn, +) => { + const {columns, idToTitle, order, data, chartColorsConfig} = args; + + const columnsWithBackgroundSettings = columns.filter( + (column): column is ServerField & {backgroundSettings: TableFieldBackgroundSettings} => + isTableFieldBackgroundSettingsEnabled(column), + ); + + const measuresWhichUsedForColorizing = columnsWithBackgroundSettings.filter( + (column) => column.backgroundSettings.settings.isContinuous, + ); + + return measuresWhichUsedForColorizing.reduce( + (acc, column) => { + const backgroundColors = column.backgroundSettings; + const guid = backgroundColors.colorFieldGuid; + const gradientState = backgroundColors.settings.gradientState; + + const colorsConfig: ChartColorsConfig = { + ...gradientState, + colors: [], + loadedColorPalettes: {}, + availablePalettes: chartColorsConfig.availablePalettes, + gradientColors: + getCurrentBackgroundGradient( + gradientState, + chartColorsConfig.loadedColorPalettes, + )?.colors || [], + }; + + const title = idToTitle[guid]; + const index = findIndexInOrder(order, column, title); + + const rgbColorValues = colorizeFlatTableColumn({ + colorsConfig: colorsConfig, + index, + data, + }); + return { + ...acc, + [backgroundColors.settingsId]: rgbColorValues, + }; + }, + {} as Record, + ); +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-flat-table-background-styles.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-flat-table-background-styles.ts new file mode 100644 index 0000000..b990dc2 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-flat-table-background-styles.ts @@ -0,0 +1,141 @@ +import type {MarkupItem} from '../../../../../../../../../shared'; +import {getDistinctValue, markupToRawString} from '../../../../../../../../../shared'; +import {selectServerPalette} from '../../../../../../../../constants'; +import {getMountedColor} from '../../../../utils/constants'; +import {findIndexInOrder} from '../../../../utils/misc-helpers'; + +import type { + GetContinuousBackgroundColorStyle, + GetDiscreteBackgroundColorStyle, + GetFlatTableCellBackgroundSettingsStylesArgs, +} from './types'; + +const getDiscreteBackgroundColorStyle = (args: GetDiscreteBackgroundColorStyle) => { + const { + idToTitle, + order, + column, + values, + backgroundSettings, + idToDataType, + loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, + } = args; + + const {settings, colorFieldGuid} = backgroundSettings; + + const colorFieldTitle = idToTitle[colorFieldGuid]; + const colorFieldDataType = idToDataType[colorFieldGuid]; + + const valueIndex = findIndexInOrder(order, column, colorFieldTitle); + + const rawValue = values[valueIndex] as number | string | null | MarkupItem; + + let value: number | string | null = null; + if (colorFieldDataType === 'markup') { + if (rawValue) { + value = markupToRawString(rawValue as MarkupItem); + } + } else { + value = getDistinctValue(rawValue); + } + + if (!value) { + return; + } + + const paletteSettings = settings.paletteState; + + const mountedColors = paletteSettings.mountedColors || {}; + + let colors; + if (paletteSettings?.palette && loadedColorPalettes[paletteSettings.palette]) { + colors = loadedColorPalettes[paletteSettings.palette].colors; + } else { + colors = selectServerPalette({ + palette: paletteSettings.palette, + availablePalettes, + defaultColorPaletteId, + }); + } + + const colorValue = getMountedColor({ + mountedColors, + colors, + value, + }); + + if (!colorValue) { + return; + } + + // eslint-disable-next-line consistent-return + return { + backgroundColor: colorValue, + color: '#FFF', + }; +}; + +const getContinuousBackgroundColorStyle = (args: GetContinuousBackgroundColorStyle) => { + const {currentRowIndex, backgroundColorsByMeasure, backgroundSettings} = args; + + const colors = backgroundColorsByMeasure[backgroundSettings.settingsId]; + const backgroundColor = colors[currentRowIndex]; + + if (backgroundColor) { + return { + backgroundColor, + color: '#FFF', + }; + } + + return {}; +}; + +export const getFlatTableBackgroundStyles = ( + args: GetFlatTableCellBackgroundSettingsStylesArgs, +) => { + const { + column, + values, + idToTitle, + order, + backgroundColorsByMeasure, + currentRowIndex, + idToDataType, + loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, + } = args; + + const backgroundSettings = column.backgroundSettings; + + if (!backgroundSettings) { + return; + } + + const {settings} = backgroundSettings; + + if (settings.isContinuous) { + // eslint-disable-next-line consistent-return + return getContinuousBackgroundColorStyle({ + backgroundColorsByMeasure, + currentRowIndex, + backgroundSettings, + }); + } + + // eslint-disable-next-line consistent-return + return getDiscreteBackgroundColorStyle({ + column, + values, + backgroundSettings, + order, + idToTitle, + idToDataType, + loadedColorPalettes, + availablePalettes, + defaultColorPaletteId, + }); +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/index.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/index.ts new file mode 100644 index 0000000..99bbdbc --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/index.ts @@ -0,0 +1,2 @@ +export * from './get-flat-table-background-styles'; +export * from './get-background-colors-map-by-continuous-column'; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/types.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/types.ts new file mode 100644 index 0000000..af7a758 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/types.ts @@ -0,0 +1,43 @@ +import type {ColorPalette, Palette, ServerField} from '../../../../../../../../../shared'; +import type {TableFieldBackgroundSettings} from '../../../../../../../../../shared/types/wizard/background-settings'; +import type {ChartColorsConfig} from '../../../../types'; +import type {PrepareFunctionDataRow, ResultDataOrder} from '../../../types'; + +export interface GetFlatTableCellBackgroundSettingsStylesArgs { + column: ServerField; + order: ResultDataOrder; + values: PrepareFunctionDataRow; + idToTitle: Record; + idToDataType: Record; + currentRowIndex: number; + backgroundColorsByMeasure: Record; + loadedColorPalettes: Record; + availablePalettes: Record; + defaultColorPaletteId: string; +} + +export interface GetContinuousBackgroundColorStyle { + currentRowIndex: number; + backgroundColorsByMeasure: Record; + backgroundSettings: TableFieldBackgroundSettings; +} + +export interface GetDiscreteBackgroundColorStyle { + idToTitle: Record; + idToDataType: Record; + column: ServerField; + order: ResultDataOrder; + values: PrepareFunctionDataRow; + backgroundSettings: TableFieldBackgroundSettings; + loadedColorPalettes: Record; + availablePalettes: Record; + defaultColorPaletteId: string; +} + +export interface GetBackgroundColorsMapByContinuousColumn { + columns: ServerField[]; + order: ResultDataOrder; + data: PrepareFunctionDataRow[]; + idToTitle: Record; + chartColorsConfig: ChartColorsConfig; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/footer/index.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/footer/index.ts new file mode 100644 index 0000000..7e5b5c2 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/footer/index.ts @@ -0,0 +1,137 @@ +import type {BarTableCell, ServerField, TableCommonCell} from '../../../../../../../../../shared'; +import {isDateField, isMarkupField, isStringField} from '../../../../../../../../../shared'; +import {TABLE_TOTALS_STYLES} from '../../../../../constants/misc'; +import { + findIndexInOrder, + getTimezoneOffsettedTime, + isNumericalDataType, + isTableBarsSettingsEnabled, +} from '../../../../utils/misc-helpers'; +import {getBarSettingsValue} from '../../../helpers/barsSettings'; + +import type {GetFooterArgs, GetFooterCellWithStylesArgs, PrepareFooterValueArgs} from './types'; + +const isTotalTitleCell = (columnIndex: number) => { + return columnIndex === 0; +}; + +export const getTotalTitle = ( + value: string | number | undefined, + i18n: (label: string, params?: Record) => string, +) => { + return value ? i18n('label_total-value', {value}) : i18n('label_total'); +}; + +export const prepareFooterValue = ( + args: PrepareFooterValueArgs, +): {column: ServerField; value: string | number} => { + const {column, params} = args; + + const {idToTitle, totals, order, columnIndex, idToDataType, i18n} = params; + + const columnTitle = idToTitle[column.guid]; + const index = findIndexInOrder(order, column, columnTitle); + const total = totals[index]; + const itemDataType = idToDataType[column.guid] || column.data_type; + + let value: number | string | undefined; + + if (total === null || total === '' || typeof total === 'undefined') { + value = ''; + } else if (isMarkupField({data_type: itemDataType})) { + value = total; + } else if (isNumericalDataType(itemDataType)) { + value = Number(total); + } else if (isDateField({data_type: itemDataType})) { + const date = new Date(total); + value = getTimezoneOffsettedTime(date); + } else if (isStringField({data_type: itemDataType})) { + value = total; + } + + if (isTotalTitleCell(columnIndex)) { + value = getTotalTitle(value, i18n); + } + + if (typeof value === 'undefined') { + value = ''; + } + + return {value, column}; +}; + +export const getFooterCellWithStyles = (args: GetFooterCellWithStylesArgs) => { + const {column, columnIndex, value, columnValuesByColumn, colorsConfig, defaultColorPaletteId} = + args; + + const cell: TableCommonCell | BarTableCell = { + value, + css: TABLE_TOTALS_STYLES, + }; + + if (isTotalTitleCell(columnIndex)) { + cell.type = 'text'; + } + + if (isTableBarsSettingsEnabled(column)) { + const columnValues = columnValuesByColumn[column.guid]; + + const barCellProperties = getBarSettingsValue({ + rowValue: String(value), + field: column, + columnValues, + isTotalCell: true, + availablePalettes: colorsConfig.availablePalettes, + loadedColorPalettes: colorsConfig.loadedColorPalettes, + defaultColorPaletteId, + }); + + cell.value = barCellProperties.value; + cell.formattedValue = barCellProperties.formattedValue; + (cell as BarTableCell).barColor = barCellProperties.barColor; + (cell as BarTableCell).showBar = Boolean(column.barsSettings.showBarsInTotals); + } + + return cell; +}; + +export const getFooter = (args: GetFooterArgs) => { + const { + columns, + idToTitle, + order, + totals, + ChartEditor, + idToDataType, + columnValuesByColumn, + colorsConfig, + defaultColorPaletteId, + } = args; + + const i18n = (key: string, params?: Record) => + ChartEditor.getTranslation('wizard.prepares', key, params); + + const valuesWithColumns = columns.map((column, columnIndex) => { + return prepareFooterValue({ + column, + params: {columnIndex, totals, order, idToTitle, idToDataType, i18n}, + }); + }); + + const cells = valuesWithColumns.map(({value, column}, columnIndex) => { + return getFooterCellWithStyles({ + column, + columnIndex, + value, + columnValuesByColumn, + colorsConfig, + defaultColorPaletteId, + }); + }); + + return [ + { + cells, + }, + ]; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/footer/types.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/footer/types.ts new file mode 100644 index 0000000..7751082 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/footer/types.ts @@ -0,0 +1,32 @@ +import type {ServerField} from '../../../../../../../../../shared'; +import type {ChartColorsConfig} from '../../../../types'; +import type {PrepareFunctionDataRow, ResultDataOrder} from '../../../types'; + +export type PrepareFooterValueArgs = { + column: ServerField; + params: Pick & { + columnIndex: number; + i18n: (label: string, params?: Record) => string; + }; +}; + +export type GetFooterCellWithStylesArgs = { + column: ServerField; + columnIndex: number; + value: string | number; + columnValuesByColumn: Record; + colorsConfig: ChartColorsConfig; + defaultColorPaletteId: string; +}; + +export type GetFooterArgs = { + columns: ServerField[]; + ChartEditor: any; + idToTitle: Record; + order: ResultDataOrder; + totals: (string | null)[]; + columnValuesByColumn: Record; + idToDataType: Record; + colorsConfig: ChartColorsConfig; + defaultColorPaletteId: string; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/misc.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/misc.ts new file mode 100644 index 0000000..04e8646 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/misc.ts @@ -0,0 +1,43 @@ +import type {ServerField} from '../../../../../../../../shared'; +import {findIndexInOrder, isTableBarsSettingsEnabled} from '../../../utils/misc-helpers'; +import type {PrepareFunctionDataRow, ResultDataOrder} from '../../types'; + +export const getColumnValuesByColumnWithBarSettings = ({ + idToTitle, + order, + columns, + values, + totals, +}: { + values: PrepareFunctionDataRow[]; + totals: PrepareFunctionDataRow; + idToTitle: Record; + columns: ServerField[]; + order: ResultDataOrder; +}) => { + const columnsWithBarSettings = columns.filter((column) => isTableBarsSettingsEnabled(column)); + + return columnsWithBarSettings.reduce( + (acc, column) => { + const columnDataTitle = idToTitle[column.guid] || column.title; + const indexInOrder = findIndexInOrder(order, column, columnDataTitle); + + const data = values.reduce((acc, row) => { + const value = row[indexInOrder]; + acc.push(value); + return acc; + }, [] as PrepareFunctionDataRow); + + if (totals && totals.length && column.barsSettings?.showBarsInTotals) { + const totalValue = totals[indexInOrder]; + + data.push(totalValue); + } + + acc[column.guid] = data; + + return acc; + }, + {} as Record, + ); +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts new file mode 100644 index 0000000..cdf3e2f --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts @@ -0,0 +1,364 @@ +import type { + BarTableCell, + Field, + TableCellsRow, + TableCommonCell, + TableHead, +} from '../../../../../../../shared'; +import { + DATASET_FIELD_TYPES, + GradientNullModes, + IS_NULL_FILTER_TEMPLATE, + MINIMUM_FRACTION_DIGITS, + getFormatOptions, + isDateField, + isDateType, + isMarkupDataType, + isNumberField, + isTreeDataType, + isUnsupportedDataType, +} from '../../../../../../../shared'; +import {getTreeState} from '../../url/helpers'; +import {mapAndColorizeTableCells} from '../../utils/color-helpers'; +import { + DEFAULT_DATETIMETZ_FORMAT, + DEFAULT_DATETIME_FORMAT, + DEFAULT_DATE_FORMAT, +} from '../../utils/constants'; +import { + findIndexInOrder, + isNumericalDataType, + isTableBarsSettingsEnabled, + isTableFieldBackgroundSettingsEnabled, +} from '../../utils/misc-helpers'; +import {addActionParamValue, canUseFieldForFiltering} from '../helpers/action-params'; +import {getBarSettingsValue, getBarSettingsViewOptions} from '../helpers/barsSettings'; +import {getColumnWidthValue} from '../helpers/columnSettings'; +import type {PrepareFunctionArgs} from '../types'; + +import { + getBackgroundColorsMapByContinuousColumn, + getFlatTableBackgroundStyles, +} from './helpers/background-settings'; +import {getFooter} from './helpers/footer'; +import {getColumnValuesByColumnWithBarSettings} from './helpers/misc'; + +function prepareFlatTable({ + placeholders, + resultData, + colors, + idToTitle, + idToDataType, + colorsConfig, + shared, + ChartEditor, + fields, + defaultColorPaletteId, +}: PrepareFunctionArgs) { + const {drillDownData} = shared.sharedData; + const widgetConfig = ChartEditor.getWidgetConfig(); + const isActionParamsEnable = widgetConfig?.actionParams?.enable; + const treeSet = new Set(getTreeState(ChartEditor.getParams())); + const pinnedColumns = shared.extraSettings?.pinnedColumns || 0; + + const currentActiveDrillDownField: Field | undefined = + drillDownData && drillDownData.fields[drillDownData.level]; + + let currentActiveDrillDownFieldIndex = -1; + + const {data, order, legend} = resultData; + const totals = resultData.totals; + + // Columns + const columns = placeholders[0].items; + + const backgroundColorsByMeasure = getBackgroundColorsMapByContinuousColumn({ + columns, + idToTitle, + order, + data, + chartColorsConfig: colorsConfig, + }); + + const columnValuesByColumn = getColumnValuesByColumnWithBarSettings({ + values: data, + totals, + columns, + idToTitle, + order, + }); + + // Draw a vertical table + const head = columns.map((item, index) => { + const isLastColumn = index === columns.length - 1; + const actualTitle = item.fakeTitle || idToTitle[item.guid]; + + const columnSettings = item.columnSettings; + const widthSettings = columnSettings?.width; + + const headCell: TableHead = { + id: item.guid, + name: actualTitle, + type: 'text', + width: getColumnWidthValue(widthSettings), + }; + + if (item.hintSettings?.enabled) { + headCell.hint = item.hintSettings.text; + } + + if (!isLastColumn && index < pinnedColumns) { + headCell.pinned = true; + } + + const dataType = idToDataType[item.guid]; + + if (isNumericalDataType(dataType)) { + const numberHeadCell: TableHead = { + ...headCell, + formatter: {}, + type: 'number', + view: 'number', + }; + + if (isTableBarsSettingsEnabled(item)) { + const columnValues = columnValuesByColumn[item.guid]; + return { + ...numberHeadCell, + ...getBarSettingsViewOptions({ + barsSettings: item.barsSettings, + columnValues, + }), + }; + } else { + // TODO: in theory, this is not necessary, because you need to look at the dataType + if (isNumberField(item)) { + const formatting = getFormatOptions(item); + if (formatting) { + numberHeadCell.formatter = { + format: formatting.format, + suffix: formatting.postfix, + prefix: formatting.prefix, + showRankDelimiter: formatting.showRankDelimiter, + unit: formatting.unit, + }; + + if (dataType === DATASET_FIELD_TYPES.FLOAT) { + numberHeadCell.formatter.precision = + formatting.precision ?? MINIMUM_FRACTION_DIGITS; + } + } else { + numberHeadCell.precision = + dataType === DATASET_FIELD_TYPES.FLOAT ? MINIMUM_FRACTION_DIGITS : 0; + } + } + + return numberHeadCell; + } + } else if (isDateField(item)) { + const dateHeadCell: TableHead = { + ...headCell, + type: 'date', + format: DEFAULT_DATE_FORMAT, + }; + + if (item.format) { + dateHeadCell.format = item.format; + } else if (dataType === 'genericdatetime') { + dateHeadCell.format = DEFAULT_DATETIME_FORMAT; + } else if (dataType === 'datetimetz') { + dateHeadCell.format = DEFAULT_DATETIMETZ_FORMAT; + } + + return dateHeadCell; + } + + return headCell; + }); + + if (currentActiveDrillDownField) { + const actualTitle = + idToTitle[currentActiveDrillDownField.guid] || currentActiveDrillDownField.title; + + currentActiveDrillDownFieldIndex = findIndexInOrder( + order, + currentActiveDrillDownField, + actualTitle, + ); + } + + const iColor = colors.length + ? findIndexInOrder(order, colors[0], idToTitle[colors[0].guid]) + : -1; + + const preparedColumns = columns.map((item) => { + const actualTitle = idToTitle[item.guid] || item.title; + const indexInOrder = findIndexInOrder(order, item, actualTitle); + const itemDataType = idToDataType[item.guid] || item.data_type; + return { + ...item, + actualTitle, + indexInOrder, + itemDataType, + isMarkupDataType: isMarkupDataType(itemDataType), + isNumericalDataType: isNumericalDataType(itemDataType), + isDateType: isDateType(itemDataType), + isTreeDataType: isTreeDataType(itemDataType), + isUnsupportedDataType: isUnsupportedDataType(itemDataType), + isTableBarsSettingsEnabled: isTableBarsSettingsEnabled(item), + isTableFieldBackgroundSettingsEnabled: isTableFieldBackgroundSettingsEnabled(item), + canUseFieldForFiltering: isActionParamsEnable && canUseFieldForFiltering(item), + }; + }); + + const rows: TableCellsRow[] = data.map((values, rowIndex) => { + // eslint-disable-next-line complexity + const cells = preparedColumns.map((item) => { + const value = values[item.indexInOrder]; + + const cell: TableCommonCell = {value, fieldId: item.guid}; + + if (value === null) { + cell.value = null; + } else if (Array.isArray(value)) { + cell.value = JSON.stringify(value); + } else if (item.isMarkupDataType) { + cell.value = value; + cell.type = 'markup'; + } else if (item.isNumericalDataType) { + cell.type = 'number'; + + if (item.isTableBarsSettingsEnabled) { + const columnValues = columnValuesByColumn[item.guid]; + + const barCellProperties = getBarSettingsValue({ + rowValue: value, + field: item, + columnValues, + isTotalCell: false, + availablePalettes: colorsConfig.availablePalettes, + loadedColorPalettes: colorsConfig.loadedColorPalettes, + defaultColorPaletteId, + }); + + cell.value = barCellProperties.value; + cell.formattedValue = barCellProperties.formattedValue; + (cell as BarTableCell).barColor = barCellProperties.barColor; + } else { + cell.value = Number(value); + } + } else if (item.isTreeDataType) { + if (legend?.length) { + const currentLegend = legend[rowIndex][item.indexInOrder]; + + const fieldData = fields.find( + (field) => field.legend_item_id === currentLegend, + ); + + if (fieldData) { + cell.treeNode = String(cell.value); + const parsedTreeNode: string[] = JSON.parse(cell.treeNode); + cell.treeOffset = parsedTreeNode.length; + cell.treeNodeState = treeSet.has(cell.treeNode) ? 'open' : 'closed'; + cell.value = parsedTreeNode[parsedTreeNode.length - 1]; + } + } + } else if (item.isUnsupportedDataType) { + ChartEditor._setError({ + code: 'ERR.CHARTS.UNSUPPORTED_DATA_TYPE', + details: { + field: item.actualTitle, + }, + }); + } + + if (drillDownData && !item.isMarkupDataType && currentActiveDrillDownFieldIndex >= 0) { + if (values[currentActiveDrillDownFieldIndex] === null) { + cell.drillDownFilterValue = IS_NULL_FILTER_TEMPLATE; + } else if (typeof values[currentActiveDrillDownFieldIndex] !== 'object') { + cell.drillDownFilterValue = String(values[currentActiveDrillDownFieldIndex]); + } + } + + if (colors.length) { + const valueColor = values[iColor]; + if (valueColor !== null || colorsConfig.nullMode === GradientNullModes.AsZero) { + cell.color = Number(valueColor); + } + } + + if (item.isTableFieldBackgroundSettingsEnabled) { + cell.css = getFlatTableBackgroundStyles({ + column: item, + order, + values, + idToTitle, + backgroundColorsByMeasure, + currentRowIndex: rowIndex, + idToDataType, + loadedColorPalettes: colorsConfig.loadedColorPalettes, + availablePalettes: colorsConfig.availablePalettes, + defaultColorPaletteId, + }); + } + + if (isActionParamsEnable) { + if (item.canUseFieldForFiltering) { + if (item.isDateType) { + const actionParams = {}; + addActionParamValue(actionParams, item, value); + + cell.custom = {actionParams}; + } + } else { + // Need to add an empty object to exclude the measure field value from the filtering data + // (otherwise cell.value will be used by default) + cell.custom = { + actionParams: {}, + }; + } + } + + return cell; + }); + + return { + cells, + }; + }); + + if (colors.length) { + mapAndColorizeTableCells(rows, colorsConfig); + } + + const page = ChartEditor.getCurrentPage(); + const limit = shared.extraSettings?.limit; + const paginationDisabled = shared.extraSettings?.pagination !== 'on'; + + // Disable the paginator if all the data came initially + // Disabling the paginator enables front-end sorting (when clicking on the column header) + const shouldDisablePaginator = page === 1 && limit && limit > data.length; + + let footer; + + const oneLineAndPaginationDisabled = + (paginationDisabled || shouldDisablePaginator) && data.length === 1; + + if (!oneLineAndPaginationDisabled && totals.length) { + footer = getFooter({ + columns, + idToTitle, + idToDataType, + totals, + ChartEditor, + order, + columnValuesByColumn, + colorsConfig, + defaultColorPaletteId, + }); + } + + return {head, rows, footer}; +} + +export default prepareFlatTable; diff --git a/src/server/modes/charts/plugins/datalens/preparers/funnel/index.ts b/src/server/modes/charts/plugins/datalens/preparers/funnel/index.ts new file mode 100644 index 0000000..03a3d79 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/funnel/index.ts @@ -0,0 +1,213 @@ +import type {ChartData, FunnelSeries} from '@gravity-ui/chartkit/gravity-charts'; +import merge from 'lodash/merge'; + +import { + PlaceholderId, + WizardVisualizationId, + getFakeTitleOrTitle, + getFormatOptions, + isMeasureName, + isMeasureValue, + isNumberField, + isPseudoField, +} from '../../../../../../../shared'; +import {getColorsSettings} from '../../../helpers/color-palettes'; +import {getBaseChartConfig} from '../../gravity-charts/utils'; +import type {ColorValue} from '../../utils/color-helpers'; +import {getColorsByMeasureField, getThresholdValues} from '../../utils/color-helpers'; +import {getColor, getMountedColor} from '../../utils/constants'; +import { + chartKitFormatNumberWrapper, + findIndexInOrder, + isGradientMode, +} from '../../utils/misc-helpers'; +import {getFormattedValue} from '../helpers/get-formatted-value'; +import {getLegendColorScale} from '../helpers/legend'; +import type {PrepareFunctionArgs} from '../types'; + +function getSegmentColor({ + colorValue, + usedColors, + colors, + mountedColors, +}: { + colorValue: string | undefined; + usedColors: Map; + colors: string[]; + mountedColors: Record; +}) { + if (!usedColors.has(colorValue)) { + usedColors.set(colorValue, getColor(usedColors.size, colors)); + } + + if (colorValue && mountedColors[colorValue]) { + return getMountedColor({ + mountedColors, + colors, + value: colorValue, + }); + } + + return usedColors.get(colorValue); +} + +export function prepareFunnel({ + shared, + idToTitle, + idToDataType, + resultData, + placeholders, + labels, + colors: colorItems, + colorsConfig, + defaultColorPaletteId, +}: PrepareFunctionArgs): Partial { + const {data, order} = resultData; + const measures = placeholders.find((p) => p.id === PlaceholderId.Measures)?.items ?? []; + + const colorItem = colorItems?.[0]; + const colorField = colorItem + ? {...colorItem, data_type: idToDataType[colorItem.guid]} + : colorItem; + const colorIndex = colorField + ? findIndexInOrder(order, colorField, idToTitle[colorField.guid]) + : -1; + + const labelItem = labels?.[0]; + const labelField = labelItem + ? {...labelItem, data_type: idToDataType[labelItem.guid]} + : labelItem; + const labelIndex = labelField + ? findIndexInOrder(order, labelField, idToTitle[labelField.guid]) + : -1; + + const series: FunnelSeries = { + type: 'funnel', + name: '', + data: [], + dataLabels: { + enabled: Boolean(labelItem), + }, + }; + + data.forEach((values) => { + measures.forEach((measureItem) => { + const actualTitle = idToTitle[measureItem.guid]; + const i = findIndexInOrder(order, measureItem, actualTitle); + const _measureValue = values[i]; + if (_measureValue === null) { + return; + } + + const measureValue = Number(_measureValue); + const measureName = getFakeTitleOrTitle(measureItem); + + let colorValue; + if (isMeasureValue(colorField)) { + colorValue = measureValue; + } else if (isNumberField(colorField)) { + colorValue = values[colorIndex]; + } + + let labelValue; + if (isPseudoField(labelField)) { + labelValue = isMeasureValue(labelField) + ? chartKitFormatNumberWrapper(measureValue, { + lang: 'ru', + ...getFormatOptions(measureItem), + }) + : measureName; + } else { + labelValue = getFormattedValue(labelField, values[labelIndex]); + } + + series.data.push({ + value: Number(measureValue), + name: measureName, + label: labelValue as string | undefined, + custom: { + colorValue: colorValue === null ? null : Number(colorValue), + }, + }); + }); + }); + + const isColoringByMeasure = isGradientMode({ + colorField: colorField, + colorFieldDataType: colorField?.data_type, + colorsConfig, + }); + + const legend: ChartData['legend'] = { + enabled: shared?.extraSettings?.legendMode !== 'hide', + }; + + if (isColoringByMeasure) { + const points = series.data.map((d) => ({colorValue: d.custom.colorValue as unknown})); + const colorValues = points.map((p) => p.colorValue) as ColorValue[]; + + const gradientThresholdValues = getThresholdValues(colorsConfig, colorValues); + const gradientColors = getColorsByMeasureField({ + values: colorValues, + colorsConfig, + gradientThresholdValues, + }); + + series.data.forEach((d) => { + const pointColorValue = Number(d.custom.colorValue); + + if (gradientColors[pointColorValue]) { + // eslint-disable-next-line no-param-reassign + d.color = gradientColors[pointColorValue]; + } + }); + + const colorScale = getLegendColorScale({ + colorsConfig, + points, + }); + + Object.assign(legend, { + type: 'continuous', + colorScale, + }); + } else if (isMeasureName(colorItem)) { + const {mountedColors, colors} = getColorsSettings({ + field: colorField, + colorsConfig: colorsConfig, + defaultColorPaletteId, + availablePalettes: colorsConfig.availablePalettes, + customColorPalettes: colorsConfig.loadedColorPalettes, + }); + + const usedColors = new Map(); + series.data.forEach((d) => { + d.color = getSegmentColor({ + colorValue: d.name, + colors, + usedColors, + mountedColors, + }); + }); + } + + return merge( + getBaseChartConfig({ + shared, + visualization: {placeholders, id: WizardVisualizationId.Funnel}, + }), + { + series: { + data: [series], + }, + legend, + chart: { + zoom: {enabled: false}, + margin: { + left: 20, + right: 20, + }, + }, + }, + ); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/geopoint-with-cluster.ts b/src/server/modes/charts/plugins/datalens/preparers/geopoint-with-cluster.ts new file mode 100644 index 0000000..aa0155e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/geopoint-with-cluster.ts @@ -0,0 +1,4 @@ +import geopoint from './geopoint'; +import type {PrepareFunctionArgs} from './types'; + +export default (options: PrepareFunctionArgs) => geopoint(options, {isClusteredPoints: true}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/geopoint/__tests__/mocks/geopoints.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/geopoint/__tests__/mocks/geopoints.mock.ts new file mode 100644 index 0000000..54c0c6d --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/geopoint/__tests__/mocks/geopoints.mock.ts @@ -0,0 +1,74 @@ +import type {ServerColor} from '../../../../../../../../../shared'; +import {DATASET_FIELD_TYPES} from '../../../../../../../../../shared'; + +const DATASET_ID = 'j43msj9o23ge9'; + +export const COORDINATES_FIELD = { + datasetId: DATASET_ID, + title: 'CoordinatesField', + guid: 'cddd9cad-52a2-4232-8898-ade9a972c864', + data_type: DATASET_FIELD_TYPES.GEOPOINT, +}; + +export const DIMENSION_COLOR_FIELD = { + datasetId: DATASET_ID, + title: 'ColorField', + guid: '38e0b1f4-d46b-48a0-8905-6a6d1e61900d', + data_type: DATASET_FIELD_TYPES.STRING, + type: 'DIMENSION', +} as ServerColor; + +export const MEASURE_COLOR_FIELD = { + datasetId: DATASET_ID, + title: 'ColorField', + guid: '38e0b1f4-d46b-48a0-8905-6a6d1e61900d', + data_type: DATASET_FIELD_TYPES.STRING, + type: 'MEASURE', +} as ServerColor; + +export const PREPARE_FUNCTION_ARGS = { + colors: [], + colorsConfig: { + loadedColorPalettes: {}, + colors: ['defaultColor', 'blue', 'red', 'orange'], + gradientColors: [], + availablePalettes: { + custom: {id: 'custom', scheme: ['defaultColor', 'blue', 'red', 'orange']}, + }, + palette: 'custom', + }, + labels: [], + tooltips: [], + shared: {}, + placeholders: [ + { + id: 'geopoint', + items: [COORDINATES_FIELD], + }, + { + id: 'size', + items: [], + }, + ], + idToTitle: { + [COORDINATES_FIELD.guid]: COORDINATES_FIELD.title, + [DIMENSION_COLOR_FIELD.guid]: DIMENSION_COLOR_FIELD.title, + [MEASURE_COLOR_FIELD.guid]: MEASURE_COLOR_FIELD.title, + }, + idToDataType: { + [COORDINATES_FIELD.guid]: DATASET_FIELD_TYPES.GEOPOINT, + [DIMENSION_COLOR_FIELD.guid]: DATASET_FIELD_TYPES.STRING, + [MEASURE_COLOR_FIELD.guid]: DATASET_FIELD_TYPES.STRING, + }, + resultData: { + data: [], + order: [], + totals: [], + }, + ChartEditor: { + getTranslation: (key: string) => key, + getWidgetConfig: () => ({}), + updateHighchartsConfig: () => {}, + }, + features: [], +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/geopoint/__tests__/prepareGeopoint.test.ts b/src/server/modes/charts/plugins/datalens/preparers/geopoint/__tests__/prepareGeopoint.test.ts new file mode 100644 index 0000000..48b79cd --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/geopoint/__tests__/prepareGeopoint.test.ts @@ -0,0 +1,157 @@ +import cloneDeep from 'lodash/cloneDeep'; +import merge from 'lodash/merge'; + +import type {PrepareFunctionArgs} from '../../types'; +import {DEFAULT_ICON_COLOR} from '../constants'; +import prepareGeopoint from '../index'; +import type {GeopointPointConfig} from '../types'; + +import { + COORDINATES_FIELD, + DIMENSION_COLOR_FIELD, + MEASURE_COLOR_FIELD, + PREPARE_FUNCTION_ARGS, +} from './mocks/geopoints.mock'; + +function getPrepareFunctionArgs(options: Partial = {}) { + return merge(cloneDeep(PREPARE_FUNCTION_ARGS), options) as unknown as PrepareFunctionArgs; +} + +describe('prepareGeopoint', () => { + test('Color field is empty: should use default color', () => { + const options = getPrepareFunctionArgs({ + colors: [], + resultData: { + data: [ + ['[43.2050370,76.6612660]'], + ['[43.2050370,76.6612660]'], + ['[40.2050370,76.6612660]'], + [null], + ], + order: [COORDINATES_FIELD], + } as unknown as PrepareFunctionArgs['resultData'], + }); + const result = prepareGeopoint(options); + + const points = result + .map((item) => (item as any).collection.children) + .flat(2) as GeopointPointConfig[]; + const pointColors = points.map((point) => ({iconColor: point.options.iconColor})); + + expect(pointColors).toEqual([ + {iconColor: DEFAULT_ICON_COLOR}, + {iconColor: DEFAULT_ICON_COLOR}, + {iconColor: DEFAULT_ICON_COLOR}, + ]); + expect(result[0].options?.colorDictionary).not.toBeDefined(); + }); + + test('Colorize by dimension, colorsConfig empty: should use different colors', () => { + const options = getPrepareFunctionArgs({ + colors: [DIMENSION_COLOR_FIELD], + colorsConfig: {} as unknown as PrepareFunctionArgs['colorsConfig'], + resultData: { + data: [ + ['[43.2050370,76.6612660]', 'Color1'], + ['[42.2050370,76.6612660]', 'Color2'], + ['[41.2050370,76.6612660]', 'Color3'], + [null, null], + ], + order: [COORDINATES_FIELD, DIMENSION_COLOR_FIELD], + } as unknown as PrepareFunctionArgs['resultData'], + }); + const result = prepareGeopoint(options); + + const points = result + .map((item) => (item as any).collection.children) + .flat(2) as GeopointPointConfig[]; + const pointColors = points.map((point) => ({iconColor: point.options.iconColor})); + + expect(pointColors).toEqual([ + {iconColor: 'defaultColor'}, + {iconColor: 'blue'}, + {iconColor: 'red'}, + ]); + expect(result[0].options?.colorDictionary).toEqual({ + Color1: 'defaultColor', + Color2: 'blue', + Color3: 'red', + }); + }); + + test('Colorize by dimension, colorsConfig not empty: should use different colors for same coordinates, different color values', () => { + const options = getPrepareFunctionArgs({ + colors: [DIMENSION_COLOR_FIELD], + colorsConfig: { + fieldGuid: DIMENSION_COLOR_FIELD.guid, + mountedColors: { + Color1: '2', + Color2: '1', + }, + } as unknown as PrepareFunctionArgs['colorsConfig'], + resultData: { + data: [ + ['[43.2050370,76.6612660]', 'Color1'], + ['[43.2050370,76.6612660]', 'Color2'], + ['[43.2050370,76.6612660]', 'Color3'], + [null, null], + ], + order: [COORDINATES_FIELD, DIMENSION_COLOR_FIELD], + } as unknown as PrepareFunctionArgs['resultData'], + }); + const result = prepareGeopoint(options); + + const points = result + .map((item) => (item as any).collection.children) + .flat(2) as GeopointPointConfig[]; + const pointColors = points.map((point) => ({iconColor: point.options.iconColor})); + + expect(pointColors).toEqual([ + {iconColor: 'red'}, + {iconColor: 'blue'}, + {iconColor: 'defaultColor'}, + ]); + expect(result[0].options?.colorDictionary).toEqual({ + Color1: 'red', + Color2: 'blue', + Color3: 'defaultColor', + }); + }); + + test('Colorize by measure: should use different colors for same coordinates, different color values', () => { + const options = getPrepareFunctionArgs({ + colors: [MEASURE_COLOR_FIELD], + colorsConfig: { + gradientMode: '2-point', + gradientPalette: 'blue', + gradientColors: ['#0044A3', '#8CCBFF'], + } as unknown as PrepareFunctionArgs['colorsConfig'], + resultData: { + data: [ + ['[40.2050370,76.6612660]', '1'], + ['[40.2050370,76.6612660]', '10.0'], + ], + order: [COORDINATES_FIELD, MEASURE_COLOR_FIELD], + } as unknown as PrepareFunctionArgs['resultData'], + }); + const result = prepareGeopoint(options); + + const points = result + .map((item) => (item as any).collection.children) + .flat(2) as GeopointPointConfig[]; + const pointColors = points.map((point) => ({ + iconColor: point.options.iconColor.replace(/[ \n]+/g, ''), + })); + + const gradientColor1 = 'rgb(0,68,163)'; + const gradientColor2 = 'rgb(140,203,255)'; + expect(pointColors).toEqual([ + { + iconColor: gradientColor1, + }, + { + iconColor: gradientColor2, + }, + ]); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/geopoint/constants.ts b/src/server/modes/charts/plugins/datalens/preparers/geopoint/constants.ts new file mode 100644 index 0000000..c761cf1 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/geopoint/constants.ts @@ -0,0 +1,3 @@ +export const DEFAULT_ICON_COLOR = 'rgb(77, 162, 241)'; + +export const DEFAULT_POINT_RADIUS = 2; diff --git a/src/server/modes/charts/plugins/datalens/preparers/geopoint/index.ts b/src/server/modes/charts/plugins/datalens/preparers/geopoint/index.ts new file mode 100644 index 0000000..28329bb --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/geopoint/index.ts @@ -0,0 +1,514 @@ +import escape from 'lodash/escape'; +import set from 'lodash/set'; + +import type { + PointSizeConfig, + ServerField, + ServerFieldFormatting, + VisualizationLayerShared, +} from '../../../../../../../shared'; +import { + MARKUP_TYPE, + MINIMUM_FRACTION_DIGITS, + WRAPPED_MARKDOWN_KEY, + ZoomMode, + getFakeTitleOrTitle, + getFormatOptions, + isMarkupDataType, +} from '../../../../../../../shared'; +import {wrapHtml} from '../../../../../../../shared/utils/ui-sandbox'; +import {getColorsSettings} from '../../../helpers/color-palettes'; +import {getColorsByMeasureField, getThresholdValues} from '../../utils/color-helpers'; +import {GEO_MAP_LAYERS_LEVEL, getMountedColor} from '../../utils/constants'; +import type {Coordinate, GradientOptions} from '../../utils/geo-helpers'; +import { + getExtremeValues, + getFlattenCoordinates, + getGradientMapOptions, + getLayerAlpha, + getMapBounds, + getMapState, +} from '../../utils/geo-helpers'; +import { + chartKitFormatNumberWrapper, + findIndexInOrder, + getPointRadius, + getTitleInOrder, + isGradientMode, + isNumericalDataType, +} from '../../utils/misc-helpers'; +import {addActionParamValue} from '../helpers/action-params'; +import type {PrepareFunctionArgs} from '../types'; + +import {DEFAULT_ICON_COLOR, DEFAULT_POINT_RADIUS} from './constants'; +import type {GeopointMapOptions, GeopointPointConfig, GeopointPrepareResult} from './types'; + +type GetPointConfigArgs = { + stringifyedCoordinates: string; + columnIndex: number; + geopointsConfig: PointSizeConfig; +}; + +const getPointConfig = ({ + stringifyedCoordinates, + columnIndex, + geopointsConfig, +}: GetPointConfigArgs): GeopointPointConfig => { + const coordinates = JSON.parse(stringifyedCoordinates); + + return { + feature: { + geometry: { + type: 'Point', + coordinates, + }, + properties: { + radius: geopointsConfig.radius || DEFAULT_POINT_RADIUS, + rawText: true, + }, + }, + options: { + iconColor: DEFAULT_ICON_COLOR, + preset: 'chartkit#chips', + zIndex: GEO_MAP_LAYERS_LEVEL.GEOPOINT, + }, + columnIndex, + }; +}; + +const prepareValue = ( + value: number | string | any, + valueType: string | undefined, + formatting: ServerFieldFormatting | undefined, +) => { + if (valueType && isNumericalDataType(valueType)) { + return chartKitFormatNumberWrapper(value as unknown as number, { + lang: 'ru', + ...(formatting ?? {precision: valueType === 'float' ? MINIMUM_FRACTION_DIGITS : 0}), + }); + } + + return value; +}; + +type SetPointPropertyArgs = { + point: GeopointPointConfig | undefined; + propName: string; + propValue: any; + propType?: string; + formatting?: ServerFieldFormatting; +}; + +const setPointProperty = ({ + point, + propName, + propValue, + propType, + formatting, +}: SetPointPropertyArgs) => { + if (!point) { + return; + } + + point.feature.properties[propName] = prepareValue(propValue, propType, formatting); +}; + +// eslint-disable-next-line complexity +export function prepareGeopoint(options: PrepareFunctionArgs, {isClusteredPoints = false} = {}) { + const { + colors, + colorsConfig, + tooltips, + tooltipConfig, + labels, + placeholders, + resultData: {data, order}, + idToTitle, + shared, + idToDataType, + ChartEditor, + defaultColorPaletteId, + } = options; + const widgetConfig = ChartEditor.getWidgetConfig(); + const isActionParamsEnabled = widgetConfig?.actionParams?.enable; + const geopointsConfig = (options.geopointsConfig || {}) as PointSizeConfig; + const layerSettings = (options.layerSettings || + {}) as VisualizationLayerShared['visualization']['layerSettings']; + + const ALPHA = getLayerAlpha(layerSettings); + + const allPoints: Record = {}; + const colorValues: number[] = []; + + const color = colors[0]; + const colorFieldDataType = color ? idToDataType[color.guid] : null; + + const gradientMode = + color && + colorFieldDataType && + isGradientMode({colorField: color, colorFieldDataType, colorsConfig}); + + const size = placeholders[1].items[0]; + const coordinates = placeholders[0].items; + const updatedTooltips = [...tooltips]; + + const label = labels[0]; + + let colorData: Record = {}, + gradientOptions: GradientOptions | null = null, + sizeMinValue: number | undefined, + sizeMaxValue: number | undefined, + leftBot: Coordinate | undefined, + rightTop: Coordinate | undefined; + const colorDictionary: Record = {}; + + const getTooltip = (dataTitle: string) => + updatedTooltips.find((tooltip) => dataTitle === tooltip.title); + + // we get the min and max for the radius, as well as the gradient values + if (size || color) { + data.forEach((values) => { + values.forEach((columnData, columnIndex) => { + if (columnData === 'null' || columnData === null) { + return; + } + + const dataTitle = getTitleInOrder(order, columnIndex, coordinates); + + if (size && size.title === dataTitle) { + const value = Number(columnData); + [sizeMinValue, sizeMaxValue] = getExtremeValues({ + min: sizeMinValue, + max: sizeMaxValue, + value, + }); + } + + if (color && color.title === dataTitle) { + const colorValue = Number(columnData); + if (!isNaN(colorValue)) { + colorValues.push(colorValue); + } + } + }); + }); + } + + let mountedColors: Record = {}; + let paletteColors: string[] = []; + + if (gradientMode) { + const gradientThresholdValues = getThresholdValues(colorsConfig, colorValues); + const {min, rangeMiddle, max} = gradientThresholdValues; + colorData = getColorsByMeasureField({ + values: colorValues, + colorsConfig, + gradientThresholdValues, + }); + + gradientOptions = { + min: min, + mid: min + rangeMiddle, + max: max, + }; + } else { + const colorSettings = getColorsSettings({ + field: color, + colorsConfig, + defaultColorPaletteId, + availablePalettes: colorsConfig.availablePalettes, + customColorPalettes: colorsConfig.loadedColorPalettes, + }); + + mountedColors = colorSettings.mountedColors; + paletteColors = colorSettings.colors; + } + + let colorIndex = -1; + if (color) { + const cTitle = idToTitle[color.guid]; + colorIndex = findIndexInOrder(order, color, cTitle); + } + + const colorsByValue = new Map(); + + data.forEach((values, valuesIndex) => { + // at each pass of the string, we collect the points into an array, assuming, + // that there can be more than one pair of coordinates in a row + allPoints[`points-${valuesIndex}`] = []; + const actionParams: Record = {}; + + // eslint-disable-next-line complexity + values.forEach((columnData, columnIndex) => { + if (columnData === 'null' || columnData === null) { + return; + } + + const dataTitle = getTitleInOrder(order, columnIndex, coordinates); + + if (coordinates.findIndex(({title}) => title === dataTitle) !== -1) { + const current = JSON.parse(columnData); + + // adjusting the borders of the map + [leftBot, rightTop] = getMapBounds({leftBot, rightTop, current}); + + allPoints[`points-${valuesIndex}`].push( + getPointConfig({ + columnIndex, + stringifyedCoordinates: columnData, + geopointsConfig, + }), + ); + } + + if (size && size.title === dataTitle) { + const radius = getPointRadius({ + current: Number(columnData), + min: sizeMinValue!, + max: sizeMaxValue!, + geopointsConfig, + }); + + allPoints[`points-${valuesIndex}`].forEach((point) => + setPointProperty({ + point, + propName: 'radius', + propValue: radius, + }), + ); + } + + if (label && label.title === dataTitle) { + const formatting = getFormatOptions(label); + allPoints[`points-${valuesIndex}`].forEach((point) => + setPointProperty({ + point, + propName: 'label', + propValue: columnData, + propType: label.data_type, + formatting, + }), + ); + } + + if (color && color.title === dataTitle) { + const colorValue = escape(values[colorIndex] as string); + let iconColor = DEFAULT_ICON_COLOR; + + if (colorValue) { + if (gradientMode) { + const key = isNaN(Number(colorValue)) + ? colorValue + : String(Number(colorValue)); + + if (colorData[key]) { + iconColor = colorData[key]; + } + } else { + let mountedColor = getMountedColor({ + colors: paletteColors, + mountedColors, + value: colorValue, + }); + + if (!mountedColor || mountedColor === 'auto') { + if (!colorsByValue.has(colorValue)) { + const key = + colorsConfig.colors[ + colorsByValue.size % colorsConfig.colors.length + ]; + colorsByValue.set(colorValue, key); + } + + mountedColor = colorsByValue.get(colorValue) || DEFAULT_ICON_COLOR; + } + + iconColor = mountedColor; + colorDictionary[colorValue] = mountedColor; + } + } + + allPoints[`points-${valuesIndex}`].forEach((point) => { + point.options.iconColor = iconColor; + }); + } + + const tooltipField = tooltips.length + ? (getTooltip(dataTitle) as ServerField) + : undefined; + if (tooltipField) { + // Due to the fact that a field that already exists in another section can be installed in a section with a tooltip, + // it (the field in the tooltip and other section) comes to the order array in a single instance, + // which in turn can lead to an incorrect order of displaying fields in the tooltip. + // Therefore, before installing the tooltip, we remember its correct index + const index = updatedTooltips.findIndex((t) => t.title === dataTitle); + const shouldUseFieldTitle = tooltipConfig?.fieldTitle !== 'off'; + const itemTitle = shouldUseFieldTitle ? getFakeTitleOrTitle(tooltipField) : ''; + + const pointData: Record = {}; + + if (isMarkupDataType(tooltipField.data_type)) { + pointData.key = itemTitle; + pointData.value = columnData; + } else { + const tooltipFieldFormatting = getFormatOptions(tooltipField); + const value = prepareValue( + columnData, + tooltipField.data_type, + tooltipFieldFormatting, + ); + const text = itemTitle ? `${itemTitle}: ${value}` : value; + + switch (tooltipField?.markupType) { + case MARKUP_TYPE.markdown: { + pointData[WRAPPED_MARKDOWN_KEY] = text; + break; + } + case MARKUP_TYPE.html: { + pointData.text = wrapHtml(text); + break; + } + default: { + pointData.text = text; + break; + } + } + } + + allPoints[`points-${valuesIndex}`].forEach((point: GeopointPointConfig) => { + if (!point) { + return; + } + + if (!point.feature.properties.data) { + point.feature.properties.data = []; + } + + if (index === 0 && tooltipConfig?.color !== 'off') { + pointData.color = point.options.iconColor; + } + + point.feature.properties.data[index] = pointData; + }); + + addActionParamValue(actionParams, tooltipField, columnData); + } + + if (isActionParamsEnabled) { + allPoints[`points-${valuesIndex}`].forEach((point: GeopointPointConfig) => { + set(point, 'feature.properties.custom.actionParams', actionParams); + }); + } + }); + }); + + if (tooltips.some((item) => item.markupType === MARKUP_TYPE.markdown)) { + ChartEditor.updateConfig({useMarkdown: true}); + } + + if (tooltips.some((item) => item.markupType === MARKUP_TYPE.html)) { + ChartEditor.updateConfig({useHtml: true}); + } + + let mapOptions: GeopointMapOptions = { + opacity: ALPHA, + showCustomLegend: true, + }; + + if (shared.extraSettings && shared.extraSettings.legendMode === 'hide') { + mapOptions.showCustomLegend = false; + } + + if (layerSettings.id) { + mapOptions.geoObjectId = layerSettings.id; + } + + mapOptions.layerTitle = + layerSettings.name || ChartEditor.getTranslation('wizard.prepares', 'label_new-layer'); + + if (size) { + mapOptions = { + ...mapOptions, + sizeMinValue, + sizeMaxValue, + sizeTitle: size.fakeTitle || idToTitle[size.guid] || size.title, + }; + } + + const shouldSetBounds = + shared?.extraSettings?.zoomMode !== ZoomMode.Manual && + shared?.extraSettings?.mapCenterMode !== ZoomMode.Manual; + const {zoom, center} = getMapState(shared, [leftBot, rightTop]); + + ChartEditor?.updateHighchartsConfig({state: {zoom, center}}); + + if (gradientOptions) { + const colorTitle = color.fakeTitle || idToTitle[color.guid] || color.title; + + mapOptions = { + ...mapOptions, + ...getGradientMapOptions(colorsConfig, colorTitle, gradientOptions), + }; + + const resultData: GeopointPrepareResult = { + collection: { + children: getFlattenCoordinates(Object.values(allPoints)), + }, + options: mapOptions, + }; + if (shouldSetBounds) { + resultData.bounds = [leftBot, rightTop]; + } + + return [resultData]; + } else { + mapOptions = { + ...mapOptions, + }; + + if (color) { + mapOptions.colorDictionary = colorDictionary; + mapOptions.mode = 'dictionary'; + mapOptions.colorTitle = color.fakeTitle || idToTitle[color.guid] || color.title; + } + } + + const resultData: GeopointPrepareResult = { + options: mapOptions, + }; + + if (shouldSetBounds) { + resultData.bounds = [leftBot, rightTop]; + } + + const flatternCoordinates = getFlattenCoordinates(Object.values(allPoints)); + + if (isClusteredPoints) { + return [ + { + ...resultData, + clusterer: flatternCoordinates, + options: { + ...resultData.options, + clusterIconLayout: 'default#pieChart', + iconPieChartCoreRadius: 15, + iconPieChartRadius: 20, + iconPieChartStrokeWidth: 1, + hasBalloon: false, + margin: 20, + }, + }, + ]; + } + + return [ + { + ...resultData, + collection: { + children: flatternCoordinates, + }, + }, + ]; +} + +export default prepareGeopoint; diff --git a/src/server/modes/charts/plugins/datalens/preparers/geopoint/types.ts b/src/server/modes/charts/plugins/datalens/preparers/geopoint/types.ts new file mode 100644 index 0000000..8b2fe59 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/geopoint/types.ts @@ -0,0 +1,44 @@ +import type {ServerTooltipConfig} from '../../../../../../../shared'; +import type {Coordinate} from '../../utils/geo-helpers'; + +export type GeopointPointConfig = { + feature: { + geometry: { + type: 'Point'; + coordinates: Coordinate[]; + }; + properties: Record; + }; + options: { + iconColor: string; + preset: string; + zIndex: number; + }; + columnIndex: number; +}; + +export type GeopointMapOptions = { + radius?: number; + dissipating?: boolean; + opacity: number; + intensityOfMidpoint?: number; + gradient?: Record; + showCustomLegend?: boolean; + colorTitle?: string; + geoObjectId?: string; + layerTitle?: string; + sizeMinValue?: number; + colorDictionary?: Record; + sizeMaxValue?: number; + mode?: string; + sizeTitle?: string; + tooltipConfig?: ServerTooltipConfig; +}; + +export type GeopointPrepareResult = { + options?: GeopointMapOptions; + bounds?: (Coordinate | undefined)[]; + collection?: { + children: unknown[]; + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/geopolygon.ts b/src/server/modes/charts/plugins/datalens/preparers/geopolygon.ts new file mode 100644 index 0000000..4bf7e44 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/geopolygon.ts @@ -0,0 +1,505 @@ +import set from 'lodash/set'; + +import type { + MarkupItem, + ServerField, + ServerFieldFormatting, + ServerTooltip, + StringParams, + VisualizationLayerShared, + WrappedMarkdown, +} from '../../../../../../shared'; +import { + DATASET_FIELD_TYPES, + MARKUP_TYPE, + MINIMUM_FRACTION_DIGITS, + WRAPPED_MARKDOWN_KEY, + ZoomMode, + getFakeTitleOrTitle, + getFormatOptions, + isHtmlField, + isMarkdownField, +} from '../../../../../../shared'; +import {wrapHtml} from '../../../../../../shared/utils/ui-sandbox'; +import {hexToRgb} from '../utils/color-helpers'; +import {GEO_MAP_LAYERS_LEVEL} from '../utils/constants'; +import type {Coordinate} from '../utils/geo-helpers'; +import { + colorizeGeoByGradient, + colorizeGeoByPalette, + getFlattenCoordinates, + getGradientMapOptions, + getLayerAlpha, + getMapBounds, + getMapState, +} from '../utils/geo-helpers'; +import { + chartKitFormatNumberWrapper, + getTitleInOrder, + isGradientMode, + isNumericalDataType, +} from '../utils/misc-helpers'; + +import {addActionParamValue} from './helpers/action-params'; +import type {PrepareFunctionArgs} from './types'; + +type GeoPolygonTooltipData = { + text?: string; + color?: string; + key?: string; + value?: MarkupItem; +} & Partial; + +type GeopolygonConfig = { + geometry: { + type: 'Polygon'; + coordinates: Coordinate[][]; + }; + options: { + strokeWidth?: number; + iconColor?: string; + strokeColor?: string; + zIndex?: number; + fillColor?: string; + fillColorDefault?: string; + }; + columnIndex?: number; + feature?: string; + + type?: string; + + properties: { + rawText?: boolean; + colorIndex?: number | null; + data?: GeoPolygonTooltipData[]; + }; +}; + +type GeopolygonMapOptions = { + fillOpacity?: number; + fillOpacityHover?: number; + fillColorEmptyPolygon?: string; + strokeColorHover?: string; + strokeWidthHover?: number; + radius?: number; + dissipating?: boolean; + opacity?: number; + intensityOfMidpoint?: number; + gradient?: Record; + showLegend?: boolean; + showCustomLegend?: boolean; + colorTitle?: string; + geoObjectId?: string; + layerTitle?: string; + strokeWidth?: number; + colorDictionary?: Record; + mode?: string; +}; + +type MeasureColorData = Record< + string, + { + backgroundColor: string; + color: string; + value: number | null; + } +>; + +type DimensionColorData = Record< + string, + { + backgroundColor?: string | undefined; + colorIndex?: number | undefined; + } +>; + +type SomeColorData = MeasureColorData | DimensionColorData; + +function prepareFormattedValue(args: { + dataType: string | undefined; + value: number | string | any; + formatting: ServerFieldFormatting | undefined; +}) { + const {dataType, formatting, value} = args; + + if (dataType && isNumericalDataType(dataType)) { + return chartKitFormatNumberWrapper(Number(value), { + lang: 'ru', + ...(formatting ?? { + precision: dataType === 'float' ? MINIMUM_FRACTION_DIGITS : 0, + }), + }); + } + + return value; +} + +// eslint-disable-next-line complexity +function prepareGeopolygon(options: PrepareFunctionArgs) { + const DEFAULT_COLOR = 'rgb(77, 162, 241)'; + + const { + colors, + colorsConfig, + tooltips, + tooltipConfig, + placeholders, + resultData: {data, order}, + idToTitle, + shared, + idToDataType, + ChartEditor, + defaultColorPaletteId, + } = options; + const widgetConfig = ChartEditor.getWidgetConfig(); + const isActionParamsEnabled = widgetConfig?.actionParams?.enable; + const layerSettings = (options.layerSettings || + {}) as VisualizationLayerShared['visualization']['layerSettings']; + + const allPolygons: Record = {}; + const hashTable: Record = {}; + + const color = colors[0]; + const colorFieldDataType = color ? idToDataType[color.guid] : null; + const coordinates = placeholders[0].items; + + const gradientMode = + color && + colorFieldDataType && + isGradientMode({colorField: color, colorFieldDataType, colorsConfig}); + + let colorizedResult: + | ReturnType + | ReturnType; + + let colorData = {}, + leftBot: Coordinate | undefined, + rightTop: Coordinate | undefined; + let colorDictionary: Record = {}; + + const getPolygonConfig = ({ + coordinates, + colorData, + columnIndex, + key, + }: { + coordinates: Coordinate[][]; + colorData: SomeColorData; + columnIndex: number; + key: string; + }): GeopolygonConfig => { + const options: GeopolygonConfig['options'] = {}; + const properties: {colorIndex?: number | null; rawText?: boolean} = {}; + + options.strokeColor = '#FFF'; + options.zIndex = GEO_MAP_LAYERS_LEVEL.POLYGON; + + if (colorData && colorData[key]) { + if (gradientMode) { + options.fillColor = colorData[key].backgroundColor; + options.fillColorDefault = colorData[key].backgroundColor; + + properties.colorIndex = (colorData as MeasureColorData)[key].value; + } else { + const {r, g, b} = hexToRgb((colorData as MeasureColorData)[key].backgroundColor); + options.fillColor = `rgb(${r}, ${g}, ${b})`; + options.fillColorDefault = `rgb(${r}, ${g}, ${b})`; + properties.colorIndex = (colorData as DimensionColorData)[key].colorIndex; + } + + if (colorsConfig.polygonBorders === 'hide') { + options.strokeWidth = 0; + } + + options.iconColor = colorData[key] ? colorData[key].backgroundColor : DEFAULT_COLOR; + } + + return { + geometry: { + type: 'Polygon', + coordinates, + }, + properties, + options, + columnIndex, + }; + }; + + const setPolygonTooltip = ({ + polygon, + text, + tooltipIndex, + }: { + polygon: GeopolygonConfig | undefined; + text: string; + tooltipIndex: number; + }) => { + if (!polygon) { + return; + } + + const tooltip = tooltips[tooltipIndex]; + const formatting = getFormatOptions(tooltip); + const formattedText = prepareFormattedValue({ + dataType: tooltip.data_type, + formatting, + value: text, + }); + const shouldUseFieldTitle = tooltipConfig?.fieldTitle !== 'off'; + const itemTitle = shouldUseFieldTitle ? getFakeTitleOrTitle(tooltip as ServerField) : ''; + const tooltipText = itemTitle ? `${itemTitle}: ${formattedText}` : formattedText; + const isMarkupField = tooltip?.data_type === DATASET_FIELD_TYPES.MARKUP; + const useHtml = isHtmlField(tooltip); + + if (isMarkupField || isMarkdownField(tooltip) || useHtml) { + polygon.properties.rawText = true; + } + + if (useHtml) { + ChartEditor.updateConfig({useHtml: true}); + } + + let tooltipData; + if (isMarkupField) { + tooltipData = {key: itemTitle, value: formattedText}; + } else if (tooltip?.markupType === MARKUP_TYPE.markdown) { + tooltipData = {[WRAPPED_MARKDOWN_KEY]: tooltipText}; + } else if (tooltip?.markupType === MARKUP_TYPE.html) { + tooltipData = {text: wrapHtml(tooltipText)}; + } else { + tooltipData = {text: tooltipText}; + } + + if (gradientMode) { + if (!polygon.properties.data) { + polygon.properties.data = []; + } + + if ( + !polygon.properties.data.some( + (entry: {text?: string}) => entry.text === tooltipText, + ) + ) { + polygon.properties.data[tooltipIndex] = { + ...tooltipData, + }; + } + } else { + if (!polygon.properties.data) { + polygon.properties.data = []; + } + + polygon.properties.data[tooltipIndex] = { + ...tooltipData, + }; + } + }; + + if (color) { + data.forEach((values, valuesIndex) => { + hashTable[`polygons-${valuesIndex}`] = []; + + values.forEach((columnData, columnIndex) => { + if (columnData === 'null' || columnData === null) { + return; + } + + const dataTitle = getTitleInOrder(order, columnIndex, coordinates); + + if (coordinates.findIndex(({title}) => title === dataTitle) !== -1) { + (hashTable[`polygons-${valuesIndex}`] as string[]).push(columnData); + } + + if (color.title === dataTitle) { + hashTable[`polygons-${valuesIndex}`] = ( + hashTable[`polygons-${valuesIndex}`] as string[] + ).map((_coord: string, coordIndex: number) => { + return {[`polygons-${valuesIndex}-${coordIndex}`]: columnData}; + }); + } + }); + }); + + if (gradientMode) { + colorizedResult = colorizeGeoByGradient(hashTable, colorsConfig); + + colorData = colorizedResult.colorData; + } else { + colorizedResult = colorizeGeoByPalette({ + data: hashTable, + colorsConfig, + colorField: color, + defaultColorPaletteId, + }); + + colorData = colorizedResult.colorData; + colorDictionary = colorizedResult.colorDictionary; + } + } + + const getTooltipIndex = ({ + tooltips, + dataTitle, + }: { + tooltips: ServerTooltip[]; + dataTitle: string; + }) => { + if (!tooltips || tooltips.length === 0) { + return -1; + } + + return tooltips.findIndex((tooltip) => tooltip.title === dataTitle); + }; + + data.forEach((values, valuesIndex) => { + allPolygons[`polygons-${valuesIndex}`] = []; + const polygons = allPolygons[`polygons-${valuesIndex}`]; + const actionParams: StringParams = {}; + + values.forEach((columnData, columnIndex) => { + if (columnData === 'null' || columnData === null) { + return; + } + + const dataTitle = getTitleInOrder(order, columnIndex, coordinates); + if (coordinates.findIndex(({title}) => title === dataTitle) !== -1) { + const polygonCoordinates: Coordinate[][] = JSON.parse(columnData); + const flattenCoordinates = getFlattenCoordinates( + polygonCoordinates, + ) as Coordinate[]; + + // we go through the points of the polygon and adjust the boundaries of the map + flattenCoordinates.forEach((current: Coordinate) => { + [leftBot, rightTop] = getMapBounds({leftBot, rightTop, current}); + }); + + polygons.push( + getPolygonConfig({ + colorData, + columnIndex, + coordinates: polygonCoordinates, + key: `polygons-${valuesIndex}-${columnIndex}`, + }), + ); + } + + const tooltipIndex = getTooltipIndex({tooltips, dataTitle}); + const tooltipField = tooltips[tooltipIndex]; + if (tooltipIndex !== -1 && polygons?.length && tooltipField) { + polygons.forEach((polygon) => { + setPolygonTooltip({ + polygon, + text: columnData, + tooltipIndex, + }); + }); + + if (tooltipIndex === 0 && tooltipConfig?.color !== 'off') { + polygons[0]!.properties!.data![0].color = + polygons[0].options.iconColor || DEFAULT_COLOR; + } + + addActionParamValue(actionParams, tooltipField as ServerField, columnData); + } + }); + + if (isActionParamsEnabled) { + polygons.forEach((polygon) => { + if (polygon) { + set(polygon, 'properties.custom.actionParams', actionParams); + } + }); + } + }); + + const polygons = { + type: 'FeatureCollection', + features: getFlattenCoordinates(Object.values(allPolygons)).map((item) => { + (item as GeopolygonConfig).type = 'Feature'; + + return item; + }), + }; + + const fillOpacity = getLayerAlpha(layerSettings); + let fillOpacityHover = fillOpacity + 0.1; + + fillOpacityHover = fillOpacityHover > 1 ? 1 : fillOpacityHover; + + let mapOptions: GeopolygonMapOptions = { + fillOpacity, + fillOpacityHover, + fillColorEmptyPolygon: DEFAULT_COLOR, + strokeColorHover: '#FFF', + strokeWidthHover: 2, + showCustomLegend: true, + }; + + if (shared.extraSettings && shared.extraSettings.legendMode === 'hide') { + mapOptions.showLegend = false; + mapOptions.showCustomLegend = false; + } + + if (layerSettings.id) { + mapOptions.geoObjectId = layerSettings.id; + } + + mapOptions.layerTitle = + layerSettings.name || + options.ChartEditor.getTranslation('wizard.prepares', 'label_new-layer'); + + if (colorsConfig.polygonBorders === 'hide') { + mapOptions.strokeWidth = 0; + } + + const shouldSetBounds = + shared?.extraSettings?.zoomMode !== ZoomMode.Manual && + shared?.extraSettings?.mapCenterMode !== ZoomMode.Manual; + const {zoom, center} = getMapState(shared, [leftBot, rightTop]); + + ChartEditor?.updateHighchartsConfig({state: {zoom, center}}); + + if (gradientMode) { + const colorTitle = color.fakeTitle || idToTitle[color.guid] || color.title; + + mapOptions = { + ...mapOptions, + ...getGradientMapOptions( + colorsConfig, + colorTitle, + colorizedResult! as ReturnType, + ), + }; + + return [ + { + polygonmap: { + polygons, + }, + options: mapOptions, + bounds: shouldSetBounds ? [leftBot, rightTop] : undefined, + }, + ]; + } else { + if (color) { + mapOptions = { + ...mapOptions, + colorDictionary, + mode: 'dictionary', + colorTitle: color.fakeTitle || idToTitle[color.guid] || color.title, + }; + } + + return [ + { + polygonmap: { + polygons, + }, + options: mapOptions, + bounds: shouldSetBounds ? [leftBot, rightTop] : undefined, + }, + ]; + } +} + +export default prepareGeopolygon; diff --git a/src/server/modes/charts/plugins/datalens/preparers/heatmap.ts b/src/server/modes/charts/plugins/datalens/preparers/heatmap.ts new file mode 100644 index 0000000..b1f18cc --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/heatmap.ts @@ -0,0 +1,214 @@ +import {type RGBColor, type VisualizationLayerShared, ZoomMode} from '../../../../../../shared'; +import {getCurrentGradient, getRgbColors, getThresholdValues} from '../utils/color-helpers'; +import type {Coordinate} from '../utils/geo-helpers'; +import { + getExtremeValues, + getFlattenCoordinates, + getLayerAlpha, + getMapBounds, + getMapState, +} from '../utils/geo-helpers'; +import {getTitleInOrder} from '../utils/misc-helpers'; + +import type {PrepareFunctionArgs} from './types'; + +type HeatmapPointConfig = { + type: 'Feature'; + geometry: { + type: 'Point'; + coordinates: any; + }; + properties: { + weight: number; + }; +}; + +type HeatmapMapOptions = { + radius: number; + /** disperse points on higher zoom levels according to radius */ + dissipating: boolean; + opacity: number; + /** Intensity of median point (from 0 to 1) */ + intensityOfMidpoint: number; + gradient: Record; + isCustomPalette: boolean; + showCustomLegend: boolean; + colorTitle: string; + geoObjectId?: string; + layerTitle?: string; +}; + +function prepareHeatmap(options: PrepareFunctionArgs) { + const { + placeholders, + colors, + resultData: {data, order}, + idToTitle, + shared, + colorsConfig, + ChartEditor, + } = options; + const layerSettings = (options.layerSettings || + {}) as VisualizationLayerShared['visualization']['layerSettings']; + + const allPoints: Record = {}; + const coordinates = placeholders[0].items; + + let min: number, max: number, leftBot: Coordinate | undefined, rightTop: Coordinate | undefined; + + if (colors.length) { + data.forEach((values) => { + values.forEach((columnData, columnIndex) => { + if (columnData === 'null' || columnData === null) { + return; + } + + const dataTitle = getTitleInOrder(order, columnIndex, coordinates); + + if (colors[0].title === dataTitle) { + const value = Number(columnData); + [min, max] = getExtremeValues({min, max, value}); + } + }); + }); + } + + // we get a step for the weight parameter (we distribute points from 1 to 10) + const step = (max! - min!) / 10; + + const getPointConfig = (coordinates: any): HeatmapPointConfig => { + return { + type: 'Feature', + geometry: { + type: 'Point', + coordinates, + }, + properties: { + weight: 5, + }, + }; + }; + + const getPointWeight = ({current, min, step}: {current: number; min: number; step: number}) => { + return Math.ceil((current - min) / step); + }; + + const setPointWeight = (point: undefined | HeatmapPointConfig, weight: number) => { + if (!point) { + return; + } + + point.properties.weight = weight ? weight : 1; + }; + const colorValues: number[] = []; + + data.forEach((values, valuesIndex) => { + allPoints[`points-${valuesIndex}`] = []; + + values.forEach((columnData, columnIndex) => { + if (columnData === 'null' || columnData === null) { + return; + } + + const dataTitle = getTitleInOrder(order, columnIndex, coordinates); + + if (coordinates.findIndex(({title}) => title === dataTitle) !== -1) { + const current = JSON.parse(columnData); + [leftBot, rightTop] = getMapBounds({leftBot, rightTop, current}); + allPoints[`points-${valuesIndex}`].push(getPointConfig(current)); + } + + if (colors.length && colors[0].title === dataTitle) { + const weight = getPointWeight({current: Number(columnData), min, step}); + + colorValues.push(weight); + allPoints[`points-${valuesIndex}`].forEach((point) => + setPointWeight(point, weight), + ); + } + }); + }); + + const isCustomPalette = Boolean(colors.length && colorsConfig.gradientColors?.length); + let middleThreshold = 0.2; + let gradient: Record = { + 0.1: 'rgba(128, 255, 0, 0.7)', + 0.2: 'rgba(255, 255, 0, 0.8)', + 0.7: 'rgba(234, 72, 58, 0.9)', + 1.0: 'rgba(162, 36, 25, 1)', + }; + + if (isCustomPalette) { + const currentGradient = getCurrentGradient(colorsConfig); + const rgbColors: RGBColor[] = getRgbColors( + currentGradient.colors, + Boolean(colorsConfig.reversed), + ); + + const [first, second, third] = rgbColors; + + if (first && second && third) { + const hasMiddleThreValue = Boolean( + colorsConfig.thresholdsMode === 'manual' && + colorsConfig.middleThreshold !== 'undefined', + ); + + if (hasMiddleThreValue) { + const {range, rangeMiddle} = getThresholdValues(colorsConfig, colorValues); + middleThreshold = rangeMiddle / range; + } + + gradient = { + '0': `rgba(${first.red}, ${first.green}, ${first.blue}, 0.7)`, + '0.5': `rgba(${second.red}, ${second.green}, ${second.blue}, 0.9)`, + '1': `rgba(${third.red}, ${third.green}, ${third.blue}, 1)`, + }; + } else if (first && second) { + gradient = { + '0': `rgba(${first.red}, ${first.green}, ${first.blue}, 0.7)`, + '1': `rgba(${second.red}, ${second.green}, ${second.blue}, 1)`, + }; + } + } + + const mapOptions: HeatmapMapOptions = { + radius: 15, + dissipating: false, + opacity: getLayerAlpha(layerSettings), + intensityOfMidpoint: middleThreshold, + gradient, + isCustomPalette: isCustomPalette, + showCustomLegend: true, + colorTitle: + coordinates[0].fakeTitle || idToTitle[coordinates[0].guid] || coordinates[0].title, + }; + + if (shared.extraSettings && shared.extraSettings.legendMode === 'hide') { + mapOptions.showCustomLegend = false; + } + + if (layerSettings.id) { + mapOptions.geoObjectId = layerSettings.id; + } + + mapOptions.layerTitle = + layerSettings.name || + options.ChartEditor.getTranslation('wizard.prepares', 'label_new-layer'); + + const shouldSetBounds = + shared?.extraSettings?.zoomMode !== ZoomMode.Manual && + shared?.extraSettings?.mapCenterMode !== ZoomMode.Manual; + const {zoom, center} = getMapState(shared, [leftBot, rightTop]); + + ChartEditor?.updateHighchartsConfig({state: {zoom, center}}); + + return [ + { + heatmap: getFlattenCoordinates(Object.values(allPoints)), + options: mapOptions, + bounds: shouldSetBounds ? [leftBot, rightTop] : undefined, + }, + ]; +} + +export default prepareHeatmap; diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/action-params.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/action-params.ts new file mode 100644 index 0000000..480a607 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/action-params.ts @@ -0,0 +1,32 @@ +import type {ServerField, StringParams} from '../../../../../../../shared'; +import {isDateField, isDimensionField, isMarkupField} from '../../../../../../../shared'; +import {formatDate, getServerDateFormat} from '../../utils/misc-helpers'; + +export function canUseFieldForFiltering(field: ServerField) { + return isDimensionField(field) && !isMarkupField(field); +} + +export function addActionParamValue( + actionParams: StringParams, + field: ServerField | undefined, + value: unknown, +): StringParams { + if (typeof value === 'undefined') { + return actionParams; + } + + if (field && canUseFieldForFiltering(field)) { + let paramValue = String(value); + if (isDateField(field)) { + paramValue = formatDate({ + valueType: field.data_type, + value: value as number, + format: getServerDateFormat(field.data_type), + }); + } + + actionParams[field.guid] = paramValue; + } + + return actionParams; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/addAxisFormatter.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/addAxisFormatter.ts new file mode 100644 index 0000000..5c2db6e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/addAxisFormatter.ts @@ -0,0 +1,37 @@ +import {ChartkitHandlers, getXAxisMode} from '../../../../../../../../shared'; +import type {ServerChartsConfig, ServerPlaceholder} from '../../../../../../../../shared'; + +import {getAxisType} from './getAxisType'; +import {isAxisLabelDateFormat} from './isAxisLabelDateFormat'; + +interface AddAxisFormatterProps { + axisConfig: Record; + placeholder?: ServerPlaceholder; + chartConfig?: Partial; + otherwiseFormatter?: string; +} + +export const addAxisFormatter = (props: AddAxisFormatterProps) => { + const {axisConfig, otherwiseFormatter, chartConfig, placeholder} = props; + if (!axisConfig || !placeholder) { + return; + } + const field = placeholder?.items?.[0]; + const axisMode = chartConfig ? getXAxisMode({config: chartConfig}) : undefined; + const axisType = getAxisType({ + field: field, + settings: placeholder?.settings, + axisMode: axisMode, + }); + + const axisLabelDateFormat = isAxisLabelDateFormat(placeholder?.settings, field, axisType); + const formatter = axisLabelDateFormat + ? ChartkitHandlers.WizardDatetimeAxisFormatter + : otherwiseFormatter; + const format = axisLabelDateFormat ? placeholder?.settings?.axisLabelDateFormat : undefined; + axisConfig.labels = { + ...(axisConfig.labels ?? {}), + formatter, + format, + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/addAxisFormatting.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/addAxisFormatting.ts new file mode 100644 index 0000000..4094851 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/addAxisFormatting.ts @@ -0,0 +1,17 @@ +import {AxisLabelFormatMode, type ServerPlaceholder} from '../../../../../../../../shared'; + +import {getAxisChartkitFormatting} from './get-axis-formatting'; + +export const addAxisFormatting = ( + arrToPush: unknown[], + visualizationId: string, + placeholder?: ServerPlaceholder, +) => { + const formatMode = placeholder?.settings?.axisFormatMode; + if (formatMode && formatMode !== AxisLabelFormatMode.Auto) { + const formatting = getAxisChartkitFormatting(placeholder, visualizationId); + if (formatting) { + arrToPush.push(formatting); + } + } +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/data-labels.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/data-labels.ts new file mode 100644 index 0000000..1caba48 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/data-labels.ts @@ -0,0 +1 @@ +export const DATA_LABEL_DEFAULT_PADDING = 3; diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/get-axis-formatting.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/get-axis-formatting.ts new file mode 100644 index 0000000..349cd7a --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/get-axis-formatting.ts @@ -0,0 +1,49 @@ +import type {ServerPlaceholder} from '../../../../../../../../shared'; +import { + AxisLabelFormatMode, + getFormatOptions, + isNumberField, + isPercentVisualization, +} from '../../../../../../../../shared'; +import {getFormatOptionsFromFieldFormatting} from '../../../utils/misc-helpers'; + +export const getAxisFormatting = ({ + placeholder, + visualizationId, +}: { + placeholder: ServerPlaceholder; + visualizationId: string; +}) => { + const field = placeholder?.items?.[0]; + + if (!field || !isNumberField(field)) { + return undefined; + } + + if (isPercentVisualization(visualizationId)) { + return undefined; + } + + switch (placeholder.settings?.axisFormatMode) { + case AxisLabelFormatMode.ByField: + return getFormatOptions(field) ?? {}; + case AxisLabelFormatMode.Manual: + return placeholder.settings?.axisLabelFormating ?? {}; + default: + return null; + } +}; + +export const getAxisChartkitFormatting = ( + placeholder: ServerPlaceholder, + visualizationId: string, +) => { + const axisFormatting = getAxisFormatting({placeholder, visualizationId}); + + if (axisFormatting) { + const field = placeholder?.items?.[0]; + return getFormatOptionsFromFieldFormatting(axisFormatting, field?.data_type); + } + + return undefined; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/getAxisType.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/getAxisType.ts new file mode 100644 index 0000000..12c8db6 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/getAxisType.ts @@ -0,0 +1,30 @@ +import type {ServerPlaceholderSettings} from '../../../../../../../../shared'; +import {AxisMode, isDateField, isNumberField} from '../../../../../../../../shared'; + +export function getAxisType(args: { + field?: {guid: string; data_type: string}; + settings?: ServerPlaceholderSettings; + axisMode?: AxisMode; +}) { + const {field, settings} = args; + let axisMode = args.axisMode; + if (!axisMode && field?.guid) { + axisMode = settings?.axisModeMap?.[field.guid]; + } + + if (axisMode !== AxisMode.Discrete) { + if (isDateField(field)) { + return 'datetime'; + } + + if (isNumberField(field)) { + return settings?.type === 'logarithmic' ? 'logarithmic' : 'linear'; + } + } + + if (field) { + return 'category'; + } + + return undefined; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/index.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/index.ts new file mode 100644 index 0000000..963f021 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/index.ts @@ -0,0 +1,34 @@ +import type {ServerPlaceholder} from '../../../../../../../../shared'; +import {PlaceholderId} from '../../../../../../../../shared'; +import type {PrepareFunctionArgs} from '../../types'; + +export {getAxisType} from './getAxisType'; +export {getAxisFormatting} from './get-axis-formatting'; +export {isAxisLabelDateFormat} from './isAxisLabelDateFormat'; +export {addAxisFormatting} from './addAxisFormatting'; +export {addAxisFormatter} from './addAxisFormatter'; + +export function getYAxisPlaceholders({ + placeholders, + shared, +}: Pick) { + const layers = shared.visualization.layers ?? []; + const allPlaceholders = + layers.length > 1 ? layers.map((l) => l.placeholders).flat() : placeholders; + const nonEmptyYPlaceholder = allPlaceholders.find( + (p) => p.id === PlaceholderId.Y && p.items?.length, + ); + const nonEmptyY2Placeholder = allPlaceholders.find( + (p) => p.id === PlaceholderId.Y2 && p.items?.length, + ); + + const yAxisItems: ServerPlaceholder[] = []; + if (nonEmptyYPlaceholder) { + yAxisItems.push(nonEmptyYPlaceholder); + } + if (nonEmptyY2Placeholder) { + yAxisItems.push(nonEmptyY2Placeholder); + } + + return yAxisItems; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/isAxisLabelDateFormat.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/isAxisLabelDateFormat.ts new file mode 100644 index 0000000..9baa788 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/axis/isAxisLabelDateFormat.ts @@ -0,0 +1,16 @@ +import type {ServerField, ServerPlaceholderSettings} from '../../../../../../../../shared'; +import {AxisLabelFormatMode, isDateField} from '../../../../../../../../shared'; + +export const isAxisLabelDateFormat = ( + settings?: ServerPlaceholderSettings, + field?: ServerField, + axisType?: string, +) => { + const {axisFormatMode, axisLabelDateFormat} = settings || {}; + return ( + axisFormatMode === AxisLabelFormatMode.Manual && + isDateField(field) && + axisType === 'datetime' && + axisLabelDateFormat + ); +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/backgroundSettings/misc.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/backgroundSettings/misc.ts new file mode 100644 index 0000000..82a3e11 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/backgroundSettings/misc.ts @@ -0,0 +1,13 @@ +import type {ColorPalette, TableFieldBackgroundSettings} from '../../../../../../../../shared'; +import {selectCurrentRGBGradient} from '../../../../../../../../shared'; + +export const getCurrentBackgroundGradient = ( + gradientState: TableFieldBackgroundSettings['settings']['gradientState'], + loadedColorPalettes: Record, +) => { + return selectCurrentRGBGradient( + gradientState.gradientMode!, + gradientState.gradientPalette!, + loadedColorPalettes, + ); +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/barsSettings/index.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/barsSettings/index.ts new file mode 100644 index 0000000..539cc7f --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/barsSettings/index.ts @@ -0,0 +1,225 @@ +import type { + BarGradientColorSettings, + BarTwoColorSettings, + BarViewOptions, + ColorPalette, + Palette, + ServerField, + TableBarsSettings, +} from '../../../../../../../../shared'; +import { + getFormatOptions, + getRangeDelta, + getRgbColorValue, + selectCurrentRGBGradient, + transformHexToRgb, +} from '../../../../../../../../shared'; +import {getColorByColorSettings} from '../../../../../../../../shared/utils/palettes'; +import {selectServerPalette} from '../../../../../../../constants'; +import {getRgbColors} from '../../../utils/color-helpers'; +import {chartKitFormatNumberWrapper} from '../../../utils/misc-helpers'; +import type {PrepareFunctionDataRow} from '../../types'; + +import type {BarValueOptions} from './types'; + +const getMinAndMaxValues = (columnValues: PrepareFunctionDataRow) => { + const parsedValues: number[] = columnValues + .filter((v) => v !== null && v !== undefined) + .map(Number); + const min = Math.min(...parsedValues); + const max = Math.max(...parsedValues); + return { + min, + max, + }; +}; +const getCurrentBarGradient = ( + colors: BarGradientColorSettings['settings'], + loadedColorPalettes: Record, +) => { + return selectCurrentRGBGradient(colors.gradientType, colors.palette, loadedColorPalettes); +}; +const getBarThresholdValues = ( + thresholds: BarGradientColorSettings['settings']['thresholds'], + columnValues: PrepareFunctionDataRow, +) => { + const preparedMinAndMax = getMinAndMaxValues(columnValues); + const max = + thresholds.mode === 'manual' && typeof thresholds.max !== 'undefined' + ? Number(thresholds.max) + : preparedMinAndMax.max; + + const min = + thresholds.mode === 'manual' && typeof thresholds.min !== 'undefined' + ? Number(thresholds.min) + : preparedMinAndMax.min; + + const range = max - min; + + const mid = + thresholds.mode === 'manual' && typeof thresholds.mid !== 'undefined' + ? Number(thresholds.mid) + : range / 2; + + const rangeMiddle = mid === range / 2 ? mid : mid - min; + + return {min, max, mid, range, rangeMiddle}; +}; +const getTwoColorBarColor = ( + rowValue: string, + colors: BarTwoColorSettings['settings'], + currentColors: string[], +) => { + const parsedValue = parseFloat(rowValue); + + const isPositive = parsedValue >= 0; + + return getColorByColorSettings({ + currentColors, + colorIndex: isPositive ? colors.positiveColorIndex : colors.negativeColorIndex, + color: isPositive ? colors.positiveColor : colors.negativeColor, + fallbackIndex: isPositive ? 2 : 1, + }); +}; +const getGradientBarColor = (args: { + columnValues: PrepareFunctionDataRow; + colors: BarGradientColorSettings['settings']; + currentColumnValue: string | null; + loadedColorPalettes: Record; +}) => { + const {colors, columnValues, currentColumnValue, loadedColorPalettes} = args; + const currentGradient = getCurrentBarGradient(colors, loadedColorPalettes); + const gradientColors = getRgbColors( + currentGradient.colors.map(transformHexToRgb), + Boolean(colors.reversed), + ); + const {rangeMiddle, range, min} = getBarThresholdValues(colors.thresholds, columnValues); + const rangeMiddleRatio = rangeMiddle / range; + + let delta; + if (range === 0) { + delta = 0.5; + } else { + const colorValue = currentColumnValue ? Number(currentColumnValue) : null; + delta = getRangeDelta(colorValue, min, range); + } + + if (typeof delta === 'number') { + return getRgbColorValue(delta, colors.gradientType, rangeMiddleRatio, gradientColors); + } + + return ''; +}; +export const getBarSettingsViewOptions = (args: { + barsSettings: TableBarsSettings; + columnValues: PrepareFunctionDataRow; +}): BarViewOptions => { + const {barsSettings, columnValues} = args; + const barViewOptions: BarViewOptions = { + view: 'bar', + showLabel: barsSettings.showLabels, + }; + + let min: number | undefined, max: number | undefined; + + if (barsSettings.scale.mode === 'auto') { + const minAndMaxValues = getMinAndMaxValues(columnValues); + min = minAndMaxValues.min; + max = minAndMaxValues.max; + } else { + min = + typeof barsSettings.scale.settings.min === 'undefined' + ? barsSettings.scale.settings.min + : Number(barsSettings.scale.settings.min); + max = + typeof barsSettings.scale.settings.max === 'undefined' + ? barsSettings.scale.settings.max + : Number(barsSettings.scale.settings.max); + } + + const isMinCorrect = typeof min === 'number' && !isNaN(min) && min <= 0; + const isMaxCorrect = typeof max === 'number' && !isNaN(max) && max >= 0; + + const isMinEqualMax = isMinCorrect && isMaxCorrect && min === max; + + if (isMinEqualMax) { + barViewOptions.max = min; + } else { + barViewOptions.min = isMinCorrect ? min : undefined; + barViewOptions.max = isMaxCorrect ? max : undefined; + } + + barViewOptions.align = barsSettings.align === 'default' ? undefined : barsSettings.align; + + return barViewOptions; +}; +export const getBarSettingsValue = (args: { + field: ServerField; + rowValue: string; + columnValues: PrepareFunctionDataRow; + isTotalCell: boolean; + loadedColorPalettes: Record; + availablePalettes: Record; + defaultColorPaletteId: string; +}): BarValueOptions => { + const { + defaultColorPaletteId, + field, + rowValue, + columnValues, + isTotalCell, + loadedColorPalettes, + availablePalettes, + } = args; + + const barSettings = field.barsSettings!; + + const formatOptions = getFormatOptions(field); + + let barColor: string; + + const currentColors = selectServerPalette({ + palette: barSettings.colorSettings.settings.palette, + availablePalettes, + customColorPalettes: loadedColorPalettes, + defaultColorPaletteId, + }); + + switch (barSettings.colorSettings.colorType) { + case 'one-color': + barColor = getColorByColorSettings({ + currentColors, + color: barSettings.colorSettings.settings.color, + colorIndex: barSettings.colorSettings.settings.colorIndex, + }); + break; + case 'two-color': + barColor = getTwoColorBarColor( + rowValue, + barSettings.colorSettings.settings, + currentColors, + ); + break; + case 'gradient': + barColor = getGradientBarColor({ + columnValues, + colors: barSettings.colorSettings.settings, + currentColumnValue: rowValue, + loadedColorPalettes, + }); + break; + default: + barColor = ''; + } + + const parsedValue = parseFloat(rowValue); + + return { + value: isNaN(parsedValue) ? 0 : parsedValue, + barColor, + formattedValue: isNaN(parsedValue) + ? rowValue + : chartKitFormatNumberWrapper(parsedValue, formatOptions), + showBar: !isTotalCell || (isTotalCell && barSettings.showBarsInTotals), + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/barsSettings/types.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/barsSettings/types.ts new file mode 100644 index 0000000..38a0142 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/barsSettings/types.ts @@ -0,0 +1,6 @@ +export type BarValueOptions = { + barColor: string; + value: number; + formattedValue: string; + showBar: boolean; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/colors.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/colors.ts new file mode 100644 index 0000000..09a0f72 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/colors.ts @@ -0,0 +1,60 @@ +import type {RGBColor} from '../../../../../../../shared'; + +// the algorithm is taken almost as is from d3.interpolateRgb Basis +// (https://d3js.org/d3-interpolate/color#interpolateRgbBasis) +export function interpolateRgbBasis(colors: RGBColor[]) { + const red: number[] = [], + green: number[] = [], + blue: number[] = []; + + colors.forEach((color, index) => { + red[index] = color.red || 0; + green[index] = color.green || 0; + blue[index] = color.blue || 0; + }); + + const redFn = spline(red); + const greenFn = spline(green); + const blueFn = spline(blue); + + return function (value: number) { + const result: RGBColor = { + red: Math.round(redFn(value)), + green: Math.round(greenFn(value)), + blue: Math.round(blueFn(value)), + }; + + return result; + }; +} + +function basis(t1: number, v0: number, v1: number, v2: number, v3: number) { + const t2 = t1 * t1, + t3 = t2 * t1; + + return ( + ((1 - 3 * t1 + 3 * t2 - t3) * v0 + + (4 - 6 * t2 + 3 * t3) * v1 + + (1 + 3 * t1 + 3 * t2 - 3 * t3) * v2 + + t3 * v3) / + 6 + ); +} + +function spline(colorValues: number[]) { + const n = colorValues.length - 1; + + return function (value: number) { + const i = + value <= 0 + ? (value = 0) + : value >= 1 + ? ((value = 1), n - 1) + : Math.floor(value * n), + v1 = colorValues[i], + v2 = colorValues[i + 1], + v0 = i > 0 ? colorValues[i - 1] : 2 * v1 - v2, + v3 = i < n - 1 ? colorValues[i + 2] : 2 * v2 - v1; + return basis((value - i / n) * n, v0, v1, v2, v3); + }; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/columnSettings.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/columnSettings.ts new file mode 100644 index 0000000..1529521 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/columnSettings.ts @@ -0,0 +1,16 @@ +import type {ColumnSettings} from '../../../../../../../shared'; + +export const getColumnWidthValue = ( + width: ColumnSettings['width'] | undefined, +): string | undefined => { + switch (width?.mode) { + case 'auto': + return undefined; + case 'percent': + return `${width.value}%`; + case 'pixel': + return `${width.value}px`; + default: + return undefined; + } +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/common.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/common.ts new file mode 100644 index 0000000..bdbc5c9 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/common.ts @@ -0,0 +1,21 @@ +import {WRAPPED_HTML_KEY, type WrappedHTML} from '../../../../../../../shared'; +import {isWrappedHTML} from '../../../../../../../shared/utils/ui-sandbox'; + +export function concatStringValues(...values: (string | WrappedHTML)[]) { + if (values.some((val) => isWrappedHTML(val))) { + return values.reduce( + (acc, val) => { + if (isWrappedHTML(val)) { + acc[WRAPPED_HTML_KEY] += String(val[WRAPPED_HTML_KEY]); + } else if (typeof val === 'string') { + acc[WRAPPED_HTML_KEY] += val; + } + + return acc; + }, + {[WRAPPED_HTML_KEY]: ''}, + ); + } + + return values.join(''); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/get-formatted-value.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/get-formatted-value.ts new file mode 100644 index 0000000..9566c94 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/get-formatted-value.ts @@ -0,0 +1,26 @@ +import type {ServerField} from '../../../../../../../shared'; +import {getFormatOptions, isDateField, isNumberField} from '../../../../../../../shared'; +import {chartKitFormatNumberWrapper, formatDate} from '../../utils/misc-helpers'; + +export function getFormattedValue(field: ServerField, value: unknown) { + if (value === null) { + return null; + } + + if (isDateField(field)) { + return formatDate({ + valueType: field.data_type, + value: value as string, + format: field.format, + }); + } + + if (isNumberField(field)) { + return chartKitFormatNumberWrapper(value as number, { + lang: 'ru', + ...getFormatOptions(field), + }); + } + + return value; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/highcharts.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/highcharts.ts new file mode 100644 index 0000000..a49d02a --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/highcharts.ts @@ -0,0 +1,47 @@ +import type {ServerField, ServerSort} from '../../../../../../../shared'; +import {WizardVisualizationId, isDateField, isNumberField} from '../../../../../../../shared'; +import type {ChartColorsConfig} from '../../types'; +import {getHighchartsGradientStops} from '../../utils/get-gradient-stops'; + +export function getHighchartsColorAxis( + graphs: any[], + colorsConfig: ChartColorsConfig, +): Highcharts.ColorAxis { + const points = graphs.reduce( + (acc: Highcharts.PointOptionsObject[], graph) => [...acc, ...graph.data], + [], + ); + const colorValues = points + .map((point) => point.colorValue) + .filter((cv): cv is number => Boolean(cv)); + + const minColorValue = Math.min(...colorValues); + const maxColorValue = Math.max(...colorValues); + + return { + startOnTick: false, + endOnTick: false, + min: minColorValue, + max: maxColorValue, + stops: getHighchartsGradientStops({colorsConfig, points, minColorValue, maxColorValue}), + }; +} + +export function isXAxisReversed( + xField: ServerField | undefined, + sortFields: ServerSort[], + visualizationId: WizardVisualizationId, +) { + if (isDateField(xField) || isNumberField(xField)) { + const sortXItem = sortFields.find((s) => s.guid === xField.guid); + if (sortXItem && (sortXItem.direction === 'DESC' || !sortXItem.direction)) { + // It turns out that in order to expand the X-axis for a Bar chart in Highcharts, you need to pass false + // While in all other types of charts you need to pass true + return ![WizardVisualizationId.Bar, WizardVisualizationId.Bar100p].includes( + visualizationId, + ); + } + } + + return undefined; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/layers.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/layers.ts new file mode 100644 index 0000000..562ebb4 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/layers.ts @@ -0,0 +1,44 @@ +import type {ServerPlaceholder, Shared} from '../../../../../../../shared'; +import {PlaceholderId, isVisualizationWithLayers} from '../../../../../../../shared'; +import {getLayerPlaceholderWithItems} from '../line/helpers/axis/getLayerPlaceholderWithItems'; +import type {PrepareFunctionArgs} from '../types'; + +export function getYPlaceholders(args: PrepareFunctionArgs) { + const {shared, placeholders, layerSettings} = args; + const yPlaceholder = placeholders.find((p) => p.id === PlaceholderId.Y); + const y2Placeholder = placeholders.find((p) => p.id === PlaceholderId.Y2); + const visualization = shared.visualization as Shared['visualization']; + + let layerYPlaceholder: ServerPlaceholder | undefined; + let layerY2Placeholder: ServerPlaceholder | undefined; + + if (isVisualizationWithLayers(visualization)) { + const lastLayer = visualization.layers[visualization.layers.length - 1]; + + if (lastLayer.layerSettings.id === layerSettings.id) { + layerYPlaceholder = yPlaceholder; + layerY2Placeholder = y2Placeholder; + + if (!layerYPlaceholder?.items.length) { + layerYPlaceholder = getLayerPlaceholderWithItems( + shared.visualization, + PlaceholderId.Y, + {isFirstFromTheTop: true}, + ); + } + + if (!layerY2Placeholder?.items.length) { + layerY2Placeholder = getLayerPlaceholderWithItems( + shared.visualization, + PlaceholderId.Y2, + {isFirstFromTheTop: true}, + ); + } + } + } else { + layerYPlaceholder = yPlaceholder; + layerY2Placeholder = y2Placeholder; + } + + return [layerYPlaceholder, layerY2Placeholder]; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/legend.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/legend.ts new file mode 100644 index 0000000..591a1d7 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/legend.ts @@ -0,0 +1,65 @@ +import type {ServerChartsConfig, ServerColor} from '../../../../../../../shared'; +import {WizardVisualizationId, isDimensionField} from '../../../../../../../shared'; +import type {ChartColorsConfig} from '../../types'; +import {getGradientStops} from '../../utils/get-gradient-stops'; +import {isGradientMode} from '../../utils/misc-helpers'; + +export function shouldUseGradientLegend( + colorField: ServerColor | undefined, + colorsConfig: ChartColorsConfig, + shared: ServerChartsConfig, +) { + if (!colorField) { + return false; + } + + const isCombinedChartColorizedBySomeDimenstion = + shared.visualization.id === WizardVisualizationId.CombinedChart && + shared.visualization.layers?.some((layer) => + layer.commonPlaceholders.colors.some(isDimensionField), + ); + + const isGradient = isGradientMode({ + colorField, + colorFieldDataType: colorField.data_type, + colorsConfig, + }); + + return isGradient && !isCombinedChartColorizedBySomeDimenstion; +} + +export function getLegendColorScale({ + colorsConfig, + points, +}: { + colorsConfig: ChartColorsConfig; + points: {colorValue?: unknown}[]; +}) { + const colorValues = points + .map((point) => point.colorValue) + .filter((d): d is number => Boolean(d)); + + const minColorValue = Math.min(...colorValues); + const maxColorValue = Math.max(...colorValues); + + const colorScaleColors = [...colorsConfig.gradientColors]; + if (colorsConfig.reversed) { + colorScaleColors.reverse(); + } + + let stops = colorsConfig.gradientColors.length === 2 ? [0, 1] : [0, 0.5, 1]; + if (colorsConfig && typeof minColorValue === 'number' && typeof maxColorValue === 'number') { + stops = getGradientStops({colorsConfig, points, minColorValue, maxColorValue}); + } + + if (stops[0] > 0) { + stops.unshift(0); + colorScaleColors.unshift(colorScaleColors[0]); + } + + if (stops[stops.length - 1] < 1) { + stops.push(1); + colorScaleColors.push(colorScaleColors[colorScaleColors.length - 1]); + } + return {colors: colorScaleColors, stops}; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/segments.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/segments.ts new file mode 100644 index 0000000..63aeb31 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/segments.ts @@ -0,0 +1,56 @@ +import {PlaceholderId} from '../../../../../../../shared'; +import {findIndexInOrder} from '../../utils/misc-helpers'; +import {getSortedSegmentsList, getY2SegmentNameKey} from '../line/helpers'; +import type {SegmentsMap} from '../line/helpers/segments/types'; +import type {PrepareFunctionArgs} from '../types'; + +export function getSegmentMap(args: PrepareFunctionArgs): SegmentsMap { + const { + placeholders, + resultData: {data, order}, + sort, + segments, + idToTitle, + } = args; + const segmentField = segments[0]; + + if (!segmentField) { + return {} as SegmentsMap; + } + + const segmentFieldTitle = idToTitle[segmentField.guid]; + + const segmentsList = getSortedSegmentsList({ + sortItem: sort?.[0], + segmentField, + segmentIndexInOrder: findIndexInOrder(order, segmentField, segmentFieldTitle), + data, + }); + + const y2Fields = placeholders.find((p) => p.id === PlaceholderId.Y2)?.items || []; + const hasOppositeYAxis = y2Fields.length > 0; + + return segmentsList.reduce((acc, segmentName, index) => { + if (!acc[segmentName]) { + const segmentIndex = Object.keys(acc).length; + const title = segmentName; + + Object.assign(acc, { + [segmentName]: {title, index: segmentIndex, isOpposite: false, plotIndex: index}, + }); + + if (hasOppositeYAxis) { + Object.assign(acc, { + [String(getY2SegmentNameKey(segmentName))]: { + title, + index: segmentIndex + 1, + isOpposite: true, + plotIndex: index, + }, + }); + } + } + + return acc; + }, {} as SegmentsMap); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/helpers/visualizations.ts b/src/server/modes/charts/plugins/datalens/preparers/helpers/visualizations.ts new file mode 100644 index 0000000..797b2b4 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/helpers/visualizations.ts @@ -0,0 +1,8 @@ +import type {ServerVisualization, WizardVisualizationId} from '../../../../../../../shared'; +import {isVisualizationWithLayers} from '../../../../../../../shared'; + +export function getAllVisualizationsIds(config: {visualization: ServerVisualization}) { + return isVisualizationWithLayers(config.visualization) + ? config.visualization.layers.map((l) => l.id as WizardVisualizationId) + : [config.visualization.id as WizardVisualizationId]; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/line-time/__tests__/line-time.test.ts b/src/server/modes/charts/plugins/datalens/preparers/line-time/__tests__/line-time.test.ts new file mode 100644 index 0000000..84b8190 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line-time/__tests__/line-time.test.ts @@ -0,0 +1,14 @@ +import type {PrepareFunctionArgs} from '../../types'; +import linetimePrepare from '../index'; + +import {expectedResult, options} from './mocks/line-time.mock'; + +describe('linetimePrepare', () => { + describe('monitoring', () => { + test('monitoring chart with 4 queries should be rendered correctly', () => { + const result = linetimePrepare(options as unknown as PrepareFunctionArgs); + + expect(result).toEqual(expectedResult); + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/line-time/__tests__/mocks/line-time.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/line-time/__tests__/mocks/line-time.mock.ts new file mode 100644 index 0000000..be1b371 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line-time/__tests__/mocks/line-time.mock.ts @@ -0,0 +1,438 @@ +import {AxisMode} from '../../../../../../../../../shared'; +import {EmptyPrepapreArgs} from '../../../__tests__/common.mock'; + +export const expectedResult = { + timeline: [1677628800, 1677715200], + timeZone: 'UTC', + graphs: [ + { + id: 'Query #0', + colorGuid: 'query #-2', + colorValue: 'Query #0', + name: 'Query #0', + color: 'rgb(0,127,0)', + data: [1, 1], + spanGaps: false, + }, + { + id: 'Query #1', + colorGuid: 'query #-2', + colorValue: 'Query #1', + name: 'Query #1', + color: 'rgb(255,255,0)', + data: [2, 2], + spanGaps: false, + }, + { + id: 'Query #2', + colorGuid: 'query #-2', + colorValue: 'Query #2', + name: 'Query #2', + color: 'rgb(255,0,0)', + data: [3, 3], + spanGaps: false, + }, + { + id: 'Query #3', + colorGuid: 'query #-2', + colorValue: 'Query #3', + name: 'Query #3', + color: 'rgb(191,0,127)', + data: [4, 4], + spanGaps: false, + }, + ], + axes: [{scale: 'x', plotLines: [{width: 3, color: '#ffa0a0'}]}], +}; + +export const options = { + ...EmptyPrepapreArgs, + placeholders: [ + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'x', + type: 'x', + title: 'section_x', + iconProps: {}, + items: [ + { + guid: 'timestamp-0', + title: 'timestamp', + datasetId: 'ql-mocked-dataset', + data_type: 'genericdatetime', + cast: 'genericdatetime', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + required: true, + capacity: 1, + settings: { + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + holidays: 'off', + axisFormatMode: 'auto', + axisModeMap: {}, + axisMode: AxisMode.Continuous, + }, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'y', + type: 'y', + title: 'section_y', + iconProps: {}, + items: [ + { + guid: 'value-1', + title: 'value', + datasetId: 'ql-mocked-dataset', + data_type: 'float', + cast: 'float', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + settings: { + scale: 'auto', + scaleValue: 'min-max', + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + nulls: 'as-0', + axisFormatMode: 'auto', + axisModeMap: {}, + }, + }, + ], + fields: [], + colors: [ + { + guid: 'query #-2', + title: 'query #', + datasetId: 'ql-mocked-dataset', + data_type: 'string', + cast: 'string', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + id: 'inserted-1682600847435', + }, + ], + colorsConfig: { + colors: [ + '#4DA2F1', + '#FF3D64', + '#8AD554', + '#FFC636', + '#FFB9DD', + '#84D1EE', + '#FF91A1', + '#54A520', + '#DB9100', + '#BA74B3', + '#1F68A9', + '#ED65A9', + '#0FA08D', + '#FF7E00', + '#E8B0A4', + '#52A6C5', + '#BE2443', + '#70C1AF', + '#FFB46C', + '#DCA3D7', + ], + gradientColors: ['#0044A3', '#8CCBFF'], + loadedColorPalettes: {}, + }, + sort: [], + visualizationId: 'area', + labels: [ + { + guid: 'attribute-6', + title: 'attribute', + datasetId: 'ql-mocked-dataset', + data_type: 'string', + cast: 'string', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + id: 'inserted-1680034721946', + mode: 'absolute', + }, + ], + tooltips: [], + datasets: [], + resultData: { + data: [ + ['2023-03-01T00:00:00', 1, 'Query #0'], + ['2023-03-02T00:00:00', 1, 'Query #0'], + ['2023-03-01T00:00:00', 2, 'Query #1'], + ['2023-03-02T00:00:00', 2, 'Query #1'], + ['2023-03-01T00:00:00', 3, 'Query #2'], + ['2023-03-02T00:00:00', 3, 'Query #2'], + ['2023-03-01T00:00:00', 4, 'Query #3'], + ['2023-03-02T00:00:00', 4, 'Query #3'], + ], + order: [ + {datasetId: 'ql-mocked-dataset', title: 'timestamp'}, + {datasetId: 'ql-mocked-dataset', title: 'value'}, + {datasetId: 'ql-mocked-dataset', title: 'query #'}, + {datasetId: 'ql-mocked-dataset', title: 'Column Names'}, + ], + totals: [], + }, + idToTitle: { + 'timestamp-0': 'timestamp', + 'value-1': 'value', + 'query #-2': 'query #', + '': 'Column Names', + }, + idToDataType: { + 'timestamp-0': 'genericdatetime', + 'value-1': 'float', + 'query #-2': 'string', + '': 'string', + }, + shared: { + type: 'ql', + chartType: 'monitoringql', + connection: {entryId: 'sko8xq9yb2yck', type: 'solomon'}, + colors: [ + { + guid: 'query #-2', + title: 'query #', + datasetId: 'ql-mocked-dataset', + data_type: 'string', + cast: 'string', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + id: 'inserted-1682600847435', + }, + ], + colorsConfig: {}, + labels: [ + { + guid: 'attribute-6', + title: 'attribute', + datasetId: 'ql-mocked-dataset', + data_type: 'string', + cast: 'string', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + id: 'inserted-1680034721946', + mode: 'absolute', + }, + ], + tooltips: [], + extraSettings: {}, + queryValue: '', + queries: [ + { + value: 'constant_line(1)', + params: [{name: 'project_id', type: 'string', defaultValue: 'reactor'}], + hidden: false, + }, + { + value: 'constant_line(2)', + params: [{name: 'project_id', type: 'string', defaultValue: 'reactor'}], + hidden: false, + }, + { + value: 'constant_line(3)', + params: [{name: 'project_id', type: 'string', defaultValue: 'reactor'}], + hidden: false, + }, + { + value: 'constant_line(4)', + params: [{name: 'project_id', type: 'string', defaultValue: 'reactor'}], + hidden: false, + }, + ], + params: [ + {type: 'datetime', name: 'from', defaultValue: '2023-03-01T00:00:00.000+00:00'}, + {type: 'datetime', name: 'to', defaultValue: '2023-03-02T00:00:00.000+00:00'}, + ], + visualization: { + id: 'area', + type: 'line', + name: 'label_visualization-area', + iconProps: {width: '24'}, + allowFilters: false, + allowColors: true, + allowSort: false, + allowSegments: false, + allowLabels: true, + availableLabelModes: ['absolute'], + placeholders: [ + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'x', + type: 'x', + title: 'section_x', + iconProps: {}, + items: [ + { + guid: 'timestamp-0', + title: 'timestamp', + datasetId: 'ql-mocked-dataset', + data_type: 'genericdatetime', + cast: 'genericdatetime', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + required: true, + capacity: 1, + settings: { + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + holidays: 'off', + axisFormatMode: 'auto', + axisModeMap: {}, + axisMode: AxisMode.Continuous, + }, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'y', + type: 'y', + title: 'section_y', + iconProps: {}, + items: [ + { + guid: 'value-1', + title: 'value', + datasetId: 'ql-mocked-dataset', + data_type: 'float', + cast: 'float', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + settings: { + scale: 'auto', + scaleValue: 'min-max', + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + nulls: 'as-0', + axisFormatMode: 'auto', + axisModeMap: {}, + }, + }, + ], + allowShapes: false, + allowAvailable: true, + }, + order: [], + preview: true, + available: [ + { + guid: 'timestamp-0', + title: 'timestamp', + datasetId: 'ql-mocked-dataset', + data_type: 'genericdatetime', + cast: 'genericdatetime', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + { + guid: 'value-1', + title: 'value', + datasetId: 'ql-mocked-dataset', + data_type: 'float', + cast: 'float', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + { + guid: 'query #-2', + title: 'query #', + datasetId: 'ql-mocked-dataset', + data_type: 'string', + cast: 'string', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + { + title: 'Column Names', + type: 'PSEUDO', + data_type: 'string', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + guid: '', + datasetId: '', + cast: 'string', + calc_mode: 'direct', + }, + ], + sort: [], + sharedData: {}, + }, + ChartEditor: { + updateLibraryConfig: () => {}, + }, + shapes: [], + segments: [], +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line-time/index.ts b/src/server/modes/charts/plugins/datalens/preparers/line-time/index.ts new file mode 100644 index 0000000..fe6b776 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line-time/index.ts @@ -0,0 +1,280 @@ +import type {ExtendedSeriesLineOptions} from '../../../../../../../shared'; +import { + AxisNullsMode, + DATALENS_QL_TYPES, + getFormatOptions, + getUtcDateTime, + isDateField, +} from '../../../../../../../shared'; +import {getLineTimeDistinctValue} from '../../../../../../../shared/modules/colors/distincts-helpers'; +import {getColorsForNames} from '../../../ql/utils/colors'; +import type { + QLRenderResultYagr, + QLRenderResultYagrGraph, + QLValue, +} from '../../../ql/utils/misc-helpers'; +import { + formatUnknownTypeValue, + parseNumberValue, + renderValue, +} from '../../../ql/utils/value-helpers'; +import {mapAndColorizeGraphsByPalette} from '../../utils/color-helpers'; +import { + findIndexInOrder, + getFormatOptionsFromFieldFormatting, + isLegendEnabled, +} from '../../utils/misc-helpers'; +import type {PrepareFunctionArgs} from '../types'; + +const collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}); + +// eslint-disable-next-line complexity +function prepareLineTime(options: PrepareFunctionArgs) { + const { + placeholders, + resultData, + colors, + idToTitle, + colorsConfig, + defaultColorPaletteId, + shared, + ChartEditor, + } = options; + + const {data, order} = resultData; + + const xField = placeholders[0].items[0]; + + if (!xField) { + return {timeline: []}; + } + + const xFieldDataType = xField.data_type; + const xFieldIndex = findIndexInOrder(order, xField, idToTitle[xField.guid] || xField.title); + const xFieldIsDate = isDateField(xField); + + const yPlaceholderSettings = placeholders[1]?.settings || {}; + + const yFields = placeholders[1].items; + const yFieldIndexes = yFields.map((yField) => + findIndexInOrder(order, yField, idToTitle[yField.guid] || yField.title), + ); + + const colorIndexes = colors.map((color) => + findIndexInOrder(order, color, idToTitle[color.guid] || color.title), + ); + + const result: QLRenderResultYagr = {timeline: [], timeZone: 'UTC'}; + + if (yFields.length > 0 && xField) { + let xValues: QLValue[] = []; + let colorValues: QLValue[] = []; + + const dataMatrix: Record> = + {}; + + data.forEach((row) => { + let xValue: QLValue = row[xFieldIndex]; + + if (typeof xValue !== 'undefined' && xValue !== null && xFieldIsDate) { + // CHARTS-6632 - revision/study of yagr is necessary, after that moment.utc(xValue) is possible.valueOf(); + xValue = (getUtcDateTime(xValue)?.valueOf() || 0) / 1000; + } else if (xFieldDataType === DATALENS_QL_TYPES.UNKNOWN) { + xValue = formatUnknownTypeValue(xValue); + } + + xValues.push(xValue); + + yFieldIndexes.forEach((yFieldIndex, i) => { + const yValue = row[yFieldIndex]; + + if (colorIndexes.length > 0) { + let colorValue = ''; + colorIndexes.forEach((colorIndex, j) => { + const colorValuePart: QLValue = + colors[j].type === 'PSEUDO' ? yFields[i].title : row[colorIndex]; + + colorValue = getLineTimeDistinctValue(colorValuePart, colorValue); + }); + + let dataCell = dataMatrix[String(xValue)] as Record< + string | number, + number | null + >; + + if (typeof dataCell === 'undefined') { + dataCell = dataMatrix[String(xValue)] = {}; + } + + if (typeof dataCell === 'object' && dataCell !== null) { + dataCell[String(colorValue)] = parseNumberValue(yValue); + } + + colorValues.push(colorValue); + } else { + dataMatrix[String(xValue)] = parseNumberValue(yValue); + } + }); + }); + + xValues = Array.from(new Set(xValues)).sort(); + colorValues = Array.from(new Set(colorValues)).sort(); + + result.timeline = xValues.map((value) => Number(value)); + + if (colors.length > 0) { + const graphs: QLRenderResultYagrGraph[] = colorValues.map((colorValue) => { + return { + id: renderValue(colorValue), + name: renderValue(colorValue), + colorValue: renderValue(colorValue), + colorGuid: colors[0].guid || null, + color: 'rgb(0,127,0)', + data: [], + }; + }); + + xValues.forEach((xValue) => { + const dataCell = dataMatrix[String(xValue)] as Record; + if (typeof dataCell === 'object' && dataCell !== null) { + colorValues.forEach((colorValue: QLValue, i) => { + if (typeof dataCell[String(colorValue)] === 'undefined') { + if (yPlaceholderSettings.nulls === AxisNullsMode.AsZero) { + graphs[i].data.push(0); + } else { + graphs[i].data.push(null); + } + } else { + graphs[i].data.push(dataCell[String(colorValue)]); + } + }); + } + }); + + result.graphs = graphs; + } else { + result.graphs = []; + + yFields.forEach((y) => { + const graph = { + id: y.title, + name: y.title, + data: xValues.map((xValue) => { + return dataMatrix[String(xValue)] as number; + }), + }; + + const formatting = getFormatOptions(y); + const tooltipOptions = getFormatOptionsFromFieldFormatting(formatting, y.data_type); + + // TODO: add other options when they will be available in Chartkit + // https://github.com/gravity-ui/chartkit/issues/476 + ChartEditor.updateLibraryConfig({ + tooltip: { + precision: tooltipOptions.chartKitPrecision, + }, + }); + + result.graphs?.push(graph); + }); + } + } else if (xField) { + let xValues: QLValue[] = []; + + data.forEach((row) => { + let xValue: QLValue = row[xFieldIndex]; + + if (typeof xValue !== 'undefined' && xValue !== null && xFieldIsDate) { + // CHARTS-6632 - revision/study of yagr is necessary, after that moment.utc(xValue) is possible.valueOf(); + xValue = (getUtcDateTime(xValue)?.valueOf() || 0).valueOf() / 1000; + } else if (xFieldDataType === DATALENS_QL_TYPES.UNKNOWN) { + xValue = formatUnknownTypeValue(xValue); + } + + xValues.push(xValue); + }); + + xValues = Array.from(new Set(xValues)).sort(); + + result.timeline = xValues.map((value) => Number(value)); + + result.graphs = [ + { + data: data.map(() => { + return null; + }), + }, + ]; + + return result; + } else { + result.graphs = []; + + return result; + } + + result.graphs.sort((graph1, graph2) => { + if (graph1.name && graph2.name) { + return collator.compare(String(graph1.name), String(graph2.name)); + } else { + return 0; + } + }); + + result.graphs.forEach((graph) => { + graph.spanGaps = yPlaceholderSettings.nulls === AxisNullsMode.Connect; + }); + + const useColorizingWithPalettes = + colorsConfig.mountedColors && Object.keys(colorsConfig.mountedColors).length > 0; + + if (useColorizingWithPalettes) { + // Use usual colorizing with datalens palettes + mapAndColorizeGraphsByPalette({ + graphs: result.graphs as unknown as ExtendedSeriesLineOptions[], + colorsConfig, + isColorsItemExists: Boolean(colors), + defaultColorPaletteId, + colorField: colors?.[0], + }); + } else { + // Else apply colorizing from YAGR for compatibility with Monitoring + let colorData: string[]; + if (shared.visualization.id === 'area' && result.graphs.length > 1) { + colorData = getColorsForNames( + result.graphs.map(({name}) => String(name)), + {type: 'gradient'}, + ); + } else { + colorData = getColorsForNames(result.graphs.map(({name}) => String(name))); + } + + result.graphs.forEach((graph, i) => { + graph.color = colorData[i]; + }); + + if (result.graphs.length > 1 && isLegendEnabled(shared.extraSettings)) { + ChartEditor.updateLibraryConfig({ + legend: { + show: true, + }, + }); + } + } + + result.axes = [ + { + scale: 'x', + plotLines: [ + { + width: 3, + color: '#ffa0a0', + }, + ], + }, + ]; + + return result; +} + +export default prepareLineTime; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/__tests__/line.test.ts b/src/server/modes/charts/plugins/datalens/preparers/line/__tests__/line.test.ts new file mode 100644 index 0000000..8e988a0 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/__tests__/line.test.ts @@ -0,0 +1,38 @@ +import {prepareHighchartsLine as linePrepare} from '../index'; + +const { + regularTitle, + calculatedTitle, + preparingDataForFieldsWithSameTitlesFromDifferentDatasets, +} = require('./mocks/bar.mock'); +const { + linesColoredByFieldWithPostfixArgs, + linesColoredByFieldWithPostfixResult, +} = require('./mocks/line.mock'); + +describe('linePrepare', () => { + describe('bar', () => { + test('should has regular title in case with one field in X section', () => { + const result = linePrepare(regularTitle); + expect(result.graphs[0].legendTitle).toEqual('Sales'); + }); + + test('should has calculated title in case with two fields in X section', () => { + const result = linePrepare(calculatedTitle); + expect(result.graphs[0].legendTitle).toEqual('Sales: Central'); + }); + + test('data of indicators with the same titles from different datasets', () => { + const result = linePrepare(preparingDataForFieldsWithSameTitlesFromDifferentDatasets); + expect(result.graphs[0].data[0].y * 2).toEqual(result.graphs[1].data[0].y); + expect(result.graphs[0].data[5].y * 2).toEqual(result.graphs[1].data[5].y); + }); + }); + + describe('line', () => { + test('should apply colors and shapes to fields with prefixes/postfixes', () => { + const result = linePrepare(linesColoredByFieldWithPostfixArgs); + expect(result).toEqual(linesColoredByFieldWithPostfixResult); + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/__tests__/mocks/bar.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/line/__tests__/mocks/bar.mock.ts new file mode 100644 index 0000000..b58e61c --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/__tests__/mocks/bar.mock.ts @@ -0,0 +1,531 @@ +import {EmptyPrepapreArgs} from '../../../__tests__/common.mock'; + +const chartEditorMock = { + getLang: () => { + return 'en'; + }, + updateHighchartsConfig: () => {}, + updateConfig: () => {}, + getWidgetConfig: () => {}, +}; + +export const calculatedTitle = { + ...EmptyPrepapreArgs, + ChartEditor: chartEditorMock, + placeholders: [ + { + id: 'x', + items: [ + { + guid: 'ac8dd226-3372-4212-bde7-17afc792a728', + source: 'Category', + data_type: 'string', + type: 'DIMENSION', + title: 'Category', + }, + { + guid: 'e6115038-dc12-41c1-beae-2f7e84d33bc2', + source: 'Region', + data_type: 'string', + type: 'DIMENSION', + title: 'Region', + }, + ], + }, + { + id: 'y', + items: [ + { + guid: '809d03e1-15b7-4bdc-b108-653603a27001', + source: 'Sales', + data_type: 'float', + title: 'Sales', + }, + ], + }, + ], + resultData: { + data: [['Furniture', 'Central', '163798.70024430752']], + order: [{title: 'Category'}, {title: 'Region'}, {title: 'Sales'}], + }, + colors: [], + sort: [], + colorsConfig: {colors: []}, + shapes: [], + segments: [], + labels: [], + idToTitle: { + 'ac8dd226-3372-4212-bde7-17afc792a728': 'Category', + 'e6115038-dc12-41c1-beae-2f7e84d33bc2': 'Region', + '809d03e1-15b7-4bdc-b108-653603a27001': 'Sales', + }, + idToDataType: { + 'ac8dd226-3372-4212-bde7-17afc792a728': 'string', + 'e6115038-dc12-41c1-beae-2f7e84d33bc2': 'string', + '809d03e1-15b7-4bdc-b108-653603a27001': 'float', + }, + visualizationId: 'column', + datasets: [], + shared: { + visualization: {id: 'bar'}, + }, +}; + +export const regularTitle = { + ...EmptyPrepapreArgs, + ChartEditor: chartEditorMock, + placeholders: [ + { + id: 'x', + items: [ + { + guid: 'ac8dd226-3372-4212-bde7-17afc792a728', + source: 'Category', + data_type: 'string', + type: 'DIMENSION', + title: 'Category', + }, + ], + }, + { + id: 'y', + items: [ + { + guid: '809d03e1-15b7-4bdc-b108-653603a27001', + source: 'Sales', + data_type: 'float', + title: 'Sales', + }, + ], + }, + ], + resultData: { + data: [['Furniture', '163798.70024430752']], + order: [{title: 'Category'}, {title: 'Sales'}], + }, + colors: [], + sort: [], + colorsConfig: {colors: []}, + shapes: [], + segments: [], + labels: [], + idToTitle: { + 'ac8dd226-3372-4212-bde7-17afc792a728': 'Category', + '809d03e1-15b7-4bdc-b108-653603a27001': 'Sales', + }, + idToDataType: { + 'ac8dd226-3372-4212-bde7-17afc792a728': 'string', + '809d03e1-15b7-4bdc-b108-653603a27001': 'float', + }, + visualizationId: 'column', + datasets: [], + shared: { + visualization: {id: 'bar'}, + }, +}; + +// /wizard/foizv5h101la9-multidatasetnyy-chart-s-odinakovymi-pokazatelyami +export const preparingDataForFieldsWithSameTitlesFromDifferentDatasets = { + ...EmptyPrepapreArgs, + ChartEditor: chartEditorMock, + placeholders: [ + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'x', + type: 'x', + title: 'section_x', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + has_auto_aggregation: false, + calc_mode: 'direct', + guid: '184e0546-c12d-4991-bb54-0e248126b142', + aggregation_locked: null, + formula: '', + description: '', + virtual: false, + managed_by: 'user', + hidden: false, + type: 'DIMENSION', + data_type: 'date', + avatar_id: '908d43f0-a3fa-11ea-898e-f5e36af92c99', + lock_aggregation: null, + initial_data_type: 'float', + valid: true, + title: 'Date', + autoaggregated: false, + source: '2428083fa6364c40', + aggregation: 'none', + cast: 'date', + datasetId: 'knqi49wfwavoe', + id: 'inserted-1615552768302', + datasetName: 'dataset-sample', + }, + ], + required: true, + capacity: 1, + settings: { + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + nulls: 'ignore', + }, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'y', + type: 'y', + title: 'section_y', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + has_auto_aggregation: false, + calc_mode: 'direct', + guid: 'ae208980-4605-47fb-9d13-53bde57fa896', + aggregation_locked: null, + formula: '', + description: '', + virtual: false, + managed_by: 'user', + hidden: false, + type: 'MEASURE', + data_type: 'integer', + avatar_id: '908d43f0-a3fa-11ea-898e-f5e36af92c99', + lock_aggregation: null, + initial_data_type: 'float', + valid: true, + title: 'ProfitProto', + autoaggregated: false, + source: '7ebddadfe6804869', + aggregation: 'count', + cast: 'float', + datasetId: 'knqi49wfwavoe', + id: 'inserted-1615552773208', + datasetName: 'dataset-sample', + }, + { + aggregation: 'none', + aggregation_locked: true, + valid: true, + description: '', + initial_data_type: 'float', + has_auto_aggregation: true, + data_type: 'integer', + managed_by: 'user', + cast: 'integer', + virtual: false, + calc_mode: 'formula', + type: 'MEASURE', + autoaggregated: true, + source: '', + lock_aggregation: false, + avatar_id: null, + title: 'Profit', + formula: '[ProfitProto] * 2', + hidden: false, + guid: 'd4de4490-832f-11eb-ac20-9f8ae368690a', + datasetId: 'r0ubmdsbk076l', + id: 'inserted-1615552783597', + datasetName: 'dataset-sample-copy', + }, + ], + capacity: null, + settings: { + scale: 'auto', + scaleValue: 'min-max', + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + }, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'y2', + type: 'y2', + title: 'section_y2', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + settings: { + scale: 'auto', + scaleValue: 'min-max', + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + }, + }, + ], + resultData: { + data: [ + ['30', '1970-01-12', '15'], + ['32', '1970-01-13', '16'], + ['12', '1970-01-08', '6'], + ['14', '1970-01-11', '7'], + ['4', '1970-01-02', '2'], + ['20', '1970-01-07', '10'], + ['22', '1970-01-10', '11'], + ['4', '1970-01-03', '2'], + ['12', '1970-01-06', '6'], + ['8', '1970-01-09', '4'], + ['6', '1970-01-04', '3'], + ['6', '1970-01-05', '3'], + ], + order: [ + { + datasetId: 'r0ubmdsbk076l', + title: 'Profit', + }, + [ + { + datasetId: 'r0ubmdsbk076l', + title: 'Date', + }, + { + datasetId: 'knqi49wfwavoe', + title: 'Date', + }, + ], + { + datasetId: 'knqi49wfwavoe', + title: 'Profit', + }, + ], + }, + colors: [ + { + title: 'Measure Names', + type: 'PSEUDO', + className: 'item pseudo-item dimension-item', + data_type: 'string', + }, + ], + shapes: [], + segments: [], + sort: [], + colorsConfig: {colors: []}, + labels: [], + idToTitle: { + 'd4de4490-832f-11eb-ac20-9f8ae368690a': 'Profit', + '21636d40-713d-11eb-a432-2dbdb05aa5a7': '_sales_half', + '6c0efe50-703d-11eb-bf6c-352aa2d4c7bf': '_url', + '184e0546-c12d-4991-bb54-0e248126b142': 'Date', + 'ae208980-4605-47fb-9d13-53bde57fa896': 'Profit', + '53c046b8-a704-427d-b4c7-b69a5baadc2a': 'Sales', + }, + idToDataType: { + 'd4de4490-832f-11eb-ac20-9f8ae368690a': 'integer', + '21636d40-713d-11eb-a432-2dbdb05aa5a7': 'float', + '6c0efe50-703d-11eb-bf6c-352aa2d4c7bf': 'markup', + '184e0546-c12d-4991-bb54-0e248126b142': 'date', + 'ae208980-4605-47fb-9d13-53bde57fa896': 'integer', + '53c046b8-a704-427d-b4c7-b69a5baadc2a': 'integer', + }, + visualizationId: 'line', + datasets: [ + { + source_avatars: [], + component_errors: { + items: [], + }, + dataset: { + source_avatars: [], + result_schema: [], + component_errors: { + items: [], + }, + obligatory_filters: [], + result_schema_aux: {}, + avatar_relations: [], + created_via: 'user', + source_features: {}, + preview_enabled: true, + sources: [], + rls: {}, + }, + result_schema_aux: {}, + obligatory_filters: [], + key: 'Users/robot-charts/dataset-sample-copy', + created_via: 'user', + preview_enabled: true, + source_features: {}, + id: 'r0ubmdsbk076l', + options: {}, + avatar_relations: [], + sources: [], + is_favorite: false, + rls: {}, + realName: 'dataset-sample-copy', + }, + { + obligatory_filters: [], + preview_enabled: true, + component_errors: { + items: [], + }, + result_schema_aux: {}, + avatar_relations: [], + source_features: {}, + options: {}, + source_avatars: [], + is_favorite: false, + sources: [], + created_via: 'user', + id: 'knqi49wfwavoe', + dataset: { + avatar_relations: [], + source_features: {}, + source_avatars: [], + result_schema: [], + created_via: 'user', + obligatory_filters: [], + sources: [], + preview_enabled: true, + rls: {}, + component_errors: { + items: [], + }, + result_schema_aux: {}, + }, + key: 'Users/robot-charts/dataset-sample', + rls: {}, + realName: 'dataset-sample', + }, + ], + shared: { + dataset: { + source_avatars: [], + component_errors: { + items: [], + }, + dataset: { + source_avatars: [], + result_schema: [], + component_errors: { + items: [], + }, + obligatory_filters: [], + result_schema_aux: {}, + avatar_relations: [], + created_via: 'user', + source_features: {}, + preview_enabled: true, + sources: [], + rls: {}, + }, + result_schema_aux: {}, + obligatory_filters: [], + key: 'Users/robot-charts/dataset-sample-copy', + created_via: 'user', + preview_enabled: true, + source_features: {}, + id: 'r0ubmdsbk076l', + options: {}, + avatar_relations: [], + sources: [], + is_favorite: false, + rls: {}, + realName: 'dataset-sample-copy', + }, + extraSettings: {}, + filters: [], + geopointsConfig: {}, + hierarchies: [], + links: [ + { + id: 'f1837d40-832f-11eb-9caf-4dc39edbbf2b', + fields: { + knqi49wfwavoe: { + field: { + guid: '184e0546-c12d-4991-bb54-0e248126b142', + title: 'Date', + }, + dataset: { + id: 'knqi49wfwavoe', + realName: 'dataset-sample', + }, + }, + r0ubmdsbk076l: { + field: { + guid: '184e0546-c12d-4991-bb54-0e248126b142', + title: 'Date', + }, + dataset: { + id: 'r0ubmdsbk076l', + realName: 'dataset-sample-copy', + }, + }, + }, + }, + ], + shapes: [], + tooltips: [], + type: 'datalens', + updates: [], + visualization: { + id: 'line', + type: 'line', + name: 'label_visualization-line', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + allowFilters: true, + allowColors: true, + allowSort: true, + allowLabels: true, + colorsCapacity: 2, + availableLabelModes: ['absolute'], + allowLayerFilters: false, + }, + sharedData: {}, + }, +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/__tests__/mocks/line.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/line/__tests__/mocks/line.mock.ts new file mode 100644 index 0000000..3a3dbb2 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/__tests__/mocks/line.mock.ts @@ -0,0 +1,464 @@ +import {COMMON_PALETTE_ID} from '../../../../../../../../../shared'; +import classic20 from '../../../../../../../../../shared/constants/colors/common/classic-20'; +import {EmptyPrepapreArgs} from '../../../__tests__/common.mock'; + +const chartEditorMock = { + getLang: () => { + return 'en'; + }, + updateHighchartsConfig: () => {}, + updateConfig: () => {}, + getWidgetConfig: () => {}, +}; + +export const linesColoredByFieldWithPostfixArgs = { + ...EmptyPrepapreArgs, + ChartEditor: chartEditorMock, + placeholders: [ + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'x', + type: 'x', + title: 'section_x', + iconProps: {}, + items: [ + { + lock_aggregation: null, + title: 'Month', + hidden: false, + guid: 'e83fb454-ca9a-4083-95be-1994e9957655', + managed_by: 'user', + cast: 'integer', + data_type: 'integer', + aggregation_locked: null, + has_auto_aggregation: null, + virtual: false, + type: 'DIMENSION', + initial_data_type: 'integer', + autoaggregated: false, + valid: true, + aggregation: 'none', + description: '', + source: 'Month', + avatar_id: '7a1555b0-33c8-4643-a8ad-d41d76569b69', + calc_mode: 'direct', + formula: '', + guid_formula: '', + datasetId: 'y55xt5d3p9csw', + id: 'dimension-7', + datasetName: 'Example', + default_value: null, + value_constraint: null, + hideLabelMode: 'show', + formatting: { + format: 'number', + showRankDelimiter: true, + prefix: '', + postfix: '', + labelMode: 'absolute', + precision: 0, + }, + backgroundSettings: { + enabled: false, + settingsId: 'b0df4800-88c2-11ee-96b1-05f14cfb8434', + settings: { + gradientState: {}, + paletteState: { + palette: COMMON_PALETTE_ID.CLASSIC_20, + mountedColors: {}, + }, + isContinuous: false, + }, + colorFieldGuid: 'e83fb454-ca9a-4083-95be-1994e9957655', + }, + }, + ], + required: true, + capacity: 1, + settings: { + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + holidays: 'off', + axisFormatMode: 'auto', + axisModeMap: { + 'e83fb454-ca9a-4083-95be-1994e9957655': 'discrete', + }, + }, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'y', + type: 'y', + title: 'section_y', + iconProps: {}, + items: [ + { + lock_aggregation: false, + title: 'Profit', + hidden: false, + guid: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + managed_by: 'user', + cast: 'integer', + data_type: 'integer', + aggregation_locked: false, + has_auto_aggregation: false, + virtual: false, + type: 'MEASURE', + initial_data_type: 'float', + autoaggregated: false, + valid: true, + aggregation: 'sum', + description: '', + source: 'Profit', + avatar_id: '7a1555b0-33c8-4643-a8ad-d41d76569b69', + calc_mode: 'direct', + formula: '', + guid_formula: '', + datasetId: 'y55xt5d3p9csw', + id: 'measure-12', + datasetName: 'Example', + default_value: null, + value_constraint: null, + }, + ], + settings: { + scale: 'auto', + scaleValue: 'min-max', + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + nulls: 'ignore', + axisFormatMode: 'auto', + }, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'y2', + type: 'y2', + title: 'section_y2', + iconProps: {}, + items: [], + settings: { + scale: 'auto', + scaleValue: 'min-max', + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + nulls: 'ignore', + axisFormatMode: 'auto', + }, + }, + ], + colors: [ + { + virtual: false, + description: '', + managed_by: 'user', + initial_data_type: 'integer', + data_type: 'integer', + aggregation_locked: null, + autoaggregated: false, + type: 'DIMENSION', + valid: true, + aggregation: 'none', + title: 'Year', + guid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + has_auto_aggregation: null, + cast: 'integer', + hidden: false, + lock_aggregation: null, + source: 'Year', + avatar_id: '7a1555b0-33c8-4643-a8ad-d41d76569b69', + calc_mode: 'direct', + formula: '', + guid_formula: '', + default_value: null, + value_constraint: null, + datasetId: 'y55xt5d3p9csw', + id: 'dimension-8', + datasetName: 'Example', + hideLabelMode: 'show', + formatting: { + format: 'number', + showRankDelimiter: true, + prefix: '', + postfix: ' г. в.', + labelMode: 'absolute', + precision: 0, + }, + }, + ], + colorsConfig: { + polygonBorders: 'show', + palette: COMMON_PALETTE_ID.CLASSIC_20, + mountedColors: {'2014': '6', '2015': '12', '2016': '18', '2017': '19'}, + fieldGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + coloredByMeasure: false, + colorMode: 'palette', + availablePalettes: {[COMMON_PALETTE_ID.CLASSIC_20]: classic20}, + gradientColors: ['#0044A3', '#8CCBFF'], + loadedColorPalettes: {}, + }, + geopointsConfig: {}, + sort: [], + visualizationId: 'line', + labels: [], + tooltips: [], + datasets: ['y55xt5d3p9csw'], + resultData: { + data: [ + ['2', '2789', '2015'], + ['1', '7074', '2017'], + ['3', '9667', '2015'], + ['2', '844', '2014'], + ['1', '2785', '2016'], + ['3', '429', '2014'], + ['3', '14646', '2017'], + ['2', '1564', '2017'], + ['1', '-3308', '2015'], + ['3', '3535', '2016'], + ['2', '4969', '2016'], + ['1', '2419', '2014'], + ], + legend: [ + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + [0, 1, 2], + ], + order: [ + {datasetId: 'y55xt5d3p9csw', title: 'Month', dataType: 'integer'}, + {datasetId: 'y55xt5d3p9csw', title: 'Profit', dataType: 'integer'}, + {datasetId: 'y55xt5d3p9csw', title: 'Year', dataType: 'integer'}, + ], + totals: [], + }, + fields: [ + { + role_spec: {role: 'row', visibility: 'visible'}, + field_type: 'DIMENSION', + title: 'Month', + data_type: 'integer', + item_type: 'field', + id: 'e83fb454-ca9a-4083-95be-1994e9957655', + legend_item_id: 0, + guid: 'e83fb454-ca9a-4083-95be-1994e9957655', + }, + { + role_spec: {role: 'measure'}, + field_type: 'MEASURE', + title: 'Profit', + data_type: 'integer', + item_type: 'field', + id: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + legend_item_id: 1, + guid: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + }, + { + role_spec: {role: 'row', visibility: 'visible'}, + field_type: 'DIMENSION', + title: 'Year', + data_type: 'integer', + item_type: 'field', + id: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + legend_item_id: 2, + guid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + }, + { + role_spec: {role: 'filter'}, + field_type: 'DIMENSION', + title: 'Month', + data_type: 'integer', + item_type: 'field', + id: 'e83fb454-ca9a-4083-95be-1994e9957655', + legend_item_id: 3, + guid: 'e83fb454-ca9a-4083-95be-1994e9957655', + }, + ], + idToTitle: { + 'e83fb454-ca9a-4083-95be-1994e9957655': 'Month', + '4b6463d1-30e1-425e-b7d2-77205bc75de6': 'Profit', + 'd0ab8220-16e9-4320-b6da-1ca12163d956': 'Year', + }, + idToDataType: { + 'e83fb454-ca9a-4083-95be-1994e9957655': 'integer', + '4b6463d1-30e1-425e-b7d2-77205bc75de6': 'integer', + 'd0ab8220-16e9-4320-b6da-1ca12163d956': 'integer', + }, + shared: { + visualization: { + id: 'line', + }, + }, + shapes: [ + { + data_type: 'integer', + virtual: false, + valid: true, + has_auto_aggregation: null, + initial_data_type: 'integer', + aggregation_locked: null, + title: 'Year', + lock_aggregation: null, + autoaggregated: false, + description: '', + guid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + aggregation: 'none', + managed_by: 'user', + cast: 'integer', + type: 'DIMENSION', + hidden: false, + avatar_id: '7a1555b0-33c8-4643-a8ad-d41d76569b69', + source: 'Year', + calc_mode: 'direct', + formula: '', + guid_formula: '', + default_value: null, + value_constraint: null, + datasetId: 'y55xt5d3p9csw', + id: 'dimension-8', + datasetName: 'Example', + hideLabelMode: 'show', + formatting: { + format: 'number', + showRankDelimiter: true, + prefix: '', + postfix: ' г.', + labelMode: 'absolute', + precision: 0, + }, + }, + ], + shapesConfig: { + mountedShapes: {'2014': 'Solid', '2015': 'Dot', '2016': 'Dot', '2017': 'Solid'}, + fieldGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + }, + segments: [], +}; + +export const linesColoredByFieldWithPostfixResult = { + graphs: [ + { + id: '2 014 г. в.', + title: '2 014 г. в.', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + {x: 0, y: 2419, dataLabels: {enabled: false}, label: ''}, + {x: 1, y: 844, dataLabels: {enabled: false}, label: ''}, + {x: 2, y: 429, dataLabels: {enabled: false}, label: ''}, + ], + dataLabels: {enabled: false, useHTML: false}, + legendTitle: '2 014 г. в.', + drillDownFilterValue: '2014', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + shapeGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: '2014', + shapeValue: '2014', + custom: {}, + color: '#FF91A1', + dashStyle: 'Solid', + }, + { + id: '2 015 г. в.', + title: '2 015 г. в.', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + {x: 0, y: -3308, dataLabels: {enabled: false}, label: ''}, + {x: 1, y: 2789, dataLabels: {enabled: false}, label: ''}, + {x: 2, y: 9667, dataLabels: {enabled: false}, label: ''}, + ], + dataLabels: {enabled: false, useHTML: false}, + legendTitle: '2 015 г. в.', + drillDownFilterValue: '2015', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + shapeGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: '2015', + shapeValue: '2015', + custom: {}, + color: '#0FA08D', + dashStyle: 'Dot', + }, + { + id: '2 016 г. в.', + title: '2 016 г. в.', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + {x: 0, y: 2785, dataLabels: {enabled: false}, label: ''}, + {x: 1, y: 4969, dataLabels: {enabled: false}, label: ''}, + {x: 2, y: 3535, dataLabels: {enabled: false}, label: ''}, + ], + dataLabels: {enabled: false, useHTML: false}, + legendTitle: '2 016 г. в.', + drillDownFilterValue: '2016', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + shapeGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: '2016', + shapeValue: '2016', + custom: {}, + color: '#FFB46C', + dashStyle: 'Dot', + }, + { + id: '2 017 г. в.', + title: '2 017 г. в.', + tooltip: {chartKitFormatting: true, chartKitPrecision: 0}, + data: [ + {x: 0, y: 7074, dataLabels: {enabled: false}, label: ''}, + {x: 1, y: 1564, dataLabels: {enabled: false}, label: ''}, + {x: 2, y: 14646, dataLabels: {enabled: false}, label: ''}, + ], + dataLabels: {enabled: false, useHTML: false}, + legendTitle: '2 017 г. в.', + drillDownFilterValue: '2017', + colorGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + shapeGuid: 'd0ab8220-16e9-4320-b6da-1ca12163d956', + connectNulls: false, + measureFieldTitle: 'Profit', + yAxis: 0, + colorValue: '2017', + shapeValue: '2017', + custom: {}, + color: '#DCA3D7', + dashStyle: 'Solid', + }, + ], + categories: ['1', '2', '3'], +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/gravity-charts.ts b/src/server/modes/charts/plugins/datalens/preparers/line/gravity-charts.ts new file mode 100644 index 0000000..82aea8f --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/gravity-charts.ts @@ -0,0 +1,415 @@ +import type { + ChartData, + ChartSeries, + ChartYAxis, + LineSeries, +} from '@gravity-ui/chartkit/gravity-charts'; +import groupBy from 'lodash/groupBy'; +import merge from 'lodash/merge'; +import sortBy from 'lodash/sortBy'; + +import type { + SeriesExportSettings, + ServerField, + WizardVisualizationId, +} from '../../../../../../../shared'; +import { + AxisMode, + PlaceholderId, + getFakeTitleOrTitle, + getIsNavigatorEnabled, + getXAxisMode, + isDateField, + isHtmlField, + isMarkdownField, + isMarkupField, + isNumberField, +} from '../../../../../../../shared'; +import type {ExtendedChartData} from '../../../../../../../shared/types/chartkit'; +import {wrapHtml} from '../../../../../../../shared/utils/ui-sandbox'; +import { + getBaseChartConfig, + getTotalsPrecisionFromSeriesTooltips, + getYAxisBaseConfig, +} from '../../gravity-charts/utils'; +import {getFieldFormatOptions} from '../../gravity-charts/utils/format'; +import {getSeriesRangeSliderConfig} from '../../gravity-charts/utils/range-slider'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import {getExportColumnSettings} from '../../utils/export-helpers'; +import {SEGMENT_TITLE_BASE_CONFIG} from '../constants'; +import {getAxisFormatting, getAxisType, getYAxisPlaceholders} from '../helpers/axis'; +import {DATA_LABEL_DEFAULT_PADDING} from '../helpers/axis/data-labels'; +import {concatStringValues} from '../helpers/common'; +import {isXAxisReversed} from '../helpers/highcharts'; +import {getSegmentMap} from '../helpers/segments'; +import type {PrepareFunctionArgs} from '../types'; +import { + mapChartkitFormatSettingsToGravityChartValueFormat, + mapToGravityChartValueFormat, +} from '../utils'; + +import {prepareLineData} from './prepare-line-data'; +import type {ExtendedLineSeries, ExtendedLineSeriesData} from './types'; + +// eslint-disable-next-line complexity +export function prepareGravityChartLine(args: PrepareFunctionArgs) { + const { + labels, + placeholders, + disableDefaultSorting = false, + shared, + idToDataType, + colors, + shapes, + segments: split, + sort, + visualizationId, + } = args; + const baseConfig = getBaseChartConfig({ + shared, + visualization: {placeholders, id: visualizationId}, + }); + const xPlaceholder = placeholders.find((p) => p.id === PlaceholderId.X); + const xField: ServerField | undefined = xPlaceholder?.items?.[0]; + const yPlaceholder = placeholders.find((p) => p.id === PlaceholderId.Y); + const yFields = yPlaceholder?.items || []; + const y2Placeholder = placeholders.find((p) => p.id === PlaceholderId.Y2); + const y2Fields = y2Placeholder?.items || []; + + const isSecondOnlyYAxis = !yFields.length && y2Fields.length; + + const labelField = labels?.[0]; + const isDataLabelsEnabled = Boolean(labelField); + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + const xAxisMode = getXAxisMode({config: chartConfig}) ?? AxisMode.Discrete; + const isCategoriesXAxis = + !xField || + getAxisType({ + field: xField, + settings: xPlaceholder?.settings, + axisMode: xAxisMode, + }) === 'category' || + disableDefaultSorting; + + const chartYAxisItems = getYAxisPlaceholders({placeholders, shared}); + + const isYAxisEmpty = !yFields.length && !y2Fields.length; + if (!xField || isYAxisEmpty) { + return { + series: { + data: [], + }, + }; + } + + const segmentField = split?.[0]; + const segmentsMap = getSegmentMap(args); + const segments = sortBy(Object.values(segmentsMap), (s) => s.index); + const isSplitEnabled = Boolean(segmentField); + const isSplitWithHtmlValues = isHtmlField(segmentField); + const isMultiAxis = Boolean(yPlaceholder?.items.length && y2Placeholder?.items.length); + const preparedData = prepareLineData(args); + const xCategories = preparedData.categories; + + const colorItem = colors[0]; + const shapeItem = shapes[0]; + + const xValueExportSetting = getExportColumnSettings({ + path: isCategoriesXAxis ? 'category' : 'x', + field: xField, + }); + const yValueExportSetting = getExportColumnSettings({path: 'y', field: yFields[0]}); + + const shouldUseHtmlForLabels = + isMarkupField(labelField) || isHtmlField(labelField) || isMarkdownField(labelField); + const inNavigatorEnabled = getIsNavigatorEnabled(shared); + + const layers = shared.visualization?.layers ?? []; + const hasMultipleLayers = layers.length > 1; + + const seriesData: ExtendedLineSeries[] = preparedData.graphs.map((graph: any) => { + const rangeSlider: LineSeries['rangeSlider'] = inNavigatorEnabled + ? { + ...getSeriesRangeSliderConfig({ + extraSettings: shared.extraSettings, + seriesName: graph.title, + }), + lineWidth: 1, + } + : undefined; + + let seriesName = graph.title; + if (graph.colorGuid && hasMultipleLayers) { + seriesName = `${graph.measureFieldTitle}: ${graph.title}`; + } + + if (graph.custom?.segmentTitle) { + seriesName = concatStringValues(graph.custom.segmentTitle, ': ', seriesName); + } + + const labelFormatting = graph.dataLabels + ? mapToGravityChartValueFormat({field: labelField, formatSettings: graph.dataLabels}) + : undefined; + let yAxisIndex = graph.yAxis; + if (!isSplitEnabled && isSecondOnlyYAxis && chartYAxisItems.length > 1) { + yAxisIndex = 1; + } + + const exportSettings: SeriesExportSettings = { + columns: [ + xValueExportSetting, + { + ...yValueExportSetting, + id: seriesName, + name: seriesName, + }, + ], + }; + + return { + name: seriesName, + type: 'line', + color: graph.color, + nullMode: graph.connectNulls ? 'connect' : 'skip', + data: graph.data.reduce((acc: ExtendedLineSeriesData[], item: any, index: number) => { + const dataItem: ExtendedLineSeriesData = { + y: item?.y ?? null, + custom: item.custom, + }; + + if (isDataLabelsEnabled) { + if (item?.y === null) { + dataItem.label = ''; + } else { + dataItem.label = item?.label; + } + } + + let x: ExtendedLineSeriesData['x']; + if (isCategoriesXAxis) { + x = index; + } else if (!item && xCategories) { + x = xCategories[index]; + } else { + x = item?.x; + } + + if (!isCategoriesXAxis && !Number.isFinite(x)) { + return acc; + } + + dataItem.x = x; + + acc.push(dataItem); + + return acc; + }, []), + dataLabels: { + enabled: isDataLabelsEnabled, + html: shouldUseHtmlForLabels, + format: labelFormatting, + padding: DATA_LABEL_DEFAULT_PADDING, + }, + legend: { + symbol: { + width: 36, + }, + groupId: graph.id, + itemText: graph.legendTitle, + }, + dashStyle: graph.dashStyle, + lineWidth: graph.lineWidth, + linecap: graph.linecap, + linejoin: graph.linejoin, + tooltip: graph.tooltip?.chartKitFormatting + ? { + valueFormat: mapChartkitFormatSettingsToGravityChartValueFormat({ + chartkitFormatSettings: graph.tooltip, + }), + } + : undefined, + yAxis: yAxisIndex, + custom: { + ...graph.custom, + exportSettings, + colorValue: graph.colorValue, + shapeValue: graph.shapeValue, + drillDownFilterValue: graph.drillDownFilterValue, + }, + rangeSlider, + }; + }); + + const shouldUseHtmlForLegend = [colorItem, shapeItem].some(isHtmlField); + const legend: ChartData['legend'] = {html: shouldUseHtmlForLegend}; + const nonEmptyLegendGroups = Array.from( + new Set(seriesData.map((s) => s.legend?.groupId).filter(Boolean)), + ); + if (isSplitEnabled && nonEmptyLegendGroups.length <= 1) { + legend.enabled = false; + } + + let xAxis: ChartData['xAxis'] = { + startOnTick: false, + endOnTick: false, + }; + + if (isCategoriesXAxis) { + xAxis = { + type: 'category', + // @ts-ignore There may be a type mismatch due to the wrapper over html, markup and markdown + categories: xCategories, + labels: { + html: isHtmlField(xField) || isMarkdownField(xField) || isMarkupField(xField), + }, + }; + } else { + if (isDateField(xField)) { + xAxis.type = 'datetime'; + } + + if (isNumberField(xField)) { + xAxis.type = xPlaceholder?.settings?.type === 'logarithmic' ? 'logarithmic' : 'linear'; + + if (xAxis.type === 'logarithmic') { + xAxis.startOnTick = true; + xAxis.endOnTick = true; + } + } + + xAxis.tickMarks = {enabled: Boolean(baseConfig.xAxis?.labels?.enabled)}; + } + + if (xAxis && isXAxisReversed(xField, sort, visualizationId as WizardVisualizationId)) { + xAxis.order = 'reverse'; + } + + let yAxis: ChartYAxis[] = []; + if (isSplitEnabled) { + yAxis = segments.reduce((acc, d) => { + const placeholder = d.isOpposite ? y2Placeholder : yPlaceholder; + const labelNumberFormat = placeholder + ? getAxisFormatting({ + placeholder, + visualizationId, + }) + : undefined; + + const axisBaseConfig = getYAxisBaseConfig({ + placeholder, + }); + const shouldUseSegmentTitle = isMultiAxis ? !d.isOpposite : placeholder?.items.length; + let axisTitle: ChartYAxis['title'] | null = null; + if (shouldUseSegmentTitle) { + let titleText: string = d.title; + if (isSplitWithHtmlValues) { + // @ts-ignore There may be a type mismatch due to the wrapper over html, markup and markdown + titleText = wrapHtml(d.title); + } + axisTitle = { + ...SEGMENT_TITLE_BASE_CONFIG, + text: titleText, + html: isSplitWithHtmlValues, + }; + } + + const chartYAxis = merge(axisBaseConfig, { + title: axisTitle, + plotIndex: d.plotIndex, + labels: { + numberFormat: labelNumberFormat ?? undefined, + }, + position: placeholder?.id === PlaceholderId.Y2 ? 'right' : 'left', + }); + + if (chartYAxis.min === undefined) { + chartYAxis.startOnTick = true; + } + + if (chartYAxis.max === undefined) { + chartYAxis.endOnTick = true; + } + + acc.push(chartYAxis); + + return acc; + }, [] as ChartYAxis[]); + } else { + yAxis = chartYAxisItems.map((placeholder) => { + const labelNumberFormat = placeholder + ? getAxisFormatting({ + placeholder, + visualizationId, + }) + : undefined; + + const axisBaseConfig = getYAxisBaseConfig({ + placeholder, + }); + + const chartYAxis = merge(axisBaseConfig, { + labels: { + numberFormat: labelNumberFormat ?? undefined, + }, + lineColor: 'transparent', + position: placeholder?.id === PlaceholderId.Y2 ? 'right' : 'left', + maxPadding: 0.001, + }); + + if (chartYAxis.min === undefined) { + chartYAxis.startOnTick = true; + } + + if (chartYAxis.max === undefined) { + chartYAxis.endOnTick = true; + } + + return chartYAxis; + }); + } + + const config: ExtendedChartData = { + series: { + data: seriesData as ChartSeries[], + }, + xAxis, + yAxis, + legend, + custom: { + tooltip: { + headerLabel: + isDateField(xField) && !isCategoriesXAxis + ? undefined + : getFakeTitleOrTitle(xField), + }, + }, + }; + + if (isSplitEnabled) { + config.split = { + enable: true, + gap: '40px', + plots: Object.values(groupBy(segments, (d) => d.plotIndex)).map(() => { + return {}; + }), + }; + } + + if (yFields[0]) { + const valueFormat = getFieldFormatOptions({field: yFields[0]}); + config.tooltip = { + valueFormat, + totals: { + valueFormat: + valueFormat?.type === 'number' && valueFormat.format + ? valueFormat + : { + type: 'number', + precision: getTotalsPrecisionFromSeriesTooltips(seriesData), + }, + }, + }; + } + + return merge(baseConfig, config); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/__tests__/getSegmentsYAxis.test.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/__tests__/getSegmentsYAxis.test.ts new file mode 100644 index 0000000..0c3caf2 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/__tests__/getSegmentsYAxis.test.ts @@ -0,0 +1,27 @@ +import type {Highcharts} from '@gravity-ui/chartkit/highcharts'; + +import {WizardVisualizationId} from '../../../../../../../../../shared'; +import {getSegmentsYAxis} from '../segments/getSegmentsYAxis'; +import type {SegmentsMap} from '../segments/types'; + +describe('getSegmentsYAxis', () => { + it('The position of a segment(top) depends on its index', () => { + const segmentsMap: SegmentsMap = { + a: {index: 1, title: 'a', isOpposite: false}, + b: {index: 0, title: 'b', isOpposite: false}, + }; + const actual = getSegmentsYAxis({ + segmentsMap, + placeholders: {}, + visualizationId: WizardVisualizationId.Column, + }).yAxisSettings.map((s) => ({ + top: (s as Highcharts.YAxisOptions).top, + title: s.title?.text, + })); + + expect(actual).toEqual([ + {top: '0%', title: 'b'}, + {top: '52%', title: 'a'}, + ]); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/__tests__/getSortedLineKeys.test.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/__tests__/getSortedLineKeys.test.ts new file mode 100644 index 0000000..5ef928e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/__tests__/getSortedLineKeys.test.ts @@ -0,0 +1,61 @@ +import type {LinesRecord} from '../../types'; +import {sortLineKeysByFirstValues} from '../getSortedLineKeys'; + +describe('sortLineKeysByFirstValues', () => { + const initialLines = [ + { + Technology: { + data: { + 0: {value: 120}, + 1: {value: -150}, + 2: {value: 230}, + }, + }, + 'Office Supplies': { + data: { + 0: {value: 320}, + 1: {value: 500}, + 2: {value: 120}, + }, + }, + Furniture: { + data: { + 0: {value: -120}, + 1: {value: 12150}, + 2: {value: 4}, + }, + }, + }, + ] as unknown as LinesRecord[]; + const categories = [0, 1, 2]; + + let lineKeys: string[][]; + + beforeEach(() => { + lineKeys = initialLines.map((line) => Object.keys(line)); + }); + + it('Should sort line keys by first value in each series in ASC order', () => { + lineKeys.forEach((lk, index) => { + sortLineKeysByFirstValues(lk, index, { + lines: initialLines, + categories, + sortItemDirection: 'ASC', + }); + }); + + expect(lineKeys).toEqual([['Furniture', 'Technology', 'Office Supplies']]); + }); + + it('Should sort line keys by first value in each series in DESC order', () => { + lineKeys.forEach((lk, index) => { + sortLineKeysByFirstValues(lk, index, { + lines: initialLines, + categories, + sortItemDirection: 'DESC', + }); + }); + + expect(lineKeys).toEqual([['Office Supplies', 'Technology', 'Furniture']]); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/axis/getLayerPlaceholderWithItems.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/axis/getLayerPlaceholderWithItems.ts new file mode 100644 index 0000000..915f607 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/axis/getLayerPlaceholderWithItems.ts @@ -0,0 +1,34 @@ +import type { + PlaceholderId, + ServerPlaceholder, + ServerVisualization, +} from '../../../../../../../../../shared'; + +import type {GetLayerPlaceholderWithItemsOptions} from './types'; + +export const getLayerPlaceholderWithItems = ( + visualization: ServerVisualization, + id: PlaceholderId, + {isFirstFromTheTop}: GetLayerPlaceholderWithItemsOptions, +): ServerPlaceholder | undefined => { + const layers = [...(visualization.layers || [])]; + + if (isFirstFromTheTop) { + layers.reverse(); + } + + let placeholder: ServerPlaceholder | undefined; + + layers.forEach((layer) => { + const layersPlaceholder = layer.placeholders; + placeholder = layersPlaceholder.find( + (layerPlaceholder) => layerPlaceholder.id === id && layerPlaceholder.items.length, + ); + + if (placeholder) { + return; + } + }); + + return placeholder; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/axis/types.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/axis/types.ts new file mode 100644 index 0000000..8def9a0 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/axis/types.ts @@ -0,0 +1,3 @@ +export type GetLayerPlaceholderWithItemsOptions = { + isFirstFromTheTop: boolean; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/color-helpers/colorizeByGradient.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/color-helpers/colorizeByGradient.ts new file mode 100644 index 0000000..a8153c1 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/color-helpers/colorizeByGradient.ts @@ -0,0 +1,27 @@ +import type {ExtendedSeriesLineOptions} from '../../../../../../../../../shared'; +import {WizardVisualizationId} from '../../../../../../../../../shared'; +import type {ChartColorsConfig} from '../../../../types'; +import {mapAndColorizeGraphsByGradient} from '../../../../utils/color-helpers'; + +import type {ColorizeByGradientOptions} from './types'; + +const colorizeColumnAndBarGraphs = ( + graphs: ExtendedSeriesLineOptions[], + colorsConfig: ChartColorsConfig, +) => { + mapAndColorizeGraphsByGradient(graphs, colorsConfig); +}; + +export const colorizeByGradient = ( + visualizationId: WizardVisualizationId, + options: ColorizeByGradientOptions, +) => { + switch (visualizationId) { + case WizardVisualizationId.Column: + case WizardVisualizationId.Column100p: + case WizardVisualizationId.Bar: + case WizardVisualizationId.Bar100p: { + colorizeColumnAndBarGraphs(options.graphs, options.colorsConfig); + } + } +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/color-helpers/types.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/color-helpers/types.ts new file mode 100644 index 0000000..20f9914 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/color-helpers/types.ts @@ -0,0 +1,7 @@ +import type {ExtendedSeriesLineOptions} from '../../../../../../../../../shared'; +import type {ChartColorsConfig} from '../../../../types'; + +export type ColorizeByGradientOptions = { + graphs: ExtendedSeriesLineOptions[]; + colorsConfig: ChartColorsConfig; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/getSortedCategories.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/getSortedCategories.ts new file mode 100644 index 0000000..dbc50c9 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/getSortedCategories.ts @@ -0,0 +1,181 @@ +import {getFakeTitleOrTitle, isMeasureField} from '../../../../../../../../shared'; +import {collator, getOriginalTitleOrTitle, numericCollator} from '../../../utils/misc-helpers'; +import type {Categories} from '../../types'; +import type {LineTemplate} from '../types'; + +import type { + GetSortedCategoriesArgs, + SortCategoriesWithColorsSection, + SortCategoriesWithYSectionArgs, +} from './types'; + +type GetCutsArgs = { + categories: Categories; + lines: Record>; + currentLine?: string; +}; + +const getCuts = ({categories, lines, currentLine}: GetCutsArgs): Record => { + if (!currentLine) { + return {}; + } + + const cuts: Record = {}; + + categories.forEach((category) => { + cuts[category] = lines[currentLine].data[category]?.value; + }); + + return cuts; +}; + +const sortCategoriesByCuts = ({ + direction, + cuts, + categories, +}: { + direction: string; + categories: Categories; + cuts: Record; +}) => { + const isASC = direction === 'ASC'; + const forward = isASC ? 1 : -1; + const backward = isASC ? -1 : 1; + + categories.sort((a, b) => { + return cuts[a] > cuts[b] ? forward : backward; + }); +}; + +const sortCategoriesWithYSection = (args: SortCategoriesWithYSectionArgs) => { + const {ySectionItems, categories, sortItem, lineKeys, lines, colorItem, isSegmentsExists} = + args; + + const sortedCategories: Categories = [...categories]; + + const sortItemSource = sortItem.source; + + const matchedYSectionItem = ySectionItems.find((ySectionItem) => { + const ySectionItemTitle = getOriginalTitleOrTitle(ySectionItem); + const sortItemTitle = getOriginalTitleOrTitle(sortItem); + + const ySectionItemSource = ySectionItem.source; + + return ( + ySectionItemTitle === sortItemTitle || + (sortItemSource && sortItemSource === ySectionItemSource) + ); + }); + + if (matchedYSectionItem) { + let cuts: Record = {}; + if ((colorItem && colorItem.type !== 'PSEUDO') || isSegmentsExists) { + lineKeys.forEach((currentLine: string) => { + const currentLineCuts = getCuts({categories, lines, currentLine}); + + categories.forEach((category) => { + if (!currentLineCuts[category]) { + return; + } + + if (Object.hasOwnProperty.call(cuts, category)) { + cuts[category] += currentLineCuts[category]; + } else { + cuts[category] = currentLineCuts[category]; + } + }); + }); + } else { + const currentLine = lineKeys.find((key) => { + return lines[key].fieldTitle === getFakeTitleOrTitle(matchedYSectionItem); + }); + + cuts = getCuts({categories, lines, currentLine}); + } + + sortCategoriesByCuts({direction: sortItem.direction, cuts, categories: sortedCategories}); + } + + return { + sortedCategories, + }; +}; + +const sortCategoriesWithColorsSection = (args: SortCategoriesWithColorsSection) => { + const {sortItem, colorItem, measureColorSortLine, categories} = args; + const sortedCategories = [...categories]; + + const isSortWithColorsSectionItem = colorItem.guid === sortItem.guid; + + if (isSortWithColorsSectionItem) { + if (isMeasureField(colorItem)) { + const cuts = getCuts({ + categories: sortedCategories, + lines: measureColorSortLine, + currentLine: getFakeTitleOrTitle(colorItem), + }); + + sortCategoriesByCuts({ + direction: sortItem.direction, + cuts, + categories: sortedCategories, + }); + } + } + + return { + categories: sortedCategories, + }; +}; + +export const getSortedCategories = (args: GetSortedCategoriesArgs): Categories => { + const { + isSortAvailable, + isXNumber, + categories, + isSortWithYSectionItem, + sortItem, + ySectionItems, + lines, + colorItem, + measureColorSortLine, + isSortBySegments, + isSegmentsExists, + } = args; + + const lineKeys = lines.map((l) => Object.keys(l)); + + if (!isSortAvailable || isSortBySegments) { + const sortFunction = isXNumber ? numericCollator : collator.compare; + // @ts-ignore + return [...categories].sort(sortFunction); + } + + let sortedCategories: Categories = categories; + + if (isSortWithYSectionItem) { + const sortResult = sortCategoriesWithYSection({ + categories, + sortItem, + ySectionItems, + lines: lines[0], + lineKeys: lineKeys[0], + colorItem, + isSegmentsExists, + }); + sortedCategories = sortResult.sortedCategories; + } + + if (colorItem) { + const sortResult = sortCategoriesWithColorsSection({ + sortItem, + colorItem, + categories: sortedCategories, + measureColorSortLine, + }); + + sortedCategories = sortResult.categories; + } + + return sortedCategories; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/getSortedLineKeys.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/getSortedLineKeys.ts new file mode 100644 index 0000000..3321c3a --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/getSortedLineKeys.ts @@ -0,0 +1,98 @@ +import { + WizardVisualizationId, + isDimensionField, + isMeasureField, +} from '../../../../../../../../shared'; +import {collator, isNumericalDataType, numericStringCollator} from '../../../utils/misc-helpers'; +import type {Categories} from '../../types'; +import type {LinesRecord} from '../types'; + +import type {GetSortedLineKeysArgs} from './types'; + +export const sortLineKeysByFirstValues = ( + lk: string[], + index: number, + { + lines, + categories, + sortItemDirection, + }: {lines: LinesRecord[]; categories: Categories; sortItemDirection: string | undefined}, +) => { + const firstCategory = categories[0]; + lk.sort((a, b) => { + const lineDataA = lines[index][a]; + const lineDataB = lines[index][b]; + + const firstValueA = lineDataA.data[firstCategory]?.value; + const firstValueB = lineDataB.data[firstCategory]?.value; + + if (sortItemDirection === 'ASC') { + return Number(firstValueA) - Number(firstValueB); + } + + return Number(firstValueB) - Number(firstValueA); + }); +}; + +export const getSortedLineKeys = ({ + colorItem, + sortItem, + isSortBySegments, + isSortAvailable, + lines, + visualizationId, + yField, + categories, +}: GetSortedLineKeysArgs): string[][] => { + const lineKeys = lines.map((l) => Object.keys(l)); + if (!isSortAvailable || isSortBySegments) { + if (!colorItem || (colorItem && colorItem.type !== 'PSEUDO')) { + lineKeys.forEach((l) => { + l.sort(collator.compare); + }); + } + + return lineKeys; + } + + if (colorItem) { + const sortedLineKeys: string[][] = [...lineKeys]; + const sortItemDirection = sortItem.direction; + + const isDirectionReversed = + sortItemDirection === 'DESC' || typeof sortItemDirection === 'undefined'; + const isAreaChart = + visualizationId === WizardVisualizationId.Area || + visualizationId === WizardVisualizationId.Area100p; + + if (colorItem.guid === sortItem.guid) { + if ( + isNumericalDataType(sortItem.data_type) && + isNumericalDataType(colorItem.data_type) && + isDimensionField(colorItem) + ) { + sortedLineKeys.forEach((lk) => { + lk.sort(numericStringCollator); + }); + } else if (!isMeasureField(colorItem)) { + sortedLineKeys.forEach((lk) => { + lk.sort(collator.compare); + }); + } + + if (isDirectionReversed) { + sortedLineKeys.forEach((lk) => { + lk.reverse(); + }); + } + } else if (sortItem.guid === yField.guid && isAreaChart) { + sortedLineKeys.forEach((lk, index) => { + sortLineKeysByFirstValues(lk, index, {lines, categories, sortItemDirection}); + }); + } + + return sortedLineKeys; + } + + return lineKeys; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/getXAxisValue.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/getXAxisValue.ts new file mode 100644 index 0000000..54a4b53 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/getXAxisValue.ts @@ -0,0 +1,57 @@ +import {DATASET_FIELD_TYPES, isDateField, isNumberField} from '../../../../../../../../shared'; +import {findIndexInOrder, getTimezoneOffsettedTime} from '../../../utils/misc-helpers'; + +import type {GetXAxisValueArgs, XAxisValue} from './types'; + +export function getDateAxisValue(value: string | number | Date, dataType: string) { + const val = new Date(value); + if (dataType === DATASET_FIELD_TYPES.GENERICDATETIME) { + val.setTime(getTimezoneOffsettedTime(val)); + } + + return val.getTime(); +} + +export const getXAxisValue = ({ + x, + ys1, + order, + values, + categories, + xDataType, + xIsPseudo, + categoriesMap, + idToTitle, +}: GetXAxisValueArgs): XAxisValue => { + let xValue; + if (xIsPseudo) { + ys1.forEach((y) => { + const title = y.fakeTitle || idToTitle[y.guid]; + + if (categoriesMap && !categoriesMap.has(title)) { + categoriesMap.set(title, true); + categories.push(title); + } + }); + } else { + const xTitle = idToTitle[x.guid]; + const xi = findIndexInOrder(order, x, xTitle); + const value = values[xi]; + if (value === null) { + return value; + } + + xValue = value; + if (isNumberField({data_type: xDataType})) { + xValue = Number(value); + } else if (isDateField({data_type: xDataType})) { + xValue = getDateAxisValue(value, xDataType); + } + + if (categoriesMap && !categoriesMap.has(xValue as string)) { + categoriesMap.set(xValue as string, true); + categories.push(xValue as string); + } + } + return xValue; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/index.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/index.ts new file mode 100644 index 0000000..a6447cc --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/index.ts @@ -0,0 +1,11 @@ +export * from './getXAxisValue'; +export * from './line-mappers/mapDataToDimensionColoredLines'; +export * from './line-mappers/mapDataToMeasureColoredLines'; +export * from './line-mappers/mapDataToLines'; +export * from './line-mappers/prepareLines'; +export * from './segments/getSegmentsIndexInOrder'; +export * from './segments/getSegmentsList'; +export * from './segments/getSegmentsMap'; +export * from './segments/getSegmentsYAxis'; +export * from './segments/getSegmentName'; +export * from './getSortedCategories'; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/__tests__/helpers.test.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/__tests__/helpers.test.ts new file mode 100644 index 0000000..51ec026 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/__tests__/helpers.test.ts @@ -0,0 +1,45 @@ +import {getColorShapeMappingValue} from '../helpers'; + +describe('getColorShapeMappingValue', () => { + const args = {shownTitle: 'field-1 [Group]', colorAndShapeKey: 'field-1'}; + const keys = ['shownTitle', 'colorAndShapeKey'] as ['shownTitle', 'colorAndShapeKey']; + it.each(keys)( + 'should return %s when mountedValues contains non auto value with that key', + (key) => { + const mountedValues = { + [args[key]]: 'red', + }; + + const {shownTitle, colorAndShapeKey} = args; + + const result = getColorShapeMappingValue({mountedValues, shownTitle, colorAndShapeKey}); + + expect(result).toEqual(args[key]); + }, + ); + + it.each(keys)('should return undefined when mountedValue is auto (%s)', (key) => { + const mountedValues = { + [args[key]]: 'auto', + }; + + const {shownTitle, colorAndShapeKey} = args; + + const result = getColorShapeMappingValue({mountedValues, shownTitle, colorAndShapeKey}); + + expect(result).toEqual(undefined); + }); + + it.each(['shownTitle', 'colorAndShapeKey'])( + "should return undefined when mountedValues doesn't contains the key (%s)", + () => { + const mountedValues = {}; + + const {shownTitle, colorAndShapeKey} = args; + + const result = getColorShapeMappingValue({mountedValues, shownTitle, colorAndShapeKey}); + + expect(result).toEqual(undefined); + }, + ); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/getColoredLineLegendTitle.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/getColoredLineLegendTitle.ts new file mode 100644 index 0000000..7101d13 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/getColoredLineLegendTitle.ts @@ -0,0 +1,55 @@ +import type { + ServerColor, + ServerField, + ServerVisualizationLayer, +} from '../../../../../../../../../shared'; +import {getFakeTitleOrTitle, isDimensionField} from '../../../../../../../../../shared'; + +interface GetColoredLineLegendTitleArgs { + formattedValue: string; + yItem: ServerField; + colorItem: ServerColor; + layers?: ServerVisualizationLayer[]; +} + +function isSameTitle(field: ServerField, otherField: ServerField) { + return getFakeTitleOrTitle(field) === getFakeTitleOrTitle(otherField); +} + +function getYItems(layer: ServerVisualizationLayer) { + return layer.placeholders[1]?.items || []; +} + +export function getColoredLineLegendTitle(args: GetColoredLineLegendTitleArgs) { + const {layers, yItem, colorItem, formattedValue} = args; + const coloredLayers = + layers?.filter( + (layer) => getYItems(layer).length && layer.commonPlaceholders.colors?.length, + ) || []; + + if (coloredLayers.length > 1) { + const isDifferentYItem = coloredLayers.some((layer) => + getYItems(layer).some((item) => !isSameTitle(item, yItem)), + ); + + const isDifferentColorItem = coloredLayers.some(({commonPlaceholders}) => + commonPlaceholders.colors.some( + (item) => isDimensionField(item) && !isSameTitle(item, colorItem), + ), + ); + + const legendTitleItems = [formattedValue]; + + if (isDifferentColorItem) { + legendTitleItems.unshift(getFakeTitleOrTitle(colorItem)); + } + + if (isDifferentYItem) { + legendTitleItems.unshift(getFakeTitleOrTitle(yItem)); + } + + return legendTitleItems.join(': '); + } + + return formattedValue; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/helpers.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/helpers.ts new file mode 100644 index 0000000..ade6ffc --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/helpers.ts @@ -0,0 +1,23 @@ +type GetColorShapeMappingValueArgs = { + shownTitle: string; + mountedValues: Record; + colorAndShapeKey: string; +}; +export const getColorShapeMappingValue = ({ + mountedValues, + shownTitle, + colorAndShapeKey, +}: GetColorShapeMappingValueArgs) => { + const isValueMountedByLegendTitle = typeof mountedValues[shownTitle] !== 'undefined'; + const isValueMountedByFieldTitle = typeof mountedValues[colorAndShapeKey] !== 'undefined'; + + if (isValueMountedByLegendTitle && mountedValues[shownTitle] !== 'auto') { + return shownTitle; + } + + if (isValueMountedByFieldTitle && mountedValues[colorAndShapeKey] !== 'auto') { + return colorAndShapeKey; + } + + return undefined; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToDimensionColoredLines.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToDimensionColoredLines.ts new file mode 100644 index 0000000..2d7a410 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToDimensionColoredLines.ts @@ -0,0 +1,209 @@ +import type {DATASET_FIELD_TYPES} from '../../../../../../../../../shared'; +import {ColorMode, getDistinctValue, isHtmlField} from '../../../../../../../../../shared'; +import {wrapHtml} from '../../../../../../../../../shared/utils/ui-sandbox'; +import {COLOR_SHAPE_SEPARATOR} from '../../../../utils/constants'; +import {formatDate, getFieldTitle} from '../../../../utils/misc-helpers'; +import type {ItemValues} from '../types'; +import {getItemsValues, getLineKey} from '../utils'; + +import {getColoredLineLegendTitle} from './getColoredLineLegendTitle'; +import type { + ColorAndShapesValues, + GetColorAndShapeValuesArgs, + GetFieldTitleForValueArgs, + MapDataToDimensionColoredLinesArgs, +} from './types'; + +const getFieldTitleForValue = ({hasField, defaultValue, yItem}: GetFieldTitleForValueArgs) => { + if (hasField) { + return getFieldTitle(yItem); + } + + return defaultValue; +}; + +const getColorAndShapeValues = ({ + yItem, + formattedValue, + combinedValue, +}: GetColorAndShapeValuesArgs): ColorAndShapesValues => { + const [colorValue, shapeValue] = combinedValue?.split(COLOR_SHAPE_SEPARATOR) || []; + + if (colorValue && shapeValue) { + return { + colorValue, + shapeValue, + }; + } + + const colorAndShapeValues: {colorValue?: string; shapeValue?: string} = {}; + + const hasColors = Boolean(colorValue); + const hasShapes = Boolean(shapeValue); + + const hasField = hasColors || hasShapes; + + colorAndShapeValues.colorValue = + colorValue || getFieldTitleForValue({yItem, hasField, defaultValue: formattedValue}); + colorAndShapeValues.shapeValue = + shapeValue || getFieldTitleForValue({yItem, hasField, defaultValue: formattedValue}); + + return colorAndShapeValues; +}; + +export const mapDataToDimensionColoredLines = ({ + items, + idToTitle, + values, + order, + x2, + x2IsDate, + x2Value, + xValue, + multiaxis, + isMultiLineWithShape, + shownTitle, + lines, + seriesOptions, + x2DataType, + yValue, + yItem, + hasColors, + isItemsAreEqual, + segmentName, + layers, + colorMode, +}: MapDataToDimensionColoredLinesArgs) => { + const mappedItemsToValues = items.map((item) => { + return getItemsValues(item, {idToTitle, values, order}); + }); + + const colorModeIsGradient = colorMode === ColorMode.GRADIENT; + + let itemValues: ItemValues; + if (isItemsAreEqual) { + const {value, formattedValue} = mappedItemsToValues[0]; + + itemValues = { + value, + formattedValue, + }; + + if (typeof value === 'string') { + itemValues.extraValue = `${value}${COLOR_SHAPE_SEPARATOR}${value}`; + } + } else { + itemValues = mappedItemsToValues.reduce( + (acc: ItemValues, curr, index) => { + let {formattedValue} = curr; + let distinctValue = getDistinctValue(curr.value); + let extraValue = distinctValue; + if (index !== items.length - 1) { + distinctValue = `${distinctValue}-`; + formattedValue = `${formattedValue}-`; + extraValue = `${extraValue}${COLOR_SHAPE_SEPARATOR}`; + } else if (!hasColors) { + extraValue = `${COLOR_SHAPE_SEPARATOR}${extraValue}`; + } + return { + ...acc, + value: `${acc.value}${distinctValue}`, + formattedValue: `${acc.formattedValue}${formattedValue}`, + extraValue: `${acc.extraValue}${extraValue}`, + }; + }, + {value: '', formattedValue: '', extraValue: ''} as ItemValues, + ); + } + + const key = getLineKey({ + shownTitle, + x2AxisValue: x2Value, + isX2Axis: Boolean(x2), + isMultiAxis: multiaxis, + isMultiLineWithShape, + value: String(itemValues.value), + segmentName, + }); + + if (!Object.hasOwnProperty.call(lines, key)) { + lines[key] = { + data: {}, + ...seriesOptions, + }; + + const line = lines[key]; + + if (x2) { + line.stack = x2Value; + + // Exactly == + // eslint-disable-next-line eqeqeq + if (itemValues.value == x2Value) { + line.title = String(itemValues.formattedValue); + line.formattedName = String(itemValues.formattedValue); + } else { + if (x2IsDate) { + x2Value = formatDate({ + valueType: x2DataType as DATASET_FIELD_TYPES, + value: x2Value, + format: x2.format, + }); + } + + line.title = `${itemValues.formattedValue}: ${x2Value}`; + line.legendTitle = `${itemValues.formattedValue}`; + line.formattedName = `${itemValues.formattedValue}: ${x2Value}`; + line.drillDownFilterValue = String(itemValues.value); + } + } else if (multiaxis || isMultiLineWithShape) { + line.title = `${itemValues.formattedValue}: ${shownTitle}`; + line.formattedName = `${itemValues.formattedValue}: ${shownTitle}`; + line.drillDownFilterValue = String(itemValues.value); + } else { + const formattedValue = String(itemValues.formattedValue); + line.title = formattedValue; + line.formattedName = formattedValue; + line.drillDownFilterValue = String(itemValues.value); + + if (isHtmlField(items[0])) { + line.legendTitle = wrapHtml(formattedValue); + } else { + line.legendTitle = getColoredLineLegendTitle({ + yItem, + colorItem: items[0], + formattedValue: formattedValue, + layers, + }); + } + } + + const {colorValue, shapeValue} = getColorAndShapeValues({ + yItem, + formattedValue: String(itemValues.formattedValue), + combinedValue: itemValues.extraValue, + }); + + line.colorValue = colorModeIsGradient ? Number(colorValue) : colorValue; + line.shapeValue = shapeValue; + } + + const lastKey = typeof xValue === 'undefined' ? shownTitle : xValue; + + const pointData: { + value: typeof yValue; + colorValue?: number; + } = {value: yValue}; + + if (colorModeIsGradient) { + pointData.colorValue = Number(lines[key].colorValue); + } + + const targetLineKey = lastKey as string | number; + + const pointConflict = typeof lines[key].data[targetLineKey] !== 'undefined'; + + lines[key].data[targetLineKey] = pointData; + + return {key, lastKey, pointConflict}; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToLines.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToLines.ts new file mode 100644 index 0000000..c5f896b --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToLines.ts @@ -0,0 +1,98 @@ +import {isMeasureNameOrValue} from '../../../../../../../../../shared'; +import {getColorsConfigKey} from '../../../../../../../../../shared/modules/colors/common-helpers'; +import {getFormattedValue} from '../../../helpers/get-formatted-value'; +import {getLineKey} from '../utils'; + +import {getColorShapeMappingValue} from './helpers'; +import type {MapDataToLinesArgs} from './types'; + +export const mapDataToLines = ({ + x2, + x2Value, + xValue, + yValue, + lines, + seriesOptions, + shownTitle, + isPseudoShapeExist, + isPseudoColorExist, + shapesConfig, + x, + segmentName, + yField, + yFields, + idToTitle, + isColorMeasureNames, +}: MapDataToLinesArgs) => { + const key = getLineKey({ + shownTitle, + isX2Axis: Boolean(x2), + value: undefined, + isMultiAxis: false, + x2AxisValue: x2Value, + segmentName, + }); + + if (!Object.hasOwnProperty.call(lines, key)) { + lines[key] = { + data: {}, + ...seriesOptions, + }; + + const line = lines[key]; + + line.colorKey = yField.fakeTitle || idToTitle[yField.guid] || yField.title; + + if (x2) { + line.stack = x2Value; + const formattedX2Value = getFormattedValue(x2, x2Value); + + // Exactly == + // eslint-disable-next-line eqeqeq + if (shownTitle == formattedX2Value) { + line.title = `${shownTitle}`; + line.legendTitle = `${shownTitle}`; + } else if (x && isMeasureNameOrValue(x)) { + line.title = `${formattedX2Value}`; + line.legendTitle = `${shownTitle}: ${formattedX2Value}`; + } else { + line.title = `${shownTitle}: ${formattedX2Value}`; + line.legendTitle = `${shownTitle}: ${formattedX2Value}`; + } + } else { + line.title = shownTitle; + } + + if (isPseudoColorExist) { + const colorKey = getColorsConfigKey(yField, yFields || [], { + isMeasureNames: isColorMeasureNames, + }); + + if (colorKey) { + line.colorKey = colorKey; + line.colorValue = colorKey; + } else { + line.colorValue = shownTitle; + } + } + + if (isPseudoShapeExist) { + const mountedValues = shapesConfig?.mountedShapes || {}; + line.shapeValue = getColorShapeMappingValue({ + mountedValues, + shownTitle, + colorAndShapeKey: yField.fakeTitle || idToTitle[yField.guid] || yField.title, + }); + } + } + + const lastKey = typeof xValue === 'undefined' ? shownTitle : xValue; + + const targetLineKey = lastKey as string | number; + + const pointConflict = typeof lines[key].data[targetLineKey] !== 'undefined'; + + lines[key].data[targetLineKey] = {value: yValue}; + + return {key, lastKey, pointConflict}; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToMeasureColoredLines.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToMeasureColoredLines.ts new file mode 100644 index 0000000..7c746dc --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/mapDataToMeasureColoredLines.ts @@ -0,0 +1,103 @@ +import {findIndexInOrder, formatDate} from '../../../../utils/misc-helpers'; +import {getLineKey} from '../utils'; + +import type {GetColorValueFromColorFieldArgs, MapDataToMeasureColoredLinesArgs} from './types'; + +const getColorValueFromColorField = ({ + idToTitle, + values, + colorItem, + order, +}: GetColorValueFromColorFieldArgs) => { + const colorItemTitle = idToTitle[colorItem.guid]; + const indexInOrder = findIndexInOrder(order, colorItem, colorItemTitle); + const value = values[indexInOrder]; + const parsedValue = Number(value); + return isNaN(parsedValue) ? undefined : parsedValue; +}; + +export const mapDataToMeasureColoredLines = (args: MapDataToMeasureColoredLinesArgs) => { + const { + shownTitle, + idToTitle, + colorItem, + order, + lines, + seriesOptions, + values, + xValue, + yValue, + isColorizeByMeasureValue, + x2, + isX2Date, + x2DataType, + segmentName, + } = args; + + let colorValue; + + if (isColorizeByMeasureValue) { + const parsedValue = Number(yValue); + colorValue = isNaN(parsedValue) ? undefined : parsedValue; + } else { + colorValue = getColorValueFromColorField({idToTitle, colorItem, order, values}); + } + + const key = getLineKey({ + value: undefined, + shownTitle, + isMultiAxis: false, + isX2Axis: Boolean(x2), + x2AxisValue: args.x2Value, + segmentName, + }); + + if (!Object.hasOwnProperty.call(lines, key)) { + lines[key] = { + data: {}, + ...seriesOptions, + }; + + const line = lines[key]; + const itemValue = shownTitle; + const itemFormattedValue = shownTitle; + + if (x2) { + line.stack = args.x2Value; + + if (isX2Date) { + args.x2Value = formatDate({ + valueType: x2DataType, + value: args.x2Value, + format: x2.format, + }); + } + + line.title = `${itemFormattedValue}: ${args.x2Value}`; + line.legendTitle = `${itemFormattedValue}`; + line.formattedName = `${itemFormattedValue}: ${args.x2Value}`; + line.drillDownFilterValue = itemValue; + } else { + line.title = itemFormattedValue; + line.formattedName = itemFormattedValue; + line.drillDownFilterValue = itemValue; + } + + // The name of the field that specifies the value for the color. + // It is necessary for colorAxis to understand where a specific point is located between the min and max values + line.colorKey = 'colorValue'; + } + + const lastKey = typeof xValue === 'undefined' || xValue === null ? shownTitle : xValue; + + const targetLineKey = lastKey as string | number; + + const pointConflict = typeof lines[key].data[targetLineKey] !== 'undefined'; + + lines[key].data[targetLineKey] = { + value: yValue, + colorValue, + }; + + return {key, lastKey, colorValue, pointConflict}; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/prepareLines.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/prepareLines.ts new file mode 100644 index 0000000..b0a7d06 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/prepareLines.ts @@ -0,0 +1,374 @@ +import isNil from 'lodash/isNil'; + +import type { + CommonNumberFormattingOptions, + DATASET_FIELD_TYPES, + MarkupItem, + ServerField, +} from '../../../../../../../../../shared'; +import { + AxisNullsMode, + getFakeTitleOrTitle, + getFormatOptions, + isDateField, + isMarkupField, + isMeasureName, + isMeasureValue, + isPseudoField, + markupToRawString, +} from '../../../../../../../../../shared'; +import type {WrappedHTML} from '../../../../../../../../../shared/types/charts'; +import { + findIndexInOrder, + getFormatOptionsFromFieldFormatting, + isNumericalDataType, +} from '../../../../utils/misc-helpers'; +import type {LineTemplate} from '../../types'; +import {getDateAxisValue} from '../getXAxisValue'; +import {getSegmentName} from '../segments/getSegmentName'; +import {getY2SegmentNameKey} from '../segments/getSegmentsMap'; +import type {RowDataValue} from '../types'; + +import {mapDataToDimensionColoredLines} from './mapDataToDimensionColoredLines'; +import {mapDataToLines} from './mapDataToLines'; +import {mapDataToMeasureColoredLines} from './mapDataToMeasureColoredLines'; +import type { + ExtendLineWithSegmentDataArgs, + GetMappedDataToDimensionColoredLinesArgs, + GetMappedDataToLinesArgs, + GetMappedDataToMeasureColoredLinesArgs, + MappedLinesKeys, + MergeLabelDataWithLinesArgs, + PrepareLinesArgs, +} from './types'; + +const getMappedDataToLines = (args: GetMappedDataToLinesArgs): MappedLinesKeys => { + return mapDataToLines(args.mapFunctionArguments); +}; + +const getMappedDataToDimensionColoredLines = ( + args: GetMappedDataToDimensionColoredLinesArgs, +): MappedLinesKeys => { + let items: ServerField[]; + let isItemsAreEqual = false; + + const {isColorItemExist, isShapeItemExist, colorItem, shapeItem} = args.options; + + if (isColorItemExist && isShapeItemExist && shapeItem) { + isItemsAreEqual = colorItem.guid === shapeItem.guid; + items = [colorItem, shapeItem]; + } else if (isShapeItemExist && shapeItem) { + items = [shapeItem]; + } else { + items = [colorItem]; + } + + return mapDataToDimensionColoredLines({ + ...args.mapFunctionArguments, + isItemsAreEqual, + items, + }); +}; + +const getMappedDataToMeasureColoredLines = ( + args: GetMappedDataToMeasureColoredLinesArgs, +): MappedLinesKeys => { + const keys = mapDataToMeasureColoredLines(args.mapFunctionArguments); + + const {colorItem, isSortByMeasureColor, measureColorSortLine} = args.options; + + if (isSortByMeasureColor) { + measureColorSortLine[getFakeTitleOrTitle(colorItem)].data[keys.lastKey as number | string] = + {value: keys.colorValue}; + } + + return { + key: keys.key, + lastKey: keys.lastKey, + }; +}; + +const mergeLabelDataWithLines = (args: MergeLabelDataWithLinesArgs) => { + const { + labelItem, + labelsValues, + yValue, + hideLabel, + yItemFormatting, + yDataType, + values, + order, + keys, + idToTitle, + idToDataType, + lines, + convertMarkupToString = true, + } = args; + const key = keys.key; + const lastKey = keys.lastKey as string | number; + + if (!labelItem || hideLabel) { + lines[key].dataLabels = {enabled: false}; + return; + } + + const labelDataType = idToDataType[labelItem.guid]; + + const isMeasureValuesLabel = isMeasureValue(labelItem); + + if (!labelsValues[key]) { + labelsValues[key] = {}; + } + + let labelValue: RowDataValue; + + if (isMeasureValuesLabel) { + labelValue = yValue; + } else { + const labelItemTitle = idToTitle[labelItem.guid]; + const labelValueIndex = findIndexInOrder(order, labelItem, labelItemTitle); + + labelValue = values[labelValueIndex]; + } + + if (!isNil(labelValue)) { + if (isNumericalDataType(labelDataType)) { + labelsValues[key][lastKey] = Number(labelValue); + } else if (isDateField({data_type: labelDataType})) { + labelsValues[key][lastKey] = new Date(labelValue); + } else if (convertMarkupToString && isMarkupField({data_type: labelDataType})) { + labelsValues[key][lastKey] = markupToRawString(labelValue as unknown as MarkupItem); + } else { + labelsValues[key][lastKey] = labelValue; + } + } + + const isLabelPseudo = isPseudoField(labelItem); + const labelFinalDataType = isLabelPseudo ? yDataType : labelDataType; + + let labelFormatting: CommonNumberFormattingOptions | undefined; + if (isLabelPseudo) { + labelFormatting = yItemFormatting; + } else { + labelFormatting = getFormatOptions(labelItem); + } + + lines[key].dataLabels = getFormatOptionsFromFieldFormatting( + labelFormatting, + labelFinalDataType, + ); +}; + +const extendLineWithSegmentsData = (args: ExtendLineWithSegmentDataArgs): LineTemplate => { + const {line, segmentNameKey} = args; + + if (line.segmentNameKey) { + return line; + } + + return { + ...line, + segmentNameKey, + }; +}; + +function getSeriesId(...str: (string | WrappedHTML)[]) { + return str.find((s) => typeof s === 'string') ?? ''; +} + +export const prepareLines = (args: PrepareLinesArgs) => { + const { + ySectionItems, + idToTitle, + idToDataType, + order, + values, + isMultiDatasets, + isColorizeByMeasureValue, + isColorizeByMeasure, + x2Field, + colorItem, + rawX2Value, + rawXValue, + x2IsDate, + isSortByMeasureColor, + measureColorSortLine, + isShapeItemExist, + isColorItemExist, + isMultiAxis, + shapeItem, + xField, + shapesConfig, + labelItem, + isSegmentsExists, + segmentIndexInOrder, + layers = [], + colorMode, + convertMarkupToString, + } = args; + + const x2DataType = x2Field ? (idToDataType[x2Field.guid] as DATASET_FIELD_TYPES) : null; + + const rawSegmentName = isSegmentsExists + ? getSegmentName(values, segmentIndexInOrder) + : undefined; + + const yFields = ySectionItems.map((ySectionItem) => ySectionItem.field); + + const isMultiLineWithShape = isShapeItemExist && ySectionItems.length > 1; + + // eslint-disable-next-line complexity + ySectionItems.forEach((mergedItem) => { + const {field, lines, labelsValues, nullsSetting, isFirstSection} = mergedItem; + const yFieldTitle = idToTitle[field.guid] || field.title; + + const segmentNameKey = isFirstSection + ? rawSegmentName + : getY2SegmentNameKey(rawSegmentName); + + const actualTitle = + isMultiDatasets && field.datasetName + ? `${yFieldTitle} (${field.datasetName})` + : yFieldTitle; + const shownTitle = field.fakeTitle || actualTitle; + + const yDataType = (idToDataType[field.guid] || field.data_type) as DATASET_FIELD_TYPES; + const i = findIndexInOrder(order, field, yFieldTitle); + + let yValue: RowDataValue = values[i]; + const x2Value = isPseudoField(x2Field) || !rawX2Value ? shownTitle : rawX2Value; + + if (isNumericalDataType(yDataType)) { + if (yValue === null) { + yValue = nullsSetting === AxisNullsMode.AsZero ? 0 : null; + } else { + yValue = Number(yValue); + } + } else if (yValue !== null && isDateField({data_type: yDataType})) { + yValue = getDateAxisValue(yValue, yDataType); + } + + const yItemFormatting = getFormatOptions(field); + const tooltipOptions = getFormatOptionsFromFieldFormatting(yItemFormatting, yDataType); + + const seriesOptions = { + tooltip: tooltipOptions, + }; + + let keys: MappedLinesKeys; + + if (isColorizeByMeasureValue || isColorizeByMeasure) { + keys = getMappedDataToMeasureColoredLines({ + mapFunctionArguments: { + x2: x2Field, + lines, + yValue, + isColorizeByMeasureValue, + colorItem, + order, + values, + idToTitle, + seriesOptions, + shownTitle, + xValue: rawXValue, + x2Value, + x2DataType: x2DataType, + isX2Date: x2IsDate, + segmentName: segmentNameKey, + }, + options: { + colorItem, + measureColorSortLine, + isSortByMeasureColor, + }, + }); + } else if (isColorItemExist || isShapeItemExist) { + keys = getMappedDataToDimensionColoredLines({ + mapFunctionArguments: { + idToTitle, + values, + order, + x2: x2Field, + x2IsDate, + x2Value, + xValue: rawXValue, + multiaxis: isMultiAxis, + isMultiLineWithShape, + shownTitle, + lines, + seriesOptions, + x2DataType, + yValue, + yItem: field, + hasShapes: isShapeItemExist, + hasColors: isColorItemExist, + segmentName: segmentNameKey, + layers, + colorMode, + }, + options: { + colorItem, + shapeItem, + isColorItemExist, + isShapeItemExist, + }, + }); + } else { + keys = getMappedDataToLines({ + mapFunctionArguments: { + x2: x2Field, + x2Value, + xValue: rawXValue, + yValue, + lines, + yField: field, + yFields, + idToTitle, + seriesOptions, + shownTitle, + isPseudoColorExist: isPseudoField(colorItem), + isPseudoShapeExist: isPseudoField(shapeItem), + isColorMeasureNames: colorItem && isMeasureName(colorItem), + shapesConfig, + x: xField, + segmentName: segmentNameKey, + }, + }); + } + + const hideLabel = field.hideLabelMode === 'hide'; + + mergeLabelDataWithLines({ + keys, + lines, + labelItem, + hideLabel, + labelsValues, + yValue, + idToTitle, + idToDataType, + order, + values, + yDataType, + yItemFormatting, + convertMarkupToString, + }); + + const currentLine = lines[keys.key]; + + if (segmentNameKey) { + lines[keys.key] = extendLineWithSegmentsData({ + line: currentLine, + segmentNameKey, + }); + } + + lines[keys.key].id = getSeriesId(currentLine.legendTitle, currentLine.title); + + if (keys.pointConflict) { + lines[keys.key].pointConflict = true; + } + + lines[keys.key].fieldTitle = getFakeTitleOrTitle(field); + }); +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/types.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/types.ts new file mode 100644 index 0000000..9839283 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/line-mappers/types.ts @@ -0,0 +1,164 @@ +import type { + ColorMode, + CommonNumberFormattingOptions, + DATASET_FIELD_TYPES, + ServerColor, + ServerField, + ServerLabel, + ServerShape, + ServerShapesConfig, + ServerVisualizationLayer, +} from '../../../../../../../../../shared'; +import type {PrepareFunctionDataRow, ResultDataOrder} from '../../../types'; +import type {LineTemplate, LinesRecord, MergedYSectionItems} from '../../types'; +import type {RowDataValue, XAxisValue} from '../types'; + +export interface PrepareLinesArgs { + isMultiDatasets: boolean; + isColorizeByMeasure: boolean; + isColorizeByMeasureValue: boolean; + isSortByMeasureColor: boolean; + isShapeItemExist: boolean; + isColorItemExist: boolean; + isSegmentsExists: boolean; + x2IsDate: boolean; + colorItem: ServerColor; + shapeItem?: ServerShape; + idToTitle: Record; + idToDataType: Record; + order: ResultDataOrder; + values: PrepareFunctionDataRow; + x2Field: ServerField | undefined; + xField: ServerField | undefined; + rawX2Value: XAxisValue; + rawXValue: XAxisValue; + isMultiAxis: boolean; + labelItem: ServerLabel; + measureColorSortLine: Record>; + shapesConfig: ServerShapesConfig | undefined; + ySectionItems: MergedYSectionItems[]; + segmentIndexInOrder: number; + layers?: ServerVisualizationLayer[]; + colorMode?: ColorMode; + convertMarkupToString?: boolean; +} + +export interface GetMappedDataToLinesArgs { + mapFunctionArguments: MapDataToLinesArgs; +} + +export interface GetMappedDataToDimensionColoredLinesArgs { + mapFunctionArguments: Omit; + options: { + isColorItemExist: boolean; + isShapeItemExist: boolean; + colorItem: ServerColor; + shapeItem: ServerShape | undefined; + }; +} + +export interface GetMappedDataToMeasureColoredLinesArgs { + mapFunctionArguments: MapDataToMeasureColoredLinesArgs; + options: { + colorItem: ServerColor; + isSortByMeasureColor: boolean; + measureColorSortLine: Record>; + }; +} + +export interface CommonMapDataToLinesArgs { + segmentName: string | undefined | null; + xValue: string | number | Date | undefined | null; + shownTitle: string; + x2: ServerField | undefined; + x2Value: string | number | Date; + lines: LinesRecord; + yValue: string | number | undefined | null; + seriesOptions: any; + layers?: ServerVisualizationLayer[]; +} + +export interface MapDataToLinesArgs extends CommonMapDataToLinesArgs { + x?: ServerField; + yField: ServerField; + idToTitle: Record; + isColorMeasureNames: boolean; + isPseudoColorExist: boolean; + isPseudoShapeExist: boolean; + shapesConfig: ServerShapesConfig | undefined; + yFields: ServerField[]; +} + +export interface MapDataToDimensionColoredLinesArgs extends CommonMapDataToLinesArgs { + items: ServerColor[]; + idToTitle: Record; + values: Array; + order: ResultDataOrder; + multiaxis: boolean; + isMultiLineWithShape: boolean; + x2IsDate: boolean; + x2DataType: DATASET_FIELD_TYPES | null; + yItem: ServerField; + hasColors: boolean; + hasShapes: boolean; + isItemsAreEqual: boolean; + colorMode?: ColorMode; +} + +export interface MapDataToMeasureColoredLinesArgs extends CommonMapDataToLinesArgs { + colorItem: ServerField; + order: ResultDataOrder; + idToTitle: Record; + values: Array; + isColorizeByMeasureValue: boolean; + x2DataType: DATASET_FIELD_TYPES | null; + isX2Date: boolean; +} + +export interface GetColorValueFromColorFieldArgs { + colorItem: ServerField; + values: Array; + idToTitle: Record; + order: ResultDataOrder; +} + +export interface GetFieldTitleForValueArgs { + yItem: ServerField; + hasField: boolean; + defaultValue: string; +} + +export interface GetColorAndShapeValuesArgs { + yItem: ServerField; + formattedValue: string; + combinedValue?: string; +} + +export interface MergeLabelDataWithLinesArgs { + keys: MappedLinesKeys; + lines: LinesRecord; + labelItem: ServerLabel; + hideLabel: boolean; + labelsValues: Record>; + yValue: RowDataValue; + idToTitle: Record; + idToDataType: Record; + order: ResultDataOrder; + values: PrepareFunctionDataRow; + yDataType: DATASET_FIELD_TYPES; + yItemFormatting: CommonNumberFormattingOptions | undefined; + convertMarkupToString?: boolean; +} + +export interface ExtendLineWithSegmentDataArgs { + line: LineTemplate; + segmentNameKey: string; +} + +export type ColorAndShapesValues = {colorValue?: string; shapeValue?: string}; + +export type MappedLinesKeys = { + key: string; + lastKey: string | number | Date | null; + pointConflict?: boolean; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentName.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentName.ts new file mode 100644 index 0000000..aacf322 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentName.ts @@ -0,0 +1,6 @@ +import type {PrepareFunctionDataRow} from '../../../types'; + +export const getSegmentName = (dataRow: PrepareFunctionDataRow, index: number): string => { + const segmentName = dataRow[index]; + return segmentName === null ? 'Null' : segmentName; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsIndexInOrder.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsIndexInOrder.ts new file mode 100644 index 0000000..550a852 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsIndexInOrder.ts @@ -0,0 +1,17 @@ +import type {ServerField} from '../../../../../../../../../shared'; +import {findIndexInOrder} from '../../../../utils/misc-helpers'; +import type {ResultDataOrder} from '../../../types'; + +export const getSegmentsIndexInOrder = ( + order: ResultDataOrder, + segmentField: ServerField | undefined, + idToTitle: Record, +) => { + if (!segmentField) { + return -1; + } + + const segmentTitle = idToTitle[segmentField.guid]; + + return findIndexInOrder(order, segmentField, segmentTitle); +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsList.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsList.ts new file mode 100644 index 0000000..fcd7f9c --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsList.ts @@ -0,0 +1,45 @@ +import {SortDirection, isNumberField} from '../../../../../../../../../shared'; +import {collator, numericStringCollator} from '../../../../utils/misc-helpers'; + +import {getSegmentName} from './getSegmentName'; +import type {GetSegmentsList, GetSortedSegmentsList} from './types'; + +export const getSegmentsList = (args: GetSegmentsList): string[] => { + const {data, segmentIndexInOrder, segmentField} = args; + + if (!segmentField) { + return []; + } + + const segmentsSet = new Set([]); + + data.forEach((dataRow) => { + const segmentName = getSegmentName(dataRow, segmentIndexInOrder); + + segmentsSet.add(segmentName); + }); + + return Array.from(segmentsSet); +}; + +export const getSortedSegmentsList = (args: GetSortedSegmentsList) => { + const {data, segmentIndexInOrder, sortItem, segmentField} = args; + + if (!segmentField) { + return []; + } + + const segments = getSegmentsList({data, segmentField, segmentIndexInOrder}); + const sortFn = isNumberField(segmentField) ? numericStringCollator : collator.compare; + segments.sort(sortFn); + + if ( + sortItem && + sortItem.guid === segmentField.guid && + sortItem.direction === SortDirection.ASC + ) { + segments.reverse(); + } + + return segments; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsMap.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsMap.ts new file mode 100644 index 0000000..99a5687 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsMap.ts @@ -0,0 +1,44 @@ +import type {GetSegmentsMap, SegmentsMap} from './types'; + +export const getY2SegmentNameKey = (segmentName: string | null | undefined) => { + if (typeof segmentName !== 'string') { + return segmentName; + } + return `${segmentName}__y2`; +}; + +export const getSegmentsMap = (args: GetSegmentsMap): SegmentsMap => { + const {segments, y2SectionItems} = args; + + if (!segments.length) { + return {}; + } + + const hasOppositeYAxis = Boolean(y2SectionItems.length); + + return segments.reduce((acc, segmentName) => { + if (!acc[segmentName]) { + const segmentIndex = Object.keys(acc).length; + + const updatedMap = { + ...acc, + [segmentName]: {title: segmentName, index: segmentIndex, isOpposite: false}, + }; + + if (hasOppositeYAxis) { + return { + ...updatedMap, + [getY2SegmentNameKey(segmentName) as string]: { + title: segmentName, + index: segmentIndex + 1, + isOpposite: true, + }, + }; + } + + return updatedMap; + } + + return acc; + }, {} as SegmentsMap); +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsYAxis.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsYAxis.ts new file mode 100644 index 0000000..126014b --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/getSegmentsYAxis.ts @@ -0,0 +1,93 @@ +import sortBy from 'lodash/sortBy'; + +import {AxisLabelFormatMode, isHtmlField} from '../../../../../../../../../shared'; +import type {ServerField, ServerPlaceholder} from '../../../../../../../../../shared'; +import {wrapHtml} from '../../../../../../../../../shared/utils/ui-sandbox'; +import type {AxisOptions} from '../../../../types'; +import {applyPlaceholderSettingsToAxis} from '../../../../utils/axis-helpers'; +import {addAxisFormatter} from '../../../helpers/axis'; +import {getAxisChartkitFormatting} from '../../../helpers/axis/get-axis-formatting'; + +import type {SegmentsMap} from './types'; + +const DEFAULT_SPACE_BETWEEN_SEGMENTS = 4; + +export const getSegmentsYAxis = (args: { + segment?: ServerField; + segmentsMap: SegmentsMap; + placeholders: {y?: ServerPlaceholder; y2?: ServerPlaceholder}; + visualizationId: string; +}): {yAxisSettings: AxisOptions[]; yAxisFormattings: any[]} => { + const {segment, segmentsMap, placeholders, visualizationId} = args; + const segments = sortBy(Object.values(segmentsMap), (s) => s.index); + const isHtmlSegment = isHtmlField(segment); + + const segmentsNumber = segments.filter((s) => !s.isOpposite).length; + const takenSpaceBetweenSegments = DEFAULT_SPACE_BETWEEN_SEGMENTS * (segmentsNumber - 1); + const freeSpaceForSegments = 100 - takenSpaceBetweenSegments; + const segmentsSpace = Math.floor(freeSpaceForSegments / segmentsNumber); + + let leftAxisSegment = -1; + let rightAxisSegment = -1; + + const yAxis = new Array(segments.length); + const yAxisFormattings = new Array(segments.length); + + segments.forEach((segment) => { + const isY2Axis = segment.isOpposite; + const yAxisIndex = segment.index; + + let segmentIndex; + + if (isY2Axis) { + rightAxisSegment += 1; + segmentIndex = rightAxisSegment; + } else { + leftAxisSegment += 1; + segmentIndex = leftAxisSegment; + } + + const segmentTitle = isHtmlSegment ? wrapHtml(segment.title) : String(segment.title); + const placeholder = isY2Axis ? placeholders.y2 : placeholders.y; + + const axis: AxisOptions = { + top: `${DEFAULT_SPACE_BETWEEN_SEGMENTS * segmentIndex + segmentsSpace * segmentIndex}%`, + height: `${segmentsSpace}%`, + offset: 0, + lineWidth: 1, + gridLineWidth: 1, + opposite: isY2Axis, + title: isY2Axis + ? undefined + : { + text: segmentTitle, + useHTML: isHtmlSegment, + align: 'middle', + textAlign: 'center', + offset: 120, + rotation: 0, + style: { + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + fontSize: '14px', + fontWeight: 'bold', + width: 120, + }, + }, + }; + + applyPlaceholderSettingsToAxis(placeholder, axis, {title: true}); + addAxisFormatter({ + axisConfig: axis, + placeholder: placeholder, + }); + yAxis[yAxisIndex] = axis; + + const formatMode = placeholder?.settings?.axisFormatMode; + if (formatMode && formatMode !== AxisLabelFormatMode.Auto) { + yAxisFormattings[yAxisIndex] = getAxisChartkitFormatting(placeholder, visualizationId); + } + }); + + return {yAxisSettings: yAxis, yAxisFormattings}; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/types.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/types.ts new file mode 100644 index 0000000..5c04bb7 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/segments/types.ts @@ -0,0 +1,27 @@ +import type {ServerField, ServerSort} from '../../../../../../../../../shared'; +import type {PrepareFunctionDataRow} from '../../../types'; + +export interface GetSortedSegmentsList extends GetSegmentsList { + sortItem: ServerSort | undefined; +} + +export interface GetSegmentsList { + data: PrepareFunctionDataRow[]; + segmentIndexInOrder: number; + segmentField: ServerField | undefined; +} + +export interface GetSegmentsMap { + segments: string[]; + y2SectionItems: ServerField[]; +} + +export type SegmentsMap = Record< + string, + { + title: string; + index: number; + isOpposite: boolean; + plotIndex?: number; + } +>; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/types.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/types.ts new file mode 100644 index 0000000..ec50675 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/types.ts @@ -0,0 +1,87 @@ +import type { + ServerColor, + ServerField, + ServerSort, + WizardVisualizationId, +} from '../../../../../../../../shared'; +import type {Categories, ResultDataOrder} from '../../types'; +import type {LineTemplate, LinesRecord} from '../types'; + +export interface GetXAxisValueArgs { + categories: (number | string)[]; + ys1: ServerField[]; + values: Array; + x: ServerField; + idToTitle: Record; + order: ResultDataOrder; + xIsDate: boolean; + xIsNumber: boolean; + xDataType: string; + xIsPseudo: boolean; + categoriesMap?: Map; +} + +export interface GetSortedCategoriesArgs { + categories: Categories; + sortItem: ServerSort; + colorItem: ServerColor | undefined; + ySectionItems: ServerField[]; + lines: LinesRecord[]; + isSortAvailable: boolean; + isSortWithYSectionItem: boolean; + isXNumber: boolean; + isSegmentsExists: boolean; + isSortBySegments: boolean; + measureColorSortLine: Record>; +} + +export interface SortCategoriesWithYSectionArgs + extends Pick< + GetSortedCategoriesArgs, + 'ySectionItems' | 'categories' | 'sortItem' | 'colorItem' | 'isSegmentsExists' + > { + lines: LinesRecord; + lineKeys: string[]; +} + +export interface SortCategoriesWithColorsSection + extends Pick { + colorItem: ServerColor; +} + +export type RowDataValue = string | number | Date | null; + +export type XAxisValue = RowDataValue | undefined; + +export type GetItemValuesOptions = { + idToTitle: Record; + order: ResultDataOrder; + values: (string | number | null)[]; +}; + +export type GetSortedLineKeysArgs = { + isSortAvailable: boolean; + isSortBySegments: boolean; + sortItem: ServerSort; + colorItem: ServerColor; + lines: LinesRecord[]; + visualizationId: WizardVisualizationId; + yField: ServerField; + categories: Categories; +}; + +export interface GetLineKeyArgs { + value: string | undefined; + x2AxisValue: string | number | Date; + isX2Axis: boolean; + isMultiAxis: boolean; + isMultiLineWithShape?: boolean; + shownTitle: string; + segmentName: string | undefined | null; +} + +export type ItemValues = { + value: string | number | null; + formattedValue: string | number; + extraValue?: string; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/helpers/utils.ts b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/utils.ts new file mode 100644 index 0000000..915b828 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/helpers/utils.ts @@ -0,0 +1,91 @@ +import {type ServerColor, type ServerShape, getFormatOptions} from '../../../../../../../../shared'; +import { + chartKitFormatNumberWrapper, + findIndexInOrder, + isNumericalDataType, +} from '../../../utils/misc-helpers'; + +import type {GetItemValuesOptions, GetLineKeyArgs, ItemValues} from './types'; + +export const getItemsValues = ( + item: ServerColor | ServerShape, + {idToTitle, values, order}: GetItemValuesOptions, +): ItemValues => { + const itemTitle = idToTitle[item.guid]; + const indexInOrder = findIndexInOrder(order, item, itemTitle); + const value = values[indexInOrder]; + + let formattedValue; + + if (item && 'formatting' in item && isNumericalDataType(item.data_type)) { + formattedValue = chartKitFormatNumberWrapper(value as number, { + ...getFormatOptions(item), + lang: 'ru', + }); + } else { + formattedValue = value ?? 'Null'; + } + + return { + value, + formattedValue, + }; +}; + +export const getLineKey = (args: GetLineKeyArgs) => { + const { + shownTitle, + isX2Axis, + isMultiAxis, + isMultiLineWithShape, + value, + x2AxisValue, + segmentName, + } = args; + const itemTitleValue = value || shownTitle; + let key = itemTitleValue; + + if (isX2Axis) { + key = `${itemTitleValue}-${x2AxisValue}`; + } else if (isMultiAxis || isMultiLineWithShape) { + key = `${itemTitleValue}-${shownTitle}`; + } + + if (segmentName) { + key = `${key}__${segmentName}`; + } + + return key; +}; + +export const mergeLinesData = >>( + target: T, + record: T, +): T => { + const result = {...target}; + + const recordProperties = Object.keys(record || {}); + + recordProperties.forEach((prop) => { + const innerProperties = Object.keys(record[prop]); + + const currentInnerObject = record[prop]; + + if (!result[prop]) { + (result as Record)[prop] = {}; + } + + innerProperties.forEach((innerProp) => { + if (typeof currentInnerObject[innerProp] === 'object') { + result[prop][innerProp] = { + ...(result[prop][innerProp] as object), + ...(currentInnerObject[innerProp] as object), + } as any; + } else { + result[prop][innerProp] = record[prop][innerProp]; + } + }); + }); + + return result; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/highcharts.ts b/src/server/modes/charts/plugins/datalens/preparers/line/highcharts.ts new file mode 100644 index 0000000..d6ac528 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/highcharts.ts @@ -0,0 +1,257 @@ +import _isEmpty from 'lodash/isEmpty'; +import set from 'lodash/set'; + +import type {Field, ServerField, WizardVisualizationId} from '../../../../../../../shared'; +import { + AxisLabelFormatMode, + AxisMode, + ChartkitHandlers, + MINIMUM_FRACTION_DIGITS, + PlaceholderId, + getAxisMode, + getFakeTitleOrTitle, + getIsNavigatorEnabled, + getXAxisMode, + isDateField, + isFloatField, + isMeasureNameOrValue, + isVisualizationWithSeveralFieldsXPlaceholder, +} from '../../../../../../../shared'; +import {isHtmlField} from '../../../../../../../shared/types/index'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import {getFieldExportingOptions} from '../../utils/export-helpers'; +import {isLegendEnabled} from '../../utils/misc-helpers'; +import {addAxisFormatter, addAxisFormatting, getAxisType} from '../helpers/axis'; +import {getAxisChartkitFormatting} from '../helpers/axis/get-axis-formatting'; +import {getHighchartsColorAxis, isXAxisReversed} from '../helpers/highcharts'; +import {getYPlaceholders} from '../helpers/layers'; +import {shouldUseGradientLegend} from '../helpers/legend'; +import {getSegmentMap} from '../helpers/segments'; +import type {PrepareFunctionArgs} from '../types'; + +import {getSegmentsYAxis} from './helpers'; +import {prepareLineData} from './prepare-line-data'; + +// eslint-disable-next-line complexity +function getHighchartsConfig(args: PrepareFunctionArgs & {graphs: any[]}) { + const { + placeholders, + colors, + colorsConfig, + sort, + visualizationId, + shared, + shapes, + graphs, + segments, + idToDataType, + } = args; + const xPlaceholder = placeholders.find((p) => p.id === PlaceholderId.X); + const xPlaceholderSettings = xPlaceholder?.settings; + const x: ServerField | undefined = xPlaceholder?.items[0]; + const isXDiscrete = getAxisMode(xPlaceholderSettings, x?.guid) === AxisMode.Discrete; + const x2 = isVisualizationWithSeveralFieldsXPlaceholder(visualizationId) + ? xPlaceholder?.items[1] + : undefined; + const yPlaceholder = placeholders.find((p) => p.id === PlaceholderId.Y); + const y2Placeholder = placeholders.find((p) => p.id === PlaceholderId.Y2); + const ySectionItems = yPlaceholder?.items || []; + const y2SectionItems = y2Placeholder?.items || []; + const mergedYSections = [...ySectionItems, ...y2SectionItems]; + const colorItem = colors[0]; + const shapeItem = shapes[0]; + const segment = segments[0]; + const segmentsMap = getSegmentMap(args); + + const xField = x ? ({guid: x.guid, data_type: idToDataType[x.guid]} as Field) : x; + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + const xAxisMode = getXAxisMode({config: chartConfig}); + const xAxisType = getAxisType({ + field: xField, + settings: xPlaceholder?.settings, + axisMode: xAxisMode, + }); + + const wizardXAxisFormatter = + isDateField(x) && xAxisType === 'category' + ? ChartkitHandlers.WizardXAxisFormatter + : undefined; + + const customConfig: any = { + xAxis: { + type: xAxisType, + reversed: isXAxisReversed(x, sort, visualizationId as WizardVisualizationId), + labels: { + useHTML: isHtmlField(x), + }, + }, + yAxis: {}, + axesFormatting: { + xAxis: [], + yAxis: [], + }, + exporting: { + csv: { + custom: { + categoryHeader: getFieldExportingOptions(x), + }, + columnHeaderFormatter: ChartkitHandlers.WizardExportColumnNamesFormatter, + }, + }, + }; + + addAxisFormatting(customConfig.axesFormatting.xAxis, visualizationId, xPlaceholder); + addAxisFormatter({ + axisConfig: customConfig.xAxis, + placeholder: xPlaceholder, + otherwiseFormatter: wizardXAxisFormatter, + chartConfig, + }); + + if (mergedYSections.length) { + if (shouldUseGradientLegend(colorItem, colorsConfig, shared)) { + customConfig.colorAxis = getHighchartsColorAxis(graphs, colorsConfig); + customConfig.legend = { + title: { + text: getFakeTitleOrTitle(colorItem), + }, + enabled: isLegendEnabled(shared.extraSettings), + symbolWidth: null, + }; + } + + if (getIsNavigatorEnabled(shared)) { + customConfig.xAxis.ordinal = isXDiscrete; + } + + if (shared.extraSettings) { + const {tooltipSum} = shared.extraSettings; + + if (typeof tooltipSum === 'undefined' || tooltipSum === 'on') { + customConfig.enableSum = true; + } + } + + if (x && !isMeasureNameOrValue(x)) { + customConfig.tooltipHeaderFormatter = getFakeTitleOrTitle(x); + } + + if (_isEmpty(segmentsMap)) { + const [layerYPlaceholder, layerY2Placeholder] = getYPlaceholders(args); + + addAxisFormatting( + customConfig.axesFormatting.yAxis, + visualizationId, + layerYPlaceholder, + ); + + const formatMode = layerY2Placeholder?.settings?.axisFormatMode; + if (formatMode && formatMode !== AxisLabelFormatMode.Auto) { + const formatting = getAxisChartkitFormatting(layerY2Placeholder, visualizationId); + if (formatting) { + if (customConfig.axesFormatting.yAxis.length === 0) { + customConfig.axesFormatting.yAxis.push({}); + } + + customConfig.axesFormatting.yAxis.push(formatting); + } + } + + if (layerYPlaceholder?.items.length && layerY2Placeholder?.items.length) { + const y1 = {}; + const y2 = {}; + addAxisFormatter({ + axisConfig: y1, + placeholder: layerYPlaceholder, + }); + addAxisFormatter({ + axisConfig: y2, + placeholder: layerY2Placeholder, + }); + customConfig.yAxis = [y1, y2]; + } else if (layerYPlaceholder?.items.length) { + addAxisFormatter({ + axisConfig: customConfig.yAxis, + placeholder: layerYPlaceholder, + }); + } else { + addAxisFormatter({ + axisConfig: customConfig.yAxis, + placeholder: layerY2Placeholder, + }); + } + } else { + customConfig.legend = { + enabled: + Boolean( + colorItem || + shapeItem || + x2 || + ySectionItems.length > 1 || + y2SectionItems.length > 1, + ) && isLegendEnabled(shared.extraSettings), + }; + + const {yAxisFormattings, yAxisSettings} = getSegmentsYAxis({ + segment, + segmentsMap, + placeholders: { + y: yPlaceholder, + y2: y2Placeholder, + }, + visualizationId, + }); + customConfig.yAxis = yAxisSettings; + customConfig.axesFormatting.yAxis = yAxisFormattings; + } + } + + const shouldUseHtmlForLegend = [colorItem, shapeItem].some(isHtmlField); + if (shouldUseHtmlForLegend) { + set(customConfig, 'legend.useHTML', true); + } + + return customConfig; +} + +function getConfig(args: PrepareFunctionArgs) { + const {placeholders, shared} = args; + const config: any = {}; + + const xFields = placeholders.find((p) => p.id === PlaceholderId.X)?.items || []; + if (xFields.some(Boolean) && getIsNavigatorEnabled(shared)) { + // For old charts. In the new charts, we put the navigator settings in navigatorSettings and + // adding to the config in config.ts + config.highstock = { + base_series_name: shared.extraSettings?.navigatorSeriesName, + }; + } + + const yFields = placeholders.find((p) => p.id === PlaceholderId.Y)?.items || []; + const y2Fields = placeholders.find((p) => p.id === PlaceholderId.Y2)?.items || []; + const hasFloatYFields = yFields.some(isFloatField) || y2Fields.some(isFloatField); + if (hasFloatYFields) { + config.precision = MINIMUM_FRACTION_DIGITS; + } + + return config; +} + +export function prepareHighchartsLine(args: PrepareFunctionArgs) { + const {ChartEditor} = args; + + const preparedData = prepareLineData(args); + + if (ChartEditor) { + const highchartsConfig = getHighchartsConfig({ + ...args, + graphs: preparedData.graphs, + }); + ChartEditor.updateHighchartsConfig(highchartsConfig); + + const config = getConfig(args); + ChartEditor.updateConfig(config); + } + + return preparedData; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/index.ts b/src/server/modes/charts/plugins/datalens/preparers/line/index.ts new file mode 100644 index 0000000..c97bfbb --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/index.ts @@ -0,0 +1,2 @@ +export {prepareHighchartsLine} from './highcharts'; +export {prepareGravityChartLine} from './gravity-charts'; diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/prepare-line-data.ts b/src/server/modes/charts/plugins/datalens/preparers/line/prepare-line-data.ts new file mode 100644 index 0000000..1aec1ee --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/prepare-line-data.ts @@ -0,0 +1,637 @@ +import _isEmpty from 'lodash/isEmpty'; +import isNil from 'lodash/isNil'; + +import type {HighchartsSeriesCustomObject} from '../../../../../../../shared'; +import { + AxisMode, + AxisNullsMode, + PlaceholderId, + WizardVisualizationId, + getAxisNullsSettings, + getFakeTitleOrTitle, + getFormatOptions, + getXAxisMode, + isDateField, + isHtmlField, + isMarkdownField, + isMarkupField, + isMeasureField, + isMeasureValue, + isNumberField, + isPercentVisualization, + isPseudoField, + isVisualizationWithSeveralFieldsXPlaceholder, +} from '../../../../../../../shared'; +import {wrapHtml} from '../../../../../../../shared/utils/ui-sandbox'; +import {mapAndColorizeGraphsByPalette} from '../../utils/color-helpers'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import { + chartKitFormatNumberWrapper, + collator, + getCategoryFormatter, + getLabelValue, + getSeriesTitleFormatter, + getTimezoneOffsettedTime, + isGradientMode, + isNumericalDataType, + numericCollator, +} from '../../utils/misc-helpers'; +import {mapAndShapeGraph} from '../../utils/shape-helpers'; +import {addActionParamValue} from '../helpers/action-params'; +import {getSegmentMap} from '../helpers/segments'; +import type {PrepareFunctionArgs} from '../types'; + +import {getSegmentsIndexInOrder, getSortedCategories, getXAxisValue, prepareLines} from './helpers'; +import {colorizeByGradient} from './helpers/color-helpers/colorizeByGradient'; +import {getSortedLineKeys} from './helpers/getSortedLineKeys'; +import type {LineTemplate, LinesRecord, MergedYSectionItems} from './types'; + +// eslint-disable-next-line complexity +export function prepareLineData(args: PrepareFunctionArgs) { + const { + ChartEditor, + placeholders, + resultData: {data, order}, + colors, + colorsConfig, + sort, + labels, + idToTitle, + idToDataType, + visualizationId, + datasets = [], + shared, + shapes, + shapesConfig, + segments, + layerChartMeta, + usedColors, + disableDefaultSorting = false, + defaultColorPaletteId, + } = args; + const widgetConfig = ChartEditor.getWidgetConfig(); + const isActionParamsEnable = widgetConfig?.actionParams?.enable; + + const xPlaceholder = placeholders.find((p) => p.id === PlaceholderId.X); + const xField = xPlaceholder?.items[0]; + const xDataType = xField ? idToDataType[xField.guid] : null; + const xIsDate = Boolean(xDataType && isDateField({data_type: xDataType})); + const xIsNumber = Boolean(xDataType && isNumberField({data_type: xDataType})); + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + const xAxisMode = getXAxisMode({config: chartConfig}); + const isHtmlX = isHtmlField(xField); + + const x2 = isVisualizationWithSeveralFieldsXPlaceholder(visualizationId) + ? xPlaceholder?.items[1] + : undefined; + const x2DataType = x2 ? idToDataType[x2.guid] : null; + + const yPlaceholder = placeholders.find((p) => p.id === PlaceholderId.Y); + const y2Placeholder = placeholders.find((p) => p.id === PlaceholderId.Y2); + + const ySectionItems = yPlaceholder?.items || []; + const y2SectionItems = y2Placeholder?.items || []; + const mergedYSections = [...ySectionItems, ...y2SectionItems]; + const isMultiAxis = Boolean(ySectionItems.length && y2SectionItems.length); + const sortItem = sort?.[0]; + const isSortItemExists = sort.length > 0; + const isSortingXAxis = sort?.some((s) => s.guid === xField?.guid); + const isSortingYAxis = mergedYSections.some((item) => item.guid === sortItem?.guid); + const isSortCategoriesAvailable = + xAxisMode === AxisMode.Discrete && + (layerChartMeta ? Boolean(layerChartMeta.isCategoriesSortAvailable) : true); + + const colorItem = colors[0]; + const colorFieldDataType = colorItem ? idToDataType[colorItem.guid] : null; + const isHtmlColor = isHtmlField(colorItem); + + const shapeItem = shapes[0]; + const isHtmlShape = isHtmlField(shapeItem); + + const gradientMode = + colorItem && + colorFieldDataType && + isGradientMode({colorField: colorItem, colorFieldDataType, colorsConfig}); + + const labelItem = labels?.[0]; + const labelsLength = labels && labels.length; + const isMarkdownLabel = isMarkdownField(labelItem); + const isMarkupLabel = isMarkupField(labelItem); + const isHtmlLabel = isHtmlField(labelItem); + + const segmentField = segments[0]; + const segmentIndexInOrder = getSegmentsIndexInOrder(order, segmentField, idToTitle); + const segmentsMap = getSegmentMap(args); + const isSegmentsExists = !_isEmpty(segmentsMap); + const isHtmlSegment = isHtmlField(segmentField); + const segmentTitleFormatter = getSeriesTitleFormatter({fields: [segmentField]}); + + const isShapeItemExist = Boolean(shapeItem && shapeItem.type !== 'PSEUDO'); + const isColorItemExist = Boolean(colorItem && colorItem.type !== 'PSEUDO'); + const isColorizeByMeasure = isMeasureField(colorItem); + const colorMode = colorsConfig.colorMode; + const isColorizeByMeasureValue = isMeasureValue(colorItem); + + /* + Specially reduced LinesRecord object. It has only one key that is equal to the colorItem header. + Needed at the moment when the user sorts the graph according to the same indicator by which it is colored. + For this sort, you need to get values from orders and match them to categories. + */ + const measureColorSortLine: Record> = {}; + const isSortByMeasureColor = sortItem && colorItem && sortItem.guid === colorItem.guid; + + if (isSortByMeasureColor) { + measureColorSortLine[getFakeTitleOrTitle(colorItem)] = {data: {}}; + } + + const nullsY1 = yPlaceholder?.settings?.nulls; + const nullsY2 = y2Placeholder?.settings?.nulls; + + const categoriesMap = new Map(); + const seriesNameFormatter = getSeriesTitleFormatter({fields: [colorItem, shapeItem]}); + + const categoryField = xField + ? {...xField, data_type: xDataType ?? xField?.data_type} + : undefined; + const categoriesFormatter = getCategoryFormatter({ + field: categoryField, + }); + + if (isHtmlLabel || isHtmlX || isHtmlColor || isHtmlShape || isHtmlSegment) { + ChartEditor.updateConfig({useHtml: true}); + } + + if (mergedYSections.length) { + let categories: (string | number)[] = []; + const categories2: (string | number)[] = []; + + const lines1: LinesRecord = {}; + const lines2: LinesRecord = {}; + + const labelsValues: Record> = {}; + + const mergedYSectionItems: MergedYSectionItems[] = [ySectionItems, y2SectionItems].reduce( + (acc, fields, index) => { + const isFirstSection = index === 0; + const items: MergedYSectionItems[] = fields.map((field): MergedYSectionItems => { + return { + field, + lines: isFirstSection ? lines1 : lines2, + nullsSetting: isFirstSection ? nullsY1 : nullsY2, + isFirstSection, + labelsValues, + }; + }); + return acc.concat(...items); + }, + [] as MergedYSectionItems[], + ); + + data.forEach((values) => { + let xValue: ReturnType; + if (xField) { + xValue = getXAxisValue({ + x: xField, + ys1: ySectionItems, + order, + values, + idToTitle, + categories, + xIsDate, + xIsNumber, + xDataType: xDataType!, + xIsPseudo: isPseudoField(xField), + categoriesMap, + }); + + if ((xValue === null || xValue === undefined) && !isPseudoField(xField)) { + return; + } + } else if (ySectionItems.length > 1) { + const value = (xValue = 'Measure Names'); + + if (!categoriesMap.has(value)) { + categoriesMap.set(value, true); + categories.push(value); + } + } else { + const ySectionItem = ySectionItems[0]; + const value = (xValue = ySectionItem.fakeTitle || idToTitle[ySectionItem.guid]); + + if (!categoriesMap.has(value)) { + categoriesMap.set(value, true); + categories.push(value); + } + } + + let x2Value: ReturnType; + if (x2) { + x2Value = getXAxisValue({ + x: x2, + ys1: ySectionItems, + categories: categories2, + idToTitle, + order, + values, + xIsNumber: Boolean(x2DataType && isNumberField({data_type: x2DataType})), + xDataType: x2DataType!, + xIsDate: Boolean(x2DataType && isDateField({data_type: x2DataType})), + xIsPseudo: isPseudoField(x2), + categoriesMap, + }); + if ((x2Value === null || x2Value === undefined) && !isPseudoField(x2)) { + return; + } + } + + prepareLines({ + ySectionItems: mergedYSectionItems, + idToTitle, + idToDataType, + order, + values, + isMultiDatasets: datasets.length > 1, + isColorizeByMeasureValue, + isColorizeByMeasure, + isSegmentsExists, + x2Field: x2, + colorItem, + rawXValue: xValue, + rawX2Value: x2Value, + x2IsDate: Boolean(x2DataType && isDateField({data_type: x2DataType})), + isSortByMeasureColor, + measureColorSortLine, + isShapeItemExist, + isColorItemExist, + isMultiAxis, + shapeItem, + xField: xField, + shapesConfig, + labelItem, + segmentIndexInOrder, + layers: shared.visualization?.layers, + colorMode, + convertMarkupToString: false, + }); + }); + + let lineKeys1 = Object.keys(lines1); + let lineKeys2 = Object.keys(lines2); + + if (xIsDate && !disableDefaultSorting) { + (categories as number[]).sort(numericCollator); + } + + const lines = [lines1, lines2]; + const isSortBySegments = Boolean( + isSortItemExists && segmentField && sortItem.guid === segmentField.guid, + ); + + const isSortableXAxis = + visualizationId !== WizardVisualizationId.Area && + !isPercentVisualization(visualizationId); + + if (!disableDefaultSorting) { + categories = getSortedCategories({ + lines, + colorItem, + categories, + ySectionItems, + isSortWithYSectionItem: Boolean(ySectionItems.length && isSortableXAxis), + sortItem, + isSortAvailable: isSortItemExists && isSortCategoriesAvailable, + isXNumber: xIsNumber, + measureColorSortLine, + isSegmentsExists, + isSortBySegments, + }); + } + + const sortedLineKeys = getSortedLineKeys({ + colorItem, + lines, + isSortAvailable: isSortItemExists, + isSortBySegments, + sortItem, + yField: ySectionItems[0], + visualizationId: visualizationId as WizardVisualizationId, + categories, + }); + + lineKeys1 = sortedLineKeys[0] || lineKeys1; + lineKeys2 = sortedLineKeys[1] || lineKeys2; + + const graphs: any[] = []; + const uniqueTitles: string[] = []; + + const isXDiscrete = xAxisMode === AxisMode.Discrete; + const isSortNumberTypeXAxisByMeasure = + isSortCategoriesAvailable && + isSortItemExists && + xIsNumber && + !isSortingXAxis && + (isSortingYAxis || isSortByMeasureColor); + + const isXCategoryAxis = + isXDiscrete || + xDataType === 'string' || + isPseudoField(xField) || + isSortNumberTypeXAxisByMeasure || + disableDefaultSorting; + + const orderedLineKeys = [lineKeys1, lineKeys2]; + + orderedLineKeys.forEach((lineKeys, lineKeysIndex) => { + lineKeys.forEach((lineKey) => { + let line: LineTemplate; + + let nulls: any; + if (lineKeysIndex === 0) { + line = lines1[lineKey]; + nulls = nullsY1; + } else { + line = lines2[lineKey]; + nulls = nullsY2; + } + + nulls = getAxisNullsSettings(nulls, visualizationId); + + const innerLabels = labelsValues[lineKey]; + + const customSeriesData: HighchartsSeriesCustomObject = {}; + + const shouldUsePreviousValueForEmptyPoint = + visualizationId === WizardVisualizationId.Area && + nulls === AxisNullsMode.UsePrevious; + let prevYValue: string | number | null | undefined = null; + const graph: any = { + id: line.id, + title: seriesNameFormatter(line.title ?? 'Null'), + tooltip: line.tooltip, + dataLabels: { + ...line.dataLabels, + useHTML: isMarkdownLabel || isMarkupLabel || isHtmlLabel, + }, + data: categories + .map((category, i) => { + const lineData = line.data[category]; + const colorValue = lineData?.colorValue; + + let value = lineData?.value; + + if (typeof value === 'undefined' && nulls === AxisNullsMode.AsZero) { + value = 0; + } + + if (shouldUsePreviousValueForEmptyPoint) { + if (isNil(value)) { + value = prevYValue ?? value; + } else { + prevYValue = value; + } + } + + // We can skip a point only if we put x in each point instead of categories + if ( + !isXCategoryAxis && + typeof value === 'undefined' && + nulls === AxisNullsMode.Connect + ) { + return null; + } + + const y = typeof value === 'number' ? Number(value) : null; + + const dataLabels = { + enabled: Boolean(labelsLength && labelItem), + }; + + const point: any = { + x: i, + y, + colorValue, + dataLabels, + }; + + if (line.segmentNameKey) { + const currentSegment = segmentsMap[line.segmentNameKey]; + const pointValue = `${currentSegment.title}: ${line.title}`; + point.custom = { + tooltipPointName: isHtmlSegment + ? wrapHtml(pointValue) + : pointValue, + }; + } + + if (!isXCategoryAxis) { + const pointX = category; + + point.x = pointX; + + if (xField && isNumericalDataType(xField.data_type)) { + const formatting = getFormatOptions(xField); + if (formatting) { + point.xFormatted = chartKitFormatNumberWrapper( + Number(pointX), + { + lang: 'ru', + ...formatting, + }, + ); + } + } + } + + point.label = getLabelValue(innerLabels?.[category], { + isMarkdownLabel, + isMarkupLabel, + isHtmlLabel, + }); + + if (isActionParamsEnable) { + const [yField] = ySectionItems || []; + const actionParams: Record = {}; + addActionParamValue(actionParams, xField, category); + addActionParamValue(actionParams, yField, point.y); + + point.custom = { + ...point.custom, + actionParams, + }; + } + + return point; + }) + .filter((point) => point !== null), + legendTitle: line.legendTitle ?? line.title ?? 'Null', + formattedName: colorItem ? undefined : seriesNameFormatter(line.formattedName), + drillDownFilterValue: line.drillDownFilterValue, + colorKey: line.colorKey, + colorGuid: colorItem?.guid || null, + shapeGuid: shapeItem?.guid || null, + measureFieldTitle: line.fieldTitle, + }; + + // For one point (non-zero), the setting of the connection empty values has a strange effect: + // the value stops being displayed on the graph + if (graph.data.length > 1) { + graph.connectNulls = nulls === AxisNullsMode.Connect; + } + + if (line.pointConflict) { + graph.pointConflict = true; + } + + if (line.segmentNameKey) { + const currentSegment = segmentsMap[line.segmentNameKey]; + graph.yAxis = currentSegment.index; + + customSeriesData.segmentTitle = segmentTitleFormatter(currentSegment.title); + } else if (lineKeysIndex === 0 || ySectionItems.length === 0) { + graph.yAxis = 0; + } else { + graph.yAxis = 1; + } + + if (uniqueTitles.indexOf(graph.legendTitle) === -1) { + uniqueTitles.push(graph.legendTitle); + } else { + graph.showInLegend = false; + } + + if (x2) { + graph.stack = line.stack; + } + + graph.colorValue = line.colorValue; + graph.shapeValue = line.shapeValue; + graph.colorShapeValue = line.colorShapeValue; + + graph.custom = customSeriesData; + + if (isActionParamsEnable) { + const actionParams: Record = {}; + + // bar-x only + addActionParamValue(actionParams, x2, line.stack); + + // bar-y only + const [, yField2] = ySectionItems || []; + addActionParamValue(actionParams, yField2, line.stack); + + addActionParamValue(actionParams, colorItem, line.colorValue); + addActionParamValue(actionParams, shapeItem, line.shapeValue); + + graph.custom = { + ...graph.custom, + actionParams, + }; + } + + graphs.push(graph); + }); + }); + + if (gradientMode) { + colorizeByGradient(visualizationId as WizardVisualizationId, { + graphs, + colorsConfig, + }); + } else { + mapAndColorizeGraphsByPalette({ + graphs, + colorsConfig, + isShapesItemExists: isShapeItemExist, + isColorsItemExists: isColorItemExist, + isSegmentsExists: isSegmentsExists, + usedColors, + colorField: colorItem, + defaultColorPaletteId, + }); + } + + if (visualizationId === WizardVisualizationId.Line) { + mapAndShapeGraph({ + graphs, + shapesConfig, + isSegmentsExists, + isShapesDefault: shapes.length === 0 || isPseudoField(shapes[0]), + }); + } + + if (isMarkdownLabel) { + ChartEditor.updateConfig({useMarkdown: true}); + } + + if (isMarkupLabel) { + ChartEditor.updateConfig({useMarkup: true}); + } + + if (isXCategoryAxis) { + return { + graphs, + categories: categories.map(categoriesFormatter), + }; + } else { + return {graphs}; + } + } else { + // If no fields are selected for the Y axis, we draw only the X axis. + + const categories: (string | number)[] = []; + + data.forEach((values) => { + const value = values[0]; + + if (value === null) { + return; + } + + let xValue; + if (xIsDate) { + const time = new Date(value); + + if (xDataType === 'genericdatetime') { + time.setTime(getTimezoneOffsettedTime(time)); + } + + xValue = time.getTime(); + } else if (xIsNumber) { + xValue = Number(value); + } else { + xValue = value; + } + + if (!categoriesMap.has(xValue)) { + categoriesMap.set(xValue, true); + categories.push(xValue); + } + }); + + // Default sorting + if ((!isSortItemExists || !isSortCategoriesAvailable) && !disableDefaultSorting) { + if (xIsNumber) { + (categories as number[]).sort(numericCollator); + } else { + (categories as string[]).sort(collator.compare); + } + } + + // Generating data + const graphs = [ + { + data: categories.map(() => null), + }, + ]; + + // If there are dates on the X axis, then we pass them as dates + if (xIsDate && xAxisMode !== AxisMode.Discrete) { + return {graphs, categories_ms: categories}; + } + + return { + graphs, + categories: categories.map(categoriesFormatter), + }; + } +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/types.ts b/src/server/modes/charts/plugins/datalens/preparers/line/types.ts new file mode 100644 index 0000000..9ed090e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/line/types.ts @@ -0,0 +1,60 @@ +import type {LineSeries, LineSeriesData} from '@gravity-ui/chartkit/gravity-charts'; + +import type { + SeriesExportSettings, + ServerField, + ServerPlaceholderSettings, + WrappedHTML, + WrappedMarkdown, +} from '../../../../../../../shared'; +import type {ChartKitFormatSettings} from '../types'; + +export type LinesRecord = { + [key: string]: LineTemplate; +}; + +export type MergedYSectionItems = { + field: ServerField; + lines: LinesRecord; + nullsSetting: ServerPlaceholderSettings['nulls']; + isFirstSection: boolean; + labelsValues: Record>; +}; + +export type LineTemplate = { + data: Record; + // colorValue for the entire series. + colorValue: string | number | undefined; + title: string; + legendTitle: string | WrappedHTML; + stack: string | number | Date | undefined; + colorKey: string | undefined; + tooltip: string; + dataLabels: ChartKitFormatSettings & {enabled?: boolean}; + formattedName: string | undefined; + drillDownFilterValue: string | undefined; + shapeValue: string | undefined; + colorShapeValue: string | undefined; + segmentNameKey: string | null | undefined; + fieldTitle: string; + id?: string; + pointConflict?: boolean; +}; + +export type LineData = { + value: string | number | undefined | null; + // colorValue for a specific point. + // So far, it is used only for coloring by indicators. + colorValue?: number; +}; + +export type ExtendedLineSeriesData = Omit & { + x?: LineSeriesData['x'] | WrappedHTML | WrappedMarkdown; +}; + +export type ExtendedLineSeries = Omit & { + custom?: { + exportSettings?: SeriesExportSettings; + }; + data: ExtendedLineSeriesData[]; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/metric/__tests__/metric.test.ts b/src/server/modes/charts/plugins/datalens/preparers/metric/__tests__/metric.test.ts new file mode 100644 index 0000000..116ba76 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/metric/__tests__/metric.test.ts @@ -0,0 +1,14 @@ +import type {PrepareFunctionArgs} from '../../types'; +import prepareMetric from '../index'; + +import {metricPrepareForQLArgs, metricPrepareForQLResult} from './mocks/metric.mock'; + +describe('prepareMetric', () => { + describe('ql', () => { + test('should render simple metric correctly', () => { + const result = prepareMetric(metricPrepareForQLArgs as unknown as PrepareFunctionArgs); + + expect(result).toEqual(metricPrepareForQLResult); + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/metric/__tests__/mocks/metric.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/metric/__tests__/mocks/metric.mock.ts new file mode 100644 index 0000000..e40155a --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/metric/__tests__/mocks/metric.mock.ts @@ -0,0 +1,93 @@ +import {COMMON_PALETTE_ID, type IChartEditor, PALETTE_ID} from '../../../../../../../../../shared'; +import classic20 from '../../../../../../../../../shared/constants/colors/common/classic-20'; + +export const metricPrepareForQLArgs = { + placeholders: [ + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measure', + iconProps: {}, + items: [ + { + guid: 'avg-0', + title: 'avg', + datasetId: 'ql-mocked-dataset', + data_type: 'float', + cast: 'float', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + required: true, + capacity: 1, + }, + ], + fields: [], + colors: [], + colorsConfig: { + colors: [ + '#4DA2F1', + '#FF3D64', + '#8AD554', + '#FFC636', + '#FFB9DD', + '#84D1EE', + '#FF91A1', + '#54A520', + '#DB9100', + '#BA74B3', + '#1F68A9', + '#ED65A9', + '#0FA08D', + '#FF7E00', + '#E8B0A4', + '#52A6C5', + '#BE2443', + '#70C1AF', + '#FFB46C', + '#DCA3D7', + ], + gradientColors: ['#0044A3', '#8CCBFF'], + loadedColorPalettes: {}, + availablePalettes: {[COMMON_PALETTE_ID.CLASSIC_20]: classic20}, + }, + sort: [], + visualizationId: 'metric', + labels: [], + tooltips: [], + datasets: [], + resultData: { + data: [[71.09428467192288]], + order: [{datasetId: 'ql-mocked-dataset', title: 'avg'}], + totals: [], + }, + idToTitle: {'avg-0': 'avg'}, + idToDataType: {'avg-0': 'float'}, + shared: { + sharedData: {}, + }, + ChartEditor: { + getWidgetConfig: () => {}, + } as IChartEditor, + shapes: [], + shapesConfig: {}, + segments: [], + disableDefaultSorting: false, + defaultColorPaletteId: PALETTE_ID.CLASSIC_20, +}; + +export const metricPrepareForQLResult = [ + { + content: {current: {value: 71.09428467192288, precision: 2}}, + size: '', + color: classic20.scheme[0], + title: 'avg', + }, +]; diff --git a/src/server/modes/charts/plugins/datalens/preparers/metric/index.ts b/src/server/modes/charts/plugins/datalens/preparers/metric/index.ts new file mode 100644 index 0000000..5a9b3b4 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/metric/index.ts @@ -0,0 +1,59 @@ +import {isMarkupItem} from '../../../../../../../shared'; +import {selectServerPalette} from '../../../../../../constants'; +import {findIndexInOrder} from '../../utils/misc-helpers'; +import type {PrepareFunctionArgs} from '../types'; + +import {prepareBasicMetricVariant} from './variants/basic'; +import {prepareMarkupMetricVariant} from './variants/markup'; + +function prepareMetric({ + placeholders, + resultData, + shared, + idToTitle, + colorsConfig, + defaultColorPaletteId, +}: PrepareFunctionArgs) { + const {data, order} = resultData; + + const measure = placeholders[0].items[0]; + + if (typeof measure === 'undefined') { + return []; + } + + const measureActualTitle = idToTitle[measure.guid]; + const measureIndex = findIndexInOrder(order, measure, measureActualTitle); + const value = data[0][measureIndex]; + + if (typeof value === 'undefined' || value === null) { + return []; + } + + const useMarkup = isMarkupItem(value); + + const currentPalette = selectServerPalette({ + palette: shared.extraSettings?.metricFontColorPalette, + availablePalettes: colorsConfig.availablePalettes, + customColorPalettes: colorsConfig.loadedColorPalettes, + defaultColorPaletteId, + }); + + if (useMarkup) { + return prepareMarkupMetricVariant({ + measure, + value, + extraSettings: shared.extraSettings, + currentPalette, + }); + } else { + return prepareBasicMetricVariant({ + measure, + value, + extraSettings: shared.extraSettings, + currentPalette, + }); + } +} + +export default prepareMetric; diff --git a/src/server/modes/charts/plugins/datalens/preparers/metric/utils.ts b/src/server/modes/charts/plugins/datalens/preparers/metric/utils.ts new file mode 100644 index 0000000..c119968 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/metric/utils.ts @@ -0,0 +1,21 @@ +import type {ServerCommonSharedExtraSettings, ServerField} from '../../../../../../../shared'; +import {IndicatorTitleMode, getFakeTitleOrTitle} from '../../../../../../../shared'; + +export function getTitle( + extraSettings: ServerCommonSharedExtraSettings | undefined, + field: ServerField, +) { + const mode = extraSettings?.indicatorTitleMode; + switch (mode) { + case IndicatorTitleMode.Hide: { + return ''; + } + case IndicatorTitleMode.Manual: { + return extraSettings?.title || ''; + } + case IndicatorTitleMode.ByField: + default: { + return getFakeTitleOrTitle(field); + } + } +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/metric/variants/basic.ts b/src/server/modes/charts/plugins/datalens/preparers/metric/variants/basic.ts new file mode 100644 index 0000000..88bdadf --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/metric/variants/basic.ts @@ -0,0 +1,89 @@ +import {dateTime} from '@gravity-ui/date-utils'; + +import type { + NumberFormatType, + NumberFormatUnit, + ServerCommonSharedExtraSettings, +} from '../../../../../../../../shared'; +import { + MINIMUM_FRACTION_DIGITS, + getFormatOptions, + isDateField, +} from '../../../../../../../../shared'; +import {getColorByColorSettings} from '../../../../../../../../shared/utils/palettes'; +import {isFloatDataType, isNumericalDataType} from '../../../utils/misc-helpers'; +import {getTitle} from '../utils'; + +type MetricCurrent = { + value: string | number | null; + precision?: number; + format?: NumberFormatType; + postfix?: string; + prefix?: string; + showRankDelimiter?: boolean; + unit?: NumberFormatUnit; +}; + +type MetricConfig = { + content: { + current: MetricCurrent; + }; + title: string; + size: string; + color: string; +}; + +export const prepareBasicMetricVariant = ({ + measure, + value, + extraSettings, + currentPalette, +}: { + measure: any; + value: string | null; + extraSettings: ServerCommonSharedExtraSettings | undefined; + currentPalette: string[]; +}) => { + const current: MetricCurrent = {value}; + if (measure && isNumericalDataType(measure.data_type)) { + current.value = Number(current.value); + const measureFormatting = getFormatOptions(measure); + + if (measureFormatting) { + current.format = measureFormatting.format; + current.postfix = measureFormatting.postfix; + current.prefix = measureFormatting.prefix; + current.showRankDelimiter = measureFormatting.showRankDelimiter; + current.unit = measureFormatting.unit; + current.precision = + isFloatDataType(measure.data_type) && + typeof measureFormatting.precision !== 'number' + ? MINIMUM_FRACTION_DIGITS + : measureFormatting.precision; + } else if (isFloatDataType(measure.data_type)) { + current.precision = MINIMUM_FRACTION_DIGITS; + } + } else if (current.value && isDateField(measure) && measure.format) { + current.value = dateTime({input: current.value}).format(measure.format); + } + + const size = (extraSettings && extraSettings.metricFontSize) || ''; + + const color = getColorByColorSettings({ + currentColors: currentPalette, + colorIndex: extraSettings?.metricFontColorIndex, + color: extraSettings?.metricFontColor, + }); + const title = getTitle(extraSettings, measure); + + const metric: MetricConfig = { + content: { + current, + }, + size, + color, + title, + }; + + return [metric]; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/metric/variants/markup.ts b/src/server/modes/charts/plugins/datalens/preparers/metric/variants/markup.ts new file mode 100644 index 0000000..8893f58 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/metric/variants/markup.ts @@ -0,0 +1,97 @@ +import {dateTime} from '@gravity-ui/date-utils'; + +import type { + CommonNumberFormattingOptions, + MarkupItem, + ServerCommonSharedExtraSettings, + ServerField, +} from '../../../../../../../../shared'; +import { + MINIMUM_FRACTION_DIGITS, + formatNumber, + getFormatOptions, + isDateField, +} from '../../../../../../../../shared'; +import {getColorByColorSettings} from '../../../../../../../../shared/utils/palettes'; +import {prepareMetricObject} from '../../../utils/markup-helpers'; +import {isFloatDataType, isNumericalDataType} from '../../../utils/misc-helpers'; +import {getTitle} from '../utils'; + +export const prepareMarkupMetricVariant = ({ + measure, + value, + extraSettings, + currentPalette, +}: { + measure: ServerField; + value: string | MarkupItem | null; + extraSettings: ServerCommonSharedExtraSettings | undefined; + currentPalette: string[]; +}) => { + if (!measure) { + return {}; + } + + const title = getTitle(extraSettings, measure); + + if (typeof value === 'object' && value !== null) { + if (title) { + return { + value: { + type: 'concat', + children: [ + { + type: 'size', + size: '16px', + content: { + className: 'markup-metric-title', + type: 'text', + content: title, + }, + }, + value, + ], + }, + }; + } else { + return {value}; + } + } else { + const size = (extraSettings && extraSettings.metricFontSize) || 'm'; + + const color = getColorByColorSettings({ + currentColors: currentPalette, + colorIndex: extraSettings?.metricFontColorIndex, + color: extraSettings?.metricFontColor, + }); + + const formatOptions: CommonNumberFormattingOptions = {}; + + let formattedValue = String(value); + + if (isNumericalDataType(measure.data_type)) { + const measureFormatting = getFormatOptions(measure); + + if (measureFormatting) { + formatOptions.format = measureFormatting.format; + formatOptions.postfix = measureFormatting.postfix; + formatOptions.prefix = measureFormatting.prefix; + formatOptions.showRankDelimiter = measureFormatting.showRankDelimiter; + formatOptions.unit = measureFormatting.unit; + formatOptions.precision = + isFloatDataType(measure.data_type) && + typeof measureFormatting.precision !== 'number' + ? MINIMUM_FRACTION_DIGITS + : measureFormatting.precision; + } else if (isFloatDataType(measure.data_type)) { + formatOptions.precision = MINIMUM_FRACTION_DIGITS; + } + + formattedValue = formatNumber(value || 0, formatOptions); + } else if (isDateField(measure) && measure.format) { + formattedValue = dateTime({input: value}).format(measure.format); + } + + return prepareMetricObject({size, title, color, value: formattedValue}); + } +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/__tests__/mocks/old-pivot-table.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/__tests__/mocks/old-pivot-table.mock.ts new file mode 100644 index 0000000..0909132 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/__tests__/mocks/old-pivot-table.mock.ts @@ -0,0 +1,6972 @@ +export const baseCase1 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'dimension-7', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Category', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: 'b9ac1e53a57248fa', + description: '', + id: 'inserted-1567172433335', + datasetId: 'v41c56fh1tnks', + }, + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: '434222f8-4348-4adf-bd91-d11925d692ac', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sub-Category', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '6d50010f264f4365', + description: '', + id: 'inserted-1567172439882', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'MEASURE', + hidden: false, + data_type: 'float', + cast: 'float', + guid: '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sales (1)', + valid: true, + aggregation_locked: false, + aggregation: 'sum', + source: '9cfffd3199c048ad', + description: '', + local: true, + id: 'inserted-1567172345255', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['First Class', 'Furniture', 'Bookcases', '21065.31889999999'], + ['First Class', 'Furniture', 'Chairs', '47706.47199999998'], + ['First Class', 'Furniture', 'Furnishings', '13157.955999999991'], + ['First Class', 'Furniture', 'Tables', '28800.77599999999'], + ['First Class', 'Office Supplies', 'Appliances', '15726.024000000001'], + ['First Class', 'Office Supplies', 'Art', '3470.0879999999997'], + ['First Class', 'Office Supplies', 'Binders', '36157.81299999997'], + ['First Class', 'Office Supplies', 'Envelopes', '2563.456'], + ['First Class', 'Office Supplies', 'Fasteners', '357.5980000000001'], + ['First Class', 'Office Supplies', 'Labels', '1835.98'], + ['First Class', 'Office Supplies', 'Paper', '11379.797999999997'], + ['First Class', 'Office Supplies', 'Storage', '26273.257999999998'], + ['First Class', 'Office Supplies', 'Supplies', '3568.294000000001'], + ['First Class', 'Technology', 'Accessories', '22226.692000000003'], + ['First Class', 'Technology', 'Copiers', '39369.630000000005'], + ['First Class', 'Technology', 'Machines', '24153.400999999998'], + ['First Class', 'Technology', 'Phones', '53615.867999999995'], + ['Same Day', 'Furniture', 'Bookcases', '2970.5820000000003'], + ['Same Day', 'Furniture', 'Chairs', '21659.715999999997'], + ['Same Day', 'Furniture', 'Furnishings', '4874.134000000001'], + ['Same Day', 'Furniture', 'Tables', '9644.347'], + ['Same Day', 'Office Supplies', 'Appliances', '6270.5920000000015'], + ['Same Day', 'Office Supplies', 'Art', '1437.4759999999999'], + ['Same Day', 'Office Supplies', 'Binders', '5819.504999999999'], + ['Same Day', 'Office Supplies', 'Envelopes', '790.476'], + ['Same Day', 'Office Supplies', 'Fasteners', '139.752'], + ['Same Day', 'Office Supplies', 'Labels', '566.448'], + ['Same Day', 'Office Supplies', 'Paper', '4705.696000000001'], + ['Same Day', 'Office Supplies', 'Storage', '8850.226'], + ['Same Day', 'Office Supplies', 'Supplies', '510.30999999999995'], + ['Same Day', 'Technology', 'Accessories', '13777.223999999993'], + ['Same Day', 'Technology', 'Copiers', '7559.8679999999995'], + ['Same Day', 'Technology', 'Machines', '22798.536999999997'], + ['Same Day', 'Technology', 'Phones', '15988.235999999995'], + ['Second Class', 'Furniture', 'Bookcases', '22307.212899999995'], + ['Second Class', 'Furniture', 'Chairs', '74872.05799999996'], + ['Second Class', 'Furniture', 'Furnishings', '15416.00599999999'], + ['Second Class', 'Furniture', 'Tables', '43693.74750000001'], + ['Second Class', 'Office Supplies', 'Appliances', '26538.238999999998'], + ['Second Class', 'Office Supplies', 'Art', '5270.03'], + ['Second Class', 'Office Supplies', 'Binders', '27115.34199999999'], + ['Second Class', 'Office Supplies', 'Envelopes', '2847.608000000001'], + ['Second Class', 'Office Supplies', 'Fasteners', '478.018'], + ['Second Class', 'Office Supplies', 'Labels', '3352.5340000000006'], + ['Second Class', 'Office Supplies', 'Paper', '17233.449999999986'], + ['Second Class', 'Office Supplies', 'Storage', '48469.37199999999'], + ['Second Class', 'Office Supplies', 'Supplies', '29378.41'], + ['Second Class', 'Technology', 'Accessories', '37572.986'], + ['Second Class', 'Technology', 'Copiers', '18899.628'], + ['Second Class', 'Technology', 'Machines', '22493.7'], + ['Second Class', 'Technology', 'Phones', '63255.228000000025'], + ['Standard Class', 'Furniture', 'Bookcases', '68536.88249999995'], + ['Standard Class', 'Furniture', 'Chairs', '184210.8570000001'], + ['Standard Class', 'Furniture', 'Furnishings', '58257.06799999995'], + ['Standard Class', 'Furniture', 'Tables', '124826.66150000005'], + ['Standard Class', 'Office Supplies', 'Appliances', '58997.30599999998'], + ['Standard Class', 'Office Supplies', 'Art', '16941.19799999999'], + ['Standard Class', 'Office Supplies', 'Binders', '134320.07300000003'], + ['Standard Class', 'Office Supplies', 'Envelopes', '10274.861999999997'], + ['Standard Class', 'Office Supplies', 'Fasteners', '2048.9120000000003'], + ['Standard Class', 'Office Supplies', 'Labels', '6731.349999999997'], + ['Standard Class', 'Office Supplies', 'Paper', '45160.261999999944'], + ['Standard Class', 'Office Supplies', 'Storage', '140250.75200000004'], + ['Standard Class', 'Office Supplies', 'Supplies', '13216.524'], + ['Standard Class', 'Technology', 'Accessories', '93803.416'], + ['Standard Class', 'Technology', 'Copiers', '83698.90399999997'], + ['Standard Class', 'Technology', 'Machines', '119792.99300000002'], + ['Standard Class', 'Technology', 'Phones', '197147.72200000004'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Category', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Sub-Category', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Sales (1)', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => 'en', + }, +}; + +export const baseCase2 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'dimension-7', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [['First Class'], ['Same Day'], ['Second Class'], ['Standard Class']], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase3 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'inserted-1567172474518', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [['First Class'], ['Same Day'], ['Second Class'], ['Standard Class']], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase4 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'MEASURE', + hidden: false, + data_type: 'float', + cast: 'float', + guid: '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sales (1)', + valid: true, + aggregation_locked: false, + aggregation: 'sum', + source: '9cfffd3199c048ad', + description: '', + local: true, + id: 'inserted-1567172486635', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [['2297200.860299955']], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Sales (1)', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase5 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'dimension-8', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'MEASURE', + hidden: false, + data_type: 'float', + cast: 'float', + guid: '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sales (1)', + valid: true, + aggregation_locked: false, + aggregation: 'sum', + source: '9cfffd3199c048ad', + description: '', + local: true, + id: 'inserted-1567172486635', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['First Class', '351428.4229000002'], + ['Same Day', '128363.125'], + ['Second Class', '459193.5693999998'], + ['Standard Class', '1358215.7429999826'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Sales (1)', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase6 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'inserted-1567172506410', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'MEASURE', + hidden: false, + data_type: 'float', + cast: 'float', + guid: '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sales (1)', + valid: true, + aggregation_locked: false, + aggregation: 'sum', + source: '9cfffd3199c048ad', + description: '', + local: true, + id: 'inserted-1567172486635', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['First Class', '351428.4229000002'], + ['Same Day', '128363.125'], + ['Second Class', '459193.5693999998'], + ['Standard Class', '1358215.7429999826'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Sales (1)', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase7 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: '6d2a3c33-9289-4152-bede-3168f95432dc', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Segment', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '3ca5370778884f13', + description: '', + id: 'inserted-1567172520693', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'inserted-1567172506410', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'MEASURE', + hidden: false, + data_type: 'float', + cast: 'float', + guid: '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sales (1)', + valid: true, + aggregation_locked: false, + aggregation: 'sum', + source: '9cfffd3199c048ad', + description: '', + local: true, + id: 'inserted-1567172486635', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['Consumer', 'First Class', '159168.965'], + ['Consumer', 'Same Day', '60596.359000000004'], + ['Consumer', 'Second Class', '231498.9496000004'], + ['Consumer', 'Standard Class', '710137.071400001'], + ['Corporate', 'First Class', '105858.4698999999'], + ['Corporate', 'Same Day', '45121.323'], + ['Corporate', 'Second Class', '146126.0388'], + ['Corporate', 'Standard Class', '409040.53510000004'], + ['Home Office', 'First Class', '86400.98799999998'], + ['Home Office', 'Same Day', '22645.442999999996'], + ['Home Office', 'Second Class', '81568.58100000002'], + ['Home Office', 'Standard Class', '239038.13650000008'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Segment', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Sales (1)', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase8 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: '6d2a3c33-9289-4152-bede-3168f95432dc', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Segment', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '3ca5370778884f13', + description: '', + id: 'inserted-1567172520693', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'inserted-1567172506410', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['Consumer', 'First Class'], + ['Consumer', 'Same Day'], + ['Consumer', 'Second Class'], + ['Consumer', 'Standard Class'], + ['Corporate', 'First Class'], + ['Corporate', 'Same Day'], + ['Corporate', 'Second Class'], + ['Corporate', 'Standard Class'], + ['Home Office', 'First Class'], + ['Home Office', 'Same Day'], + ['Home Office', 'Second Class'], + ['Home Office', 'Standard Class'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Segment', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase9 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: '6d2a3c33-9289-4152-bede-3168f95432dc', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Segment', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '3ca5370778884f13', + description: '', + id: 'inserted-1567172520693', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'inserted-1567172506410', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['Consumer', 'First Class', '159168.965'], + ['Consumer', 'Same Day', '60596.359000000004'], + ['Consumer', 'Second Class', '231498.9496000004'], + ['Consumer', 'Standard Class', '710137.071400001'], + ['Corporate', 'First Class', '105858.4698999999'], + ['Corporate', 'Same Day', '45121.323'], + ['Corporate', 'Second Class', '146126.0388'], + ['Corporate', 'Standard Class', '409040.53510000004'], + ['Home Office', 'First Class', '86400.98799999998'], + ['Home Office', 'Same Day', '22645.442999999996'], + ['Home Office', 'Second Class', '81568.58100000002'], + ['Home Office', 'Standard Class', '239038.13650000008'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Segment', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Sales (1)', + }, + ], + }, + colors: [ + { + type: 'MEASURE', + hidden: false, + data_type: 'float', + cast: 'float', + guid: '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sales (1)', + valid: true, + aggregation_locked: false, + aggregation: 'sum', + source: '9cfffd3199c048ad', + description: '', + local: true, + id: 'inserted-1567172540352', + datasetId: 'v41c56fh1tnks', + }, + ], + sort: [], + colorsConfig: {gradientColors: ['#0044A3', '#8CCBFF'], gradientPalette: 'blue'}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase10 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: '6d2a3c33-9289-4152-bede-3168f95432dc', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Segment', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '3ca5370778884f13', + description: '', + id: 'inserted-1567172520693', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'inserted-1567172506410', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'MEASURE', + hidden: false, + data_type: 'float', + cast: 'float', + guid: '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sales (1)', + valid: true, + aggregation_locked: false, + aggregation: 'sum', + source: '9cfffd3199c048ad', + description: '', + local: true, + id: 'inserted-1567172540352', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['Consumer', 'First Class', '21374.043600000005', '159168.965'], + ['Consumer', 'Same Day', '9874.205399999997', '60596.359000000004'], + ['Consumer', 'Second Class', '24946.91120000001', '231498.9496000004'], + ['Consumer', 'Standard Class', '77924.04899999977', '710137.071400001'], + ['Corporate', 'First Class', '14464.472399999995', '105858.4698999999'], + ['Corporate', 'Same Day', '1818.1417999999974', '45121.323'], + ['Corporate', 'Second Class', '18225.713100000004', '146126.0388'], + ['Corporate', 'Standard Class', '57470.80670000002', '409040.53510000004'], + ['Home Office', 'First Class', '13131.323899999992', '86400.98799999998'], + ['Home Office', 'Same Day', '4199.411699999998', '22645.442999999996'], + ['Home Office', 'Second Class', '14274.011100000003', '81568.58100000002'], + ['Home Office', 'Standard Class', '28693.931799999955', '239038.13650000008'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Segment', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Profit (1)', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Sales (1)', + }, + ], + }, + colors: [ + { + type: 'MEASURE', + hidden: false, + data_type: 'float', + cast: 'float', + guid: '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sales (1)', + valid: true, + aggregation_locked: false, + aggregation: 'sum', + source: '9cfffd3199c048ad', + description: '', + local: true, + id: 'inserted-1567172540352', + datasetId: 'v41c56fh1tnks', + }, + ], + sort: [], + colorsConfig: {gradientColors: ['#0044A3', '#8CCBFF'], gradientPalette: 'blue'}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '079b0430-cb2c-11e9-bcd8-f93d581ea3a9': 'Profit (1)', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '079b0430-cb2c-11e9-bcd8-f93d581ea3a9': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase11 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: '6d2a3c33-9289-4152-bede-3168f95432dc', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Segment', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '3ca5370778884f13', + description: '', + id: 'inserted-1567172520693', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'inserted-1567172506410', + datasetId: 'v41c56fh1tnks', + }, + { + hidden: false, + source: '24d104ff10bb4c05', + aggregation_locked: false, + has_auto_aggregation: null, + initial_data_type: null, + cast: 'string', + managed_by: 'user', + calc_mode: 'direct', + formula: '', + type: 'DIMENSION', + description: '', + data_type: 'string', + avatar_id: 'd76f325d-1b0a-4647-b396-cd030214557f', + guid: 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a', + autoaggregated: false, + title: 'State', + valid: true, + aggregation: 'none', + lock_aggregation: false, + virtual: false, + datasetId: 'v41c56fh1tnks', + id: 'dimension-10', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'MEASURE', + hidden: false, + data_type: 'float', + cast: 'float', + guid: '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sales (1)', + valid: true, + aggregation_locked: false, + aggregation: 'sum', + source: '9cfffd3199c048ad', + description: '', + local: true, + id: 'inserted-1567172486635', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['Consumer', 'First Class', 'Alabama', '8.96'], + ['Consumer', 'First Class', 'Arizona', '1988.721'], + ['Consumer', 'First Class', 'Arkansas', '1465.7600000000004'], + ['Consumer', 'First Class', 'California', '28117.52899999998'], + ['Consumer', 'First Class', 'Colorado', '1691.117'], + ['Consumer', 'First Class', 'Connecticut', '866.2900000000001'], + ['Consumer', 'First Class', 'Delaware', '933.7600000000001'], + ['Consumer', 'First Class', 'Florida', '2187.5049999999997'], + ['Consumer', 'First Class', 'Georgia', '2236.54'], + ['Consumer', 'First Class', 'Idaho', '904.496'], + ['Consumer', 'First Class', 'Illinois', '6315.234000000001'], + ['Consumer', 'First Class', 'Indiana', '1419.14'], + ['Consumer', 'First Class', 'Iowa', '278.4'], + ['Consumer', 'First Class', 'Kansas', '245.85999999999999'], + ['Consumer', 'First Class', 'Kentucky', '1691.4699999999998'], + ['Consumer', 'First Class', 'Louisiana', '3640.69'], + ['Consumer', 'First Class', 'Maryland', '1854.511'], + ['Consumer', 'First Class', 'Massachusetts', '4722.7699999999995'], + ['Consumer', 'First Class', 'Michigan', '2679.01'], + ['Consumer', 'First Class', 'Minnesota', '2457.73'], + ['Consumer', 'First Class', 'Mississippi', '514.0300000000001'], + ['Consumer', 'First Class', 'Missouri', '512.9'], + ['Consumer', 'First Class', 'Nebraska', '178.37'], + ['Consumer', 'First Class', 'New Hampshire', '136.74'], + ['Consumer', 'First Class', 'New Jersey', '1134.21'], + ['Consumer', 'First Class', 'New York', '23376.207999999988'], + ['Consumer', 'First Class', 'North Carolina', '1796.6820000000002'], + ['Consumer', 'First Class', 'Ohio', '10279.053000000005'], + ['Consumer', 'First Class', 'Oklahoma', '889.8'], + ['Consumer', 'First Class', 'Oregon', '1180.757'], + ['Consumer', 'First Class', 'Pennsylvania', '13832.029999999999'], + ['Consumer', 'First Class', 'Rhode Island', '126.38'], + ['Consumer', 'First Class', 'South Carolina', '59.480000000000004'], + ['Consumer', 'First Class', 'Tennessee', '520.946'], + ['Consumer', 'First Class', 'Texas', '8441.824000000002'], + ['Consumer', 'First Class', 'Utah', '151.72'], + ['Consumer', 'First Class', 'Virginia', '6680.110000000001'], + ['Consumer', 'First Class', 'Washington', '19858.521999999994'], + ['Consumer', 'First Class', 'Wisconsin', '3793.71'], + ['Consumer', 'Same Day', 'Alabama', '29.0'], + ['Consumer', 'Same Day', 'Arizona', '1065.666'], + ['Consumer', 'Same Day', 'California', '11963.938999999998'], + ['Consumer', 'Same Day', 'Colorado', '484.196'], + ['Consumer', 'Same Day', 'Connecticut', '714.2270000000001'], + ['Consumer', 'Same Day', 'Delaware', '162.51999999999998'], + ['Consumer', 'Same Day', 'Florida', '3550.7500000000005'], + ['Consumer', 'Same Day', 'Georgia', '210.36999999999998'], + ['Consumer', 'Same Day', 'Illinois', '1489.352'], + ['Consumer', 'Same Day', 'Indiana', '1441.18'], + ['Consumer', 'Same Day', 'Iowa', '6.48'], + ['Consumer', 'Same Day', 'Kentucky', '10.74'], + ['Consumer', 'Same Day', 'Louisiana', '89.32000000000001'], + ['Consumer', 'Same Day', 'Maryland', '2073.3500000000004'], + ['Consumer', 'Same Day', 'Michigan', '3022.616'], + ['Consumer', 'Same Day', 'Minnesota', '375.71'], + ['Consumer', 'Same Day', 'Missouri', '248.82'], + ['Consumer', 'Same Day', 'New Jersey', '70.95'], + ['Consumer', 'Same Day', 'New York', '14522.056999999999'], + ['Consumer', 'Same Day', 'North Carolina', '3451.6850000000004'], + ['Consumer', 'Same Day', 'Ohio', '2609.145000000001'], + ['Consumer', 'Same Day', 'Oklahoma', '1936.53'], + ['Consumer', 'Same Day', 'Pennsylvania', '65.55'], + ['Consumer', 'Same Day', 'South Carolina', '452.25'], + ['Consumer', 'Same Day', 'Tennessee', '80.564'], + ['Consumer', 'Same Day', 'Texas', '5566.520000000002'], + ['Consumer', 'Same Day', 'Utah', '97.91799999999999'], + ['Consumer', 'Same Day', 'Virginia', '257.73'], + ['Consumer', 'Same Day', 'Washington', '3838.7500000000005'], + ['Consumer', 'Same Day', 'West Virginia', '673.344'], + ['Consumer', 'Same Day', 'Wisconsin', '35.129999999999995'], + ['Consumer', 'Second Class', 'Alabama', '3481.5200000000004'], + ['Consumer', 'Second Class', 'Arizona', '2702.91'], + ['Consumer', 'Second Class', 'Arkansas', '353.43'], + ['Consumer', 'Second Class', 'California', '52717.70549999998'], + ['Consumer', 'Second Class', 'Colorado', '3145.9620000000004'], + ['Consumer', 'Second Class', 'Connecticut', '1954.3700000000001'], + ['Consumer', 'Second Class', 'Delaware', '420.64000000000004'], + ['Consumer', 'Second Class', 'Florida', '7858.7065'], + ['Consumer', 'Second Class', 'Georgia', '2935.66'], + ['Consumer', 'Second Class', 'Idaho', '530.4159999999999'], + ['Consumer', 'Second Class', 'Illinois', '7110.49'], + ['Consumer', 'Second Class', 'Indiana', '2931.15'], + ['Consumer', 'Second Class', 'Iowa', '136.20000000000002'], + ['Consumer', 'Second Class', 'Kentucky', '8216.060000000001'], + ['Consumer', 'Second Class', 'Louisiana', '346.18000000000006'], + ['Consumer', 'Second Class', 'Maryland', '666.44'], + ['Consumer', 'Second Class', 'Massachusetts', '3980.6440000000002'], + ['Consumer', 'Second Class', 'Michigan', '8414.72'], + ['Consumer', 'Second Class', 'Minnesota', '123.18000000000002'], + ['Consumer', 'Second Class', 'Mississippi', '914.1899999999999'], + ['Consumer', 'Second Class', 'Missouri', '1578.5800000000004'], + ['Consumer', 'Second Class', 'Nebraska', '49.86'], + ['Consumer', 'Second Class', 'Nevada', '1855.84'], + ['Consumer', 'Second Class', 'New Hampshire', '14.82'], + ['Consumer', 'Second Class', 'New Jersey', '3559.1200000000003'], + ['Consumer', 'Second Class', 'New Mexico', '119.26599999999999'], + ['Consumer', 'Second Class', 'New York', '26224.13'], + ['Consumer', 'Second Class', 'North Carolina', '6254.836'], + ['Consumer', 'Second Class', 'North Dakota', '891.53'], + ['Consumer', 'Second Class', 'Ohio', '7937.081999999999'], + ['Consumer', 'Second Class', 'Oklahoma', '1289.01'], + ['Consumer', 'Second Class', 'Oregon', '852.666'], + ['Consumer', 'Second Class', 'Pennsylvania', '15460.034000000001'], + ['Consumer', 'Second Class', 'Rhode Island', '2239.126'], + ['Consumer', 'Second Class', 'South Carolina', '1396.0600000000002'], + ['Consumer', 'Second Class', 'South Dakota', '25.5'], + ['Consumer', 'Second Class', 'Tennessee', '3936.1020000000003'], + ['Consumer', 'Second Class', 'Texas', '16219.107600000001'], + ['Consumer', 'Second Class', 'Utah', '2481.55'], + ['Consumer', 'Second Class', 'Vermont', '715.2'], + ['Consumer', 'Second Class', 'Virginia', '14031.51'], + ['Consumer', 'Second Class', 'Washington', '15370.625999999993'], + ['Consumer', 'Second Class', 'Wisconsin', '56.82'], + ['Consumer', 'Standard Class', 'Alabama', '4018.0600000000004'], + ['Consumer', 'Standard Class', 'Arizona', '10667.125'], + ['Consumer', 'Standard Class', 'Arkansas', '6982.82'], + ['Consumer', 'Standard Class', 'California', '136836.90649999995'], + ['Consumer', 'Standard Class', 'Colorado', '10473.216999999999'], + ['Consumer', 'Standard Class', 'Connecticut', '2398.59'], + ['Consumer', 'Standard Class', 'Delaware', '15444.843'], + ['Consumer', 'Standard Class', 'District of Columbia', '2753.3399999999997'], + ['Consumer', 'Standard Class', 'Florida', '19104.23449999999'], + ['Consumer', 'Standard Class', 'Georgia', '19064.55'], + ['Consumer', 'Standard Class', 'Idaho', '9.584'], + ['Consumer', 'Standard Class', 'Illinois', '30267.118999999988'], + ['Consumer', 'Standard Class', 'Indiana', '9195.49'], + ['Consumer', 'Standard Class', 'Iowa', '1678.99'], + ['Consumer', 'Standard Class', 'Kansas', '451.31999999999994'], + ['Consumer', 'Standard Class', 'Kentucky', '10512.449999999999'], + ['Consumer', 'Standard Class', 'Louisiana', '2098.07'], + ['Consumer', 'Standard Class', 'Maryland', '5459.7119999999995'], + ['Consumer', 'Standard Class', 'Massachusetts', '2448.1260000000007'], + ['Consumer', 'Standard Class', 'Michigan', '22593.565'], + ['Consumer', 'Standard Class', 'Minnesota', '16278.559999999996'], + ['Consumer', 'Standard Class', 'Mississippi', '6260.360000000001'], + ['Consumer', 'Standard Class', 'Missouri', '2810.62'], + ['Consumer', 'Standard Class', 'Montana', '898.088'], + ['Consumer', 'Standard Class', 'Nebraska', '5033.0199999999995'], + ['Consumer', 'Standard Class', 'Nevada', '4728.574'], + ['Consumer', 'Standard Class', 'New Hampshire', '757.08'], + ['Consumer', 'Standard Class', 'New Jersey', '8569.702'], + ['Consumer', 'Standard Class', 'New Mexico', '2067.0580000000004'], + ['Consumer', 'Standard Class', 'New York', '111086.64'], + ['Consumer', 'Standard Class', 'North Carolina', '18494.022999999997'], + ['Consumer', 'Standard Class', 'Ohio', '22368.744'], + ['Consumer', 'Standard Class', 'Oklahoma', '7446.429999999999'], + ['Consumer', 'Standard Class', 'Oregon', '6860.51'], + ['Consumer', 'Standard Class', 'Pennsylvania', '37541.67900000001'], + ['Consumer', 'Standard Class', 'Rhode Island', '117.83000000000001'], + ['Consumer', 'Standard Class', 'South Carolina', '3631.96'], + ['Consumer', 'Standard Class', 'South Dakota', '20.23'], + ['Consumer', 'Standard Class', 'Tennessee', '12041.326999999997'], + ['Consumer', 'Standard Class', 'Texas', '65748.92639999998'], + ['Consumer', 'Standard Class', 'Utah', '4420.815999999999'], + ['Consumer', 'Standard Class', 'Vermont', '637.1800000000001'], + ['Consumer', 'Standard Class', 'Virginia', '14714.28'], + ['Consumer', 'Standard Class', 'Washington', '34798.62200000002'], + ['Consumer', 'Standard Class', 'Wisconsin', '10346.699999999999'], + ['Corporate', 'First Class', 'Alabama', '3964.53'], + ['Corporate', 'First Class', 'Arizona', '2697.2569999999996'], + ['Corporate', 'First Class', 'California', '30564.1925'], + ['Corporate', 'First Class', 'Colorado', '1675.3860000000004'], + ['Corporate', 'First Class', 'Connecticut', '992.2800000000001'], + ['Corporate', 'First Class', 'Delaware', '1325.836'], + ['Corporate', 'First Class', 'Florida', '5200.918'], + ['Corporate', 'First Class', 'Georgia', '1332.9800000000002'], + ['Corporate', 'First Class', 'Illinois', '2052.66'], + ['Corporate', 'First Class', 'Indiana', '1406.92'], + ['Corporate', 'First Class', 'Kansas', '186.95'], + ['Corporate', 'First Class', 'Kentucky', '247.95999999999998'], + ['Corporate', 'First Class', 'Louisiana', '87.28'], + ['Corporate', 'First Class', 'Maryland', '559.17'], + ['Corporate', 'First Class', 'Massachusetts', '96.82'], + ['Corporate', 'First Class', 'Michigan', '3438.254'], + ['Corporate', 'First Class', 'Minnesota', '63.44'], + ['Corporate', 'First Class', 'Montana', '39.9'], + ['Corporate', 'First Class', 'Nebraska', '148.67000000000002'], + ['Corporate', 'First Class', 'Nevada', '1946.6380000000001'], + ['Corporate', 'First Class', 'New Jersey', '80.63000000000001'], + ['Corporate', 'First Class', 'New Mexico', '159.99'], + ['Corporate', 'First Class', 'New York', '9371.088'], + ['Corporate', 'First Class', 'North Carolina', '4250.99'], + ['Corporate', 'First Class', 'Ohio', '2434.6470000000004'], + ['Corporate', 'First Class', 'Oklahoma', '69.93'], + ['Corporate', 'First Class', 'Oregon', '860.0020000000001'], + ['Corporate', 'First Class', 'Pennsylvania', '2534.687'], + ['Corporate', 'First Class', 'Rhode Island', '1485.82'], + ['Corporate', 'First Class', 'South Carolina', '127.95'], + ['Corporate', 'First Class', 'South Dakota', '152.64000000000001'], + ['Corporate', 'First Class', 'Tennessee', '1208.719'], + ['Corporate', 'First Class', 'Texas', '10573.135400000001'], + ['Corporate', 'First Class', 'Virginia', '4371.279999999999'], + ['Corporate', 'First Class', 'Washington', '9906.41'], + ['Corporate', 'First Class', 'Wisconsin', '242.51'], + ['Corporate', 'Same Day', 'Arizona', '269.408'], + ['Corporate', 'Same Day', 'California', '12279.131999999998'], + ['Corporate', 'Same Day', 'Colorado', '582.336'], + ['Corporate', 'Same Day', 'Connecticut', '1179.27'], + ['Corporate', 'Same Day', 'Florida', '111.904'], + ['Corporate', 'Same Day', 'Kansas', '33.29'], + ['Corporate', 'Same Day', 'Kentucky', '158.70999999999998'], + ['Corporate', 'Same Day', 'Maryland', '2107.14'], + ['Corporate', 'Same Day', 'Massachusetts', '870.1200000000001'], + ['Corporate', 'Same Day', 'Michigan', '583.4580000000001'], + ['Corporate', 'Same Day', 'Mississippi', '839.2500000000001'], + ['Corporate', 'Same Day', 'Montana', '8.288'], + ['Corporate', 'Same Day', 'Nebraska', '750.68'], + ['Corporate', 'Same Day', 'New Mexico', '255.85'], + ['Corporate', 'Same Day', 'New York', '8585.117'], + ['Corporate', 'Same Day', 'North Carolina', '8797.444'], + ['Corporate', 'Same Day', 'Ohio', '4856.055'], + ['Corporate', 'Same Day', 'Pennsylvania', '1887.6390000000001'], + ['Corporate', 'Same Day', 'Texas', '224.592'], + ['Corporate', 'Same Day', 'Washington', '598.1600000000001'], + ['Corporate', 'Same Day', 'Wisconsin', '143.48'], + ['Corporate', 'Second Class', 'Alabama', '3569.5499999999997'], + ['Corporate', 'Second Class', 'Arizona', '654.9920000000001'], + ['Corporate', 'Second Class', 'Arkansas', '1152.8700000000001'], + ['Corporate', 'Second Class', 'California', '26260.419'], + ['Corporate', 'Second Class', 'Colorado', '1117.4540000000002'], + ['Corporate', 'Second Class', 'Connecticut', '1230.4299999999998'], + ['Corporate', 'Second Class', 'Delaware', '566.46'], + ['Corporate', 'Second Class', 'Florida', '5870.644999999999'], + ['Corporate', 'Second Class', 'Georgia', '7481.499999999999'], + ['Corporate', 'Second Class', 'Illinois', '1976.983'], + ['Corporate', 'Second Class', 'Indiana', '5746.960000000001'], + ['Corporate', 'Second Class', 'Iowa', '455.39'], + ['Corporate', 'Second Class', 'Kentucky', '5436.43'], + ['Corporate', 'Second Class', 'Louisiana', '1287.26'], + ['Corporate', 'Second Class', 'Maryland', '3494.51'], + ['Corporate', 'Second Class', 'Massachusetts', '2409.89'], + ['Corporate', 'Second Class', 'Michigan', '5708.532999999999'], + ['Corporate', 'Second Class', 'Minnesota', '182.10000000000002'], + ['Corporate', 'Second Class', 'Mississippi', '117.81'], + ['Corporate', 'Second Class', 'Missouri', '4645.25'], + ['Corporate', 'Second Class', 'Nebraska', '143.72000000000003'], + ['Corporate', 'Second Class', 'Nevada', '264.88'], + ['Corporate', 'Second Class', 'New Hampshire', '751.9200000000001'], + ['Corporate', 'Second Class', 'New Jersey', '639.3'], + ['Corporate', 'Second Class', 'New York', '11741.372000000005'], + ['Corporate', 'Second Class', 'North Carolina', '1353.3139999999999'], + ['Corporate', 'Second Class', 'Ohio', '4428.638'], + ['Corporate', 'Second Class', 'Oregon', '1874.816'], + ['Corporate', 'Second Class', 'Pennsylvania', '8894.088000000002'], + ['Corporate', 'Second Class', 'Rhode Island', '3231.02'], + ['Corporate', 'Second Class', 'South Carolina', '1148.46'], + ['Corporate', 'Second Class', 'Tennessee', '908.192'], + ['Corporate', 'Second Class', 'Texas', '14216.470799999996'], + ['Corporate', 'Second Class', 'Utah', '1816.2280000000003'], + ['Corporate', 'Second Class', 'Virginia', '6917.200000000001'], + ['Corporate', 'Second Class', 'Washington', '5784.424'], + ['Corporate', 'Second Class', 'Wisconsin', '2646.56'], + ['Corporate', 'Standard Class', 'Alabama', '3435.3'], + ['Corporate', 'Standard Class', 'Arizona', '8114.664999999999'], + ['Corporate', 'Standard Class', 'Arkansas', '1310.91'], + ['Corporate', 'Standard Class', 'California', '78070.98299999988'], + ['Corporate', 'Standard Class', 'Colorado', '6570.736000000001'], + ['Corporate', 'Standard Class', 'Connecticut', '2313.71'], + ['Corporate', 'Standard Class', 'Delaware', '6419.36'], + ['Corporate', 'Standard Class', 'Florida', '11294.124499999998'], + ['Corporate', 'Standard Class', 'Georgia', '7167.769999999998'], + ['Corporate', 'Standard Class', 'Idaho', '2630.25'], + ['Corporate', 'Standard Class', 'Illinois', '11954.636999999999'], + ['Corporate', 'Standard Class', 'Indiana', '24634.859999999997'], + ['Corporate', 'Standard Class', 'Iowa', '456.05999999999995'], + ['Corporate', 'Standard Class', 'Kansas', '677.94'], + ['Corporate', 'Standard Class', 'Kentucky', '2084.7300000000005'], + ['Corporate', 'Standard Class', 'Louisiana', '507.80999999999995'], + ['Corporate', 'Standard Class', 'Maine', '1164.45'], + ['Corporate', 'Standard Class', 'Maryland', '5225.31'], + ['Corporate', 'Standard Class', 'Massachusetts', '6262.763999999999'], + ['Corporate', 'Standard Class', 'Michigan', '13661.308'], + ['Corporate', 'Standard Class', 'Minnesota', '2865.57'], + ['Corporate', 'Standard Class', 'Mississippi', '405.65999999999997'], + ['Corporate', 'Standard Class', 'Missouri', '5855.18'], + ['Corporate', 'Standard Class', 'Nebraska', '223.67000000000002'], + ['Corporate', 'Standard Class', 'Nevada', '2590.888'], + ['Corporate', 'Standard Class', 'New Hampshire', '216.98'], + ['Corporate', 'Standard Class', 'New Jersey', '17548.25999999999'], + ['Corporate', 'Standard Class', 'New Mexico', '853.936'], + ['Corporate', 'Standard Class', 'New York', '48253.73599999999'], + ['Corporate', 'Standard Class', 'North Carolina', '4254.9980000000005'], + ['Corporate', 'Standard Class', 'Ohio', '12490.632999999996'], + ['Corporate', 'Standard Class', 'Oklahoma', '2499.8199999999997'], + ['Corporate', 'Standard Class', 'Oregon', '3829.152'], + ['Corporate', 'Standard Class', 'Pennsylvania', '17813.646999999994'], + ['Corporate', 'Standard Class', 'Rhode Island', '664.3100000000001'], + ['Corporate', 'Standard Class', 'South Carolina', '1639.63'], + ['Corporate', 'Standard Class', 'South Dakota', '1117.19'], + ['Corporate', 'Standard Class', 'Tennessee', '7628.854'], + ['Corporate', 'Standard Class', 'Texas', '28893.921599999987'], + ['Corporate', 'Standard Class', 'Utah', '140.38600000000002'], + ['Corporate', 'Standard Class', 'Vermont', '6282.24'], + ['Corporate', 'Standard Class', 'Virginia', '16212.999999999998'], + ['Corporate', 'Standard Class', 'Washington', '23438.11600000001'], + ['Corporate', 'Standard Class', 'Wisconsin', '9363.08'], + ['Home Office', 'First Class', 'Arizona', '1123.625'], + ['Home Office', 'First Class', 'California', '20794.533'], + ['Home Office', 'First Class', 'Colorado', '1553.953'], + ['Home Office', 'First Class', 'Connecticut', '84.45'], + ['Home Office', 'First Class', 'Delaware', '807.54'], + ['Home Office', 'First Class', 'Florida', '629.256'], + ['Home Office', 'First Class', 'Georgia', '893.5799999999999'], + ['Home Office', 'First Class', 'Illinois', '2736.511'], + ['Home Office', 'First Class', 'Indiana', '281.03000000000003'], + ['Home Office', 'First Class', 'Kentucky', '204.98000000000002'], + ['Home Office', 'First Class', 'Massachusetts', '177.17000000000002'], + ['Home Office', 'First Class', 'Michigan', '4816.782'], + ['Home Office', 'First Class', 'Minnesota', '318.04999999999995'], + ['Home Office', 'First Class', 'Missouri', '8.76'], + ['Home Office', 'First Class', 'Nebraska', '563.94'], + ['Home Office', 'First Class', 'Nevada', '14.56'], + ['Home Office', 'First Class', 'New York', '28225.492'], + ['Home Office', 'First Class', 'Ohio', '2902.239'], + ['Home Office', 'First Class', 'Oklahoma', '821.94'], + ['Home Office', 'First Class', 'Oregon', '1016.562'], + ['Home Office', 'First Class', 'Pennsylvania', '2702.112'], + ['Home Office', 'First Class', 'Rhode Island', '2545.12'], + ['Home Office', 'First Class', 'Tennessee', '2547.6400000000003'], + ['Home Office', 'First Class', 'Texas', '3227.705'], + ['Home Office', 'First Class', 'Utah', '1548.3500000000001'], + ['Home Office', 'First Class', 'Virginia', '3462.3299999999995'], + ['Home Office', 'First Class', 'Washington', '1967.6680000000003'], + ['Home Office', 'First Class', 'Wisconsin', '425.11'], + ['Home Office', 'Same Day', 'Arizona', '1276.072'], + ['Home Office', 'Same Day', 'Arkansas', '117.62'], + ['Home Office', 'Same Day', 'California', '5536.159000000001'], + ['Home Office', 'Same Day', 'Colorado', '230.088'], + ['Home Office', 'Same Day', 'Florida', '255.968'], + ['Home Office', 'Same Day', 'Georgia', '2334.8'], + ['Home Office', 'Same Day', 'Illinois', '2170.2999999999997'], + ['Home Office', 'Same Day', 'Indiana', '10.74'], + ['Home Office', 'Same Day', 'Massachusetts', '85.3'], + ['Home Office', 'Same Day', 'Michigan', '547.97'], + ['Home Office', 'Same Day', 'Nebraska', '50.42'], + ['Home Office', 'Same Day', 'Nevada', '475.944'], + ['Home Office', 'Same Day', 'New York', '1446.1639999999995'], + ['Home Office', 'Same Day', 'North Carolina', '7.968'], + ['Home Office', 'Same Day', 'Ohio', '1377.8020000000004'], + ['Home Office', 'Same Day', 'Oklahoma', '1702.0300000000002'], + ['Home Office', 'Same Day', 'Pennsylvania', '41.081999999999994'], + ['Home Office', 'Same Day', 'Texas', '76.112'], + ['Home Office', 'Same Day', 'Virginia', '261.1'], + ['Home Office', 'Same Day', 'Washington', '4641.803999999998'], + ['Home Office', 'Second Class', 'Alabama', '819.2800000000001'], + ['Home Office', 'Second Class', 'Arizona', '284.99199999999996'], + ['Home Office', 'Second Class', 'California', '13714.855'], + ['Home Office', 'Second Class', 'Delaware', '299.52'], + ['Home Office', 'Second Class', 'District of Columbia', '111.67999999999999'], + ['Home Office', 'Second Class', 'Florida', '1316.612'], + ['Home Office', 'Second Class', 'Georgia', '837.28'], + ['Home Office', 'Second Class', 'Illinois', '7910.616999999998'], + ['Home Office', 'Second Class', 'Indiana', '999.98'], + ['Home Office', 'Second Class', 'Iowa', '38.58'], + ['Home Office', 'Second Class', 'Kansas', '149.2'], + ['Home Office', 'Second Class', 'Kentucky', '840.29'], + ['Home Office', 'Second Class', 'Louisiana', '1160.42'], + ['Home Office', 'Second Class', 'Massachusetts', '1582.4080000000001'], + ['Home Office', 'Second Class', 'Michigan', '3012.4500000000003'], + ['Home Office', 'Second Class', 'Minnesota', '3785.9100000000003'], + ['Home Office', 'Second Class', 'Mississippi', '684.7800000000001'], + ['Home Office', 'Second Class', 'Missouri', '3297.04'], + ['Home Office', 'Second Class', 'Nevada', '4685.076'], + ['Home Office', 'Second Class', 'New Hampshire', '3517.634'], + ['Home Office', 'Second Class', 'New Jersey', '236.77'], + ['Home Office', 'Second Class', 'New Mexico', '780.086'], + ['Home Office', 'Second Class', 'New York', '5778.049000000003'], + ['Home Office', 'Second Class', 'North Carolina', '2831.09'], + ['Home Office', 'Second Class', 'Ohio', '1488.8550000000005'], + ['Home Office', 'Second Class', 'Pennsylvania', '1930.424'], + ['Home Office', 'Second Class', 'Rhode Island', '1040.95'], + ['Home Office', 'Second Class', 'Tennessee', '159.035'], + ['Home Office', 'Second Class', 'Texas', '6887.694000000002'], + ['Home Office', 'Second Class', 'Utah', '166.44'], + ['Home Office', 'Second Class', 'Virginia', '142.34'], + ['Home Office', 'Second Class', 'Washington', '8157.824'], + ['Home Office', 'Second Class', 'Wisconsin', '2920.42'], + ['Home Office', 'Standard Class', 'Alabama', '184.44'], + ['Home Office', 'Standard Class', 'Arizona', '4436.568'], + ['Home Office', 'Standard Class', 'Arkansas', '294.72'], + ['Home Office', 'Standard Class', 'California', '40831.27799999999'], + ['Home Office', 'Standard Class', 'Colorado', '4583.673000000001'], + ['Home Office', 'Standard Class', 'Connecticut', '1650.7399999999998'], + ['Home Office', 'Standard Class', 'Delaware', '1070.59'], + ['Home Office', 'Standard Class', 'Florida', '32093.08449999999'], + ['Home Office', 'Standard Class', 'Georgia', '4600.81'], + ['Home Office', 'Standard Class', 'Idaho', '307.74'], + ['Home Office', 'Standard Class', 'Illinois', '6182.1979999999985'], + ['Home Office', 'Standard Class', 'Indiana', '5487.910000000002'], + ['Home Office', 'Standard Class', 'Iowa', '1529.6599999999999'], + ['Home Office', 'Standard Class', 'Kansas', '1169.7499999999998'], + ['Home Office', 'Standard Class', 'Kentucky', '7187.929999999999'], + ['Home Office', 'Standard Class', 'Maine', '106.08000000000001'], + ['Home Office', 'Standard Class', 'Maryland', '2265.38'], + ['Home Office', 'Standard Class', 'Massachusetts', '5998.422000000001'], + ['Home Office', 'Standard Class', 'Michigan', '7790.9479999999985'], + ['Home Office', 'Standard Class', 'Minnesota', '3412.8999999999996'], + ['Home Office', 'Standard Class', 'Mississippi', '1035.26'], + ['Home Office', 'Standard Class', 'Missouri', '3248.0'], + ['Home Office', 'Standard Class', 'Montana', '4643.075999999998'], + ['Home Office', 'Standard Class', 'Nebraska', '322.58000000000004'], + ['Home Office', 'Standard Class', 'Nevada', '166.70200000000003'], + ['Home Office', 'Standard Class', 'New Hampshire', '1897.35'], + ['Home Office', 'Standard Class', 'New Jersey', '3925.3700000000003'], + ['Home Office', 'Standard Class', 'New Mexico', '547.3359999999999'], + ['Home Office', 'Standard Class', 'New York', '22266.21799999999'], + ['Home Office', 'Standard Class', 'North Carolina', '4110.134000000001'], + ['Home Office', 'Standard Class', 'North Dakota', '28.38'], + ['Home Office', 'Standard Class', 'Ohio', '5085.243000000001'], + ['Home Office', 'Standard Class', 'Oklahoma', '3027.9000000000005'], + ['Home Office', 'Standard Class', 'Oregon', '956.6850000000001'], + ['Home Office', 'Standard Class', 'Pennsylvania', '13808.941999999997'], + ['Home Office', 'Standard Class', 'Rhode Island', '11177.399999999998'], + ['Home Office', 'Standard Class', 'South Carolina', '25.92'], + ['Home Office', 'Standard Class', 'Tennessee', '1630.494'], + ['Home Office', 'Standard Class', 'Texas', '10112.037'], + ['Home Office', 'Standard Class', 'Utah', '396.648'], + ['Home Office', 'Standard Class', 'Vermont', '1294.75'], + ['Home Office', 'Standard Class', 'Virginia', '3585.84'], + ['Home Office', 'Standard Class', 'Washington', '10280.343999999996'], + ['Home Office', 'Standard Class', 'West Virginia', '536.48'], + ['Home Office', 'Standard Class', 'Wisconsin', '2141.0899999999997'], + ['Home Office', 'Standard Class', 'Wyoming', '1603.136'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Segment', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'State', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Sales (1)', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase12 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: '6d2a3c33-9289-4152-bede-3168f95432dc', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Segment', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '3ca5370778884f13', + description: '', + id: 'inserted-1567172520693', + datasetId: 'v41c56fh1tnks', + }, + { + hidden: false, + source: '24d104ff10bb4c05', + aggregation_locked: false, + has_auto_aggregation: null, + initial_data_type: null, + cast: 'string', + managed_by: 'user', + calc_mode: 'direct', + formula: '', + type: 'DIMENSION', + description: '', + data_type: 'string', + avatar_id: 'd76f325d-1b0a-4647-b396-cd030214557f', + guid: 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a', + autoaggregated: false, + title: 'State', + valid: true, + aggregation: 'none', + lock_aggregation: false, + virtual: false, + datasetId: 'v41c56fh1tnks', + id: 'inserted-1580120492586', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'inserted-1567172506410', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'MEASURE', + hidden: false, + data_type: 'float', + cast: 'float', + guid: '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Sales (1)', + valid: true, + aggregation_locked: false, + aggregation: 'sum', + source: '9cfffd3199c048ad', + description: '', + local: true, + id: 'inserted-1567172486635', + datasetId: 'v41c56fh1tnks', + }, + ], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['Consumer', 'Alabama', 'First Class', '8.96'], + ['Consumer', 'Alabama', 'Same Day', '29.0'], + ['Consumer', 'Alabama', 'Second Class', '3481.5200000000004'], + ['Consumer', 'Alabama', 'Standard Class', '4018.0600000000004'], + ['Consumer', 'Arizona', 'First Class', '1988.721'], + ['Consumer', 'Arizona', 'Same Day', '1065.666'], + ['Consumer', 'Arizona', 'Second Class', '2702.91'], + ['Consumer', 'Arizona', 'Standard Class', '10667.125'], + ['Consumer', 'Arkansas', 'First Class', '1465.7600000000004'], + ['Consumer', 'Arkansas', 'Second Class', '353.43'], + ['Consumer', 'Arkansas', 'Standard Class', '6982.82'], + ['Consumer', 'California', 'First Class', '28117.52899999998'], + ['Consumer', 'California', 'Same Day', '11963.938999999998'], + ['Consumer', 'California', 'Second Class', '52717.70549999998'], + ['Consumer', 'California', 'Standard Class', '136836.90649999995'], + ['Consumer', 'Colorado', 'First Class', '1691.117'], + ['Consumer', 'Colorado', 'Same Day', '484.196'], + ['Consumer', 'Colorado', 'Second Class', '3145.9620000000004'], + ['Consumer', 'Colorado', 'Standard Class', '10473.216999999999'], + ['Consumer', 'Connecticut', 'First Class', '866.2900000000001'], + ['Consumer', 'Connecticut', 'Same Day', '714.2270000000001'], + ['Consumer', 'Connecticut', 'Second Class', '1954.3700000000001'], + ['Consumer', 'Connecticut', 'Standard Class', '2398.59'], + ['Consumer', 'Delaware', 'First Class', '933.7600000000001'], + ['Consumer', 'Delaware', 'Same Day', '162.51999999999998'], + ['Consumer', 'Delaware', 'Second Class', '420.64000000000004'], + ['Consumer', 'Delaware', 'Standard Class', '15444.843'], + ['Consumer', 'District of Columbia', 'Standard Class', '2753.3399999999997'], + ['Consumer', 'Florida', 'First Class', '2187.5049999999997'], + ['Consumer', 'Florida', 'Same Day', '3550.7500000000005'], + ['Consumer', 'Florida', 'Second Class', '7858.7065'], + ['Consumer', 'Florida', 'Standard Class', '19104.23449999999'], + ['Consumer', 'Georgia', 'First Class', '2236.54'], + ['Consumer', 'Georgia', 'Same Day', '210.36999999999998'], + ['Consumer', 'Georgia', 'Second Class', '2935.66'], + ['Consumer', 'Georgia', 'Standard Class', '19064.55'], + ['Consumer', 'Idaho', 'First Class', '904.496'], + ['Consumer', 'Idaho', 'Second Class', '530.4159999999999'], + ['Consumer', 'Idaho', 'Standard Class', '9.584'], + ['Consumer', 'Illinois', 'First Class', '6315.234000000001'], + ['Consumer', 'Illinois', 'Same Day', '1489.352'], + ['Consumer', 'Illinois', 'Second Class', '7110.49'], + ['Consumer', 'Illinois', 'Standard Class', '30267.118999999988'], + ['Consumer', 'Indiana', 'First Class', '1419.14'], + ['Consumer', 'Indiana', 'Same Day', '1441.18'], + ['Consumer', 'Indiana', 'Second Class', '2931.15'], + ['Consumer', 'Indiana', 'Standard Class', '9195.49'], + ['Consumer', 'Iowa', 'First Class', '278.4'], + ['Consumer', 'Iowa', 'Same Day', '6.48'], + ['Consumer', 'Iowa', 'Second Class', '136.20000000000002'], + ['Consumer', 'Iowa', 'Standard Class', '1678.99'], + ['Consumer', 'Kansas', 'First Class', '245.85999999999999'], + ['Consumer', 'Kansas', 'Standard Class', '451.31999999999994'], + ['Consumer', 'Kentucky', 'First Class', '1691.4699999999998'], + ['Consumer', 'Kentucky', 'Same Day', '10.74'], + ['Consumer', 'Kentucky', 'Second Class', '8216.060000000001'], + ['Consumer', 'Kentucky', 'Standard Class', '10512.449999999999'], + ['Consumer', 'Louisiana', 'First Class', '3640.69'], + ['Consumer', 'Louisiana', 'Same Day', '89.32000000000001'], + ['Consumer', 'Louisiana', 'Second Class', '346.18000000000006'], + ['Consumer', 'Louisiana', 'Standard Class', '2098.07'], + ['Consumer', 'Maryland', 'First Class', '1854.511'], + ['Consumer', 'Maryland', 'Same Day', '2073.3500000000004'], + ['Consumer', 'Maryland', 'Second Class', '666.44'], + ['Consumer', 'Maryland', 'Standard Class', '5459.7119999999995'], + ['Consumer', 'Massachusetts', 'First Class', '4722.7699999999995'], + ['Consumer', 'Massachusetts', 'Second Class', '3980.6440000000002'], + ['Consumer', 'Massachusetts', 'Standard Class', '2448.1260000000007'], + ['Consumer', 'Michigan', 'First Class', '2679.01'], + ['Consumer', 'Michigan', 'Same Day', '3022.616'], + ['Consumer', 'Michigan', 'Second Class', '8414.72'], + ['Consumer', 'Michigan', 'Standard Class', '22593.565'], + ['Consumer', 'Minnesota', 'First Class', '2457.73'], + ['Consumer', 'Minnesota', 'Same Day', '375.71'], + ['Consumer', 'Minnesota', 'Second Class', '123.18000000000002'], + ['Consumer', 'Minnesota', 'Standard Class', '16278.559999999996'], + ['Consumer', 'Mississippi', 'First Class', '514.0300000000001'], + ['Consumer', 'Mississippi', 'Second Class', '914.1899999999999'], + ['Consumer', 'Mississippi', 'Standard Class', '6260.360000000001'], + ['Consumer', 'Missouri', 'First Class', '512.9'], + ['Consumer', 'Missouri', 'Same Day', '248.82'], + ['Consumer', 'Missouri', 'Second Class', '1578.5800000000004'], + ['Consumer', 'Missouri', 'Standard Class', '2810.62'], + ['Consumer', 'Montana', 'Standard Class', '898.088'], + ['Consumer', 'Nebraska', 'First Class', '178.37'], + ['Consumer', 'Nebraska', 'Second Class', '49.86'], + ['Consumer', 'Nebraska', 'Standard Class', '5033.0199999999995'], + ['Consumer', 'Nevada', 'Second Class', '1855.84'], + ['Consumer', 'Nevada', 'Standard Class', '4728.574'], + ['Consumer', 'New Hampshire', 'First Class', '136.74'], + ['Consumer', 'New Hampshire', 'Second Class', '14.82'], + ['Consumer', 'New Hampshire', 'Standard Class', '757.08'], + ['Consumer', 'New Jersey', 'First Class', '1134.21'], + ['Consumer', 'New Jersey', 'Same Day', '70.95'], + ['Consumer', 'New Jersey', 'Second Class', '3559.1200000000003'], + ['Consumer', 'New Jersey', 'Standard Class', '8569.702'], + ['Consumer', 'New Mexico', 'Second Class', '119.26599999999999'], + ['Consumer', 'New Mexico', 'Standard Class', '2067.0580000000004'], + ['Consumer', 'New York', 'First Class', '23376.207999999988'], + ['Consumer', 'New York', 'Same Day', '14522.056999999999'], + ['Consumer', 'New York', 'Second Class', '26224.13'], + ['Consumer', 'New York', 'Standard Class', '111086.64'], + ['Consumer', 'North Carolina', 'First Class', '1796.6820000000002'], + ['Consumer', 'North Carolina', 'Same Day', '3451.6850000000004'], + ['Consumer', 'North Carolina', 'Second Class', '6254.836'], + ['Consumer', 'North Carolina', 'Standard Class', '18494.022999999997'], + ['Consumer', 'North Dakota', 'Second Class', '891.53'], + ['Consumer', 'Ohio', 'First Class', '10279.053000000005'], + ['Consumer', 'Ohio', 'Same Day', '2609.145000000001'], + ['Consumer', 'Ohio', 'Second Class', '7937.081999999999'], + ['Consumer', 'Ohio', 'Standard Class', '22368.744'], + ['Consumer', 'Oklahoma', 'First Class', '889.8'], + ['Consumer', 'Oklahoma', 'Same Day', '1936.53'], + ['Consumer', 'Oklahoma', 'Second Class', '1289.01'], + ['Consumer', 'Oklahoma', 'Standard Class', '7446.429999999999'], + ['Consumer', 'Oregon', 'First Class', '1180.757'], + ['Consumer', 'Oregon', 'Second Class', '852.666'], + ['Consumer', 'Oregon', 'Standard Class', '6860.51'], + ['Consumer', 'Pennsylvania', 'First Class', '13832.029999999999'], + ['Consumer', 'Pennsylvania', 'Same Day', '65.55'], + ['Consumer', 'Pennsylvania', 'Second Class', '15460.034000000001'], + ['Consumer', 'Pennsylvania', 'Standard Class', '37541.67900000001'], + ['Consumer', 'Rhode Island', 'First Class', '126.38'], + ['Consumer', 'Rhode Island', 'Second Class', '2239.126'], + ['Consumer', 'Rhode Island', 'Standard Class', '117.83000000000001'], + ['Consumer', 'South Carolina', 'First Class', '59.480000000000004'], + ['Consumer', 'South Carolina', 'Same Day', '452.25'], + ['Consumer', 'South Carolina', 'Second Class', '1396.0600000000002'], + ['Consumer', 'South Carolina', 'Standard Class', '3631.96'], + ['Consumer', 'South Dakota', 'Second Class', '25.5'], + ['Consumer', 'South Dakota', 'Standard Class', '20.23'], + ['Consumer', 'Tennessee', 'First Class', '520.946'], + ['Consumer', 'Tennessee', 'Same Day', '80.564'], + ['Consumer', 'Tennessee', 'Second Class', '3936.1020000000003'], + ['Consumer', 'Tennessee', 'Standard Class', '12041.326999999997'], + ['Consumer', 'Texas', 'First Class', '8441.824000000002'], + ['Consumer', 'Texas', 'Same Day', '5566.520000000002'], + ['Consumer', 'Texas', 'Second Class', '16219.107600000001'], + ['Consumer', 'Texas', 'Standard Class', '65748.92639999998'], + ['Consumer', 'Utah', 'First Class', '151.72'], + ['Consumer', 'Utah', 'Same Day', '97.91799999999999'], + ['Consumer', 'Utah', 'Second Class', '2481.55'], + ['Consumer', 'Utah', 'Standard Class', '4420.815999999999'], + ['Consumer', 'Vermont', 'Second Class', '715.2'], + ['Consumer', 'Vermont', 'Standard Class', '637.1800000000001'], + ['Consumer', 'Virginia', 'First Class', '6680.110000000001'], + ['Consumer', 'Virginia', 'Same Day', '257.73'], + ['Consumer', 'Virginia', 'Second Class', '14031.51'], + ['Consumer', 'Virginia', 'Standard Class', '14714.28'], + ['Consumer', 'Washington', 'First Class', '19858.521999999994'], + ['Consumer', 'Washington', 'Same Day', '3838.7500000000005'], + ['Consumer', 'Washington', 'Second Class', '15370.625999999993'], + ['Consumer', 'Washington', 'Standard Class', '34798.62200000002'], + ['Consumer', 'West Virginia', 'Same Day', '673.344'], + ['Consumer', 'Wisconsin', 'First Class', '3793.71'], + ['Consumer', 'Wisconsin', 'Same Day', '35.129999999999995'], + ['Consumer', 'Wisconsin', 'Second Class', '56.82'], + ['Consumer', 'Wisconsin', 'Standard Class', '10346.699999999999'], + ['Corporate', 'Alabama', 'First Class', '3964.53'], + ['Corporate', 'Alabama', 'Second Class', '3569.5499999999997'], + ['Corporate', 'Alabama', 'Standard Class', '3435.3'], + ['Corporate', 'Arizona', 'First Class', '2697.2569999999996'], + ['Corporate', 'Arizona', 'Same Day', '269.408'], + ['Corporate', 'Arizona', 'Second Class', '654.9920000000001'], + ['Corporate', 'Arizona', 'Standard Class', '8114.664999999999'], + ['Corporate', 'Arkansas', 'Second Class', '1152.8700000000001'], + ['Corporate', 'Arkansas', 'Standard Class', '1310.91'], + ['Corporate', 'California', 'First Class', '30564.1925'], + ['Corporate', 'California', 'Same Day', '12279.131999999998'], + ['Corporate', 'California', 'Second Class', '26260.419'], + ['Corporate', 'California', 'Standard Class', '78070.98299999988'], + ['Corporate', 'Colorado', 'First Class', '1675.3860000000004'], + ['Corporate', 'Colorado', 'Same Day', '582.336'], + ['Corporate', 'Colorado', 'Second Class', '1117.4540000000002'], + ['Corporate', 'Colorado', 'Standard Class', '6570.736000000001'], + ['Corporate', 'Connecticut', 'First Class', '992.2800000000001'], + ['Corporate', 'Connecticut', 'Same Day', '1179.27'], + ['Corporate', 'Connecticut', 'Second Class', '1230.4299999999998'], + ['Corporate', 'Connecticut', 'Standard Class', '2313.71'], + ['Corporate', 'Delaware', 'First Class', '1325.836'], + ['Corporate', 'Delaware', 'Second Class', '566.46'], + ['Corporate', 'Delaware', 'Standard Class', '6419.36'], + ['Corporate', 'Florida', 'First Class', '5200.918'], + ['Corporate', 'Florida', 'Same Day', '111.904'], + ['Corporate', 'Florida', 'Second Class', '5870.644999999999'], + ['Corporate', 'Florida', 'Standard Class', '11294.124499999998'], + ['Corporate', 'Georgia', 'First Class', '1332.9800000000002'], + ['Corporate', 'Georgia', 'Second Class', '7481.499999999999'], + ['Corporate', 'Georgia', 'Standard Class', '7167.769999999998'], + ['Corporate', 'Idaho', 'Standard Class', '2630.25'], + ['Corporate', 'Illinois', 'First Class', '2052.66'], + ['Corporate', 'Illinois', 'Second Class', '1976.983'], + ['Corporate', 'Illinois', 'Standard Class', '11954.636999999999'], + ['Corporate', 'Indiana', 'First Class', '1406.92'], + ['Corporate', 'Indiana', 'Second Class', '5746.960000000001'], + ['Corporate', 'Indiana', 'Standard Class', '24634.859999999997'], + ['Corporate', 'Iowa', 'Second Class', '455.39'], + ['Corporate', 'Iowa', 'Standard Class', '456.05999999999995'], + ['Corporate', 'Kansas', 'First Class', '186.95'], + ['Corporate', 'Kansas', 'Same Day', '33.29'], + ['Corporate', 'Kansas', 'Standard Class', '677.94'], + ['Corporate', 'Kentucky', 'First Class', '247.95999999999998'], + ['Corporate', 'Kentucky', 'Same Day', '158.70999999999998'], + ['Corporate', 'Kentucky', 'Second Class', '5436.43'], + ['Corporate', 'Kentucky', 'Standard Class', '2084.7300000000005'], + ['Corporate', 'Louisiana', 'First Class', '87.28'], + ['Corporate', 'Louisiana', 'Second Class', '1287.26'], + ['Corporate', 'Louisiana', 'Standard Class', '507.80999999999995'], + ['Corporate', 'Maine', 'Standard Class', '1164.45'], + ['Corporate', 'Maryland', 'First Class', '559.17'], + ['Corporate', 'Maryland', 'Same Day', '2107.14'], + ['Corporate', 'Maryland', 'Second Class', '3494.51'], + ['Corporate', 'Maryland', 'Standard Class', '5225.31'], + ['Corporate', 'Massachusetts', 'First Class', '96.82'], + ['Corporate', 'Massachusetts', 'Same Day', '870.1200000000001'], + ['Corporate', 'Massachusetts', 'Second Class', '2409.89'], + ['Corporate', 'Massachusetts', 'Standard Class', '6262.763999999999'], + ['Corporate', 'Michigan', 'First Class', '3438.254'], + ['Corporate', 'Michigan', 'Same Day', '583.4580000000001'], + ['Corporate', 'Michigan', 'Second Class', '5708.532999999999'], + ['Corporate', 'Michigan', 'Standard Class', '13661.308'], + ['Corporate', 'Minnesota', 'First Class', '63.44'], + ['Corporate', 'Minnesota', 'Second Class', '182.10000000000002'], + ['Corporate', 'Minnesota', 'Standard Class', '2865.57'], + ['Corporate', 'Mississippi', 'Same Day', '839.2500000000001'], + ['Corporate', 'Mississippi', 'Second Class', '117.81'], + ['Corporate', 'Mississippi', 'Standard Class', '405.65999999999997'], + ['Corporate', 'Missouri', 'Second Class', '4645.25'], + ['Corporate', 'Missouri', 'Standard Class', '5855.18'], + ['Corporate', 'Montana', 'First Class', '39.9'], + ['Corporate', 'Montana', 'Same Day', '8.288'], + ['Corporate', 'Nebraska', 'First Class', '148.67000000000002'], + ['Corporate', 'Nebraska', 'Same Day', '750.68'], + ['Corporate', 'Nebraska', 'Second Class', '143.72000000000003'], + ['Corporate', 'Nebraska', 'Standard Class', '223.67000000000002'], + ['Corporate', 'Nevada', 'First Class', '1946.6380000000001'], + ['Corporate', 'Nevada', 'Second Class', '264.88'], + ['Corporate', 'Nevada', 'Standard Class', '2590.888'], + ['Corporate', 'New Hampshire', 'Second Class', '751.9200000000001'], + ['Corporate', 'New Hampshire', 'Standard Class', '216.98'], + ['Corporate', 'New Jersey', 'First Class', '80.63000000000001'], + ['Corporate', 'New Jersey', 'Second Class', '639.3'], + ['Corporate', 'New Jersey', 'Standard Class', '17548.25999999999'], + ['Corporate', 'New Mexico', 'First Class', '159.99'], + ['Corporate', 'New Mexico', 'Same Day', '255.85'], + ['Corporate', 'New Mexico', 'Standard Class', '853.936'], + ['Corporate', 'New York', 'First Class', '9371.088'], + ['Corporate', 'New York', 'Same Day', '8585.117'], + ['Corporate', 'New York', 'Second Class', '11741.372000000005'], + ['Corporate', 'New York', 'Standard Class', '48253.73599999999'], + ['Corporate', 'North Carolina', 'First Class', '4250.99'], + ['Corporate', 'North Carolina', 'Same Day', '8797.444'], + ['Corporate', 'North Carolina', 'Second Class', '1353.3139999999999'], + ['Corporate', 'North Carolina', 'Standard Class', '4254.9980000000005'], + ['Corporate', 'Ohio', 'First Class', '2434.6470000000004'], + ['Corporate', 'Ohio', 'Same Day', '4856.055'], + ['Corporate', 'Ohio', 'Second Class', '4428.638'], + ['Corporate', 'Ohio', 'Standard Class', '12490.632999999996'], + ['Corporate', 'Oklahoma', 'First Class', '69.93'], + ['Corporate', 'Oklahoma', 'Standard Class', '2499.8199999999997'], + ['Corporate', 'Oregon', 'First Class', '860.0020000000001'], + ['Corporate', 'Oregon', 'Second Class', '1874.816'], + ['Corporate', 'Oregon', 'Standard Class', '3829.152'], + ['Corporate', 'Pennsylvania', 'First Class', '2534.687'], + ['Corporate', 'Pennsylvania', 'Same Day', '1887.6390000000001'], + ['Corporate', 'Pennsylvania', 'Second Class', '8894.088000000002'], + ['Corporate', 'Pennsylvania', 'Standard Class', '17813.646999999994'], + ['Corporate', 'Rhode Island', 'First Class', '1485.82'], + ['Corporate', 'Rhode Island', 'Second Class', '3231.02'], + ['Corporate', 'Rhode Island', 'Standard Class', '664.3100000000001'], + ['Corporate', 'South Carolina', 'First Class', '127.95'], + ['Corporate', 'South Carolina', 'Second Class', '1148.46'], + ['Corporate', 'South Carolina', 'Standard Class', '1639.63'], + ['Corporate', 'South Dakota', 'First Class', '152.64000000000001'], + ['Corporate', 'South Dakota', 'Standard Class', '1117.19'], + ['Corporate', 'Tennessee', 'First Class', '1208.719'], + ['Corporate', 'Tennessee', 'Second Class', '908.192'], + ['Corporate', 'Tennessee', 'Standard Class', '7628.854'], + ['Corporate', 'Texas', 'First Class', '10573.135400000001'], + ['Corporate', 'Texas', 'Same Day', '224.592'], + ['Corporate', 'Texas', 'Second Class', '14216.470799999996'], + ['Corporate', 'Texas', 'Standard Class', '28893.921599999987'], + ['Corporate', 'Utah', 'Second Class', '1816.2280000000003'], + ['Corporate', 'Utah', 'Standard Class', '140.38600000000002'], + ['Corporate', 'Vermont', 'Standard Class', '6282.24'], + ['Corporate', 'Virginia', 'First Class', '4371.279999999999'], + ['Corporate', 'Virginia', 'Second Class', '6917.200000000001'], + ['Corporate', 'Virginia', 'Standard Class', '16212.999999999998'], + ['Corporate', 'Washington', 'First Class', '9906.41'], + ['Corporate', 'Washington', 'Same Day', '598.1600000000001'], + ['Corporate', 'Washington', 'Second Class', '5784.424'], + ['Corporate', 'Washington', 'Standard Class', '23438.11600000001'], + ['Corporate', 'Wisconsin', 'First Class', '242.51'], + ['Corporate', 'Wisconsin', 'Same Day', '143.48'], + ['Corporate', 'Wisconsin', 'Second Class', '2646.56'], + ['Corporate', 'Wisconsin', 'Standard Class', '9363.08'], + ['Home Office', 'Alabama', 'Second Class', '819.2800000000001'], + ['Home Office', 'Alabama', 'Standard Class', '184.44'], + ['Home Office', 'Arizona', 'First Class', '1123.625'], + ['Home Office', 'Arizona', 'Same Day', '1276.072'], + ['Home Office', 'Arizona', 'Second Class', '284.99199999999996'], + ['Home Office', 'Arizona', 'Standard Class', '4436.568'], + ['Home Office', 'Arkansas', 'Same Day', '117.62'], + ['Home Office', 'Arkansas', 'Standard Class', '294.72'], + ['Home Office', 'California', 'First Class', '20794.533'], + ['Home Office', 'California', 'Same Day', '5536.159000000001'], + ['Home Office', 'California', 'Second Class', '13714.855'], + ['Home Office', 'California', 'Standard Class', '40831.27799999999'], + ['Home Office', 'Colorado', 'First Class', '1553.953'], + ['Home Office', 'Colorado', 'Same Day', '230.088'], + ['Home Office', 'Colorado', 'Standard Class', '4583.673000000001'], + ['Home Office', 'Connecticut', 'First Class', '84.45'], + ['Home Office', 'Connecticut', 'Standard Class', '1650.7399999999998'], + ['Home Office', 'Delaware', 'First Class', '807.54'], + ['Home Office', 'Delaware', 'Second Class', '299.52'], + ['Home Office', 'Delaware', 'Standard Class', '1070.59'], + ['Home Office', 'District of Columbia', 'Second Class', '111.67999999999999'], + ['Home Office', 'Florida', 'First Class', '629.256'], + ['Home Office', 'Florida', 'Same Day', '255.968'], + ['Home Office', 'Florida', 'Second Class', '1316.612'], + ['Home Office', 'Florida', 'Standard Class', '32093.08449999999'], + ['Home Office', 'Georgia', 'First Class', '893.5799999999999'], + ['Home Office', 'Georgia', 'Same Day', '2334.8'], + ['Home Office', 'Georgia', 'Second Class', '837.28'], + ['Home Office', 'Georgia', 'Standard Class', '4600.81'], + ['Home Office', 'Idaho', 'Standard Class', '307.74'], + ['Home Office', 'Illinois', 'First Class', '2736.511'], + ['Home Office', 'Illinois', 'Same Day', '2170.2999999999997'], + ['Home Office', 'Illinois', 'Second Class', '7910.616999999998'], + ['Home Office', 'Illinois', 'Standard Class', '6182.1979999999985'], + ['Home Office', 'Indiana', 'First Class', '281.03000000000003'], + ['Home Office', 'Indiana', 'Same Day', '10.74'], + ['Home Office', 'Indiana', 'Second Class', '999.98'], + ['Home Office', 'Indiana', 'Standard Class', '5487.910000000002'], + ['Home Office', 'Iowa', 'Second Class', '38.58'], + ['Home Office', 'Iowa', 'Standard Class', '1529.6599999999999'], + ['Home Office', 'Kansas', 'Second Class', '149.2'], + ['Home Office', 'Kansas', 'Standard Class', '1169.7499999999998'], + ['Home Office', 'Kentucky', 'First Class', '204.98000000000002'], + ['Home Office', 'Kentucky', 'Second Class', '840.29'], + ['Home Office', 'Kentucky', 'Standard Class', '7187.929999999999'], + ['Home Office', 'Louisiana', 'Second Class', '1160.42'], + ['Home Office', 'Maine', 'Standard Class', '106.08000000000001'], + ['Home Office', 'Maryland', 'Standard Class', '2265.38'], + ['Home Office', 'Massachusetts', 'First Class', '177.17000000000002'], + ['Home Office', 'Massachusetts', 'Same Day', '85.3'], + ['Home Office', 'Massachusetts', 'Second Class', '1582.4080000000001'], + ['Home Office', 'Massachusetts', 'Standard Class', '5998.422000000001'], + ['Home Office', 'Michigan', 'First Class', '4816.782'], + ['Home Office', 'Michigan', 'Same Day', '547.97'], + ['Home Office', 'Michigan', 'Second Class', '3012.4500000000003'], + ['Home Office', 'Michigan', 'Standard Class', '7790.9479999999985'], + ['Home Office', 'Minnesota', 'First Class', '318.04999999999995'], + ['Home Office', 'Minnesota', 'Second Class', '3785.9100000000003'], + ['Home Office', 'Minnesota', 'Standard Class', '3412.8999999999996'], + ['Home Office', 'Mississippi', 'Second Class', '684.7800000000001'], + ['Home Office', 'Mississippi', 'Standard Class', '1035.26'], + ['Home Office', 'Missouri', 'First Class', '8.76'], + ['Home Office', 'Missouri', 'Second Class', '3297.04'], + ['Home Office', 'Missouri', 'Standard Class', '3248.0'], + ['Home Office', 'Montana', 'Standard Class', '4643.075999999998'], + ['Home Office', 'Nebraska', 'First Class', '563.94'], + ['Home Office', 'Nebraska', 'Same Day', '50.42'], + ['Home Office', 'Nebraska', 'Standard Class', '322.58000000000004'], + ['Home Office', 'Nevada', 'First Class', '14.56'], + ['Home Office', 'Nevada', 'Same Day', '475.944'], + ['Home Office', 'Nevada', 'Second Class', '4685.076'], + ['Home Office', 'Nevada', 'Standard Class', '166.70200000000003'], + ['Home Office', 'New Hampshire', 'Second Class', '3517.634'], + ['Home Office', 'New Hampshire', 'Standard Class', '1897.35'], + ['Home Office', 'New Jersey', 'Second Class', '236.77'], + ['Home Office', 'New Jersey', 'Standard Class', '3925.3700000000003'], + ['Home Office', 'New Mexico', 'Second Class', '780.086'], + ['Home Office', 'New Mexico', 'Standard Class', '547.3359999999999'], + ['Home Office', 'New York', 'First Class', '28225.492'], + ['Home Office', 'New York', 'Same Day', '1446.1639999999995'], + ['Home Office', 'New York', 'Second Class', '5778.049000000003'], + ['Home Office', 'New York', 'Standard Class', '22266.21799999999'], + ['Home Office', 'North Carolina', 'Same Day', '7.968'], + ['Home Office', 'North Carolina', 'Second Class', '2831.09'], + ['Home Office', 'North Carolina', 'Standard Class', '4110.134000000001'], + ['Home Office', 'North Dakota', 'Standard Class', '28.38'], + ['Home Office', 'Ohio', 'First Class', '2902.239'], + ['Home Office', 'Ohio', 'Same Day', '1377.8020000000004'], + ['Home Office', 'Ohio', 'Second Class', '1488.8550000000005'], + ['Home Office', 'Ohio', 'Standard Class', '5085.243000000001'], + ['Home Office', 'Oklahoma', 'First Class', '821.94'], + ['Home Office', 'Oklahoma', 'Same Day', '1702.0300000000002'], + ['Home Office', 'Oklahoma', 'Standard Class', '3027.9000000000005'], + ['Home Office', 'Oregon', 'First Class', '1016.562'], + ['Home Office', 'Oregon', 'Standard Class', '956.6850000000001'], + ['Home Office', 'Pennsylvania', 'First Class', '2702.112'], + ['Home Office', 'Pennsylvania', 'Same Day', '41.081999999999994'], + ['Home Office', 'Pennsylvania', 'Second Class', '1930.424'], + ['Home Office', 'Pennsylvania', 'Standard Class', '13808.941999999997'], + ['Home Office', 'Rhode Island', 'First Class', '2545.12'], + ['Home Office', 'Rhode Island', 'Second Class', '1040.95'], + ['Home Office', 'Rhode Island', 'Standard Class', '11177.399999999998'], + ['Home Office', 'South Carolina', 'Standard Class', '25.92'], + ['Home Office', 'Tennessee', 'First Class', '2547.6400000000003'], + ['Home Office', 'Tennessee', 'Second Class', '159.035'], + ['Home Office', 'Tennessee', 'Standard Class', '1630.494'], + ['Home Office', 'Texas', 'First Class', '3227.705'], + ['Home Office', 'Texas', 'Same Day', '76.112'], + ['Home Office', 'Texas', 'Second Class', '6887.694000000002'], + ['Home Office', 'Texas', 'Standard Class', '10112.037'], + ['Home Office', 'Utah', 'First Class', '1548.3500000000001'], + ['Home Office', 'Utah', 'Second Class', '166.44'], + ['Home Office', 'Utah', 'Standard Class', '396.648'], + ['Home Office', 'Vermont', 'Standard Class', '1294.75'], + ['Home Office', 'Virginia', 'First Class', '3462.3299999999995'], + ['Home Office', 'Virginia', 'Same Day', '261.1'], + ['Home Office', 'Virginia', 'Second Class', '142.34'], + ['Home Office', 'Virginia', 'Standard Class', '3585.84'], + ['Home Office', 'Washington', 'First Class', '1967.6680000000003'], + ['Home Office', 'Washington', 'Same Day', '4641.803999999998'], + ['Home Office', 'Washington', 'Second Class', '8157.824'], + ['Home Office', 'Washington', 'Standard Class', '10280.343999999996'], + ['Home Office', 'West Virginia', 'Standard Class', '536.48'], + ['Home Office', 'Wisconsin', 'First Class', '425.11'], + ['Home Office', 'Wisconsin', 'Second Class', '2920.42'], + ['Home Office', 'Wisconsin', 'Standard Class', '2141.0899999999997'], + ['Home Office', 'Wyoming', 'Standard Class', '1603.136'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Segment', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'State', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'Sales (1)', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase13 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'inserted-1567172506410', + datasetId: 'v41c56fh1tnks', + }, + { + hidden: false, + source: '24d104ff10bb4c05', + aggregation_locked: false, + has_auto_aggregation: null, + initial_data_type: null, + cast: 'string', + managed_by: 'user', + calc_mode: 'direct', + formula: '', + type: 'DIMENSION', + description: '', + data_type: 'string', + avatar_id: 'd76f325d-1b0a-4647-b396-cd030214557f', + guid: 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a', + autoaggregated: false, + title: 'State', + valid: true, + aggregation: 'none', + lock_aggregation: false, + virtual: false, + datasetId: 'v41c56fh1tnks', + id: 'dimension-10', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['First Class', 'Alabama'], + ['First Class', 'Arizona'], + ['First Class', 'Arkansas'], + ['First Class', 'California'], + ['First Class', 'Colorado'], + ['First Class', 'Connecticut'], + ['First Class', 'Delaware'], + ['First Class', 'Florida'], + ['First Class', 'Georgia'], + ['First Class', 'Idaho'], + ['First Class', 'Illinois'], + ['First Class', 'Indiana'], + ['First Class', 'Iowa'], + ['First Class', 'Kansas'], + ['First Class', 'Kentucky'], + ['First Class', 'Louisiana'], + ['First Class', 'Maryland'], + ['First Class', 'Massachusetts'], + ['First Class', 'Michigan'], + ['First Class', 'Minnesota'], + ['First Class', 'Mississippi'], + ['First Class', 'Missouri'], + ['First Class', 'Montana'], + ['First Class', 'Nebraska'], + ['First Class', 'Nevada'], + ['First Class', 'New Hampshire'], + ['First Class', 'New Jersey'], + ['First Class', 'New Mexico'], + ['First Class', 'New York'], + ['First Class', 'North Carolina'], + ['First Class', 'Ohio'], + ['First Class', 'Oklahoma'], + ['First Class', 'Oregon'], + ['First Class', 'Pennsylvania'], + ['First Class', 'Rhode Island'], + ['First Class', 'South Carolina'], + ['First Class', 'South Dakota'], + ['First Class', 'Tennessee'], + ['First Class', 'Texas'], + ['First Class', 'Utah'], + ['First Class', 'Virginia'], + ['First Class', 'Washington'], + ['First Class', 'Wisconsin'], + ['Same Day', 'Alabama'], + ['Same Day', 'Arizona'], + ['Same Day', 'Arkansas'], + ['Same Day', 'California'], + ['Same Day', 'Colorado'], + ['Same Day', 'Connecticut'], + ['Same Day', 'Delaware'], + ['Same Day', 'Florida'], + ['Same Day', 'Georgia'], + ['Same Day', 'Illinois'], + ['Same Day', 'Indiana'], + ['Same Day', 'Iowa'], + ['Same Day', 'Kansas'], + ['Same Day', 'Kentucky'], + ['Same Day', 'Louisiana'], + ['Same Day', 'Maryland'], + ['Same Day', 'Massachusetts'], + ['Same Day', 'Michigan'], + ['Same Day', 'Minnesota'], + ['Same Day', 'Mississippi'], + ['Same Day', 'Missouri'], + ['Same Day', 'Montana'], + ['Same Day', 'Nebraska'], + ['Same Day', 'Nevada'], + ['Same Day', 'New Jersey'], + ['Same Day', 'New Mexico'], + ['Same Day', 'New York'], + ['Same Day', 'North Carolina'], + ['Same Day', 'Ohio'], + ['Same Day', 'Oklahoma'], + ['Same Day', 'Pennsylvania'], + ['Same Day', 'South Carolina'], + ['Same Day', 'Tennessee'], + ['Same Day', 'Texas'], + ['Same Day', 'Utah'], + ['Same Day', 'Virginia'], + ['Same Day', 'Washington'], + ['Same Day', 'West Virginia'], + ['Same Day', 'Wisconsin'], + ['Second Class', 'Alabama'], + ['Second Class', 'Arizona'], + ['Second Class', 'Arkansas'], + ['Second Class', 'California'], + ['Second Class', 'Colorado'], + ['Second Class', 'Connecticut'], + ['Second Class', 'Delaware'], + ['Second Class', 'District of Columbia'], + ['Second Class', 'Florida'], + ['Second Class', 'Georgia'], + ['Second Class', 'Idaho'], + ['Second Class', 'Illinois'], + ['Second Class', 'Indiana'], + ['Second Class', 'Iowa'], + ['Second Class', 'Kansas'], + ['Second Class', 'Kentucky'], + ['Second Class', 'Louisiana'], + ['Second Class', 'Maryland'], + ['Second Class', 'Massachusetts'], + ['Second Class', 'Michigan'], + ['Second Class', 'Minnesota'], + ['Second Class', 'Mississippi'], + ['Second Class', 'Missouri'], + ['Second Class', 'Nebraska'], + ['Second Class', 'Nevada'], + ['Second Class', 'New Hampshire'], + ['Second Class', 'New Jersey'], + ['Second Class', 'New Mexico'], + ['Second Class', 'New York'], + ['Second Class', 'North Carolina'], + ['Second Class', 'North Dakota'], + ['Second Class', 'Ohio'], + ['Second Class', 'Oklahoma'], + ['Second Class', 'Oregon'], + ['Second Class', 'Pennsylvania'], + ['Second Class', 'Rhode Island'], + ['Second Class', 'South Carolina'], + ['Second Class', 'South Dakota'], + ['Second Class', 'Tennessee'], + ['Second Class', 'Texas'], + ['Second Class', 'Utah'], + ['Second Class', 'Vermont'], + ['Second Class', 'Virginia'], + ['Second Class', 'Washington'], + ['Second Class', 'Wisconsin'], + ['Standard Class', 'Alabama'], + ['Standard Class', 'Arizona'], + ['Standard Class', 'Arkansas'], + ['Standard Class', 'California'], + ['Standard Class', 'Colorado'], + ['Standard Class', 'Connecticut'], + ['Standard Class', 'Delaware'], + ['Standard Class', 'District of Columbia'], + ['Standard Class', 'Florida'], + ['Standard Class', 'Georgia'], + ['Standard Class', 'Idaho'], + ['Standard Class', 'Illinois'], + ['Standard Class', 'Indiana'], + ['Standard Class', 'Iowa'], + ['Standard Class', 'Kansas'], + ['Standard Class', 'Kentucky'], + ['Standard Class', 'Louisiana'], + ['Standard Class', 'Maine'], + ['Standard Class', 'Maryland'], + ['Standard Class', 'Massachusetts'], + ['Standard Class', 'Michigan'], + ['Standard Class', 'Minnesota'], + ['Standard Class', 'Mississippi'], + ['Standard Class', 'Missouri'], + ['Standard Class', 'Montana'], + ['Standard Class', 'Nebraska'], + ['Standard Class', 'Nevada'], + ['Standard Class', 'New Hampshire'], + ['Standard Class', 'New Jersey'], + ['Standard Class', 'New Mexico'], + ['Standard Class', 'New York'], + ['Standard Class', 'North Carolina'], + ['Standard Class', 'North Dakota'], + ['Standard Class', 'Ohio'], + ['Standard Class', 'Oklahoma'], + ['Standard Class', 'Oregon'], + ['Standard Class', 'Pennsylvania'], + ['Standard Class', 'Rhode Island'], + ['Standard Class', 'South Carolina'], + ['Standard Class', 'South Dakota'], + ['Standard Class', 'Tennessee'], + ['Standard Class', 'Texas'], + ['Standard Class', 'Utah'], + ['Standard Class', 'Vermont'], + ['Standard Class', 'Virginia'], + ['Standard Class', 'Washington'], + ['Standard Class', 'West Virginia'], + ['Standard Class', 'Wisconsin'], + ['Standard Class', 'Wyoming'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'State', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const baseCase14 = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + type: 'DIMENSION', + hidden: false, + data_type: 'string', + cast: 'string', + guid: 'e910b9b9-caa9-49ba-b076-b567e6ca846b', + calc_mode: 'direct', + autoaggregated: false, + has_auto_aggregation: false, + formula: '', + lock_aggregation: false, + title: 'Ship Mode', + valid: true, + aggregation_locked: false, + aggregation: 'none', + source: '0685357d861048d2', + description: '', + id: 'inserted-1615903941283', + datasetId: 'v41c56fh1tnks', + }, + { + hidden: false, + source: '24d104ff10bb4c05', + aggregation_locked: false, + has_auto_aggregation: null, + initial_data_type: null, + cast: 'string', + managed_by: 'user', + calc_mode: 'direct', + formula: '', + type: 'DIMENSION', + description: '', + data_type: 'string', + avatar_id: 'd76f325d-1b0a-4647-b396-cd030214557f', + guid: 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a', + autoaggregated: false, + title: 'State', + valid: true, + aggregation: 'none', + lock_aggregation: false, + virtual: false, + datasetId: 'v41c56fh1tnks', + id: 'inserted-1615903943782', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [], + capacity: null, + allowedFinalTypes: {}, + settings: {}, + }, + ], + resultData: { + data: [ + ['First Class', 'Alabama'], + ['First Class', 'Arizona'], + ['First Class', 'Arkansas'], + ['First Class', 'California'], + ['First Class', 'Colorado'], + ['First Class', 'Connecticut'], + ['First Class', 'Delaware'], + ['First Class', 'Florida'], + ['First Class', 'Georgia'], + ['First Class', 'Idaho'], + ['First Class', 'Illinois'], + ['First Class', 'Indiana'], + ['First Class', 'Iowa'], + ['First Class', 'Kansas'], + ['First Class', 'Kentucky'], + ['First Class', 'Louisiana'], + ['First Class', 'Maryland'], + ['First Class', 'Massachusetts'], + ['First Class', 'Michigan'], + ['First Class', 'Minnesota'], + ['First Class', 'Mississippi'], + ['First Class', 'Missouri'], + ['First Class', 'Montana'], + ['First Class', 'Nebraska'], + ['First Class', 'Nevada'], + ['First Class', 'New Hampshire'], + ['First Class', 'New Jersey'], + ['First Class', 'New Mexico'], + ['First Class', 'New York'], + ['First Class', 'North Carolina'], + ['First Class', 'Ohio'], + ['First Class', 'Oklahoma'], + ['First Class', 'Oregon'], + ['First Class', 'Pennsylvania'], + ['First Class', 'Rhode Island'], + ['First Class', 'South Carolina'], + ['First Class', 'South Dakota'], + ['First Class', 'Tennessee'], + ['First Class', 'Texas'], + ['First Class', 'Utah'], + ['First Class', 'Virginia'], + ['First Class', 'Washington'], + ['First Class', 'Wisconsin'], + ['Same Day', 'Alabama'], + ['Same Day', 'Arizona'], + ['Same Day', 'Arkansas'], + ['Same Day', 'California'], + ['Same Day', 'Colorado'], + ['Same Day', 'Connecticut'], + ['Same Day', 'Delaware'], + ['Same Day', 'Florida'], + ['Same Day', 'Georgia'], + ['Same Day', 'Illinois'], + ['Same Day', 'Indiana'], + ['Same Day', 'Iowa'], + ['Same Day', 'Kansas'], + ['Same Day', 'Kentucky'], + ['Same Day', 'Louisiana'], + ['Same Day', 'Maryland'], + ['Same Day', 'Massachusetts'], + ['Same Day', 'Michigan'], + ['Same Day', 'Minnesota'], + ['Same Day', 'Mississippi'], + ['Same Day', 'Missouri'], + ['Same Day', 'Montana'], + ['Same Day', 'Nebraska'], + ['Same Day', 'Nevada'], + ['Same Day', 'New Jersey'], + ['Same Day', 'New Mexico'], + ['Same Day', 'New York'], + ['Same Day', 'North Carolina'], + ['Same Day', 'Ohio'], + ['Same Day', 'Oklahoma'], + ['Same Day', 'Pennsylvania'], + ['Same Day', 'South Carolina'], + ['Same Day', 'Tennessee'], + ['Same Day', 'Texas'], + ['Same Day', 'Utah'], + ['Same Day', 'Virginia'], + ['Same Day', 'Washington'], + ['Same Day', 'West Virginia'], + ['Same Day', 'Wisconsin'], + ['Second Class', 'Alabama'], + ['Second Class', 'Arizona'], + ['Second Class', 'Arkansas'], + ['Second Class', 'California'], + ['Second Class', 'Colorado'], + ['Second Class', 'Connecticut'], + ['Second Class', 'Delaware'], + ['Second Class', 'District of Columbia'], + ['Second Class', 'Florida'], + ['Second Class', 'Georgia'], + ['Second Class', 'Idaho'], + ['Second Class', 'Illinois'], + ['Second Class', 'Indiana'], + ['Second Class', 'Iowa'], + ['Second Class', 'Kansas'], + ['Second Class', 'Kentucky'], + ['Second Class', 'Louisiana'], + ['Second Class', 'Maryland'], + ['Second Class', 'Massachusetts'], + ['Second Class', 'Michigan'], + ['Second Class', 'Minnesota'], + ['Second Class', 'Mississippi'], + ['Second Class', 'Missouri'], + ['Second Class', 'Nebraska'], + ['Second Class', 'Nevada'], + ['Second Class', 'New Hampshire'], + ['Second Class', 'New Jersey'], + ['Second Class', 'New Mexico'], + ['Second Class', 'New York'], + ['Second Class', 'North Carolina'], + ['Second Class', 'North Dakota'], + ['Second Class', 'Ohio'], + ['Second Class', 'Oklahoma'], + ['Second Class', 'Oregon'], + ['Second Class', 'Pennsylvania'], + ['Second Class', 'Rhode Island'], + ['Second Class', 'South Carolina'], + ['Second Class', 'South Dakota'], + ['Second Class', 'Tennessee'], + ['Second Class', 'Texas'], + ['Second Class', 'Utah'], + ['Second Class', 'Vermont'], + ['Second Class', 'Virginia'], + ['Second Class', 'Washington'], + ['Second Class', 'Wisconsin'], + ['Standard Class', 'Alabama'], + ['Standard Class', 'Arizona'], + ['Standard Class', 'Arkansas'], + ['Standard Class', 'California'], + ['Standard Class', 'Colorado'], + ['Standard Class', 'Connecticut'], + ['Standard Class', 'Delaware'], + ['Standard Class', 'District of Columbia'], + ['Standard Class', 'Florida'], + ['Standard Class', 'Georgia'], + ['Standard Class', 'Idaho'], + ['Standard Class', 'Illinois'], + ['Standard Class', 'Indiana'], + ['Standard Class', 'Iowa'], + ['Standard Class', 'Kansas'], + ['Standard Class', 'Kentucky'], + ['Standard Class', 'Louisiana'], + ['Standard Class', 'Maine'], + ['Standard Class', 'Maryland'], + ['Standard Class', 'Massachusetts'], + ['Standard Class', 'Michigan'], + ['Standard Class', 'Minnesota'], + ['Standard Class', 'Mississippi'], + ['Standard Class', 'Missouri'], + ['Standard Class', 'Montana'], + ['Standard Class', 'Nebraska'], + ['Standard Class', 'Nevada'], + ['Standard Class', 'New Hampshire'], + ['Standard Class', 'New Jersey'], + ['Standard Class', 'New Mexico'], + ['Standard Class', 'New York'], + ['Standard Class', 'North Carolina'], + ['Standard Class', 'North Dakota'], + ['Standard Class', 'Ohio'], + ['Standard Class', 'Oklahoma'], + ['Standard Class', 'Oregon'], + ['Standard Class', 'Pennsylvania'], + ['Standard Class', 'Rhode Island'], + ['Standard Class', 'South Carolina'], + ['Standard Class', 'South Dakota'], + ['Standard Class', 'Tennessee'], + ['Standard Class', 'Texas'], + ['Standard Class', 'Utah'], + ['Standard Class', 'Vermont'], + ['Standard Class', 'Virginia'], + ['Standard Class', 'Washington'], + ['Standard Class', 'West Virginia'], + ['Standard Class', 'Wisconsin'], + ['Standard Class', 'Wyoming'], + ], + order: [ + { + datasetId: 'v41c56fh1tnks', + title: 'Ship Mode', + }, + { + datasetId: 'v41c56fh1tnks', + title: 'State', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'Row ID', + 'd95f5722-af15-4642-8f19-56387de92506': 'Order ID', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'Order Date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'Ship Date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'Ship Mode', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'Customer Name', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'Segment', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'Country', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'City', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'State', + '0c6b9808-0658-4256-845d-3c536571e516': 'Postal Code', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'Region', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'Product ID', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'Category', + '434222f8-4348-4adf-bd91-d11925d692ac': 'Sub-Category', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'Product Name', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'Sales', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'Quantity', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'Discount', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'Profit', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'Sales (1)', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'title-64826450-cb2b-11e9-bcd8-f93d581ea3a9', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'title-61174ce0-cb2b-11e9-bcd8-f93d581ea3a9', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'Ship Date Year', + }, + idToDataType: { + 'd7b05642-6e22-408a-a215-c50906a7dc89': 'integer', + 'd95f5722-af15-4642-8f19-56387de92506': 'string', + 'd1af4999-6203-448e-9a63-92ae50ae6d1e': 'date', + '51eec7a9-c281-4c10-abf6-da85f63b9be5': 'date', + 'e910b9b9-caa9-49ba-b076-b567e6ca846b': 'string', + '7d00a5a1-af87-4c70-9874-bb00ccc143dc': 'string', + '6d2a3c33-9289-4152-bede-3168f95432dc': 'string', + '3bd4dfda-e6c0-4e42-9c66-dd2c1bae37ff': 'string', + '7dd66836-57e3-4691-beef-d40394c6df1d': 'string', + 'd1ce084d-2471-42e3-a0da-e6fb68f2a02a': 'string', + '0c6b9808-0658-4256-845d-3c536571e516': 'integer', + '3e5816f3-01ff-4d11-8fd2-3efc4ab4aafe': 'string', + 'c38fd264-64aa-467b-a10b-90d4a0f2450a': 'string', + 'b1cbbcb5-fad0-4ef0-a584-4024c687c8e7': 'string', + '434222f8-4348-4adf-bd91-d11925d692ac': 'string', + 'fe83c499-6a4b-4382-b004-efa63c0177c5': 'string', + 'c3095edb-7efd-4183-a8bc-1bfb05109e0b': 'float', + '27bf16a1-a7ea-43e5-8224-cc64c5111c4d': 'integer', + '985f9eff-e060-4185-ad72-16e1ea370ee1': 'float', + '16b9c0e4-2ed4-469d-8c83-73d0ef7b9308': 'float', + '87d5f890-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '64826450-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '61174ce0-cb2b-11e9-bcd8-f93d581ea3a9': 'float', + '24f3cae0-cb2b-11e9-bcd8-f93d581ea3a9': 'integer', + }, + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, +}; + +export const preparingGrouppedMarkupDataWith1ColumnLevel = { + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, + placeholders: [ + { + items: [ + { + data_type: 'integer', + datasetId: '7o5arrsuk8ms2', + title: 'Start year', + guid: '77030768-272c-4596-af68-3510351b63d1', + type: 'DIMENSION', + }, + ], + }, + { + items: [ + { + title: 'link', + data_type: 'markup', + datasetId: '7o5arrsuk8ms2', + type: 'DIMENSION', + guid: '67a54530-4604-11eb-ae06-150e6e6a5acd', + }, + ], + }, + { + items: [], + }, + ], + resultData: { + data: [ + [ + '1984', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=documentary', + content: { + type: 'text', + content: 'documentary', + }, + }, + ], + [ + '1987', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=documentary', + content: { + type: 'text', + content: 'documentary', + }, + }, + ], + [ + '1988', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=documentary', + content: { + type: 'text', + content: 'documentary', + }, + }, + ], + [ + '1981', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=documentary', + content: { + type: 'text', + content: 'documentary', + }, + }, + ], + [ + '1982', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=documentary', + content: { + type: 'text', + content: 'documentary', + }, + }, + ], + [ + '1983', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=documentary', + content: { + type: 'text', + content: 'documentary', + }, + }, + ], + [ + '1980', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=documentary', + content: { + type: 'text', + content: 'documentary', + }, + }, + ], + [ + '1989', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=documentary', + content: { + type: 'text', + content: 'documentary', + }, + }, + ], + [ + '1986', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=documentary', + content: { + type: 'text', + content: 'documentary', + }, + }, + ], + [ + '1985', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=documentary', + content: { + type: 'text', + content: 'documentary', + }, + }, + ], + [ + '1988', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=action', + content: { + type: 'text', + content: 'action', + }, + }, + ], + [ + '1989', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=action', + content: { + type: 'text', + content: 'action', + }, + }, + ], + [ + '1987', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=action', + content: { + type: 'text', + content: 'action', + }, + }, + ], + [ + '1986', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=action', + content: { + type: 'text', + content: 'action', + }, + }, + ], + [ + '1985', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=action', + content: { + type: 'text', + content: 'action', + }, + }, + ], + [ + '1984', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=action', + content: { + type: 'text', + content: 'action', + }, + }, + ], + [ + '1981', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=action', + content: { + type: 'text', + content: 'action', + }, + }, + ], + [ + '1980', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=action', + content: { + type: 'text', + content: 'action', + }, + }, + ], + [ + '1983', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=action', + content: { + type: 'text', + content: 'action', + }, + }, + ], + [ + '1982', + { + type: 'url', + url: 'https://www.some.site.com/index.php?kp_query=action', + content: { + type: 'text', + content: 'action', + }, + }, + ], + ], + order: [ + { + datasetId: '7o5arrsuk8ms2', + title: 'Start year', + }, + { + datasetId: '7o5arrsuk8ms2', + title: 'link', + }, + ], + }, + colors: [], + colorsConfig: {}, + sort: [ + { + data_type: 'markup', + title: 'link', + guid: '67a54530-4604-11eb-ae06-150e6e6a5acd', + type: 'DIMENSION', + }, + ], + idToTitle: { + '67a54530-4604-11eb-ae06-150e6e6a5acd': 'link', + 'ca560e80-13c3-11eb-9775-db57fb79d44c': 'max within', + '05b293d0-13c0-11eb-9775-db57fb79d44c': 'if', + 'ef5ddf40-13bf-11eb-9775-db57fb79d44c': 'max among', + '77030768-272c-4596-af68-3510351b63d1': 'Start year', + 'ae4bc369-fd7c-462d-941e-7c6d04d327c1': 'Finish year', + '5b5356fd-d138-478f-8dc1-36ead997cc73': 'Type', + '7aaf3661-962f-4e3e-81f2-e97871e3c53f': 'Genre', + '45b54b14-9287-40b8-a450-af38ebba25b2': 'Genre 2', + 'fc37ac1d-f076-4463-b6d4-9101694c7e7a': 'Genre 3', + 'a0c2eecc-223c-4e72-bb97-ad0191c2781b': 'ID', + 'd8fa84b0-13bf-11eb-9775-db57fb79d44c': 'CountD ID', + 'a4698439-502c-45f0-89c7-ecf1719aadb2': 'Country of manufacture', + 'c4777d70-1139-41b3-9319-5d2cd2c0e55a': 'Name', + '7eef0da4-c952-44f5-96c6-b7f426ffb3cb': 'Voices count', + '43a977eb-0f78-45c6-ac84-7eddb74e4445': 'Duration (min)', + '8cd8c22a-d7da-4f2d-8b0e-a4694bffeb6f': 'Average rating', + }, + idToDataType: { + '67a54530-4604-11eb-ae06-150e6e6a5acd': 'markup', + 'ca560e80-13c3-11eb-9775-db57fb79d44c': 'integer', + '05b293d0-13c0-11eb-9775-db57fb79d44c': 'boolean', + 'ef5ddf40-13bf-11eb-9775-db57fb79d44c': 'integer', + '77030768-272c-4596-af68-3510351b63d1': 'integer', + 'ae4bc369-fd7c-462d-941e-7c6d04d327c1': 'integer', + '5b5356fd-d138-478f-8dc1-36ead997cc73': 'string', + '7aaf3661-962f-4e3e-81f2-e97871e3c53f': 'string', + '45b54b14-9287-40b8-a450-af38ebba25b2': 'string', + 'fc37ac1d-f076-4463-b6d4-9101694c7e7a': 'string', + 'a0c2eecc-223c-4e72-bb97-ad0191c2781b': 'integer', + 'd8fa84b0-13bf-11eb-9775-db57fb79d44c': 'integer', + 'a4698439-502c-45f0-89c7-ecf1719aadb2': 'string', + 'c4777d70-1139-41b3-9319-5d2cd2c0e55a': 'string', + '7eef0da4-c952-44f5-96c6-b7f426ffb3cb': 'integer', + '43a977eb-0f78-45c6-ac84-7eddb74e4445': 'integer', + '8cd8c22a-d7da-4f2d-8b0e-a4694bffeb6f': 'float', + }, +}; + +export const preparingGrouppedMarkupDataWith2ColumnLevel = { + ChartEditor: { + getLang: () => { + return 'en'; + }, + }, + colors: [], + colorsConfig: {}, + idToDataType: { + '01684d24-ebb2-4eee-b411-32018db3db90': 'string', + '016e6bfb-361d-486f-aae3-67682c7ac5a8': 'string', + '025e27fb-601a-47be-8d80-9bf4b20b7641': 'string', + '02e8f0ab-b2ed-4911-bb92-efd9846eb816': 'float', + '04a746a0-894d-11ea-be98-1f0d7ccd1e29': 'integer', + '07cb1471-340f-4a7f-9ad4-44f0945894c6': 'string', + '0b41a1ab-6b9e-4de3-a96e-c8c85f74d34f': 'string', + '0bfbaacb-39f4-4ee4-bbae-f47662727a91': 'string', + '10148fc4-badb-44c9-8064-ae3028c07310': 'float', + '250f8ea9-9bac-42bb-9ba4-63c07acd3111': 'string', + '283848c3-c561-4844-9722-43eefeb0de3d': 'string', + '2927700c-6926-4518-a569-ea76f3d0ef86': 'string', + '2cf5c440-23e4-428e-aa3b-06c3fdfef8ad': 'string', + '2e380901-4e3d-4153-be36-510654079831': 'string', + '2e589049-5312-476f-8d0a-11a62912463c': 'integer', + '3373576b-c25a-4747-b9d6-3286ce83f03e': 'string', + '34967e92-4fce-4d2d-9c94-6a011632b0a8': 'integer', + '3b84b4a0-589b-11eb-825d-852061118ac8': 'string', + '4027e778-4a67-4ea6-90b7-4e9dc9562e6c': 'string', + '43050765-f1e2-4fe6-af0e-5017e58f259d': 'string', + '491b61c4-2fcf-4ad4-921b-2526c952f2ef': 'string', + '4a29f9e0-892d-11ea-bcfb-95ce9ce897cb': 'date', + '4f15039d-bccd-4165-9559-98a011febf1e': 'string', + '50dfb340-888f-11ea-a8d3-ed5cfdad2a11': 'string', + '51855e50-892d-11ea-bcfb-95ce9ce897cb': 'string', + '55ea0917-4ce9-4e47-ac48-48b184ca3773': 'string', + '5653f330-b086-11ea-943a-9ba109847452': 'float', + '5da7cb80-888f-11ea-a8d3-ed5cfdad2a11': 'string', + '6083fb7a-b326-46e6-b93a-f5d8856cf9a7': 'date', + '643eb0c0-7341-11ea-878c-670dcff77600': 'string', + '668360aa-5d34-4c35-b4f8-d3c31c0da81a': 'string', + '6d990ed0-5564-11eb-a149-03d715c5b20f': 'string', + '6dbb44b2-c93e-4c6a-87fb-98a7044ec4ca': 'string', + '6ea686d8-daef-4ddf-9560-b6666b83af3a': 'string', + '728b6d30-7341-11ea-878c-670dcff77600': 'integer', + '7482a20d-2c48-4d29-b37e-b86b3c32fd8d': 'string', + '7826f930-7341-11ea-878c-670dcff77600': 'integer', + '78582854-6507-4f29-9617-ab5644415af9': 'integer', + '7a8af7d9-708a-4758-a673-ebeb089f60e7': 'string', + '7ad3c10a-cfbb-40f7-98d8-fe01966088c4': 'string', + '7dae39e0-7341-11ea-878c-670dcff77600': 'integer', + '807617ce-30fa-4b10-a916-0e20cdaba475': 'string', + '82e18e30-7341-11ea-878c-670dcff77600': 'integer', + '834189ec-0992-43d8-81ec-ce725797e30d': 'float', + '885a0e5f-16be-4c4f-93a4-4e32a68d82f6': 'string', + '8b63cf50-894e-11ea-be98-1f0d7ccd1e29': 'integer', + '92574bd3-e1e5-40a7-99ce-3d0e96aa9649': 'string', + '985b9d89-ebbc-4352-a60e-04f4892f99df': 'string', + '9fa499fb-4e48-4d88-9237-a80547af07f9': 'string', + 'a1a2a7bf-45cc-405f-9f7a-39e7e38d299d': 'float', + 'a4df6137-8026-4096-9b9b-b1c1edce24c2': 'string', + 'abd2a5e4-9c63-4541-bf19-f14484148042': 'string', + 'b1e31d7b-eb03-4aa4-a7b3-3db6d2b2788a': 'string', + 'b2953af0-3eb1-11eb-8e73-b92dbf75b914': 'string', + 'b4d5e947-4e47-4f59-802b-47ebe0cb3548': 'string', + 'b5217c2e-5efe-4f11-94de-0318ad13a8fe': 'string', + 'b8e0a0d1-f1aa-4e60-88a6-2b34af9207d9': 'string', + 'b9d68de0-2fee-11eb-bdff-3b57c2d67e49': 'float', + 'ba7475a1-6a6b-4674-bdc0-d2112536b6ef': 'string', + 'bc70aea0-594c-11eb-bb5b-89b98c4fd65d': 'string', + 'bef4c4e0-892c-11ea-bcfb-95ce9ce897cb': 'integer', + 'bf137802-6e69-4e70-8995-6fef07eed40a': 'string', + 'c175c040-b085-11ea-943a-9ba109847452': 'markup', + 'c5833c60-892c-11ea-bcfb-95ce9ce897cb': 'integer', + 'c9f2d2cc-4290-4f05-9f20-0e36dd2496d7': 'string', + 'cab840a0-8606-11ea-9115-23e3779185d8': 'string', + 'cc6692d2-c9ee-4192-ae1a-a794c883f15c': 'string', + 'cd78b621-0cd2-48ac-abe1-dfe595f058f6': 'string', + 'd79978b6-3103-4e95-8493-479bf9acf2d9': 'string', + 'd8d4e480-6519-11ea-b5aa-93e451f740d6': 'string', + 'd903d933-4dda-407d-a6ef-d8408e872b27': 'string', + 'db729e90-853e-11ea-a51e-8b52b96cf5f4': 'string', + 'dccb986c-07cd-4da8-bcb6-d2a6429b1af6': 'integer', + 'df4be1b0-2b45-44e0-9893-66002a7d0f4a': 'float', + 'e0703c30-1929-4e3b-8c40-5b76b56f9b35': 'string', + 'e0b57d11-e85f-4d00-8378-ee5b7d7f217d': 'string', + 'e168c8f0-6519-11ea-b5aa-93e451f740d6': 'integer', + 'e86d6119-eafc-4a70-956e-981e6912483a': 'string', + 'eb8d9a45-f64e-42e2-96dc-5c4ae6f600a6': 'string', + 'eeae9184-47f2-42f4-bb74-3c787eb9d657': 'string', + 'f0566430-fcc2-11ea-805e-d1e3f3a676ad': 'boolean', + 'f99e9e0b-31ec-45ab-9636-bddb85146be3': 'string', + 'fc791946-cfba-4b21-b53b-3a1e540849c8': 'string', + 'fee51bd0-b088-11ea-943a-9ba109847452': 'float', + 'ffd76232-8f87-40a0-8691-8a32b6ee66a8': 'string', + }, + idToTitle: { + '01684d24-ebb2-4eee-b411-32018db3db90': 'agency_rus_region_name', + '016e6bfb-361d-486f-aae3-67682c7ac5a8': 'agency_sales_manager_name', + '025e27fb-601a-47be-8d80-9bf4b20b7641': 'client_login', + '02e8f0ab-b2ed-4911-bb92-efd9846eb816': 'money', + '04a746a0-894d-11ea-be98-1f0d7ccd1e29': 'title-04a746a0-894d-11ea-be98-1f0d7ccd1e29', + '07cb1471-340f-4a7f-9ad4-44f0945894c6': 'currency', + '0b41a1ab-6b9e-4de3-a96e-c8c85f74d34f': 'agency_sales_manager_login', + '0bfbaacb-39f4-4ee4-bbae-f47662727a91': 'slice', + '10148fc4-badb-44c9-8064-ae3028c07310': 'money_pp', + '250f8ea9-9bac-42bb-9ba4-63c07acd3111': 'analyst_manager_office', + '283848c3-c561-4844-9722-43eefeb0de3d': 'counterparty_name_init', + '2927700c-6926-4518-a569-ea76f3d0ef86': 'agency_name', + '2cf5c440-23e4-428e-aa3b-06c3fdfef8ad': 'subindustry', + '2e380901-4e3d-4153-be36-510654079831': 'account_manager_office', + '2e589049-5312-476f-8d0a-11a62912463c': 'client_id', + '3373576b-c25a-4747-b9d6-3286ce83f03e': 'country', + '34967e92-4fce-4d2d-9c94-6a011632b0a8': 'counterparty_id', + '3b84b4a0-589b-11eb-825d-852061118ac8': 'hrcy_0', + '4027e778-4a67-4ea6-90b7-4e9dc9562e6c': 'account_manager_name', + '43050765-f1e2-4fe6-af0e-5017e58f259d': 'quarter', + '491b61c4-2fcf-4ad4-921b-2526c952f2ef': 'servicing', + '4a29f9e0-892d-11ea-bcfb-95ce9ce897cb': 'title-4a29f9e0-892d-11ea-bcfb-95ce9ce897cb', + '4f15039d-bccd-4165-9559-98a011febf1e': 'scale', + '50dfb340-888f-11ea-a8d3-ed5cfdad2a11': 'title-50dfb340-888f-11ea-a8d3-ed5cfdad2a11', + '51855e50-892d-11ea-bcfb-95ce9ce897cb': 'title-51855e50-892d-11ea-bcfb-95ce9ce897cb', + '55ea0917-4ce9-4e47-ac48-48b184ca3773': 'rus_macroregion_name', + '5653f330-b086-11ea-943a-9ba109847452': 'YoY', + '5da7cb80-888f-11ea-a8d3-ed5cfdad2a11': 'title-5da7cb80-888f-11ea-a8d3-ed5cfdad2a11', + '6083fb7a-b326-46e6-b93a-f5d8856cf9a7': 'period', + '643eb0c0-7341-11ea-878c-670dcff77600': 'title-643eb0c0-7341-11ea-878c-670dcff77600', + '668360aa-5d34-4c35-b4f8-d3c31c0da81a': 'analyst_manager_login', + '6d990ed0-5564-11eb-a149-03d715c5b20f': 'client_id_login', + '6dbb44b2-c93e-4c6a-87fb-98a7044ec4ca': 'sales_manager_login', + '6ea686d8-daef-4ddf-9560-b6666b83af3a': 'reporting_subtier', + '728b6d30-7341-11ea-878c-670dcff77600': 'title-728b6d30-7341-11ea-878c-670dcff77600', + '7482a20d-2c48-4d29-b37e-b86b3c32fd8d': 'agency_account_manager_login', + '7826f930-7341-11ea-878c-670dcff77600': 'title-7826f930-7341-11ea-878c-670dcff77600', + '78582854-6507-4f29-9617-ab5644415af9': 'agency_id', + '7a8af7d9-708a-4758-a673-ebeb089f60e7': 'product', + '7ad3c10a-cfbb-40f7-98d8-fe01966088c4': 'agency_account_manager_name', + '7dae39e0-7341-11ea-878c-670dcff77600': 'title-7dae39e0-7341-11ea-878c-670dcff77600', + '807617ce-30fa-4b10-a916-0e20cdaba475': 'agency_login', + '82e18e30-7341-11ea-878c-670dcff77600': 'title-82e18e30-7341-11ea-878c-670dcff77600', + '834189ec-0992-43d8-81ec-ce725797e30d': 'money_and_forec', + '885a0e5f-16be-4c4f-93a4-4e32a68d82f6': 'distr_channel', + '8b63cf50-894e-11ea-be98-1f0d7ccd1e29': 'title-8b63cf50-894e-11ea-be98-1f0d7ccd1e29', + '92574bd3-e1e5-40a7-99ce-3d0e96aa9649': 'agency_sales_manager_office', + '985b9d89-ebbc-4352-a60e-04f4892f99df': 'agency_country', + '9fa499fb-4e48-4d88-9237-a80547af07f9': 'agency_tier', + 'a1a2a7bf-45cc-405f-9f7a-39e7e38d299d': 'money_wo_discount', + 'a4df6137-8026-4096-9b9b-b1c1edce24c2': 'age_segment', + 'abd2a5e4-9c63-4541-bf19-f14484148042': 'source', + 'b1e31d7b-eb03-4aa4-a7b3-3db6d2b2788a': 'product_1', + 'b2953af0-3eb1-11eb-8e73-b92dbf75b914': 'Service', + 'b4d5e947-4e47-4f59-802b-47ebe0cb3548': 'day', + 'b5217c2e-5efe-4f11-94de-0318ad13a8fe': 'macroindustry', + 'b8e0a0d1-f1aa-4e60-88a6-2b34af9207d9': 'sales_manager_office', + 'b9d68de0-2fee-11eb-bdff-3b57c2d67e49': 'bonus_raw', + 'ba7475a1-6a6b-4674-bdc0-d2112536b6ef': 'business_type', + 'bc70aea0-594c-11eb-bb5b-89b98c4fd65d': 'territory_int', + 'bef4c4e0-892c-11ea-bcfb-95ce9ce897cb': 'title-bef4c4e0-892c-11ea-bcfb-95ce9ce897cb', + 'bf137802-6e69-4e70-8995-6fef07eed40a': 'year', + 'c175c040-b085-11ea-943a-9ba109847452': 'counterparty_name', + 'c5833c60-892c-11ea-bcfb-95ce9ce897cb': 'title-c5833c60-892c-11ea-bcfb-95ce9ce897cb', + 'c9f2d2cc-4290-4f05-9f20-0e36dd2496d7': 'analyst_manager_name', + 'cab840a0-8606-11ea-9115-23e3779185d8': 'title-cab840a0-8606-11ea-9115-23e3779185d8', + 'cc6692d2-c9ee-4192-ae1a-a794c883f15c': 'planning_industry', + 'cd78b621-0cd2-48ac-abe1-dfe595f058f6': 'week', + 'd79978b6-3103-4e95-8493-479bf9acf2d9': 'reporting_tier', + 'd8d4e480-6519-11ea-b5aa-93e451f740d6': 'title-d8d4e480-6519-11ea-b5aa-93e451f740d6', + 'd903d933-4dda-407d-a6ef-d8408e872b27': 'money_segment_1m', + 'db729e90-853e-11ea-a51e-8b52b96cf5f4': 'title-db729e90-853e-11ea-a51e-8b52b96cf5f4', + 'dccb986c-07cd-4da8-bcb6-d2a6429b1af6': 'agency_account_id', + 'df4be1b0-2b45-44e0-9893-66002a7d0f4a': 'money_ly', + 'e0703c30-1929-4e3b-8c40-5b76b56f9b35': 'money_segment_3m', + 'e0b57d11-e85f-4d00-8378-ee5b7d7f217d': 'status', + 'e168c8f0-6519-11ea-b5aa-93e451f740d6': 'title-e168c8f0-6519-11ea-b5aa-93e451f740d6', + 'e86d6119-eafc-4a70-956e-981e6912483a': 'rus_region_name', + 'eb8d9a45-f64e-42e2-96dc-5c4ae6f600a6': 'account_manager_login', + 'eeae9184-47f2-42f4-bb74-3c787eb9d657': 'sales_manager_name', + 'f0566430-fcc2-11ea-805e-d1e3f3a676ad': 'is_managed', + 'f99e9e0b-31ec-45ab-9636-bddb85146be3': 'industry', + 'fc791946-cfba-4b21-b53b-3a1e540849c8': 'month', + 'fee51bd0-b088-11ea-943a-9ba109847452': 'PoP', + 'ffd76232-8f87-40a0-8691-8a32b6ee66a8': 'agency_rus_macroregion_name', + }, + placeholders: [ + { + items: [ + { + data_type: 'date', + guid: '4a29f9e0-892d-11ea-bcfb-95ce9ce897cb', + title: 'title-4a29f9e0-892d-11ea-bcfb-95ce9ce897cb', + type: 'DIMENSION', + datasetId: '5g4ejq84a9xa1', + }, + { + data_type: 'string', + title: 'Measure Names', + type: 'PSEUDO', + }, + ], + }, + { + items: [ + { + data_type: 'markup', + guid: 'c175c040-b085-11ea-943a-9ba109847452', + title: 'counterparty_name', + type: 'DIMENSION', + datasetId: '5g4ejq84a9xa1', + }, + { + data_type: 'string', + guid: '6d990ed0-5564-11eb-a149-03d715c5b20f', + title: 'client_id_login', + type: 'DIMENSION', + datasetId: '5g4ejq84a9xa1', + }, + ], + }, + { + items: [ + { + data_type: 'integer', + guid: '04a746a0-894d-11ea-be98-1f0d7ccd1e29', + title: 'title-04a746a0-894d-11ea-be98-1f0d7ccd1e29', + type: 'MEASURE', + datasetId: '5g4ejq84a9xa1', + }, + { + data_type: 'integer', + guid: '8b63cf50-894e-11ea-be98-1f0d7ccd1e29', + title: 'title-8b63cf50-894e-11ea-be98-1f0d7ccd1e29', + type: 'MEASURE', + datasetId: '5g4ejq84a9xa1', + }, + { + data_type: 'float', + guid: '5653f330-b086-11ea-943a-9ba109847452', + title: 'YoY', + type: 'MEASURE', + datasetId: '5g4ejq84a9xa1', + }, + ], + }, + ], + resultData: { + data: [ + [ + '2021-01-01', + { + content: { + content: 'Super-Bank', + type: 'text', + }, + type: 'url', + url: 'https://superbank.com', + }, + '8003072 | superbank2018', + '7949244', + '9993115', + '-20.45', + '7949244.517', + '2021-01-01', + ], + [ + '2021-01-01', + { + content: { + content: 'Super-Bank', + type: 'text', + }, + type: 'url', + url: 'https://superbank.com', + }, + '71818472 | superbank-mgcom-pil', + '2775298', + '2012184', + '37.92', + '2775298.622', + '2021-01-01', + ], + [ + '2021-01-01', + { + content: { + content: 'Super-Bank', + type: 'text', + }, + type: 'url', + url: 'https://superbank.com', + }, + '91276857 | super-bank-rko', + '2655592', + null, + null, + '2655592.424', + '2021-01-01', + ], + [ + '2021-01-01', + { + content: { + content: 'Super-Bank', + type: 'text', + }, + type: 'url', + url: 'https://superbank.com', + }, + '36103132 | super-bankb2b', + '1948189', + '3875998', + '-49.74', + '1948189.321', + '2021-01-01', + ], + [ + '2021-01-01', + { + content: { + content: 'Super-Bank', + type: 'text', + }, + type: 'url', + url: 'https://superbank.com', + }, + '71817225 | superbank-mgcom-ipoteka', + '1899756', + '1720016', + '10.45', + '1899756.472', + '2021-01-01', + ], + [ + '2021-01-01', + { + content: { + content: 'Super-Bank', + type: 'text', + }, + type: 'url', + url: 'https://superbank.com', + }, + '54863378 | super-forex-rf-rw', + '1521025', + '223284', + '581.21', + '1521025.3479999998', + '2021-01-01', + ], + [ + '2021-01-01', + { + content: { + content: 'Super-Bank', + type: 'text', + }, + type: 'url', + url: 'https://superbank.com', + }, + '63676098 | superleasing-rw', + '1267122', + '982444', + '28.98', + '1267122.991', + '2021-01-01', + ], + [ + '2021-01-01', + { + content: { + content: 'Super-Bank', + type: 'text', + }, + type: 'url', + url: 'https://superbank.com', + }, + '71818039 | superbank-mgcom-dc', + '535118', + '608523', + '-12.06', + '535118.891', + '2021-01-01', + ], + [ + '2021-01-01', + { + content: { + content: 'Super-Bank', + type: 'text', + }, + type: 'url', + url: 'https://superbank.com', + }, + '91712056 | superbank-mgcom-virtcard', + '27500', + null, + null, + '27500.671', + '2021-01-01', + ], + ], + order: [ + { + datasetId: '5g4ejq84a9xa1', + title: 'title-4a29f9e0-892d-11ea-bcfb-95ce9ce897cb', + }, + { + datasetId: '5g4ejq84a9xa1', + title: 'counterparty_name', + }, + { + datasetId: '5g4ejq84a9xa1', + title: 'client_id_login', + }, + { + datasetId: '5g4ejq84a9xa1', + title: 'title-04a746a0-894d-11ea-be98-1f0d7ccd1e29', + }, + { + datasetId: '5g4ejq84a9xa1', + title: 'title-8b63cf50-894e-11ea-be98-1f0d7ccd1e29', + }, + { + datasetId: '5g4ejq84a9xa1', + title: 'YoY', + }, + { + datasetId: '5g4ejq84a9xa1', + title: 'money_and_forec', + }, + { + datasetId: '5g4ejq84a9xa1', + title: 'period', + }, + ], + }, + sort: [ + { + data_type: 'float', + guid: '834189ec-0992-43d8-81ec-ce725797e30d', + title: 'money_and_forec', + type: 'MEASURE', + }, + { + data_type: 'date', + guid: '6083fb7a-b326-46e6-b93a-f5d8856cf9a7', + title: 'period', + type: 'DIMENSION', + }, + ], +}; + +export const preparingSortedTable = { + placeholders: [ + { + allowedTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + items: [ + { + data_type: 'string', + guid: '1406793d-91e0-4999-b4c4-035b92a4c84b', + type: 'DIMENSION', + title: 'OrderStatus', + cast: 'string', + aggregation: 'none', + datasetId: 'kniwu5p9t0fqf', + }, + { + data_type: 'string', + guid: '8b5546fb-1250-4ff9-875d-b400e8fe6fc2', + type: 'DIMENSION', + title: 'OrderStatusDescription', + cast: 'string', + aggregation: 'none', + datasetId: 'kniwu5p9t0fqf', + }, + { + title: 'Measure Names', + type: 'PSEUDO', + className: 'item pseudo-item dimension-item', + data_type: 'string', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + items: [ + { + data_type: 'string', + guid: '765c83ac-a004-4cf5-aa8e-f7a67d8cc6d5', + type: 'DIMENSION', + title: 'CarrierName', + cast: 'string', + aggregation: 'none', + datasetId: 'kniwu5p9t0fqf', + }, + { + data_type: 'string', + guid: '047a8c3c-e246-4a8d-98df-4882e881a384', + type: 'DIMENSION', + title: 'CarrierCode', + cast: 'string', + aggregation: 'none', + datasetId: 'kniwu5p9t0fqf', + }, + { + data_type: 'datetime', + guid: 'b04877ac-27b2-4aa4-ac42-dd9ac22a902a', + type: 'DIMENSION', + title: 'ShippingCutoffLocal', + cast: 'datetime', + aggregation: 'none', + datasetId: 'kniwu5p9t0fqf', + }, + ], + capacity: null, + allowedDataTypes: {}, + settings: {}, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + items: [ + { + title: 'title-6d1ded00-dfd4-11ea-a848-a3e2058e677e', + data_type: 'integer', + type: 'MEASURE', + aggregation: 'countunique', + guid: '6d1ded00-dfd4-11ea-a848-a3e2058e677e', + cast: 'string', + fakeTitle: 'Orders', + originalTitle: 'OrderKey', + datasetId: 'kniwu5p9t0fqf', + }, + { + title: 'title-a90f6dc0-e12d-11ea-9381-ede115ae9885', + data_type: 'integer', + type: 'MEASURE', + aggregation: 'sum', + guid: 'a90f6dc0-e12d-11ea-9381-ede115ae9885', + cast: 'integer', + fakeTitle: 'Items', + originalTitle: 'OriginalQty', + datasetId: 'kniwu5p9t0fqf', + }, + ], + capacity: null, + settings: {}, + }, + ], + resultData: { + data: [ + ['65', 'Packed', 'DPD', '1003937', '2021-02-13T08:00:00', '1', '2'], + ['95', 'Shipment completed', 'DPD', '1003937', '2021-02-13T08:00:00', '3379', '13036'], + ['98', 'Cancelled externally', 'DPD', '1003937', '2021-02-13T08:00:00', '135', '523'], + ['99', 'Cancelled internally', 'DPD', '1003937', '2021-02-13T08:00:00', '41', '71'], + [ + '99', + 'Cancelled internally', + 'Some.Company', + '1005363', + '2021-02-13T17:00:00', + '1', + '4', + ], + [ + '92', + 'Partly shipped', + 'Some Example Company', + '1005447', + '2021-02-13T18:00:00', + '1', + '6', + ], + [ + '95', + 'Shipment completed', + 'Some Example Company', + '1005447', + '2021-02-13T18:00:00', + '2072', + '6369', + ], + [ + '98', + 'Cancelled externally', + 'Some Example Company', + '1005447', + '2021-02-13T18:00:00', + '51', + '156', + ], + [ + '99', + 'Cancelled internally', + 'Some Example Company', + '1005447', + '2021-02-13T18:00:00', + '83', + '209', + ], + ['65', 'Packed', 'Group 2', '1005546', '2021-02-13T22:00:00', '1', '1'], + [ + '95', + 'Shipment completed', + 'Group 2', + '1005546', + '2021-02-13T22:00:00', + '732', + '2300', + ], + ['98', 'Cancelled externally', 'Group 2', '1005546', '2021-02-13T22:00:00', '23', '83'], + ['99', 'Cancelled internally', 'Group 2', '1005546', '2021-02-13T22:00:00', '13', '26'], + ['65', 'Packed', 'X-5', '200', '2021-02-13T23:00:00', '2', '2'], + ['95', 'Shipment completed', 'X-5', '200', '2021-02-13T23:00:00', '3047', '7706'], + ['98', 'Cancelled externally', 'X-5', '200', '2021-02-13T23:00:00', '119', '278'], + ['99', 'Cancelled internally', 'X-5', '200', '2021-02-13T23:00:00', '86', '213'], + ['95', 'Shipment completed', 'KSE', '19', '2021-02-13T23:30:00', '850', '2634'], + [ + '95', + 'Shipment completed', + 'Other Company', + '1005396', + '2021-02-13T23:30:00', + '240', + '695', + ], + ['98', 'Cancelled externally', 'KSE', '19', '2021-02-13T23:30:00', '38', '155'], + [ + '98', + 'Cancelled externally', + 'Other Company', + '1005396', + '2021-02-13T23:30:00', + '13', + '78', + ], + ['99', 'Cancelled internally', 'KSE', '19', '2021-02-13T23:30:00', '28', '56'], + [ + '99', + 'Cancelled internally', + 'Other Company', + '1005396', + '2021-02-13T23:30:00', + '1', + '2', + ], + [ + '95', + 'Shipment completed', + 'BoxSomeDeliveryCompany', + '106', + '2021-02-13T23:59:00', + '666', + '1935', + ], + [ + '98', + 'Cancelled externally', + 'BoxSomeDeliveryCompany', + '106', + '2021-02-13T23:59:00', + '26', + '54', + ], + [ + '99', + 'Cancelled internally', + 'BoxSomeDeliveryCompany', + '106', + '2021-02-13T23:59:00', + '17', + '43', + ], + ['65', 'Packed', 'One more company (Shiptor)', '126', '2021-02-14T00:30:00', '1', '1'], + [ + '95', + 'Shipment completed', + 'One more company (Shiptor)', + '126', + '2021-02-14T00:30:00', + '1300', + '5355', + ], + [ + '98', + 'Cancelled externally', + 'One more company (Shiptor)', + '126', + '2021-02-14T00:30:00', + '58', + '238', + ], + [ + '99', + 'Cancelled internally', + 'One more company (Shiptor)', + '126', + '2021-02-14T00:30:00', + '47', + '293', + ], + [ + '95', + 'Shipment completed', + 'Some Example Company 2', + '1005606', + '2021-02-14T01:00:00', + '1477', + '5581', + ], + [ + '98', + 'Cancelled externally', + 'Some Example Company 2', + '1005606', + '2021-02-14T01:00:00', + '59', + '230', + ], + [ + '99', + 'Cancelled internally', + 'Some Example Company 2', + '1005606', + '2021-02-14T01:00:00', + '24', + '35', + ], + ['65', 'Packed', 'Pickpoint', '107', '2021-02-14T01:30:00', '1', '1'], + [ + '95', + 'Shipment completed', + 'PickPoi', + '1004954', + '2021-02-14T01:30:00', + '1673', + '4670', + ], + ['95', 'Shipment completed', 'Pickpoint', '107', '2021-02-14T01:30:00', '1890', '5077'], + [ + '98', + 'Cancelled externally', + 'PickPoi', + '1004954', + '2021-02-14T01:30:00', + '58', + '153', + ], + ['98', 'Cancelled externally', 'Pickpoint', '107', '2021-02-14T01:30:00', '73', '188'], + ['99', 'Cancelled internally', 'PickPoi', '1004954', '2021-02-14T01:30:00', '31', '75'], + ['99', 'Cancelled internally', 'Pickpoint', '107', '2021-02-14T01:30:00', '72', '227'], + ['65', 'Packed', 'And another company', '1005522', '2021-02-14T02:00:00', '1', '3'], + [ + '95', + 'Shipment completed', + 'And another company', + '1005522', + '2021-02-14T02:00:00', + '546', + '1428', + ], + [ + '98', + 'Cancelled externally', + 'And another company', + '1005522', + '2021-02-14T02:00:00', + '16', + '55', + ], + [ + '99', + 'Cancelled internally', + 'And another company', + '1005522', + '2021-02-14T02:00:00', + '8', + '12', + ], + ['65', 'Packed', 'Swift', '48', '2021-02-14T02:30:00', '3', '44'], + ['95', 'Shipment completed', 'Swift', '48', '2021-02-14T02:30:00', '880', '3848'], + ['98', 'Cancelled externally', 'Swift', '48', '2021-02-14T02:30:00', '31', '106'], + ['99', 'Cancelled internally', 'Swift', '48', '2021-02-14T02:30:00', '9', '19'], + ['-1', 'Unknown', 'A-B-C Group', '1005477', '2021-02-14T03:00:00', '1', '1'], + ['65', 'Packed', 'X-Y-Z Group', '1005705', '2021-02-14T03:00:00', '1', '2'], + ['65', 'Packed', 'A-B-C Group', '1005477', '2021-02-14T03:00:00', '1', '4'], + [ + '95', + 'Shipment completed', + '1-2-3 Group', + '1005492', + '2021-02-14T03:00:00', + '1685', + '6289', + ], + [ + '95', + 'Shipment completed', + 'X-Y-Z Group', + '1005705', + '2021-02-14T03:00:00', + '1381', + '5423', + ], + [ + '95', + 'Shipment completed', + 'A-B-C Group', + '1005477', + '2021-02-14T03:00:00', + '2188', + '8347', + ], + [ + '95', + 'Shipment completed', + 'Group 4', + '1005528', + '2021-02-14T03:00:00', + '924', + '3699', + ], + [ + '98', + 'Cancelled externally', + '1-2-3 Group', + '1005492', + '2021-02-14T03:00:00', + '76', + '354', + ], + [ + '98', + 'Cancelled externally', + 'X-Y-Z Group', + '1005705', + '2021-02-14T03:00:00', + '41', + '148', + ], + [ + '98', + 'Cancelled externally', + 'A-B-C Group', + '1005477', + '2021-02-14T03:00:00', + '76', + '311', + ], + [ + '98', + 'Cancelled externally', + 'Group 4', + '1005528', + '2021-02-14T03:00:00', + '28', + '189', + ], + [ + '99', + 'Cancelled internally', + '1-2-3 Group', + '1005492', + '2021-02-14T03:00:00', + '39', + '96', + ], + [ + '99', + 'Cancelled internally', + 'X-Y-Z Group', + '1005705', + '2021-02-14T03:00:00', + '30', + '163', + ], + [ + '99', + 'Cancelled internally', + 'A-B-C Group', + '1005477', + '2021-02-14T03:00:00', + '56', + '171', + ], + ['99', 'Cancelled internally', 'Group 4', '1005528', '2021-02-14T03:00:00', '19', '66'], + ['65', 'Packed', 'Messenger', '179', '2021-02-14T03:30:00', '2', '3'], + ['92', 'Partly shipped', 'Messenger', '179', '2021-02-14T03:30:00', '1', '11'], + ['95', 'Shipment completed', 'Messenger', '179', '2021-02-14T03:30:00', '481', '1831'], + ['98', 'Cancelled externally', 'Messenger', '179', '2021-02-14T03:30:00', '11', '34'], + ['99', 'Cancelled internally', 'Messenger', '179', '2021-02-14T03:30:00', '11', '25'], + ['65', 'Packed', 'Group 3', '1005429', '2021-02-14T04:00:00', '1', '2'], + [ + '95', + 'Shipment completed', + 'Group 3', + '1005429', + '2021-02-14T04:00:00', + '685', + '1762', + ], + [ + '95', + 'Shipment completed', + 'Regular post', + '1005486', + '2021-02-14T04:00:00', + '252', + '1805', + ], + ['95', 'Shipment completed', 'SDT', '1005117', '2021-02-14T04:00:00', '326', '853'], + [ + '98', + 'Cancelled externally', + 'Group 3', + '1005429', + '2021-02-14T04:00:00', + '31', + '126', + ], + [ + '98', + 'Cancelled externally', + 'Regular post', + '1005486', + '2021-02-14T04:00:00', + '14', + '78', + ], + ['98', 'Cancelled externally', 'SDT', '1005117', '2021-02-14T04:00:00', '29', '55'], + ['99', 'Cancelled internally', 'Group 3', '1005429', '2021-02-14T04:00:00', '9', '26'], + [ + '99', + 'Cancelled internally', + 'Regular post', + '1005486', + '2021-02-14T04:00:00', + '14', + '149', + ], + ['99', 'Cancelled internally', 'SDT', '1005117', '2021-02-14T04:00:00', '7', '25'], + [ + '95', + 'Shipment completed', + 'X-Y-Z Group 2', + '1005706', + '2021-02-14T06:00:00', + '5', + '13', + ], + [ + '95', + 'Shipment completed', + 'Group 1', + '1005357', + '2021-02-14T06:00:00', + '481', + '1382', + ], + [ + '95', + 'Shipment completed', + 'A-B-C Group 2', + '1005473', + '2021-02-14T06:00:00', + '55', + '254', + ], + [ + '98', + 'Cancelled externally', + 'X-Y-Z Group 2', + '1005706', + '2021-02-14T06:00:00', + '1', + '1', + ], + ['98', 'Cancelled externally', 'Group 1', '1005357', '2021-02-14T06:00:00', '23', '82'], + [ + '98', + 'Cancelled externally', + 'A-B-C Group 2', + '1005473', + '2021-02-14T06:00:00', + '2', + '14', + ], + ['99', 'Cancelled internally', 'Group 1', '1005357', '2021-02-14T06:00:00', '8', '10'], + ], + order: [ + { + datasetId: 'kniwu5p9t0fqf', + title: 'OrderStatus', + }, + { + datasetId: 'kniwu5p9t0fqf', + title: 'OrderStatusDescription', + }, + { + datasetId: 'kniwu5p9t0fqf', + title: 'CarrierName', + }, + { + datasetId: 'kniwu5p9t0fqf', + title: 'CarrierCode', + }, + { + datasetId: 'kniwu5p9t0fqf', + title: 'ShippingCutoffLocal', + }, + { + datasetId: 'kniwu5p9t0fqf', + title: 'title-6d1ded00-dfd4-11ea-a848-a3e2058e677e', + }, + { + datasetId: 'kniwu5p9t0fqf', + title: 'title-a90f6dc0-e12d-11ea-9381-ede115ae9885', + }, + ], + }, + colors: [], + sort: [ + { + managed_by: 'user', + formula: '', + lock_aggregation: false, + autoaggregated: false, + guid: 'b04877ac-27b2-4aa4-ac42-dd9ac22a902a', + title: 'ShippingCutoffLocal', + description: '', + aggregation: 'none', + virtual: false, + valid: true, + avatar_id: 'a79db5a0-d877-11ea-91a0-1ba1f931b373', + initial_data_type: 'datetime', + cast: 'datetime', + type: 'DIMENSION', + source: 'ShippingCutoffLocal', + hidden: false, + aggregation_locked: false, + data_type: 'datetime', + has_auto_aggregation: false, + calc_mode: 'direct', + datasetId: 'kniwu5p9t0fqf', + id: 'inserted-1602832875807', + datasetName: 'ControlPointsOrdersAggregated', + direction: 'ASC', + }, + { + description: '', + has_auto_aggregation: false, + autoaggregated: false, + data_type: 'string', + initial_data_type: 'string', + guid: '1406793d-91e0-4999-b4c4-035b92a4c84b', + calc_mode: 'direct', + type: 'DIMENSION', + aggregation_locked: false, + virtual: false, + lock_aggregation: false, + title: 'OrderStatus', + valid: true, + formula: '', + cast: 'string', + aggregation: 'none', + source: 'OrderStatus', + hidden: false, + avatar_id: 'a79db5a0-d877-11ea-91a0-1ba1f931b373', + managed_by: 'user', + datasetId: 'kniwu5p9t0fqf', + id: 'dimension-8', + datasetName: 'ControlPointsOrdersAggregated', + direction: 'ASC', + }, + ], + colorsConfig: { + gradientMode: '2-point', + gradientPalette: 'blue', + polygonBorders: 'show', + }, + idToTitle: { + '147a2c1e-4d66-4bc1-b551-a258f804699f': 'OperationDayId', + '12ab0ab6-f1d9-4c10-b73a-f799e7ddbfa3': 'ScheduledShipdate', + '765c83ac-a004-4cf5-aa8e-f7a67d8cc6d5': 'CarrierName', + '047a8c3c-e246-4a8d-98df-4882e881a384': 'CarrierCode', + 'ff4114b1-d210-4a7a-8b39-78a029e2bed9': 'ShippingCutoff', + '95edb63e-bfb6-43bb-8535-91be637fd97d': 'OrderType', + '61431c57-bda0-4ccf-bb61-9f557b21def4': 'GroupName', + '1406793d-91e0-4999-b4c4-035b92a4c84b': 'OrderStatus', + '8b5546fb-1250-4ff9-875d-b400e8fe6fc2': 'OrderStatusDescription', + 'b04877ac-27b2-4aa4-ac42-dd9ac22a902a': 'ShippingCutoffLocal', + 'ad3aa01e-f738-46df-9081-fee5c79f7c68': 'OrderKey', + '63edf987-5231-4b94-8ab4-d165623c0758': 'OriginalQty', + '505a8205-1127-4e8b-938a-43de909247e7': 'UnitOperationType', + 'acc14ab5-534e-4c6f-89ec-c74700d69442': 'InitOrderType', + 'ec31faaa-7c77-4a1c-9cb5-1ce7505a976c': 'OrderDetailSerialKey', + 'a90f6dc0-e12d-11ea-9381-ede115ae9885': 'title-a90f6dc0-e12d-11ea-9381-ede115ae9885', + 'd2bbf470-dfd6-11ea-a848-a3e2058e677e': 'title-d2bbf470-dfd6-11ea-a848-a3e2058e677e', + 'cbeaa790-dfd6-11ea-a848-a3e2058e677e': 'title-cbeaa790-dfd6-11ea-a848-a3e2058e677e', + '6d1ded00-dfd4-11ea-a848-a3e2058e677e': 'title-6d1ded00-dfd4-11ea-a848-a3e2058e677e', + '9b6d85b0-dfcc-11ea-9db5-ff1c4a45672c': 'title-9b6d85b0-dfcc-11ea-9db5-ff1c4a45672c', + '5be00840-d87a-11ea-aab7-3ddc74950dae': 'title-5be00840-d87a-11ea-aab7-3ddc74950dae', + }, + idToDataType: { + '147a2c1e-4d66-4bc1-b551-a258f804699f': 'integer', + '12ab0ab6-f1d9-4c10-b73a-f799e7ddbfa3': 'date', + '765c83ac-a004-4cf5-aa8e-f7a67d8cc6d5': 'string', + '047a8c3c-e246-4a8d-98df-4882e881a384': 'string', + 'ff4114b1-d210-4a7a-8b39-78a029e2bed9': 'datetime', + '95edb63e-bfb6-43bb-8535-91be637fd97d': 'string', + '61431c57-bda0-4ccf-bb61-9f557b21def4': 'string', + '1406793d-91e0-4999-b4c4-035b92a4c84b': 'string', + '8b5546fb-1250-4ff9-875d-b400e8fe6fc2': 'string', + 'b04877ac-27b2-4aa4-ac42-dd9ac22a902a': 'datetime', + 'ad3aa01e-f738-46df-9081-fee5c79f7c68': 'string', + '63edf987-5231-4b94-8ab4-d165623c0758': 'integer', + '505a8205-1127-4e8b-938a-43de909247e7': 'string', + 'acc14ab5-534e-4c6f-89ec-c74700d69442': 'string', + 'ec31faaa-7c77-4a1c-9cb5-1ce7505a976c': 'integer', + 'a90f6dc0-e12d-11ea-9381-ede115ae9885': 'integer', + 'd2bbf470-dfd6-11ea-a848-a3e2058e677e': 'integer', + 'cbeaa790-dfd6-11ea-a848-a3e2058e677e': 'integer', + '6d1ded00-dfd4-11ea-a848-a3e2058e677e': 'integer', + '9b6d85b0-dfcc-11ea-9db5-ff1c4a45672c': 'integer', + '5be00840-d87a-11ea-aab7-3ddc74950dae': 'integer', + }, + ChartEditor: { + getLang: () => 'en', + }, +}; + +export const preparingPivotTableWithEmptyAndNullValues = { + placeholders: [ + { + items: [ + { + title: 'System', + data_type: 'string', + type: 'DIMENSION', + cast: 'string', + guid: '6dd3fa7a-a604-493b-8f96-d0670d3f53b7', + datasetId: 'kqbhty1yfegke', + }, + ], + }, + { + items: [ + { + data_type: 'string', + cast: 'string', + type: 'DIMENSION', + guid: '723e29c9-c607-437a-a908-d3031bf0510d', + title: 'agency', + datasetId: 'kqbhty1yfegke', + }, + { + title: 'Project', + data_type: 'string', + type: 'DIMENSION', + cast: 'string', + guid: '8f16b262-16a5-4d4b-81ec-c41cee743c97', + datasetId: 'kqbhty1yfegke', + }, + ], + }, + { + items: [ + { + type: 'MEASURE', + title: 'title-83a46b70-7a5d-11eb-a88e-9dab961125b2', + data_type: 'float', + cast: 'float', + guid: '83a46b70-7a5d-11eb-a88e-9dab961125b2', + datasetId: 'kqbhty1yfegke', + }, + ], + }, + ], + resultData: { + data: [ + ['F', '', 'adamas1', '8257.2523776'], + ['F', '', 'hatsandcaps', '16059.324'], + ['F', '', 'missislazer', '2406.492'], + ['F', '2 banks', 'dvaberegagoogle', '226945.128'], + ['F', '2r-media', 'glavstroi', '2509700.58'], + ['F', 'admixer (performics)', 'elxua', '36752.4894444'], + ['F', 'advisor', 'coopervision', '44219.028'], + ['F', 'mgcom', 'mgcom_auto', '319801.692'], + ['F', 'mgcom', 'mgcom_europlan_ru', '1919952.924'], + ['F', 'mgcom', 'mgcom_rolf', '257414.448'], + ['F', 'ms group', 'vwartan', '7513.6385784'], + ['F', 'ms group', 'vwartanmotors', '8542.6904496'], + ['F', 'profitator', 'profitator1', '812446.068'], + ['F', 'rush agency', 'rushagencyinfoflotcom', '45634.5'], + ['F', 's1carpet.com', 'carpetcom', '543141.588'], + ['F', 's2mno', 'projectmts', '4824064.5'], + ['F', 's3aas', 'mbkcredit', '310492.1616132'], + ['F', 's3aas', 'webinar', '9710.148'], + ['F', null, '1qm', '0.0'], + ['F', null, '404', '0.0'], + ['F', null, '4px', '0.0'], + ['F', null, '585', '0.0'], + ['F', null, 'abe', '0.0'], + ['F', null, 'ace', '0.0'], + ['F', null, 'ala', '0.0'], + ['F', null, 'ala', '0.0'], + ['F', null, 'all', '0.0'], + ['F', null, 'all', '0.0'], + ['F', null, 'alo', '0.0'], + ['F', null, 'alw', '0.0'], + ['F', null, 'ana', '0.0'], + ['F', null, 'anc', '0.0'], + ['F', null, 'ape', '0.0'], + ['F', null, 'api', '0.0'], + ['F', null, 'ari', '0.0'], + ['F', null, 'art', '0.0'], + ['F', null, 'art', '0.0'], + ['F', null, 'asa', '0.0'], + ['F', null, 'ast', '0.0'], + ['F', null, 'avt', '0.0'], + ['F', null, 'avt', '0.0'], + ['F', null, 'b2b', '0.0'], + ['F', null, 'b2b', '0.0'], + ['F', null, 'bb1', '0.0'], + ['F', null, 'bea', '0.0'], + ['F', null, 'bee', '0.0'], + ['F', null, 'bes', '0.0'], + ['F', null, 'bip', '0.0'], + ['F', null, 'bon', '0.0'], + ['F', null, 'bor', '0.0'], + ['F', null, 'bou', '0.0'], + ['F', null, 'box', '0.0'], + ['F', null, 'bsc', '0.0'], + ['F', null, 'buk', '0.0'], + ['F', null, 'car', '0.0'], + ['F', null, 'car', '0.0'], + ['F', null, 'cim', '0.0'], + ['F', null, 'cli', '0.0'], + ['F', null, 'cor', '0.0'], + ['F', null, 'cor', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'cub', '0.0'], + ['F', null, 'dar', '0.0'], + ['F', null, 'dev', '0.0'], + ['F', null, 'dir', '0.0'], + ['F', null, 'diz', '0.0'], + ['F', null, 'dov', '0.0'], + ['F', null, 'eas', '0.0'], + ['F', null, 'ems', '0.0'], + ['F', null, 'est', '0.0'], + ['F', null, 'eta', '0.0'], + ['F', null, 'evr', '0.0'], + ['F', null, 'exc', '0.0'], + ['F', null, 'Fv1', '0.0'], + ['F', null, 'fas', '0.0'], + ['F', null, 'fas', '0.0'], + ['F', null, 'fav', '0.0'], + ['F', null, 'faz', '0.0'], + ['F', null, 'fbt', '0.0'], + ['F', null, 'fcz', '0.0'], + ['F', null, 'fcz', '0.0'], + ['F', null, 'fcz', '0.0'], + ['F', null, 'fer', '0.0'], + ['F', null, 'fey', '0.0'], + ['F', null, 'fin', '0.0'], + ['F', null, 'fix', '0.0'], + ['F', null, 'for', '0.0'], + ['F', null, 'for', '0.0'], + ['F', null, 'for', '0.0'], + ['F', null, 'for', '0.0'], + ['F', null, 'for', '0.0'], + ['F', null, 'for', '0.0'], + ['F', null, 'for', '0.0'], + ['F', null, 'for', '0.0'], + ['F', null, 'for', '0.0'], + ['F', null, 'fud', '0.0'], + ['F', null, 'gaz', '0.0'], + ['F', null, 'gen', '0.0'], + ['F', null, 'gra', '0.0'], + ['F', null, 'gru', '0.0'], + ['F', null, 'gru', '0.0'], + ['F', null, 'hic', '0.0'], + ['F', null, 'hic', '0.0'], + ['F', null, 'hic', '0.0'], + ['F', null, 'hom', '0.0'], + ['F', null, 'ibr', '0.0'], + ['F', null, 'ile', '0.0'], + ['F', null, 'imp', '0.0'], + ['F', null, 'imp', '0.0'], + ['F', null, 'inc', '0.0'], + ['F', null, 'ind', '0.0'], + ['F', null, 'inl', '0.0'], + ['F', null, 'int', '0.0'], + ['F', null, 'ipr', '0.0'], + ['F', null, 'irr', '0.0'], + ['F', null, 'ita', '0.0'], + ['F', null, 'k50', '0.0'], + ['F', null, 'k50', '0.0'], + ['F', null, 'k50', '0.0'], + ['F', null, 'k50', '0.0'], + ['F', null, 'k50', '0.0'], + ['F', null, 'k50', '0.0'], + ['F', null, 'kal', '0.0'], + ['F', null, 'kar', '0.0'], + ['F', null, 'kep', '0.0'], + ['F', null, 'kli', '0.0'], + ['F', null, 'kli', '0.0'], + ['F', null, 'kli', '0.0'], + ['F', null, 'kli', '0.0'], + ['F', null, 'kli', '0.0'], + ['F', null, 'kno', '0.0'], + ['F', null, 'kok', '0.0'], + ['F', null, 'kon', '0.0'], + ['F', null, 'kon', '0.0'], + ['F', null, 'kry', '0.0'], + ['F', null, 'kvs', '0.0'], + ['F', null, 'lan', '0.0'], + ['F', null, 'lan', '0.0'], + ['F', null, 'lan', '0.0'], + ['F', null, 'lan', '0.0'], + ['F', null, 'lan', '0.0'], + ['F', null, 'lan', '0.0'], + ['F', null, 'lea', '0.0'], + ['F', null, 'len', '0.0'], + ['F', null, 'lid', '0.0'], + ['F', null, 'llc', '0.0'], + ['F', null, 'log', '0.0'], + ['F', null, 'maf', '0.0'], + ['F', null, 'mai', '0.0'], + ['F', null, 'max', '0.0'], + ['F', null, 'mcr', '0.0'], + ['F', null, 'med', '0.0'], + ['F', null, 'med', '0.0'], + ['F', null, 'med', '0.0'], + ['F', null, 'med', '0.0'], + ['F', null, 'med', '0.0'], + ['F', null, 'med', '0.0'], + ['F', null, 'med', '0.0'], + ['F', null, 'mel', '0.0'], + ['F', null, 'mgb', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgc', '0.0'], + ['F', null, 'mgl', '0.0'], + ['F', null, 'mgm', '0.0'], + ['F', null, 'mgr', '0.0'], + ['F', null, 'mgt', '0.0'], + ['F', null, 'mic', '0.0'], + ['F', null, 'mig', '0.0'], + ['F', null, 'mno', '0.0'], + ['F', null, 'mod', '0.0'], + ['F', null, 'mon', '0.0'], + ['F', null, 'mut', '0.0'], + ['F', null, 'mut', '0.0'], + ['F', null, 'myd', '0.0'], + ['F', null, 'nad', '0.0'], + ['F', null, 'nap', '0.0'], + ['F', null, 'nec', '0.0'], + ['F', null, 'nik', '0.0'], + ['F', null, 'nom', '0.0'], + ['F', null, 'noo', '0.0'], + ['F', null, 'nov', '0.0'], + ['F', null, 'off', '0.0'], + ['F', null, 'okl', '0.0'], + ['F', null, 'ole', '0.0'], + ['F', null, 'omd', '0.0'], + ['F', null, 'osi', '0.0'], + ['F', null, 'osi', '0.0'], + ['F', null, 'osi', '0.0'], + ['F', null, 'osi', '0.0'], + ['F', null, 'pav', '0.0'], + ['F', null, 'pbd', '0.0'], + ['F', null, 'pel', '0.0'], + ['F', null, 'per', '0.0'], + ['F', null, 'per', '0.0'], + ['F', null, 'pob', '0.0'], + ['F', null, 'pod', '0.0'], + ['F', null, 'pon', '0.0'], + ['F', null, 'pri', '0.0'], + ['F', null, 'pro', '0.0'], + ['F', null, 'put', '0.0'], + ['F', null, 'quo', '0.0'], + ['F', null, 'rek', '0.0'], + ['F', null, 'ren', '0.0'], + ['F', null, 'ren', '0.0'], + ['F', null, 'rgs', '0.0'], + ['F', null, 'riv', '0.0'], + ['F', null, 'rob', '0.0'], + ['F', null, 'rol', '0.0'], + ['F', null, 'rub', '0.0'], + ['F', null, 'rum', '0.0'], + ['F', null, 'sav', '0.0'], + ['F', null, 'sel', '0.0'], + ['F', null, 'sel', '0.0'], + ['F', null, 'ser', '0.0'], + ['F', null, 'sfh', '0.0'], + ['F', null, 'she', '0.0'], + ['F', null, 'sht', '0.0'], + ['F', null, 'sib', '0.0'], + ['F', null, 'sil', '0.0'], + ['F', null, 'sin', '0.0'], + ['F', null, 'sin', '0.0'], + ['F', null, 'ski', '0.0'], + ['F', null, 'skl', '0.0'], + ['F', null, 'sma', '0.0'], + ['F', null, 'smi', '0.0'], + ['F', null, 'smi', '0.0'], + ['F', null, 'smi', '0.0'], + ['F', null, 'smi', '0.0'], + ['F', null, 'soc', '0.0'], + ['F', null, 'spb', '0.0'], + ['F', null, 'spb', '0.0'], + ['F', null, 'sta', '0.0'], + ['F', null, 'sta', '0.0'], + ['F', null, 'str', '0.0'], + ['F', null, 'sup', '0.0'], + ['F', null, 't16', '0.0'], + ['F', null, 't20', '0.0'], + ['F', null, 'tas', '0.0'], + ['F', null, 'tea', '0.0'], + ['F', null, 'tol', '0.0'], + ['F', null, 'tra', '0.0'], + ['F', null, 'trc', '0.0'], + ['F', null, 'uis', '0.0'], + ['F', null, 'ule', '0.0'], + ['F', null, 'uly', '0.0'], + ['F', null, 'uly', '0.0'], + ['F', null, 'uly', '0.0'], + ['F', null, 'uni', '0.0'], + ['F', null, 'uni', '0.0'], + ['F', null, 'uni', '0.0'], + ['F', null, 'uni', '0.0'], + ['F', null, 'usm', '0.0'], + ['F', null, 'uyu', '0.0'], + ['F', null, 'van', '0.0'], + ['F', null, 'vid', '0.0'], + ['F', null, 'vol', '0.0'], + ['F', null, 'vyb', '0.0'], + ['F', null, 'wkv', '0.0'], + ['F', null, 'wkv', '0.0'], + ['F', null, 'xtr', '0.0'], + ['F', null, 'yb', '0.0'], + ['F', null, 'yc', '0.0'], + ['F', null, 'ye', '0.0'], + ['F', null, 'yt', '0.0'], + ['F', null, 'ydm', '0.0'], + ['F', null, 'you', '0.0'], + ['F', null, 'zic', '0.0'], + ['F', null, 'zol', '0.0'], + ['F', null, 'zub', '0.0'], + ], + order: [ + { + datasetId: 'kqbhty1yfegke', + title: 'System', + }, + { + datasetId: 'kqbhty1yfegke', + title: 'agency', + }, + { + datasetId: 'kqbhty1yfegke', + title: 'Project', + }, + { + datasetId: 'kqbhty1yfegke', + title: 'title-83a46b70-7a5d-11eb-a88e-9dab961125b2', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + '55fd0f92-bdf4-4157-b76d-bcd9d65352f3': 'Active', + '6456b8f5-42d3-421f-ba92-ba2c43188e5d': 'Events', + '8f16b262-16a5-4d4b-81ec-c41cee743c97': 'Project', + '74692b14-352d-411f-a23f-7afa0f9751ca': 'Service', + '6dd3fa7a-a604-493b-8f96-d0670d3f53b7': 'System', + '723e29c9-c607-437a-a908-d3031bf0510d': 'agency', + '2e04e2fc-4f62-40e9-93bc-5ef67b8a59b5': 'aum', + '12a35383-3897-4a3d-b36a-864eff2caa75': 'aum_active_new', + '83a46b70-7a5d-11eb-a88e-9dab961125b2': 'title-83a46b70-7a5d-11eb-a88e-9dab961125b2', + }, + idToDataType: { + '55fd0f92-bdf4-4157-b76d-bcd9d65352f3': 'string', + '6456b8f5-42d3-421f-ba92-ba2c43188e5d': 'string', + '8f16b262-16a5-4d4b-81ec-c41cee743c97': 'string', + '74692b14-352d-411f-a23f-7afa0f9751ca': 'string', + '6dd3fa7a-a604-493b-8f96-d0670d3f53b7': 'string', + '723e29c9-c607-437a-a908-d3031bf0510d': 'string', + '2e04e2fc-4f62-40e9-93bc-5ef67b8a59b5': 'float', + '12a35383-3897-4a3d-b36a-864eff2caa75': 'float', + '83a46b70-7a5d-11eb-a88e-9dab961125b2': 'float', + }, + ChartEditor: { + getLang: () => 'en', + }, +}; + +export const preparingDataWithShiftedMeasureNames = { + placeholders: [ + { + items: [ + { + type: 'DIMENSION', + title: 'quarter', + guid: 'fd30cec0-678f-11ea-870f-db534f6a4131', + data_type: 'string', + datasetId: '6goomjvpsmp42', + cast: 'string', + }, + ], + }, + { + items: [ + { + title: 'Measure Names', + type: 'PSEUDO', + className: 'item pseudo-item dimension-item', + data_type: 'string', + datasetId: '6goomjvpsmp42', + id: 'inserted-1615462595380', + }, + { + guid: '2df54c97-b8fc-4bd9-8af0-d0bf698dee93', + type: 'DIMENSION', + cast: 'string', + title: 'service', + data_type: 'string', + datasetId: '6goomjvpsmp42', + }, + { + cast: 'string', + guid: '71d629bd-ad18-4e2e-93e3-852ca12ea4ad', + title: 'devicetype', + type: 'DIMENSION', + data_type: 'string', + datasetId: '6goomjvpsmp42', + }, + ], + }, + { + items: [ + { + cast: 'integer', + guid: '61787ee0-679b-11ea-870f-db534f6a4131', + type: 'MEASURE', + title: 'dau', + data_type: 'integer', + datasetId: '6goomjvpsmp42', + }, + { + cast: 'integer', + guid: '7d2fd520-679b-11ea-870f-db534f6a4131', + type: 'MEASURE', + title: 'mau', + data_type: 'integer', + datasetId: '6goomjvpsmp42', + }, + { + type: 'MEASURE', + guid: '3e036e00-679d-11ea-870f-db534f6a4131', + cast: 'float', + data_type: 'float', + title: 'Dau to MAU', + datasetId: '6goomjvpsmp42', + }, + { + type: 'MEASURE', + cast: 'float', + title: 'ts_per_visit', + data_type: 'float', + guid: '59f361e0-75be-11ea-932e-4d7cbc3ea853', + datasetId: '6goomjvpsmp42', + }, + { + cast: 'float', + data_type: 'float', + title: 'hits_per_visit', + guid: 'af1cf5a0-f74b-11ea-9ddb-09794bd875da', + type: 'MEASURE', + datasetId: '6goomjvpsmp42', + }, + { + type: 'MEASURE', + guid: '5de485e0-679f-11ea-870f-db534f6a4131', + cast: 'float', + data_type: 'float', + title: 'Logged in, %', + datasetId: '6goomjvpsmp42', + }, + { + guid: '8f634d30-6791-11ea-870f-db534f6a4131', + type: 'MEASURE', + cast: 'float', + title: 'Visits YoY, %', + data_type: 'float', + datasetId: '6goomjvpsmp42', + }, + { + guid: 'd759d910-6791-11ea-870f-db534f6a4131', + type: 'MEASURE', + cast: 'float', + title: 'DAU YoY, %', + data_type: 'float', + datasetId: '6goomjvpsmp42', + }, + { + type: 'MEASURE', + cast: 'float', + title: 'MAU YoY, %', + data_type: 'float', + guid: 'f36283a0-6791-11ea-870f-db534f6a4131', + datasetId: '6goomjvpsmp42', + }, + { + type: 'MEASURE', + cast: 'float', + title: 'Timespent per visit YoY, %', + data_type: 'float', + guid: 'f9ab18f0-75bd-11ea-932e-4d7cbc3ea853', + datasetId: '6goomjvpsmp42', + }, + { + cast: 'float', + data_type: 'float', + guid: 'ded17be0-f74b-11ea-9ddb-09794bd875da', + type: 'MEASURE', + title: 'Hits per visit YoY, %', + datasetId: '6goomjvpsmp42', + }, + ], + }, + ], + resultData: { + data: [ + [ + '2020 Q3', + 'news', + 'desktop', + '3574310', + '25297239', + '0.14', + '239.95', + '2.056748458090112', + '49.6', + '-15.1', + '-12.6', + '-22.1', + '-16.1', + '-15.21', + ], + [ + '2020 Q3', + 'news', + 'tablet', + '424458', + '3132014', + '0.14', + '267.02', + '2.481435352570215', + '29.3', + '-15.0', + '-14.6', + '-27.3', + '-10.02', + '-11.32', + ], + [ + '2020 Q3', + 'news', + 'touch', + '8557825', + '50479601', + '0.17', + '211.1', + '2.9068003918111165', + '41.1', + '27.5', + '26.7', + '8.3', + '-0.42', + '9.96', + ], + [ + '2020 Q3', + 'sport', + 'desktop', + '259697', + '3482930', + '0.07', + '145.32', + '1.6551254064200056', + '49.7', + '38.4', + '42.6', + '46.6', + '-23.05', + '-23.56', + ], + [ + '2020 Q3', + 'sport', + 'tablet', + '34831', + '445061', + '0.08', + '158.35', + '1.7950364580404505', + '29.6', + '44.3', + '48.4', + '38.7', + '-20.45', + '-23.17', + ], + [ + '2020 Q3', + 'sport', + 'touch', + '682765', + '7673198', + '0.09', + '131.35', + '2.3777301948573446', + '42.0', + '487.7', + '467.9', + '306.4', + '0.56', + '5.25', + ], + [ + '2020 Q4', + 'news', + 'desktop', + '3767506', + '28514803', + '0.13', + '242.62', + '2.0643503007200543', + '50.2', + '-5.7', + '-4.5', + '-12.4', + '-15.36', + '-13.63', + ], + [ + '2020 Q4', + 'news', + 'tablet', + '455526', + '3516476', + '0.13', + '274.97', + '2.539370256384438', + '28.9', + '2.9', + '0.7', + '-10.9', + '-7.38', + '-8.58', + ], + [ + '2020 Q4', + 'news', + 'touch', + '8875736', + '53974662', + '0.16', + '212.42', + '2.8934132850945926', + '40.3', + '54.6', + '46.0', + '26.1', + '-2.43', + '8.82', + ], + [ + '2020 Q4', + 'sport', + 'desktop', + '347927', + '4593959', + '0.08', + '148.55', + '1.6432479594125688', + '50.8', + '9.1', + '9.0', + '22.3', + '2.18', + '12.82', + ], + [ + '2020 Q4', + 'sport', + 'tablet', + '47537', + '562450', + '0.08', + '163.66', + '1.8028434051640705', + '29.7', + '34.3', + '31.1', + '31.5', + '11.34', + '14.14', + ], + [ + '2020 Q4', + 'sport', + 'touch', + '880925', + '8976486', + '0.1', + '131.27', + '2.3223845969698003', + '41.2', + '137.3', + '123.7', + '129.4', + '44.41', + '49.07', + ], + [ + '2021 Q1', + 'news', + 'desktop', + '3142528', + '25295557', + '0.12', + '243.79', + '2.056381280831942', + '50.2', + '-20.8', + '-18.7', + '-18.4', + '-11.91', + '-9.89', + ], + [ + '2021 Q1', + 'news', + 'tablet', + '424900', + '3297679', + '0.13', + '283.13', + '2.5498995616680142', + '29.3', + '-5.8', + '-8.4', + '-15.8', + '-2.28', + '-6.71', + ], + [ + '2021 Q1', + 'news', + 'touch', + '8544785', + '51343716', + '0.17', + '216.67', + '2.913424973502825', + '40.5', + '29.9', + '24.7', + '13.1', + '-3.56', + '3.78', + ], + [ + '2021 Q1', + 'sport', + 'desktop', + '263891', + '3766835', + '0.07', + '147.64', + '1.6719273950334697', + '49.5', + '-41.4', + '-40.9', + '-35.8', + '9.76', + '14.18', + ], + [ + '2021 Q1', + 'sport', + 'tablet', + '44224', + '538646', + '0.08', + '166.76', + '1.83358761001671', + '29.1', + '-16.6', + '-18.4', + '-21.8', + '20.14', + '17.58', + ], + [ + '2021 Q1', + 'sport', + 'touch', + '828676', + '8284665', + '0.1', + '132.43', + '2.3766670501280425', + '39.9', + '-0.7', + '-4.4', + '-7.6', + '28.24', + '50.06', + ], + ], + order: [ + { + datasetId: '6goomjvpsmp42', + title: 'quarter', + }, + { + datasetId: '6goomjvpsmp42', + title: 'service', + }, + { + datasetId: '6goomjvpsmp42', + title: 'devicetype', + }, + { + datasetId: '6goomjvpsmp42', + title: 'dau', + }, + { + datasetId: '6goomjvpsmp42', + title: 'mau', + }, + { + datasetId: '6goomjvpsmp42', + title: 'Dau to MAU', + }, + { + datasetId: '6goomjvpsmp42', + title: 'ts_per_visit', + }, + { + datasetId: '6goomjvpsmp42', + title: 'hits_per_visit', + }, + { + datasetId: '6goomjvpsmp42', + title: 'Logged in, %', + }, + { + datasetId: '6goomjvpsmp42', + title: 'Visits YoY, %', + }, + { + datasetId: '6goomjvpsmp42', + title: 'DAU YoY, %', + }, + { + datasetId: '6goomjvpsmp42', + title: 'MAU YoY, %', + }, + { + datasetId: '6goomjvpsmp42', + title: 'Timespent per visit YoY, %', + }, + { + datasetId: '6goomjvpsmp42', + title: 'Hits per visit YoY, %', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: {}, + idToTitle: { + '6306a820-75c1-11ea-8cc6-cf00598ef3e9': 'Month_str', + '66fec4b0-75be-11ea-932e-4d7cbc3ea853': 'ts_per_visit_year_ago', + 'b0f474c0-f74b-11ea-9ddb-09794bd875da': 'hits_per_visit_year_ago', + '59f361e0-75be-11ea-932e-4d7cbc3ea853': 'ts_per_visit', + 'af1cf5a0-f74b-11ea-9ddb-09794bd875da': 'hits_per_visit', + 'f9ab18f0-75bd-11ea-932e-4d7cbc3ea853': 'Timespent per visit YoY, %', + 'ded17be0-f74b-11ea-9ddb-09794bd875da': 'Hits per visit YoY, %', + 'aba83350-75bc-11ea-932e-4d7cbc3ea853': 'SPU week YoY, %', + '92f1e810-75bc-11ea-932e-4d7cbc3ea853': 'wau_year_ago', + '75317b60-75bc-11ea-932e-4d7cbc3ea853': 'spu_week_year_ago', + '259d7900-75bc-11ea-932e-4d7cbc3ea853': 'spu_week', + 'ad366440-67a1-11ea-870f-db534f6a4131': 'wau', + '5de485e0-679f-11ea-870f-db534f6a4131': 'Logged in, %', + '3e036e00-679d-11ea-870f-db534f6a4131': 'Dau to MAU', + '7d2fd520-679b-11ea-870f-db534f6a4131': 'mau', + '61787ee0-679b-11ea-870f-db534f6a4131': 'dau', + '0eec3a30-6792-11ea-870f-db534f6a4131': 'WAU YoY, %', + 'f36283a0-6791-11ea-870f-db534f6a4131': 'MAU YoY, %', + 'bb99f750-6791-11ea-870f-db534f6a4131': 'spu_year_ago', + 'aa686a70-6791-11ea-870f-db534f6a4131': 'spu', + 'fd30cec0-678f-11ea-870f-db534f6a4131': 'quarter', + 'c9878b80-678b-11ea-ae05-7f5a1a4cf9c5': 'users_mau_yoy', + '868ed6f0-6789-11ea-ae05-7f5a1a4cf9c5': 'is_eofmonth', + '25c3b120-6783-11ea-ae05-7f5a1a4cf9c5': 'is_sunday', + '13245ae0-676c-11ea-9fcc-e7ee62cb2819': 'week', + 'feb5f280-676b-11ea-9fcc-e7ee62cb2819': 'month', + 'e90427e0-633d-11ea-b4eb-637d61c2bb81': 'users_wau_yoy', + 'ba01b390-633d-11ea-b4eb-637d61c2bb81': 'users_yoy', + 'd759d910-6791-11ea-870f-db534f6a4131': 'DAU YoY, %', + '8f634d30-6791-11ea-870f-db534f6a4131': 'Visits YoY, %', + '2e50985b-f169-4f92-9628-21fb5c87386d': 'morda_content', + '2df54c97-b8fc-4bd9-8af0-d0bf698dee93': 'service', + 'bc9608a1-4144-49af-890f-a8394c1042ad': 'day', + '0e910773-6c02-4ad8-a478-5a30383a0cab': 'browser', + '71d629bd-ad18-4e2e-93e3-852ca12ea4ad': 'devicetype', + '7e1e23c6-9767-4548-a017-1ad599d1717f': 'os', + '7892e91f-ef03-42b5-b737-56e6ff5e67ab': 'country', + '6845061b-731c-451c-b39b-163182e22e78': 'sessions_longer_than_5', + 'd8ec28b8-e532-43c2-ba0f-d2383f207e80': 'hits_year_ago', + 'e9ef1ad2-0b62-4dfa-9992-274e6708603b': 'sessions_longer_than_5_year_ago', + '171f175d-2b1b-4ba6-92d6-13045157f123': 'visits', + '87bfb207-b38d-41a2-a75a-e89692448239': 'users', + '258d17b8-032e-482b-a763-8bb2e9792dbc': 'hits', + '921a6557-d221-4a31-b47a-d1eb226f0996': 'users_wau_year_ago', + '508717d1-2290-4f4f-b695-c6d9f47453b5': 'users_wau', + '2e2b48f0-e3b8-49ca-a1e0-d8638ec732d1': 'timespent', + '5b542707-b315-410f-8f32-4b7ac6727258': 'users_mau', + '11cc4cdf-c624-4fd4-a1df-7b8e65615295': 'sessions_longer_than_120_year_ago', + '342438a7-26b0-47ec-b846-277fab54d40e': 'sessions_longer_than_120', + '92ba5793-bb00-43cd-8d50-901bcbea9924': 'logged_in_visits', + '9771fc5e-fa23-4359-b7aa-00fae045c88e': 'timespent_year_ago', + 'f1cb1d72-5fe0-4c29-a732-e7cef6faea2c': 'logged_in_users_year_ago', + 'adbd87ab-0829-47ff-8f7e-54bfd5897c36': 'logged_in_users', + 'be815136-1105-4e06-93a2-1bf83d07b00b': 'visits_year_ago', + 'e709abbd-8f80-4fea-8e9e-9f37e428ef80': 'users_mau_year_ago', + 'ca54d2cd-daa5-4f69-b2c4-21caabdc40d5': 'logged_in_visits_year_ago', + '31eacf3c-055d-40c9-932e-07492a188390': 'users_year_ago', + }, + idToDataType: { + '6306a820-75c1-11ea-8cc6-cf00598ef3e9': 'string', + '66fec4b0-75be-11ea-932e-4d7cbc3ea853': 'float', + 'b0f474c0-f74b-11ea-9ddb-09794bd875da': 'float', + '59f361e0-75be-11ea-932e-4d7cbc3ea853': 'float', + 'af1cf5a0-f74b-11ea-9ddb-09794bd875da': 'float', + 'f9ab18f0-75bd-11ea-932e-4d7cbc3ea853': 'float', + 'ded17be0-f74b-11ea-9ddb-09794bd875da': 'float', + 'aba83350-75bc-11ea-932e-4d7cbc3ea853': 'float', + '92f1e810-75bc-11ea-932e-4d7cbc3ea853': 'integer', + '75317b60-75bc-11ea-932e-4d7cbc3ea853': 'float', + '259d7900-75bc-11ea-932e-4d7cbc3ea853': 'float', + 'ad366440-67a1-11ea-870f-db534f6a4131': 'integer', + '5de485e0-679f-11ea-870f-db534f6a4131': 'float', + '3e036e00-679d-11ea-870f-db534f6a4131': 'float', + '7d2fd520-679b-11ea-870f-db534f6a4131': 'integer', + '61787ee0-679b-11ea-870f-db534f6a4131': 'integer', + '0eec3a30-6792-11ea-870f-db534f6a4131': 'float', + 'f36283a0-6791-11ea-870f-db534f6a4131': 'float', + 'bb99f750-6791-11ea-870f-db534f6a4131': 'float', + 'aa686a70-6791-11ea-870f-db534f6a4131': 'float', + 'fd30cec0-678f-11ea-870f-db534f6a4131': 'string', + 'c9878b80-678b-11ea-ae05-7f5a1a4cf9c5': 'float', + '868ed6f0-6789-11ea-ae05-7f5a1a4cf9c5': 'integer', + '25c3b120-6783-11ea-ae05-7f5a1a4cf9c5': 'integer', + '13245ae0-676c-11ea-9fcc-e7ee62cb2819': 'date', + 'feb5f280-676b-11ea-9fcc-e7ee62cb2819': 'date', + 'e90427e0-633d-11ea-b4eb-637d61c2bb81': 'float', + 'ba01b390-633d-11ea-b4eb-637d61c2bb81': 'float', + 'd759d910-6791-11ea-870f-db534f6a4131': 'float', + '8f634d30-6791-11ea-870f-db534f6a4131': 'float', + '2e50985b-f169-4f92-9628-21fb5c87386d': 'string', + '2df54c97-b8fc-4bd9-8af0-d0bf698dee93': 'string', + 'bc9608a1-4144-49af-890f-a8394c1042ad': 'date', + '0e910773-6c02-4ad8-a478-5a30383a0cab': 'string', + '71d629bd-ad18-4e2e-93e3-852ca12ea4ad': 'string', + '7e1e23c6-9767-4548-a017-1ad599d1717f': 'string', + '7892e91f-ef03-42b5-b737-56e6ff5e67ab': 'string', + '6845061b-731c-451c-b39b-163182e22e78': 'integer', + 'd8ec28b8-e532-43c2-ba0f-d2383f207e80': 'integer', + 'e9ef1ad2-0b62-4dfa-9992-274e6708603b': 'integer', + '171f175d-2b1b-4ba6-92d6-13045157f123': 'integer', + '87bfb207-b38d-41a2-a75a-e89692448239': 'integer', + '258d17b8-032e-482b-a763-8bb2e9792dbc': 'integer', + '921a6557-d221-4a31-b47a-d1eb226f0996': 'integer', + '508717d1-2290-4f4f-b695-c6d9f47453b5': 'integer', + '2e2b48f0-e3b8-49ca-a1e0-d8638ec732d1': 'float', + '5b542707-b315-410f-8f32-4b7ac6727258': 'integer', + '11cc4cdf-c624-4fd4-a1df-7b8e65615295': 'integer', + '342438a7-26b0-47ec-b846-277fab54d40e': 'integer', + '92ba5793-bb00-43cd-8d50-901bcbea9924': 'integer', + '9771fc5e-fa23-4359-b7aa-00fae045c88e': 'float', + 'f1cb1d72-5fe0-4c29-a732-e7cef6faea2c': 'integer', + 'adbd87ab-0829-47ff-8f7e-54bfd5897c36': 'integer', + 'be815136-1105-4e06-93a2-1bf83d07b00b': 'integer', + 'e709abbd-8f80-4fea-8e9e-9f37e428ef80': 'integer', + 'ca54d2cd-daa5-4f69-b2c4-21caabdc40d5': 'integer', + '31eacf3c-055d-40c9-932e-07492a188390': 'integer', + }, + ChartEditor: { + getLang: () => 'en', + }, +}; + +export const preparingDataWithDataByNullKeys = { + placeholders: [ + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'pivot-table-columns', + type: 'pivot-table-columns', + title: 'section_columns', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + title: 'Measure Names', + type: 'PSEUDO', + className: 'item pseudo-item dimension-item', + data_type: 'string', + id: 'inserted-1615557201370', + }, + ], + capacity: null, + settings: {}, + }, + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'rows', + type: 'rows', + title: 'section_rows', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + aggregation_locked: false, + autoaggregated: false, + managed_by: 'user', + avatar_id: 'aac134d0-eb3a-11e9-8282-bb1239b8c85a', + data_type: 'string', + title: 'operator_name', + source: 'operator_name', + initial_data_type: 'string', + cast: 'string', + has_auto_aggregation: false, + aggregation: 'none', + formula: '', + lock_aggregation: false, + valid: true, + hidden: false, + type: 'DIMENSION', + description: '', + calc_mode: 'direct', + guid: '4e6654e9-156c-4768-93c4-62ddd9726b01', + virtual: false, + datasetId: '3kcs27fvs3t40', + id: 'dimension-91', + datasetName: 'booking_conversions', + }, + ], + capacity: null, + settings: {}, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + icon: { + key: null, + ref: null, + props: { + width: '24', + }, + _owner: null, + _store: {}, + }, + items: [ + { + title: 'order_num', + virtual: false, + calc_mode: 'formula', + formula: 'COUNT_IF(NOT ISNULL([order_status]))', + source: '', + initial_data_type: 'integer', + guid: '65b4d6e0-ec06-11e9-b132-cd1da1ed493f', + aggregation_locked: true, + cast: 'integer', + valid: true, + data_type: 'integer', + hidden: false, + avatar_id: null, + lock_aggregation: false, + managed_by: 'user', + description: '', + autoaggregated: true, + has_auto_aggregation: true, + aggregation: 'none', + type: 'MEASURE', + datasetId: '3kcs27fvs3t40', + id: 'inserted-1615555660640', + datasetName: 'booking_conversions', + }, + { + title: 'title-242f54f0-6c4f-11eb-9403-6ff7759f0178', + virtual: false, + calc_mode: 'direct', + formula: '', + source: 'order_amount_rub', + initial_data_type: 'float', + guid: '242f54f0-6c4f-11eb-9403-6ff7759f0178', + aggregation_locked: false, + cast: 'float', + valid: true, + data_type: 'float', + hidden: false, + avatar_id: 'aac134d0-eb3a-11e9-8282-bb1239b8c85a', + lock_aggregation: false, + managed_by: 'user', + description: '', + autoaggregated: false, + has_auto_aggregation: false, + aggregation: 'sum', + type: 'MEASURE', + datasetId: '3kcs27fvs3t40', + id: 'dimension-89', + datasetName: 'booking_conversions', + fakeTitle: 'order_amount_rub', + originalTitle: 'order_amount_rub', + quickFormula: true, + local: true, + }, + ], + capacity: null, + settings: {}, + }, + ], + resultData: { + data: [ + ['101Hotels.ru', '7308', '53862048.519999996'], + ['AMOMA.COM', '76', '255425.39627437002'], + ['Accorhotels.com', '840', '1710295.77477938'], + ['Agoda.com', '13969', '10374752.323177468'], + ['Booking.com', '310736', '3419579449.3565755'], + ['Hotels.com', '3862', '5454466.423210201'], + ['Ostrovok.ru', '37301', '400476085.93000007'], + ['Otel.com', '0', null], + ['Prestigia.com', '15', '54381.58974838001'], + ['Revato', '5', '10354.031375679999'], + ['Tvil.ru', '4073', '78181774.0'], + ['OneTwoTrip', '28814', '410986167.64'], + [null, '177689', '2433471983.5393696'], + ], + order: [ + { + datasetId: '3kcs27fvs3t40', + title: 'operator_name', + }, + { + datasetId: '3kcs27fvs3t40', + title: 'order_num', + }, + { + datasetId: '3kcs27fvs3t40', + title: 'title-242f54f0-6c4f-11eb-9403-6ff7759f0178', + }, + ], + }, + colors: [], + sort: [], + colorsConfig: { + palette: 'defaultScheme', + mountedColors: { + conversion: '#FF3D64', + redirect_num: '#1F68A9', + }, + fieldGuid: '42c469e0-ec09-11e9-b132-cd1da1ed493f', + coloredByMeasure: true, + }, + idToTitle: { + 'fc1d78b0-8270-11eb-8f57-7947fc1d0a86': 'is_order_deferred', + '35e7c3a0-71b6-11eb-8775-476bbb97b6e1': 'confirmed_order_num', + '1c5dbe40-388a-11eb-ad96-37a72d01149c': 'boy_order_share', + '4b344870-151b-11eb-b76d-cbee14f15349': 'order_promo_codes_source', + '908023e0-092c-11eb-afbf-d3cc0b4e8fb3': 'confirmed_profit_share', + '1806c540-092c-11eb-afbf-d3cc0b4e8fb3': 'confirmed_order_amount_share', + 'c0581800-0929-11eb-afbf-d3cc0b4e8fb3': 'confirmed_rate', + '23f1a440-0929-11eb-afbf-d3cc0b4e8fb3': 'confirmed_profit_ex_tax_share', + '88ff23f0-ff45-11ea-b678-2dcb464a1d1d': 'redirs_for_ab_comparison', + '1cb6d7a0-f848-11ea-a69d-81f2413a9448': 'length_of_stay_classified', + '35cb9570-f017-11ea-b309-fd721c0b2179': 'same_region_prop', + '82930a80-edcb-11ea-ad76-ffcc1a538385': 'is_portal_order', + '6547cf10-ed21-11ea-a1f4-cf5cba291d11': 'one_night_proportion', + '913d5cf0-ec2e-11ea-986e-e95e1338b28c': 'order_profit_percent', + '626b6000-eb95-11ea-b807-9db21843fbe0': 'fielddate__ms', + '5d96a540-e90a-11ea-a2a9-59647c8860ce': 'median_length_of_stay', + '914c6120-e849-11ea-99e3-13f695ba189d': 'boy_ratio', + '2e583cc0-e6c2-11ea-bd4f-ff9d3d5fb83e': 'is_boy_partner', + 'f78a2bb0-dafd-11ea-94de-ef023f70032a': 'Type_country', + '5dd417e0-c204-11ea-ad8f-7b0ac215bcaa': 'hotel_city_or_region', + '034f6e30-aef1-11ea-83a8-affbe88081f0': 'redirects_per_user', + 'd24d0b30-aef0-11ea-83a8-affbe88081f0': 'num_of_users', + '55da7bb0-9040-11ea-a754-3570ad7dbefb': 'horizon_by_order', + '10abe130-89e4-11ea-88af-7322b2368d02': 'horizon_classified', + '93b7ece0-8151-11ea-8562-0d276a18089a': 'hotel_rubric_frequent', + 'd238e190-807a-11ea-8ee9-8fa4e5986b2c': 'order_created_at_int', + '7fac2920-62a0-11ea-9141-5d74a5e8c6ee': 'same_region', + 'b9696e20-41b2-11ea-8024-29d58105b4ea': 'small_horizon', + '23b9de40-41ae-11ea-9cb9-a595b886d1bf': 'is_russia', + '67d286c0-32d3-11ea-89be-85468591c684': 'date_plus_year', + '5f305170-1058-11ea-ab65-096a3dde920d': 'ytravel_partner', + '871ca0a0-07a5-11ea-b1f1-09c513881bef': 'nbc_gbv', + '003d9aa0-078a-11ea-b1f1-09c513881bef': 'order_year', + '86b31a20-0789-11ea-b1f1-09c513881bef': 'checkout_in_the_past', + '131e5690-f651-11e9-a82d-eb02b41d1cd9': 'last_week_order_created_at', + '8cc00f30-ee67-11e9-8ad6-2dcfd776711b': 'checkout_period', + '300dbe40-ee67-11e9-8ad6-2dcfd776711b': 'cancellation_rate', + '1b88c0a0-ee67-11e9-8ad6-2dcfd776711b': 'num_of_cancelled', + '4527d850-ee59-11e9-8ad6-2dcfd776711b': 'date_mismatch', + '08b2f770-ee58-11e9-8ad6-2dcfd776711b': 'last_month_order_created_at', + 'a65d7630-ee53-11e9-8ad6-2dcfd776711b': 'traffic_source', + '266b1900-ee53-11e9-8ad6-2dcfd776711b': 'platform', + 'a57ce320-ec43-11e9-ac66-77c3941d51a8': 'NBC (Net Booked Comission)', + '7ddd7460-ec43-11e9-ac66-77c3941d51a8': 'NBC (Net Booked Comission) excluding VAT', + '364f9560-ec43-11e9-ac66-77c3941d51a8': 'GBV (Gross Booked Volume)', + '09cdff90-ec43-11e9-ac66-77c3941d51a8': 'ADR-2', + 'e2f38e80-ec42-11e9-ac66-77c3941d51a8': 'ALoS (Average Length of Stay)', + 'bb9aecc0-ec42-11e9-ac66-77c3941d51a8': 'ADR (Average Daily Rate)', + 'a72187e0-ec42-11e9-ac66-77c3941d51a8': 'order_amount_per_night', + '1ff509e0-ec42-11e9-ac66-77c3941d51a8': 'length_of_stay', + 'a927fbb0-ec41-11e9-ac66-77c3941d51a8': 'AOV (average order value)', + 'fcfe9880-ec3b-11e9-ac66-77c3941d51a8': 'date', + 'fb730bf0-ec3a-11e9-ac66-77c3941d51a8': 'last_month', + '49257280-ec3a-11e9-ac66-77c3941d51a8': 'order_created_date', + '8cc83c00-ec37-11e9-ac66-77c3941d51a8': 'partner', + 'f8425f10-ec32-11e9-ac66-77c3941d51a8': 'eCPC', + 'd6b7a3f0-ec32-11e9-ac66-77c3941d51a8': 'GBC (Gross Booked Comission)', + '42c469e0-ec09-11e9-b132-cd1da1ed493f': 'conversion', + '1df7bfa0-ec08-11e9-b132-cd1da1ed493f': 'redirect_num', + '65b4d6e0-ec06-11e9-b132-cd1da1ed493f': 'order_num', + '715f5f60-193c-11eb-909d-0930749fc902': 'portal_order_num', + 'f7fdb140-19b7-11eb-909d-0930749fc902': 'portal_order_num_test_buckets', + '4470f6a0-193f-11eb-909d-0930749fc902': 'boy_order_num', + '31a561f0-1058-11ea-ab65-096a3dde920d': 'ytravel_order_num', + '5d2663c1-c7e5-44a3-a12d-7eb1c4a746e2': 'source', + '76622e3c-489b-40b9-9721-fa3cb470b092': 'reqid', + '59b4e126-5f29-4fd8-a447-6aee5dadded6': 'hotel_region', + '2144f21e-cd6a-456d-90f9-e70bfbd33dc2': 'order_profit_amount_ex_tax', + '78b732c9-445a-4161-b0e2-3ed0b9554e41': 'check_out', + 'a75cb9a4-654f-4a4b-86a9-dc2028eac36e': 'offer_id', + 'b6dcabe5-8a03-4131-88ca-639051338d07': 'operator_id', + '1f7ad85c-2d63-4437-8d9e-e1088679fb16': 'order_created_at', + '81e5ff24-164d-46cf-8ba5-28c377d1c087': 'label', + '5908b958-2edf-4713-8f09-f9e043e1c3d0': 'checkin_date', + 'c0f3ab19-a665-4824-89d0-85b557510339': 'order_partner_name', + 'f7386075-0ed6-4d36-8f07-ea26a72459c9': 'hotel_rubric_name', + '46e2d3e2-26b9-496b-a0e6-9937bce7a150': 'uid', + '68aa9580-8f44-43e2-b7d7-66c4e3b7125c': 'test_ids', + 'd2f59507-11fb-4213-bfa5-db691afadaa1': 'hotel_geo_id', + '28f20776-602d-487e-b738-9dd5e53cf0a0': 'medium', + '23668919-6c6c-4696-a614-4412d0864bc4': 'hotel_coords', + 'feceddc7-bda6-4a9a-b92e-dfbfc56fe1cc': 'hotel_city', + 'abb35b53-9288-4a7a-a91d-528aa6544e66': 'price', + 'd217a40c-e128-414a-854c-415e6d57b67a': 'partner_code', + '45f54b1c-20ce-4d0a-8765-2349ce06b747': 'hotel_name', + 'ada13bb4-c5f8-487b-a2de-c0eb4e4073f8': 'hotel_rubric_permalink', + '763fea51-f5a6-43e7-bb8e-8dfe514be44d': 'icookie', + '7c045fae-c6e8-4406-8a0c-21da608fc077': 'order_updated_at', + '4011ae32-b037-4f66-9907-0e1f4befc778': 'permalink', + 'b8dad928-7eff-4c46-82fc-d309fd9a4749': 'order_amount_rub', + 'db119ef1-5513-46aa-ab35-7120d949926d': 'user_region_coords', + '33d16f5f-df22-4a55-8d43-c02e8839fcc4': 'clientid', + '4e6654e9-156c-4768-93c4-62ddd9726b01': 'operator_name', + '634702b1-80b0-473c-9fec-680a96bf6021': 'order_horizon', + '13e9608d-7320-40ac-8fda-2fc2e3d1a339': 'user_region_country', + '6c3e2590-7dab-4eb1-8eac-6f36ef6c3efc': 'partner_id', + 'c8569a48-1909-4521-a8cc-efdfc9c3c9d0': 'passport_uid', + '87470f3c-d184-4522-bf74-39204257b606': 'campaign', + 'abac2653-c8eb-498b-b935-656468d897aa': 'cluster_permalink', + 'f3b531b3-f079-41cd-a041-320ed4355a47': 'nights', + '4b172266-6e27-4985-8f0d-2e7fdeaaceeb': 'content', + '2d6aee5e-12fc-4900-be0e-57f7a6ece730': 'query', + 'e92a6aff-63e7-4dd5-85ad-a24d0df9f952': 'uuid', + '9cf7d1e8-ef0e-41aa-a036-4c32a9c85e11': 'user_region', + '100b99eb-e130-439f-a4d4-7565ae625578': 'order_status', + '4f16081a-29d7-441d-b941-26e1d5960bbe': 'occupancy', + '4623fa4d-e037-4bdd-a103-b0949cf5af38': 'hotel_country', + '87dbc168-77f3-4733-981a-4ada2be166f8': 'redirect_period_in_seconds', + 'c623e05a-b310-4b0e-9c17-a8570a8fe978': 'order_is_suspicious', + 'e88cc473-5292-486f-b5a7-bceb1c6a876a': 'redirect_count', + '758e1b9b-9481-4bd0-b7f6-528f84c88703': 'hotel_is_platinum', + 'e4566919-026f-4255-a888-74187e715755': 'cache_timestamp', + '05695e95-d462-457f-8540-3d2974433f7a': 'user_region_city', + '03718a73-43e6-45c8-b800-b3dbd219ed6d': 'redirect_timestamp', + 'a8f4bcd4-c4b1-4115-b7ba-1a695574bc2b': 'check_in', + 'e00d7080-9182-4696-97a7-dd9fb4bfb5bd': 'hotel_status', + '09ecd19f-4367-4f32-9996-b25687d9984b': 'horizon_in_days', + 'fce3d228-6c48-4c32-9e05-7c23a281e62b': 'redirect_date', + 'cdb3350d-e6b6-496e-aafa-798c59105db5': 'delta_in_seconds', + 'effd58fc-0356-4756-9829-ce791bbeedbd': 'order_first_seen_at', + '7f1726d6-ea9e-4cf5-aa69-a7e12ca25287': 'order_profit_amount_rub', + '195e1acc-cc55-4036-ae50-4869f5cfa6ea': 'hotel_popularity', + '0ec278e0-6749-4b7a-a24c-0aaa45c9637c': 'user_region_region', + '889629e4-8ebb-4a1e-967f-ea6031d10c1c': 'origin', + 'c5697ef9-6829-4523-81f1-ee28c66024f6': 'hotel_stars', + '005f675d-3e08-4707-8f53-3165397b1daf': 'offer_source', + '44379e0e-3500-4e8d-8c50-f66e694a2978': 'order_partner_order_id', + 'd5869bad-4330-40f7-baaa-7e276308583c': 'Surface', + '2737ce53-41ea-4077-bd3a-1555e4871026': 'datefield', + 'c8377b3f-4245-4824-aec0-f64831e99b33': 'order_date', + '663e1cbb-75ae-4a28-bcce-7639698998c1': 'test_buckets', + '532fb44b-b49e-4be7-93de-d2f360d7b1d3': 'order_promo_codes', + 'ea2c594e-a927-486c-ac7e-7b1c5761771e': 'order_discount_amount', + '8da311ac-f821-4669-8c74-ae845a19ad07': 'order_privetmir_oct2020_eligible', + '762b5fc3-cfcf-45a4-adf8-f6ec0ac6ba60': 'is_staff_user', + '782643ca-250c-4eae-8ce5-dfb7b65a1eed': 'order_amount_payable', + '2e691b83-3cdb-4103-882c-4d462858db49': 'order_amount_received_from_user', + '6f2f6e0e-491d-4d66-a2ec-116b88b7cc52': 'order_deferred_payment_eligibility', + '6cf909a2-8ef7-46a3-8f81-5e37b1c41d49': 'order_fully_paid_at', + '4a537af9-e8bc-4695-a486-418fc35771a1': 'order_initial_payment_amount', + '5da6c555-45da-43b6-941d-b3f0e77d67e7': 'order_last_payment_scheduled_at', + 'df8a3b07-45a8-4977-b54f-7d64af91752b': 'order_penalty_amount_if_unpaid', + '970e0cf9-068f-4d71-b50a-43232976861a': 'order_promo_actions', + '25f96a8b-0d56-411c-a342-c73c4842f593': 'order_refund_reason', + '66819fd9-be85-405e-9346-7098eea01711': 'order_use_deferred_payment', + '7f362e72-c52a-435d-8590-5e4175c7dadc': 'crypta_id', + '242f54f0-6c4f-11eb-9403-6ff7759f0178': 'title-242f54f0-6c4f-11eb-9403-6ff7759f0178', + }, + idToDataType: { + 'fc1d78b0-8270-11eb-8f57-7947fc1d0a86': 'string', + '35e7c3a0-71b6-11eb-8775-476bbb97b6e1': 'integer', + '1c5dbe40-388a-11eb-ad96-37a72d01149c': 'float', + '4b344870-151b-11eb-b76d-cbee14f15349': 'string', + '908023e0-092c-11eb-afbf-d3cc0b4e8fb3': 'float', + '1806c540-092c-11eb-afbf-d3cc0b4e8fb3': 'float', + 'c0581800-0929-11eb-afbf-d3cc0b4e8fb3': 'float', + '23f1a440-0929-11eb-afbf-d3cc0b4e8fb3': 'float', + '88ff23f0-ff45-11ea-b678-2dcb464a1d1d': 'integer', + '1cb6d7a0-f848-11ea-a69d-81f2413a9448': 'string', + '35cb9570-f017-11ea-b309-fd721c0b2179': 'float', + '82930a80-edcb-11ea-ad76-ffcc1a538385': 'string', + '6547cf10-ed21-11ea-a1f4-cf5cba291d11': 'float', + '913d5cf0-ec2e-11ea-986e-e95e1338b28c': 'float', + '626b6000-eb95-11ea-b807-9db21843fbe0': 'integer', + '5d96a540-e90a-11ea-a2a9-59647c8860ce': 'integer', + '914c6120-e849-11ea-99e3-13f695ba189d': 'float', + '2e583cc0-e6c2-11ea-bd4f-ff9d3d5fb83e': 'string', + 'f78a2bb0-dafd-11ea-94de-ef023f70032a': 'string', + '5dd417e0-c204-11ea-ad8f-7b0ac215bcaa': 'string', + '034f6e30-aef1-11ea-83a8-affbe88081f0': 'float', + 'd24d0b30-aef0-11ea-83a8-affbe88081f0': 'integer', + '55da7bb0-9040-11ea-a754-3570ad7dbefb': 'integer', + '10abe130-89e4-11ea-88af-7322b2368d02': 'string', + '93b7ece0-8151-11ea-8562-0d276a18089a': 'string', + 'd238e190-807a-11ea-8ee9-8fa4e5986b2c': 'integer', + '7fac2920-62a0-11ea-9141-5d74a5e8c6ee': 'boolean', + 'b9696e20-41b2-11ea-8024-29d58105b4ea': 'boolean', + '23b9de40-41ae-11ea-9cb9-a595b886d1bf': 'boolean', + '67d286c0-32d3-11ea-89be-85468591c684': 'date', + '5f305170-1058-11ea-ab65-096a3dde920d': 'boolean', + '871ca0a0-07a5-11ea-b1f1-09c513881bef': 'float', + '003d9aa0-078a-11ea-b1f1-09c513881bef': 'integer', + '86b31a20-0789-11ea-b1f1-09c513881bef': 'boolean', + '131e5690-f651-11e9-a82d-eb02b41d1cd9': 'boolean', + '8cc00f30-ee67-11e9-8ad6-2dcfd776711b': 'boolean', + '300dbe40-ee67-11e9-8ad6-2dcfd776711b': 'float', + '1b88c0a0-ee67-11e9-8ad6-2dcfd776711b': 'integer', + '4527d850-ee59-11e9-8ad6-2dcfd776711b': 'boolean', + '08b2f770-ee58-11e9-8ad6-2dcfd776711b': 'boolean', + 'a65d7630-ee53-11e9-8ad6-2dcfd776711b': 'string', + '266b1900-ee53-11e9-8ad6-2dcfd776711b': 'string', + 'a57ce320-ec43-11e9-ac66-77c3941d51a8': 'float', + '7ddd7460-ec43-11e9-ac66-77c3941d51a8': 'float', + '364f9560-ec43-11e9-ac66-77c3941d51a8': 'float', + '09cdff90-ec43-11e9-ac66-77c3941d51a8': 'float', + 'e2f38e80-ec42-11e9-ac66-77c3941d51a8': 'float', + 'bb9aecc0-ec42-11e9-ac66-77c3941d51a8': 'float', + 'a72187e0-ec42-11e9-ac66-77c3941d51a8': 'float', + '1ff509e0-ec42-11e9-ac66-77c3941d51a8': 'integer', + 'a927fbb0-ec41-11e9-ac66-77c3941d51a8': 'float', + 'fcfe9880-ec3b-11e9-ac66-77c3941d51a8': 'date', + 'fb730bf0-ec3a-11e9-ac66-77c3941d51a8': 'boolean', + '49257280-ec3a-11e9-ac66-77c3941d51a8': 'date', + '8cc83c00-ec37-11e9-ac66-77c3941d51a8': 'string', + 'f8425f10-ec32-11e9-ac66-77c3941d51a8': 'float', + 'd6b7a3f0-ec32-11e9-ac66-77c3941d51a8': 'float', + '42c469e0-ec09-11e9-b132-cd1da1ed493f': 'float', + '1df7bfa0-ec08-11e9-b132-cd1da1ed493f': 'integer', + '65b4d6e0-ec06-11e9-b132-cd1da1ed493f': 'integer', + '715f5f60-193c-11eb-909d-0930749fc902': 'integer', + 'f7fdb140-19b7-11eb-909d-0930749fc902': 'integer', + '4470f6a0-193f-11eb-909d-0930749fc902': 'integer', + '31a561f0-1058-11ea-ab65-096a3dde920d': 'integer', + '5d2663c1-c7e5-44a3-a12d-7eb1c4a746e2': 'string', + '76622e3c-489b-40b9-9721-fa3cb470b092': 'string', + '59b4e126-5f29-4fd8-a447-6aee5dadded6': 'string', + '2144f21e-cd6a-456d-90f9-e70bfbd33dc2': 'float', + '78b732c9-445a-4161-b0e2-3ed0b9554e41': 'date', + 'a75cb9a4-654f-4a4b-86a9-dc2028eac36e': 'string', + 'b6dcabe5-8a03-4131-88ca-639051338d07': 'integer', + '1f7ad85c-2d63-4437-8d9e-e1088679fb16': 'datetime', + '81e5ff24-164d-46cf-8ba5-28c377d1c087': 'string', + '5908b958-2edf-4713-8f09-f9e043e1c3d0': 'string', + 'c0f3ab19-a665-4824-89d0-85b557510339': 'string', + 'f7386075-0ed6-4d36-8f07-ea26a72459c9': 'string', + '46e2d3e2-26b9-496b-a0e6-9937bce7a150': 'string', + '68aa9580-8f44-43e2-b7d7-66c4e3b7125c': 'string', + 'd2f59507-11fb-4213-bfa5-db691afadaa1': 'integer', + '28f20776-602d-487e-b738-9dd5e53cf0a0': 'string', + '23668919-6c6c-4696-a614-4412d0864bc4': 'string', + 'feceddc7-bda6-4a9a-b92e-dfbfc56fe1cc': 'string', + 'abb35b53-9288-4a7a-a91d-528aa6544e66': 'integer', + 'd217a40c-e128-414a-854c-415e6d57b67a': 'string', + '45f54b1c-20ce-4d0a-8765-2349ce06b747': 'string', + 'ada13bb4-c5f8-487b-a2de-c0eb4e4073f8': 'string', + '763fea51-f5a6-43e7-bb8e-8dfe514be44d': 'string', + '7c045fae-c6e8-4406-8a0c-21da608fc077': 'datetime', + '4011ae32-b037-4f66-9907-0e1f4befc778': 'integer', + 'b8dad928-7eff-4c46-82fc-d309fd9a4749': 'float', + 'db119ef1-5513-46aa-ab35-7120d949926d': 'string', + '33d16f5f-df22-4a55-8d43-c02e8839fcc4': 'string', + '4e6654e9-156c-4768-93c4-62ddd9726b01': 'string', + '634702b1-80b0-473c-9fec-680a96bf6021': 'integer', + '13e9608d-7320-40ac-8fda-2fc2e3d1a339': 'string', + '6c3e2590-7dab-4eb1-8eac-6f36ef6c3efc': 'integer', + 'c8569a48-1909-4521-a8cc-efdfc9c3c9d0': 'string', + '87470f3c-d184-4522-bf74-39204257b606': 'string', + 'abac2653-c8eb-498b-b935-656468d897aa': 'integer', + 'f3b531b3-f079-41cd-a041-320ed4355a47': 'integer', + '4b172266-6e27-4985-8f0d-2e7fdeaaceeb': 'string', + '2d6aee5e-12fc-4900-be0e-57f7a6ece730': 'string', + 'e92a6aff-63e7-4dd5-85ad-a24d0df9f952': 'string', + '9cf7d1e8-ef0e-41aa-a036-4c32a9c85e11': 'integer', + '100b99eb-e130-439f-a4d4-7565ae625578': 'string', + '4f16081a-29d7-441d-b941-26e1d5960bbe': 'string', + '4623fa4d-e037-4bdd-a103-b0949cf5af38': 'string', + '87dbc168-77f3-4733-981a-4ada2be166f8': 'integer', + 'c623e05a-b310-4b0e-9c17-a8570a8fe978': 'integer', + 'e88cc473-5292-486f-b5a7-bceb1c6a876a': 'integer', + '758e1b9b-9481-4bd0-b7f6-528f84c88703': 'integer', + 'e4566919-026f-4255-a888-74187e715755': 'datetime', + '05695e95-d462-457f-8540-3d2974433f7a': 'string', + '03718a73-43e6-45c8-b800-b3dbd219ed6d': 'float', + 'a8f4bcd4-c4b1-4115-b7ba-1a695574bc2b': 'date', + 'e00d7080-9182-4696-97a7-dd9fb4bfb5bd': 'string', + '09ecd19f-4367-4f32-9996-b25687d9984b': 'integer', + 'fce3d228-6c48-4c32-9e05-7c23a281e62b': 'date', + 'cdb3350d-e6b6-496e-aafa-798c59105db5': 'integer', + 'effd58fc-0356-4756-9829-ce791bbeedbd': 'integer', + '7f1726d6-ea9e-4cf5-aa69-a7e12ca25287': 'float', + '195e1acc-cc55-4036-ae50-4869f5cfa6ea': 'float', + '0ec278e0-6749-4b7a-a24c-0aaa45c9637c': 'string', + '889629e4-8ebb-4a1e-967f-ea6031d10c1c': 'string', + 'c5697ef9-6829-4523-81f1-ee28c66024f6': 'string', + '005f675d-3e08-4707-8f53-3165397b1daf': 'string', + '44379e0e-3500-4e8d-8c50-f66e694a2978': 'string', + 'd5869bad-4330-40f7-baaa-7e276308583c': 'string', + '2737ce53-41ea-4077-bd3a-1555e4871026': 'date', + 'c8377b3f-4245-4824-aec0-f64831e99b33': 'date', + '663e1cbb-75ae-4a28-bcce-7639698998c1': 'string', + '532fb44b-b49e-4be7-93de-d2f360d7b1d3': 'string', + 'ea2c594e-a927-486c-ac7e-7b1c5761771e': 'float', + '8da311ac-f821-4669-8c74-ae845a19ad07': 'integer', + '762b5fc3-cfcf-45a4-adf8-f6ec0ac6ba60': 'string', + '782643ca-250c-4eae-8ce5-dfb7b65a1eed': 'float', + '2e691b83-3cdb-4103-882c-4d462858db49': 'float', + '6f2f6e0e-491d-4d66-a2ec-116b88b7cc52': 'string', + '6cf909a2-8ef7-46a3-8f81-5e37b1c41d49': 'integer', + '4a537af9-e8bc-4695-a486-418fc35771a1': 'float', + '5da6c555-45da-43b6-941d-b3f0e77d67e7': 'integer', + 'df8a3b07-45a8-4977-b54f-7d64af91752b': 'float', + '970e0cf9-068f-4d71-b50a-43232976861a': 'string', + '25f96a8b-0d56-411c-a342-c73c4842f593': 'string', + '66819fd9-be85-405e-9346-7098eea01711': 'integer', + '7f362e72-c52a-435d-8590-5e4175c7dadc': 'string', + '242f54f0-6c4f-11eb-9403-6ff7759f0178': 'float', + }, + ChartEditor: { + getLang: () => 'en', + }, +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/__tests__/old-pivot-table.test.ts b/src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/__tests__/old-pivot-table.test.ts new file mode 100644 index 0000000..a75da69 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/__tests__/old-pivot-table.test.ts @@ -0,0 +1,262 @@ +import preparePivotTable from '../old-pivot-table'; +const { + baseCase1, + baseCase2, + baseCase3, + baseCase4, + baseCase5, + baseCase6, + baseCase7, + baseCase8, + baseCase9, + baseCase10, + baseCase11, + baseCase12, + baseCase13, + baseCase14, + + preparingGrouppedMarkupDataWith1ColumnLevel, + preparingGrouppedMarkupDataWith2ColumnLevel, + preparingPivotTableWithEmptyAndNullValues, + preparingSortedTable, + preparingDataWithShiftedMeasureNames, + preparingDataWithDataByNullKeys, +} = require('./mocks/old-pivot-table.mock'); + +describe('pivot-table', () => { + test('base case 1', () => { + const result = preparePivotTable(baseCase1) as any; + + expect(result.head.length).toEqual(6); + expect(result.rows.length).toEqual(17); + + expect(result.rows[0].cells[2].value).toBeCloseTo(21065.32); + expect(result.rows[0].cells[5].value).toBeCloseTo(68536.88); + expect(result.rows[16].cells[2].value).toBeCloseTo(53615.87); + expect(result.rows[16].cells[5].value).toBeCloseTo(197147.72); + }); + + test('base case 2', () => { + const result = preparePivotTable(baseCase2) as any; + + expect(result.head.length).toEqual(4); + expect(result.rows.length).toEqual(1); + + expect(result.head[0].name).toEqual('First Class'); + expect(result.head[3].name).toEqual('Standard Class'); + }); + + test('base case 3', () => { + const result = preparePivotTable(baseCase3) as any; + + expect(result.head.length).toEqual(2); + expect(result.rows.length).toEqual(4); + + expect(result.rows[0].cells[0].value).toEqual('First Class'); + expect(result.rows[3].cells[0].value).toEqual('Standard Class'); + }); + + test('base case 4', () => { + const result = preparePivotTable(baseCase4) as any; + + expect(result.head.length).toEqual(1); + expect(result.rows.length).toEqual(1); + + expect(result.head[0].name).toEqual('Sales (1)'); + expect(result.rows[0].cells[0].value).toBeCloseTo(2297200.86); + }); + + test('base case 5', () => { + const result = preparePivotTable(baseCase5) as any; + + expect(result.head.length).toEqual(5); + expect(result.rows.length).toEqual(1); + + expect(result.rows[0].cells[0].value).toEqual('Sales (1)'); + expect(result.rows[0].cells[4].value).toBeCloseTo(1358215.74); + }); + + test('base case 6', () => { + const result = preparePivotTable(baseCase6) as any; + + expect(result.head.length).toEqual(2); + expect(result.rows.length).toEqual(4); + + expect(result.head[1].name).toEqual('Sales (1)'); + expect(result.rows[0].cells[0].value).toEqual('First Class'); + expect(result.rows[0].cells[1].value).toBeCloseTo(351428.42); + expect(result.rows[3].cells[1].value).toBeCloseTo(1358215.74); + }); + + test('base case 7', () => { + const result = preparePivotTable(baseCase7) as any; + + expect(result.head.length).toEqual(4); + expect(result.rows.length).toEqual(4); + + expect(result.head[1].name).toEqual('Consumer'); + expect(result.rows[0].cells[0].value).toEqual('First Class'); + expect(result.rows[0].cells[1].value).toBeCloseTo(159168.96, 1); + expect(result.rows[3].cells[1].value).toBeCloseTo(710137.07); + expect(result.rows[0].cells[3].value).toBeCloseTo(86400.99); + expect(result.rows[3].cells[3].value).toBeCloseTo(239038.14); + }); + + test('base case 8', () => { + const result = preparePivotTable(baseCase8) as any; + + expect(result.head.length).toEqual(4); + expect(result.rows.length).toEqual(4); + + expect(result.head[1].name).toEqual('Consumer'); + expect(result.rows[0].cells[0].value).toEqual('First Class'); + }); + + test('base case 9', () => { + const result = preparePivotTable(baseCase9) as any; + + expect(result.head.length).toEqual(4); + expect(result.rows.length).toEqual(4); + + expect(result.head[1].name).toEqual('Consumer'); + expect(result.rows[0].cells[0].value).toEqual('First Class'); + expect(result.rows[0].cells[1].css.backgroundColor.replace(/\s/g, '')).toEqual( + 'rgb(27,94,181)', + ); + expect(result.rows[0].cells[3].css.backgroundColor.replace(/\s/g, '')).toEqual( + 'rgb(12,80,171)', + ); + expect(result.rows[3].cells[1].css.backgroundColor.replace(/\s/g, '')).toEqual( + 'rgb(140,203,255)', + ); + expect(result.rows[3].cells[3].css.backgroundColor.replace(/\s/g, '')).toEqual( + 'rgb(44,110,191)', + ); + }); + + test('base case 10', () => { + const result = preparePivotTable(baseCase10) as any; + + expect(result.head.length).toEqual(4); + expect(result.rows.length).toEqual(4); + + expect(result.head[1].name).toEqual('Consumer'); + expect(result.rows[0].cells[0].value).toEqual('First Class'); + expect(result.rows[0].cells[1].css.backgroundColor.replace(/\s/g, '')).toEqual( + 'rgb(27,94,181)', + ); + expect(result.rows[0].cells[3].css.backgroundColor.replace(/\s/g, '')).toEqual( + 'rgb(12,80,171)', + ); + expect(result.rows[3].cells[1].css.backgroundColor.replace(/\s/g, '')).toEqual( + 'rgb(140,203,255)', + ); + expect(result.rows[3].cells[3].css.backgroundColor.replace(/\s/g, '')).toEqual( + 'rgb(44,110,191)', + ); + expect(result.rows[0].cells[1].value).toBeCloseTo(159168.96, 1); + expect(result.rows[3].cells[1].value).toBeCloseTo(710137.07); + expect(result.rows[0].cells[3].value).toBeCloseTo(86400.99); + expect(result.rows[3].cells[3].value).toBeCloseTo(239038.14); + }); + + test('base case 11', () => { + const result = preparePivotTable(baseCase11) as any; + + expect(result.head.length).toEqual(5); + expect(result.rows.length).toEqual(176); + + expect(result.head[2].name).toEqual('Consumer'); + expect(result.rows[0].cells[0].value).toEqual('First Class'); + expect(result.rows[0].cells[1].value).toEqual('Alabama'); + expect(result.rows[0].cells[2].value).toBeCloseTo(8.96); + expect(result.rows[1].cells[0].value).toEqual('First Class'); + expect(result.rows[1].cells[1].value).toEqual('Arizona'); + expect(result.rows[1].cells[2].value).toBeCloseTo(1988.72); + }); + + test('base case 12', () => { + const result = preparePivotTable(baseCase12) as any; + + expect(result.head.length).toEqual(4); + expect(result.head[1].sub.length).toEqual(47); + expect(result.rows.length).toEqual(4); + + expect(result.head[1].name).toEqual('Consumer'); + expect(result.rows[0].cells[0].value).toEqual('First Class'); + expect(result.rows[0].cells[1].value).toBeCloseTo(8.96); + expect(result.rows[1].cells[0].value).toEqual('Same Day'); + expect(result.rows[1].cells[1].value).toBeCloseTo(29); + }); + + test('base case 13', () => { + const result = preparePivotTable(baseCase13) as any; + + expect(result.head.length).toEqual(3); + expect(result.rows.length).toEqual(176); + }); + + test('base case 14', () => { + const result = preparePivotTable(baseCase14) as any; + + expect(result.head.length).toEqual(4); + expect(result.head[1].sub.length).toEqual(39); + expect(result.rows.length).toEqual(1); + }); + + test('should prepare 10+1 columns and 2 rows table', () => { + const result = preparePivotTable(preparingGrouppedMarkupDataWith1ColumnLevel) as any; + expect(result.head.length).toEqual(11); + expect(result.rows.length).toEqual(2); + }); + + test('should prepare 2+1 columns and 9 rows table', () => { + const result = preparePivotTable(preparingGrouppedMarkupDataWith2ColumnLevel) as any; + expect(result.head.length).toEqual(3); + expect(result.rows.length).toEqual(9); + }); + + test('should sort dates ascending and order status ascending', () => { + const result = preparePivotTable(preparingSortedTable) as any; + expect(result.head[3].name).toEqual('-1'); + expect(result.head[4].name).toEqual('65'); + expect(result.head[5].name).toEqual('92'); + expect(result.head[8].name).toEqual('99'); + + expect(result.rows[0].cells[2].value).toEqual('13.02.2021 08:00:00'); + expect(result.rows[1].cells[2].value).toEqual('13.02.2021 17:00:00'); + expect(result.rows[2].cells[2].value).toEqual('13.02.2021 18:00:00'); + expect(result.rows[24].cells[2].value).toEqual('14.02.2021 06:00:00'); + }); + + test('should prepare table with null and empty string values', () => { + const result = preparePivotTable(preparingPivotTableWithEmptyAndNullValues) as any; + expect(result.head.length).toEqual(3); + expect(result.rows.length).toEqual(205); + + expect(result.rows[0].cells[2].value).toEqual(8257.2523776); + expect(result.rows[12].cells[2].value).toEqual(812446.068); + expect(result.rows[18].cells[0].value).toEqual(null); + }); + + test('should prepare table with shifted measure names correctly', () => { + const result = preparePivotTable(preparingDataWithShiftedMeasureNames) as any; + expect(result.head.length).toEqual(6); + expect(result.rows.length).toEqual(66); + + expect(result.rows[0].cells[3].value).toEqual(3574310); + expect(result.rows[0].cells[5].value).toEqual(3142528); + expect(result.rows[65].cells[3].value).toEqual(5.25); + expect(result.rows[65].cells[5].value).toEqual(50.06); + }); + + test('should prepare table with data by null keys', () => { + const result = preparePivotTable(preparingDataWithDataByNullKeys) as any; + expect(result.head.length).toEqual(3); + expect(result.rows.length).toEqual(13); + + expect(result.rows[12].cells[0].value).toEqual(null); + expect(result.rows[12].cells[1].value).toEqual(177689); + expect(result.rows[12].cells[2].value).toEqual(2433471983.5393696); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/old-pivot-table.ts b/src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/old-pivot-table.ts new file mode 100644 index 0000000..dd3481c --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/old-pivot-table/old-pivot-table.ts @@ -0,0 +1,1154 @@ +import type {CommonNumberFormattingOptions} from '../../../../../../../shared'; +import { + DATASET_FIELD_TYPES, + MINIMUM_FRACTION_DIGITS, + isDateField, + isMarkupField, + markupToRawString, +} from '../../../../../../../shared'; +import {mapAndColorizeHashTableByGradient} from '../../utils/color-helpers'; +import { + chartKitFormatNumberWrapper, + collator, + findIndexInOrder, + formatDate, + getTimezoneOffsettedTime, + isFloatDataType, + isNumericalDataType, + log, + logTiming, +} from '../../utils/misc-helpers'; +import type {PrepareFunctionArgs} from '../types'; + +// A secure delimiter that connects keys +const SPECIAL_DELIMITER = '#$#'; + +function sorter(a: string | null, b: string | null) { + if (a === null) { + return 1; + } else if (b === null) { + return -1; + } + + return collator.compare(a, b); +} + +// The function that generates the structure, +// by which the pivot table is rendered in ChartKit +// eslint-disable-next-line +function preparePivotTable({ + placeholders, + resultData, + colors, + sort, + colorsConfig, + idToTitle, + idToDataType, + ChartEditor, +}: PrepareFunctionArgs) { + const {data, order} = resultData; + + // Fields dragged into "columns" + const c: any[] = placeholders[0].items; + + // Types of fields dragged into "columns" + const cTypes = c.map((item) => idToDataType[item.guid] || item.data_type); + + // Fields dragged into "rows" + const r: any[] = placeholders[1].items; + + // Types of fields dragged into "rows" + const rTypes = r.map((item) => idToDataType[item.guid] || item.data_type); + + // Fields dragged into "indicators" + const m = placeholders[2].items; + + // Types of fields dragged into "indicators" + const mTypes = m.map((item) => idToDataType[item.guid] || item.data_type); + + // Fields dragged into "sorting" + const s = sort; + + // Number of fields dragged into "columns" + const cl = c.length; + + // Number of fields dragged into "rows" + const rl = r.length; + + // Number of fields dragged into "indicators" + const ml = m.length; + + // Number of fields dragged into "sorting" + const sl = s.length; + + // Enumeration of unique values for the lowercase header by levels + // The lowercase header is the vertical header on the left in the table + const headRow: any[] = []; + + // Enumeration of unique values for columnar caps by levels + // A columnar header is a horizontal header at the top of the table + const headColumn: any[] = []; + + // Table "cell key (intersection of row and column)" - "value in cell" + const hashTable: Record = {}; + + // Table "cell key (intersection of row and column)" - "formatted value in cell" + const hashTableFormatted: Record = {}; + + // Table "cell key (intersection of row and column)" - "type of value in the cell" + const typeTable: Record = {}; + + // Table "cell key (intersection of row and column)" - "color of the value in the cell" + const colorHashTable: Record = {}; + + // Flag indicating that more than 1 metric is used + let multimeasure = false; + + // Flag, meaning that the pseudo-field "Measure Names" in the lowercase header + let measureNamesInRow = false; + + // Flag, meaning that the pseudo-field "Measure Names" in the columnar cap + let measureNamesLevel: any = null; + + // Service flag telling data-table to use grouping + const useGroup = rl > 1; + + // Array of displayed indicator names + const measureNames = m.map((measureItem) => { + return measureItem.fakeTitle || idToTitle[measureItem.guid]; + }); + + if (ml > 1) { + multimeasure = true; + } + + // Flag indicating that the "Measure Names" pseudo field is present in the visualization + let measureNamesExists = false; + + // Flag indicating that the "lowercase" header will not be displayed + let hideHeadRows = false; + + // Table "displayed field name" - "its index in the back response" + const indices: Record = {}; + + // Table "columnar key" - "true/false" + // (depending on whether there are values for this key) + const hashTableCKeys: Record = {}; + const colorHashTableCKeys: Record = {}; + + // Table "lowercase key" - "true/false" + // (depending on whether there are values for this key) + const hashTableRKeys: Record = {}; + const colorHashTableRKeys: Record = {}; + + // Table "columnar key or part of it" - "true/false" + // (depending on whether there are values for this key) + // Auxiliary table to reduce the number of iterations + // when generating a columnar header + const auxHashTableCKeys: Record = {}; + + // Table "lowercase key or part of it" - "true/false" + // (depending on whether there are values for this key) + // Auxiliary table to reduce the number of iterations + // when generating a lowercase header + const auxHashTableRKeys: Record = {}; + + // Report point for debugging time metrics + const startTime = new Date(); + + // Table "level - value - true/false" for columnar header + // Auxiliary table to speed up the analysis of data from the backup + // Previously, indexOf was used + const headColumnMapping: Record = {}; + + // Table "level - value - true/false" for the lowercase header + // Auxiliary table to speed up the analysis of data from the backup + // Previously, indexOf was used + const headRowMapping: Record = {}; + + // An object that stores the number of unique parts of lowercase keys for each level + // Used to estimate the execution time + const urcountbylvl: Record = {}; + + // An object that stores the number of existing parts of lowercase keys for each level + // Used to estimate the execution time + const ercountbylvl: Record = {}; + + // An object that stores the number of unique parts of columnar keys for each level + // Used to estimate the execution time + const uccountbylvl: Record = {}; + + // An object that stores the number of existing parts of columnar keys for each level + // Used to estimate the execution time + const eccountbylvl: Record = {}; + + // Determining where the "Measure Names" is located + // "Measure Names" cannot be if there are no indicators + if (ml > 0) { + if ( + r.some((item, level) => { + if (item.type === 'PSEUDO') { + measureNamesLevel = level; + measureNamesExists = true; + + return true; + } else { + return false; + } + }) + ) { + measureNamesInRow = true; + } else { + c.some((item, level) => { + if (item.type === 'PSEUDO') { + measureNamesLevel = level; + measureNamesExists = true; + + return true; + } else { + return false; + } + }); + } + } + + // We determine the lack of headers + if (!rl && !cl) { + if (ml) { + c.push({ + type: 'PSEUDO', + title: 'Measure Names', + }); + measureNamesExists = true; + } + + hideHeadRows = true; + measureNamesLevel = measureNamesLevel || 0; + } else if (!cl) { + if (!measureNamesExists) { + if (ml) { + c.push({ + type: 'PSEUDO', + title: 'Measure Names', + }); + measureNamesExists = true; + } + + measureNamesLevel = 0; + } + } else if (!rl) { + if (!measureNamesExists) { + if (ml) { + r.push({ + type: 'PSEUDO', + title: 'Measure Names', + }); + measureNamesExists = true; + measureNamesInRow = true; + } + + measureNamesLevel = 0; + } + } + + // We begin a line-by-line analysis of the data from the backup + data.forEach((values) => { + // Array storing a columnar key + const cPath: any[] = []; + + let replaceMeasureName = false; + + c.forEach((headColumnItem, level) => { + if (headColumnItem.type === 'PSEUDO') { + cPath.push('$measureName$'); + + const cKey = cPath.join(SPECIAL_DELIMITER); + + if (!auxHashTableCKeys[cKey]) { + auxHashTableCKeys[cKey] = true; + } + + measureNames.forEach((measureName) => { + const customRKey = cKey.replace('$measureName$', measureName); + + if (!auxHashTableCKeys[customRKey]) { + auxHashTableCKeys[customRKey] = true; + } + }); + + replaceMeasureName = true; + + if (!eccountbylvl[level]) { + eccountbylvl[level] = measureNames.length; + } + + if (!uccountbylvl[level]) { + uccountbylvl[level] = measureNames.length; + } + + return; + } + + const actualTitle = idToTitle[headColumnItem.guid]; + const actualDataType = idToDataType[headColumnItem.guid]; + + let i; + if (typeof indices[actualTitle] === 'undefined') { + i = indices[actualTitle] = findIndexInOrder(order, headColumnItem, actualTitle); + } else { + i = indices[actualTitle]; + } + + let value = values[i]; + let stringifiedValue = String(undefined); + + if (actualDataType === 'markup') { + stringifiedValue = JSON.stringify(value); + } + + if (!headColumnMapping[level]) { + headColumnMapping[level] = {}; + } + + if (Array.isArray(headColumn[level])) { + const levelValues = headColumn[level]; + + if (actualDataType === 'markup') { + if (!headColumnMapping[level][stringifiedValue]) { + ++uccountbylvl[level]; + levelValues.push(value); + headColumnMapping[level][stringifiedValue] = true; + } + } else if (!headColumnMapping[level][String(value)]) { + ++uccountbylvl[level]; + levelValues.push(value); + headColumnMapping[level][String(value)] = true; + } + } else { + if (actualDataType === 'markup') { + headColumnMapping[level] = {[stringifiedValue]: true}; + } else { + headColumnMapping[level] = {[String(value)]: true}; + } + + uccountbylvl[level] = 1; + headColumn[level] = [value]; + } + + if (value === null) { + value = '__null'; + } else if (typeof value === 'undefined') { + value = '__undefined'; + } + + if (actualDataType === 'markup') { + cPath.push(stringifiedValue); + } else { + cPath.push(value); + } + + const cKey = cPath.join(SPECIAL_DELIMITER); + + if (!auxHashTableCKeys[cKey]) { + auxHashTableCKeys[cKey] = true; + + if (replaceMeasureName) { + measureNames.forEach((measureName) => { + const customCKey = cKey.replace('$measureName$', measureName); + + if (!auxHashTableCKeys[customCKey]) { + auxHashTableCKeys[customCKey] = true; + } + }); + } + + if (eccountbylvl[level]) { + ++eccountbylvl[level]; + } else { + eccountbylvl[level] = 1; + } + } + }); + + const rPath: any[] = []; + r.forEach((headRowItem, level) => { + if (headRowItem.type === 'PSEUDO') { + rPath.push('$measureName$'); + + const rKey = rPath.join(SPECIAL_DELIMITER); + + if (!auxHashTableRKeys[rKey]) { + auxHashTableRKeys[rKey] = true; + } + + measureNames.forEach((measureName) => { + const customRKey = rKey.replace('$measureName$', measureName); + + if (!auxHashTableRKeys[customRKey]) { + auxHashTableRKeys[customRKey] = true; + } + }); + + replaceMeasureName = true; + + if (!ercountbylvl[level]) { + ercountbylvl[level] = measureNames.length; + } + + if (!urcountbylvl[level]) { + urcountbylvl[level] = measureNames.length; + } + + return; + } + + const actualTitle = idToTitle[headRowItem.guid]; + const actualDataType = idToDataType[headRowItem.guid]; + + let i; + if (typeof indices[actualTitle] === 'undefined') { + i = indices[actualTitle] = findIndexInOrder(order, headRowItem, actualTitle); + } else { + i = indices[actualTitle]; + } + + let value = values[i]; + let stringifiedValue = String(undefined); + + if (actualDataType === 'markup') { + stringifiedValue = JSON.stringify(value); + } + + if (!headRowMapping[level]) { + headRowMapping[level] = {}; + } + + if (Array.isArray(headRow[level])) { + const levelValues = headRow[level]; + + if (actualDataType === 'markup') { + if (!headRowMapping[level][stringifiedValue]) { + ++urcountbylvl[level]; + levelValues.push(value); + headRowMapping[level][stringifiedValue] = true; + } + } else if (!headRowMapping[level][String(value)]) { + ++urcountbylvl[level]; + levelValues.push(value); + headRowMapping[level][String(value)] = true; + } + } else { + if (actualDataType === 'markup') { + headRowMapping[level] = {[stringifiedValue]: true}; + } else { + headRowMapping[level] = {[String(value)]: true}; + } + + urcountbylvl[level] = 1; + headRow[level] = [value]; + } + + if (value === null) { + value = '__null'; + } else if (typeof value === 'undefined') { + value = '__undefined'; + } + + if (actualDataType === 'markup') { + rPath.push(stringifiedValue); + } else { + rPath.push(value); + } + + const rKey = rPath.join(SPECIAL_DELIMITER); + + if (!auxHashTableRKeys[rKey]) { + auxHashTableRKeys[rKey] = true; + + if (replaceMeasureName) { + measureNames.forEach((measureName) => { + const customRKey = rKey.replace('$measureName$', measureName); + + if (!auxHashTableRKeys[customRKey]) { + auxHashTableRKeys[customRKey] = true; + } + }); + } + + if (ercountbylvl[level]) { + ++ercountbylvl[level]; + } else { + ercountbylvl[level] = 1; + } + } + }); + + const cKey = cPath.join(SPECIAL_DELIMITER); + const rKey = rPath.join(SPECIAL_DELIMITER); + + // Getting the full key + // The intersection of a line and a line is the union of a lowercase and columnar key + const key = `${cKey};${rKey}`; + + // eslint-disable-next-line complexity + m.forEach((measureItem) => { + const actualTitle = idToTitle[measureItem.guid]; + const {data_type: dataType} = measureItem; + + const shownTitle = measureItem.fakeTitle || idToTitle[measureItem.guid]; + + let i; + if (typeof indices[actualTitle] === 'undefined') { + i = indices[actualTitle] = findIndexInOrder(order, measureItem, actualTitle); + } else { + i = indices[actualTitle]; + } + + let value: string | number | null = values[i]; + let formattedValue = ''; + + if (value !== null) { + if (isNumericalDataType(dataType)) { + value = Number(value); + formattedValue = chartKitFormatNumberWrapper(value, { + lang: 'ru', + ...(measureItem.formatting ?? { + precision: isFloatDataType(dataType) ? MINIMUM_FRACTION_DIGITS : 0, + }), + }); + } else if (isDateField(measureItem)) { + value = getTimezoneOffsettedTime(new Date(value)); + } + } + + // If necessary, you need to supplement the key with a pseudo-field "Measure Names" + if (measureNamesExists || !rl || !cl) { + let rPathSpecial: any = [...rPath]; + let cPathSpecial: any = [...cPath]; + + rPathSpecial = rPathSpecial.join(SPECIAL_DELIMITER); + cPathSpecial = cPathSpecial.join(SPECIAL_DELIMITER); + + if (measureNamesInRow) { + rPathSpecial = rPathSpecial.replace('$measureName$', shownTitle); + } else { + cPathSpecial = cPathSpecial.replace('$measureName$', shownTitle); + } + + if (!hashTableRKeys[rPathSpecial]) { + hashTableRKeys[rPathSpecial] = true; + } + + if (!hashTableCKeys[cPathSpecial]) { + hashTableCKeys[cPathSpecial] = true; + } + + if (!auxHashTableRKeys[rPathSpecial]) { + auxHashTableRKeys[rPathSpecial] = true; + } + + if (!auxHashTableCKeys[cPathSpecial]) { + auxHashTableCKeys[cPathSpecial] = true; + } + + const specialKey = `${cPathSpecial};${rPathSpecial}`; + + hashTable[specialKey] = [value]; + hashTableFormatted[specialKey] = [formattedValue]; + typeTable[specialKey] = dataType; + + if (colors.length) { + const actualColorTitle = idToTitle[colors[0].guid]; + + let j; + if (typeof indices[actualColorTitle] === 'undefined') { + j = indices[actualColorTitle] = findIndexInOrder( + order, + colors[0], + actualColorTitle, + ); + } else { + j = indices[actualColorTitle]; + } + + colorHashTable[specialKey] = values[j]; + } + } else { + hashTable[key] = [value]; + hashTableFormatted[key] = [formattedValue]; + typeTable[key] = dataType; + + const rKey = rPath.join(SPECIAL_DELIMITER); + const cKey = cPath.join(SPECIAL_DELIMITER); + + if (!hashTableRKeys[rKey]) { + hashTableRKeys[rKey] = true; + } + + if (!hashTableCKeys[cKey]) { + hashTableCKeys[cKey] = true; + } + } + }); + + if (!multimeasure) { + if (colors.length) { + const actualColorTitle = idToTitle[colors[0].guid]; + + let i; + if (typeof indices[actualColorTitle] === 'undefined') { + i = indices[actualColorTitle] = findIndexInOrder( + order, + colors[0], + actualColorTitle, + ); + } else { + i = indices[actualColorTitle]; + } + + const value = values[i]; + + colorHashTableCKeys[cKey] = true; + colorHashTableRKeys[rKey] = true; + + colorHashTable[key] = value; + } + } + }); + + if (sl) { + s.forEach((sortItem) => { + r.forEach((item, level) => { + if (sortItem.guid === item.guid) { + if (headRow[level]) { + headRow[level].sort(sorter); + + if (sortItem.direction !== 'ASC') { + headRow[level].reverse(); + } + } + } + }); + + c.forEach((item, level) => { + if (sortItem.guid === item.guid) { + if (headColumn[level]) { + headColumn[level].sort(sorter); + + if (sortItem.direction !== 'ASC') { + headColumn[level].reverse(); + } + } + } + }); + }); + } else { + r.forEach((item, level) => { + if (item.type !== 'PSEUDO') { + if (headRow[level]) { + headRow[level].sort(sorter); + } + } + }); + + c.forEach((item, level) => { + if (item.type !== 'PSEUDO') { + if (headColumn[level]) { + headColumn[level].sort(sorter); + } + } + }); + } + + // At this stage, after 0.5-1.5 seconds, we can cut off the execution, + // if we understand that we will not meet the 10-second limit + + // The time for which the initial analysis of data from the backend occurred + const initTime = (new Date() as unknown as number) - (startTime as unknown as number); + + logTiming('Inited', initTime); + + // "Predicted Head Row Iterations Count" + // Number of iterations required to generate a lowercase header + let phric = ercountbylvl[0] || 1; + if (rl > 1) { + for (let i = 1; i < rl; ++i) { + phric += ercountbylvl[i - 1] * urcountbylvl[i]; + } + } + + // "Predicted Head Column Iterations Count" + // The number of iterations that will be required to generate a columnar header + let phcic = eccountbylvl[0] || 1; + if (cl > 1) { + for (let i = 1; i < cl; ++i) { + phcic += eccountbylvl[i - 1] * uccountbylvl[i]; + } + } + + // Experiments have shown that each iteration of generating caps + // takes ~0.0006-0.0015 ms + // Therefore, we can say in advance how long it will take to generate these caps + const TIME_FOR_HEAD_GENERATION_ITERATION = 0.0012; + + const thric = phric * TIME_FOR_HEAD_GENERATION_ITERATION; + const thcic = phcic * TIME_FOR_HEAD_GENERATION_ITERATION; + + // A similar dependency with the execution of getPaths: + const TIME_FOR_GET_PATHS_ITERATION = 0.003; + + const tpric = phric * TIME_FOR_GET_PATHS_ITERATION; + const tpcic = phcic * TIME_FOR_GET_PATHS_ITERATION; + + // And a similar dependency with the last nested loop: + const TIME_FOR_DATA_LOOP_ITERATION = 0.003; + + const flic = + Object.keys(hashTableRKeys).length * + Object.keys(hashTableCKeys).length * + TIME_FOR_DATA_LOOP_ITERATION; + + // The allowed limit is 10 seconds + // It's already been - "initTime" seconds + // Also, probably, we don't want users to draw end-to-end under 10 seconds of the table + // Let's take another buffer in 1 second + const SECOND = 1000; + const ALLOWED_EXECUTON_TIME = 10000; + + const estimatedResultTime = thric + thcic + tpric + tpcic + flic + initTime + SECOND; + + log('estimated result time:', estimatedResultTime); + + if (estimatedResultTime > ALLOWED_EXECUTON_TIME) { + ChartEditor._setError({ + code: 'ERR.CHARTS.ROWS_NUMBER_OVERSIZE', + details: { + estimatedResultTime, + }, + }); + + return {}; + } + + // We determine in which header we put Measure Names + if (ml) { + if (measureNamesInRow) { + headRow[measureNamesLevel] = measureNames; + } else { + headColumn[measureNamesLevel] = measureNames; + } + } + + // Lowercase header generation function + function generateHeadRows() { + function generateHeadRowLevel(level: any, accumulated: any): any[] { + const localResult: any[] = []; + + accumulated.forEach((accumulatedRow: any) => { + const keyPrefix = accumulatedRow.length + ? accumulatedRow + .map((cell: any) => { + return cell.originalValue; + }) + .join(SPECIAL_DELIMITER) + : null; + + headRow[level].forEach((headRowLevel: any) => { + let value = headRowLevel; + let formattedValue = ''; + let originalValue = headRowLevel; + + if (originalValue === null) { + originalValue = '__null'; + } else if (typeof originalValue === 'undefined') { + originalValue = '__undefined'; + } + + if (rTypes[level] === 'markup') { + originalValue = JSON.stringify(value); + } + + const currentKey = + keyPrefix === null + ? originalValue + : `${keyPrefix}${SPECIAL_DELIMITER}${originalValue}`; + + if (!auxHashTableRKeys[currentKey]) { + return; + } + + if (headRowLevel === null) { + value = null; + } else if (isDateField({data_type: rTypes[level]})) { + value = formatDate({ + valueType: rTypes[level], + value: headRowLevel, + format: r[level].format, + }); + } else if (isNumericalDataType(rTypes[level]) && r[level]?.formatting) { + formattedValue = chartKitFormatNumberWrapper(Number(headRowLevel), { + lang: 'ru', + ...r[level].formatting, + }); + } + + const cell = { + value, + originalValue, + formattedValue, + key: currentKey, + css: { + fontSize: '13px', + lineHeight: '15px', + }, + }; + + localResult.push([...accumulatedRow, cell]); + }); + }); + + if (headRow[level + 1]) { + return generateHeadRowLevel(level + 1, localResult); + } else { + return localResult; + } + } + + let rows; + if (headRow[0]) { + rows = generateHeadRowLevel(0, [[]]); + + let rowId = 0; + rows = rows.map((cells) => { + const row = { + cells, + id: ++rowId, + }; + + return row; + }); + } else { + rows = [{cells: []}]; + } + + return rows; + } + + // Header cap generation function + function generateHeadColumns() { + let headerId = 0; + + function generateHeadColumnLevel(level: any, key?: any) { + return ( + headColumn[level] + // eslint-disable-next-line complexity + .map((entry: any, entryIndex: number) => { + const cell: any = { + id: ++headerId, + type: 'text', + }; + + let mType; + let mItem; + + let value = entry; + if (value === null) { + value = '__null'; + } + if (typeof value === 'undefined') { + value = '__undefined'; + } + + const cType = cTypes[level]; + + if (cType === 'markup') { + value = JSON.stringify(value); + } + + if ( + !measureNamesInRow && + (measureNamesLevel === null || measureNamesLevel === level) + ) { + if (multimeasure) { + mType = mTypes[entryIndex]; + mItem = m[entryIndex]; + } else { + mType = mTypes[0]; + mItem = m[0]; + } + + if (isNumericalDataType(mType)) { + cell.type = 'number'; + cell.sortable = true; + const mItemFormatting = mItem?.formatting as + | CommonNumberFormattingOptions + | undefined; + if (mItemFormatting) { + cell.formatter = { + format: mItemFormatting.format, + suffix: mItemFormatting.postfix, + prefix: mItemFormatting.prefix, + showRankDelimiter: mItemFormatting.showRankDelimiter, + unit: mItemFormatting.unit, + precision: + mType === DATASET_FIELD_TYPES.FLOAT && + typeof mItemFormatting.precision !== 'number' + ? MINIMUM_FRACTION_DIGITS + : mItemFormatting.precision, + }; + } else { + cell.precision = + mType === DATASET_FIELD_TYPES.FLOAT + ? MINIMUM_FRACTION_DIGITS + : 0; + } + } else if (isDateField({data_type: mType})) { + cell.type = 'date'; + cell.sortable = true; + cell.format = mItem.format; + } + } + + if (isDateField({data_type: cType})) { + cell.name = formatDate({ + valueType: cType, + value: entry, + format: c[level].format, + }); + } else if (isMarkupField({data_type: cType})) { + cell.name = markupToRawString(entry); + cell.markup = entry; + } else if (isNumericalDataType(cType) && c[level]?.formatting) { + cell.name = entry === null ? 'null' : entry; + cell.formattedName = chartKitFormatNumberWrapper(entry, { + lang: 'ru', + ...c[level].formatting, + }); + } else { + cell.name = entry === null ? 'null' : entry; + } + + const currentKey = + typeof key === 'string' + ? `${key}${SPECIAL_DELIMITER}${value}` + : `${value}`; + const nextLevel = headColumn[level + 1]; + const noValues = !auxHashTableCKeys[currentKey]; + + if (noValues) { + return null; + } else if (nextLevel) { + cell.sub = generateHeadColumnLevel(level + 1, currentKey); + + if (cell.sub.length === 0) { + return null; + } + } + + return cell; + }) + .filter((entry: any) => entry !== null) + ); + } + + let result; + + if (headColumn[0]) { + result = generateHeadColumnLevel(0); + } else { + result = [ + { + name: '', + group: useGroup, + autogroup: false, + sortable: false, + }, + ]; + } + + if (!hideHeadRows) { + // How many levels are there in the headRow - so many need to insert an empty cell into the header + headRow.forEach(() => { + result.unshift({ + name: '', + group: useGroup, + autogroup: false, + sortable: false, + }); + }); + } + + return result; + } + + const rows = generateHeadRows(); + + logTiming( + 'Generated rows', + (new Date() as unknown as number) - (startTime as unknown as number), + ); + + const head = generateHeadColumns(); + + logTiming( + 'Generated head', + (new Date() as unknown as number) - (startTime as unknown as number), + ); + + let colorData: any; + if (colors.length) { + colorData = mapAndColorizeHashTableByGradient(colorHashTable, colorsConfig).colorData; + + logTiming( + 'Map and colorize', + (new Date() as unknown as number) - (startTime as unknown as number), + ); + } + + // The function of generating keys of existing data in the correct order + function getPaths(structure: any, level: any, current: any, target: any, direction: any) { + if (current.length) { + if (direction === 'row' && !auxHashTableRKeys[current.join(SPECIAL_DELIMITER)]) { + return; + } else if ( + direction === 'column' && + !auxHashTableCKeys[current.join(SPECIAL_DELIMITER)] + ) { + return; + } + } + + if (structure[level + 1]) { + structure[level].forEach((entry: any) => { + let pathPart = entry; + + if (entry === null) { + pathPart = '__null'; + } else if (typeof entry === 'undefined') { + pathPart = '__undefined'; + } else if (typeof entry === 'object') { + pathPart = JSON.stringify(pathPart); + } + + getPaths(structure, level + 1, [...current, pathPart], target, direction); + }); + } else if (structure[level]) { + structure[level].forEach((entry: any) => { + let pathPart = entry; + + if (entry === null) { + pathPart = '__null'; + } else if (typeof entry === 'undefined') { + pathPart = '__undefined'; + } else if (typeof entry === 'object') { + pathPart = JSON.stringify(pathPart); + } + + target.push([...current, pathPart]); + }); + } + } + + let headRowPaths: any[] = []; + getPaths(headRow, 0, [], headRowPaths, 'row'); + + logTiming( + 'getPaths headRow', + (new Date() as unknown as number) - (startTime as unknown as number), + ); + + if (headRowPaths.length === 0) { + headRowPaths = [['']]; + } + + let headColumnPaths: any[] = []; + getPaths(headColumn, 0, [], headColumnPaths, 'column'); + + logTiming( + 'getPaths headColumn', + (new Date() as unknown as number) - (startTime as unknown as number), + ); + + if (headColumnPaths.length === 0) { + headColumnPaths = [['']]; + } + + let cellId = 0; + let rCounter = 0; + + // The final cycle - we fill the cells with data + headRowPaths.forEach((headRowPath) => { + const rKey = headRowPath.join(SPECIAL_DELIMITER); + const rKeyExisting = hashTableRKeys[rKey] || colorHashTableRKeys[rKey]; + + if (!rKeyExisting) { + return; + } + + const dataCells: any[] = []; + + headColumnPaths.forEach((headColumnPath) => { + const cKey = headColumnPath.join(SPECIAL_DELIMITER); + const cKeyExisting = hashTableCKeys[cKey] || colorHashTableCKeys[cKey]; + + if (!cKeyExisting) { + return; + } + + const key = `${cKey};${rKey}`; + const value = hashTable[key] && hashTable[key][0]; + const formattedValue = hashTableFormatted[key] && hashTableFormatted[key][0]; + const type = typeTable[key]; + + const cell: any = { + value, + id: ++cellId, + }; + + if (type === 'markup') { + cell.value = value; + cell.type = 'markup'; + } else if (type === 'float') { + cell.precision = 2; + cell.type = 'number'; + cell.formattedValue = formattedValue; + } else if (isNumericalDataType(type)) { + cell.type = 'number'; + cell.formattedValue = formattedValue; + } else if (isDateField({data_type: type})) { + cell.type = 'date'; + + if (type === 'datetime' || type === 'genericdatetime') { + cell.format = 'DD.MM.YYYY HH:mm:ss'; + } else { + cell.format = 'DD.MM.YYYY'; + } + } + + if (colors.length && colorData && colorData[key]) { + cell.css = colorData[key]; + } + + if (!cell.css) { + cell.css = {}; + } + + Object.assign(cell.css, { + fontSize: '13px', + lineHeight: '15px', + }); + + dataCells.push(cell); + }); + + rows[rCounter].cells = [...rows[rCounter].cells, ...dataCells]; + + ++rCounter; + }); + + logTiming('Result', (new Date() as unknown as number) - (startTime as unknown as number)); + + return {head, rows}; +} + +export default preparePivotTable; diff --git a/src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/highcharts.test.ts b/src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/highcharts.test.ts new file mode 100644 index 0000000..c4b43c1 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/highcharts.test.ts @@ -0,0 +1,16 @@ +import type {PrepareFunctionArgs} from '../../types'; +import {prepareHighchartsPie} from '../highcharts'; + +import {piePrepareForQLArgs, piePrepareForQLResult} from './mocks/pie.mock'; + +describe('prepareHighchartsPie', () => { + describe('ql', () => { + test('should render simple pie correctly', () => { + const result = prepareHighchartsPie( + piePrepareForQLArgs as unknown as PrepareFunctionArgs, + ); + + expect(result).toEqual(piePrepareForQLResult); + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/mocks/pie.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/mocks/pie.mock.ts new file mode 100644 index 0000000..6b91ea0 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/mocks/pie.mock.ts @@ -0,0 +1,326 @@ +import cloneDeep from 'lodash/cloneDeep'; +import merge from 'lodash/merge'; + +import type {IChartEditor} from '../../../../../../../../../shared'; +import {COMMON_PALETTE_ID, DATASET_FIELD_TYPES} from '../../../../../../../../../shared'; +import classic20 from '../../../../../../../../../shared/constants/colors/common/classic-20'; +import {EmptyPrepapreArgs} from '../../../__tests__/common.mock'; +import type {PrepareFunctionArgs} from '../../../types'; + +const chartEditorMock = { + getLang: () => { + return 'en'; + }, + updateHighchartsConfig: () => {}, + updateConfig: () => {}, + getWidgetConfig: () => {}, +}; + +const datasetId = 'j43msj9o23ge9'; + +export const colorFieldDimension = { + type: 'DIMENSION', + datasetId: datasetId, + title: 'ColorField', + guid: 'colorFieldDimension_guid', + data_type: DATASET_FIELD_TYPES.STRING, +}; + +export const colorFieldDimensionFloat = { + type: 'DIMENSION', + datasetId: datasetId, + title: 'ColorField', + guid: 'colorFieldDimensionFloat_guid', + data_type: DATASET_FIELD_TYPES.FLOAT, +}; + +export const colorFieldMeasureNumber = { + type: 'MEASURE', + datasetId: datasetId, + title: 'ColorField', + guid: 'colorFieldMeasureNumber_guid', + data_type: DATASET_FIELD_TYPES.INTEGER, +}; + +export const colorFieldMeasureString = { + type: 'MEASURE', + datasetId: datasetId, + title: 'ColorField', + guid: 'colorFieldMeasureString_guid', + data_type: DATASET_FIELD_TYPES.STRING, +}; + +export const measureField = { + type: 'MEASURE', + datasetId: datasetId, + title: 'MeasureField', + guid: 'a6b94410-e219-11e9-a279-0b30c0a74ab7', + data_type: DATASET_FIELD_TYPES.INTEGER, +}; + +export const piePrepareBaseArgs = { + placeholders: [ + { + id: 'colors', + items: [colorFieldDimension], + }, + { + id: 'measures', + items: [measureField], + }, + ], + colors: [colorFieldDimension], + resultData: { + data: [ + ['1', '1'], + ['2', '2'], + ], + order: [colorFieldDimension, measureField], + totals: [], + }, + colorsConfig: { + availablePalettes: {[COMMON_PALETTE_ID.CLASSIC_20]: classic20}, + gradientColors: ['#0044A3', '#8CCBFF'], + loadedColorPalettes: {}, + }, + features: {}, + defaultColorPaletteId: COMMON_PALETTE_ID.CLASSIC_20, +}; + +export const measureNumberAndMeasure = { + placeholders: [ + { + id: 'colors', + items: [colorFieldMeasureNumber], + }, + { + id: 'measures', + items: [measureField], + }, + ], + resultData: { + data: [ + ['1', '1'], + ['2', '2'], + ], + order: [colorFieldMeasureNumber, measureField], + totals: [], + }, + colorsConfig: { + gradientMode: '2-point', + gradientPalette: 'violet', + polygonBorders: 'show', + reversed: false, + availablePalettes: {[COMMON_PALETTE_ID.CLASSIC_20]: classic20}, + gradientColors: ['#6B32C9', '#D0A3FF'], + loadedColorPalettes: {}, + }, + defaultColorPaletteId: COMMON_PALETTE_ID.CLASSIC_20, +}; + +export const measureTextAndMeasure = { + placeholders: [ + { + id: 'colors', + items: [colorFieldMeasureString], + }, + { + id: 'measures', + items: [measureField], + }, + ], + colors: [colorFieldMeasureString], + resultData: { + data: [ + ['1', '1'], + ['2', '2'], + ], + order: [colorFieldMeasureString, measureField], + totals: [], + }, + colorsConfig: { + availablePalettes: {[COMMON_PALETTE_ID.CLASSIC_20]: classic20}, + gradientColors: ['#0044A3', '#8CCBFF'], + loadedColorPalettes: {}, + }, + defaultColorPaletteId: COMMON_PALETTE_ID.CLASSIC_20, +}; + +export const piePrepareArgs = { + ChartEditor: { + getWidgetConfig: () => {}, + } as IChartEditor, + datasets: [], + fields: [], + shapes: [], + visualizationId: 'pie', + shared: { + type: 'datalens', + }, + idToDataType: { + [colorFieldDimension.guid]: colorFieldDimension.data_type, + [colorFieldDimensionFloat.guid]: colorFieldDimensionFloat.data_type, + [colorFieldMeasureNumber.guid]: colorFieldMeasureNumber.data_type, + [colorFieldMeasureString.guid]: colorFieldMeasureString.data_type, + [measureField.guid]: measureField.data_type, + }, + idToTitle: { + [colorFieldDimension.guid]: colorFieldDimension.title, + [colorFieldDimensionFloat.guid]: colorFieldDimensionFloat.title, + [colorFieldMeasureNumber.guid]: colorFieldMeasureNumber.title, + [colorFieldMeasureString.guid]: colorFieldMeasureString.title, + [measureField.guid]: measureField.title, + }, + ...piePrepareBaseArgs, +}; + +export const piePrepareForQLArgs = { + ...EmptyPrepapreArgs, + placeholders: [ + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'colors', + type: 'colors', + title: 'section_color', + iconProps: {}, + items: [ + { + guid: 'wall_material-1', + title: 'wall_material', + datasetId: 'ql-mocked-dataset', + data_type: 'string', + cast: 'string', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + required: true, + capacity: 1, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'measures', + type: 'measures', + title: 'section_measures', + iconProps: {}, + items: [ + { + guid: 'iznos-0', + title: 'iznos', + datasetId: 'ql-mocked-dataset', + data_type: 'float', + cast: 'float', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + required: true, + capacity: 1, + }, + ], + fields: [], + colors: [], + colorsConfig: { + availablePalettes: {[COMMON_PALETTE_ID.CLASSIC_20]: classic20}, + gradientColors: ['#0044A3', '#8CCBFF'], + loadedColorPalettes: {}, + }, + sort: [], + visualizationId: 'pie', + labels: [], + tooltips: [], + datasets: [], + resultData: { + data: [ + ['Не заполнено', 67], + ['Не заполнено', 100], + ['Смешанные', 70], + ['Деревянные', 40], + ['Не заполнено', null], + ['Не заполнено', null], + ['Не заполнено', null], + ['Кирпич', 78], + ['Деревянные', 67], + ['Не заполнено', 72], + ], + order: [ + {datasetId: 'ql-mocked-dataset', title: 'wall_material'}, + {datasetId: 'ql-mocked-dataset', title: 'iznos'}, + {datasetId: 'ql-mocked-dataset', title: 'Column Names'}, + ], + totals: [], + }, + idToTitle: {'wall_material-1': 'wall_material', 'iznos-0': 'iznos', '': 'Column Names'}, + idToDataType: {'wall_material-1': 'string', 'iznos-0': 'float', '': 'string'}, + shared: { + sharedData: {}, + }, + ChartEditor: chartEditorMock, + shapes: [], + shapesConfig: {}, + segments: [], + disableDefaultSorting: false, + defaultColorPaletteId: COMMON_PALETTE_ID.CLASSIC_20, +}; + +export const piePrepareForQLResult = { + graphs: [ + { + name: 'iznos', + pointConflict: true, + tooltip: {chartKitFormatting: true, chartKitPrecision: 2}, + dataLabels: {chartKitFormatting: true, chartKitPrecision: 0, useHTML: false}, + data: [ + { + name: 'Кирпич', + formattedName: 'Кирпич', + drillDownFilterValue: 'Кирпич', + y: 78, + colorGuid: 'wall_material-1', + colorValue: 'Кирпич', + color: '#4DA2F1', + }, + { + name: 'Не заполнено', + formattedName: 'Не заполнено', + drillDownFilterValue: 'Не заполнено', + y: 72, + colorGuid: 'wall_material-1', + colorValue: 'Не заполнено', + color: '#FF3D64', + }, + { + name: 'Смешанные', + formattedName: 'Смешанные', + drillDownFilterValue: 'Смешанные', + y: 70, + colorGuid: 'wall_material-1', + colorValue: 'Смешанные', + color: '#8AD554', + }, + { + name: 'Деревянные', + formattedName: 'Деревянные', + drillDownFilterValue: 'Деревянные', + y: 67, + colorGuid: 'wall_material-1', + colorValue: 'Деревянные', + color: '#FFC636', + }, + ], + }, + ], +}; + +export function getPrepareFunctionArgs(options: Partial = {}) { + return merge(cloneDeep(piePrepareArgs), options) as unknown as PrepareFunctionArgs; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/pie.test.ts b/src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/pie.test.ts new file mode 100644 index 0000000..568e1e9 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/pie/__tests__/pie.test.ts @@ -0,0 +1,95 @@ +import type {PrepareFunctionArgs} from '../../types'; +import {preparePieData} from '../prepare-pie-data'; + +import { + colorFieldDimensionFloat, + getPrepareFunctionArgs, + measureField, + measureNumberAndMeasure, + measureTextAndMeasure, + piePrepareBaseArgs, +} from './mocks/pie.mock'; + +describe('preparePie', () => { + test('dimension + measure: colorizing', () => { + const options = getPrepareFunctionArgs( + piePrepareBaseArgs as unknown as Partial, + ); + + const result = preparePieData(options); + const items = result.graphs[0].data?.map((item) => ({ + colorValue: item.colorValue, + color: item.color, + })); + + expect(items).toEqual([ + {color: '#4DA2F1', colorValue: '2'}, + {color: '#FF3D64', colorValue: '1'}, + ]); + }); + + test('measure number + measure: colorizing', () => { + const options = getPrepareFunctionArgs( + measureNumberAndMeasure as unknown as Partial, + ); + const result = preparePieData(options); + const items = result.graphs[0].data?.map((item) => ({ + colorValue: item.colorValue, + color: item.color, + })); + + expect(items).toEqual([ + { + color: 'rgb(157, 106, 228)', + colorValue: 2, + }, + ]); + }); + + test('Fractional values in the "Color" section: the colors specified by the user are used', () => { + const options = getPrepareFunctionArgs({ + placeholders: [{items: [colorFieldDimensionFloat]}], + resultData: { + data: [ + ['2.0', '3'], + ['0.5', '2'], + ['1.0', '1'], + ], + order: [colorFieldDimensionFloat, measureField], + totals: [], + }, + colorsConfig: { + fieldGuid: colorFieldDimensionFloat.guid, + availablePalettes: { + custom: {id: 'custom', scheme: ['color_0.5', 'color_1.0', 'color_2.0']}, + }, + palette: 'custom', + mountedColors: { + '0.5': '0', + '1.0': '1', + '2.0': '2', + }, + }, + } as unknown as PrepareFunctionArgs); + const result = preparePieData(options); + const items = result.graphs[0].data?.map((item) => item.color); + + expect(items).toEqual(['color_2.0', 'color_0.5', 'color_1.0']); + }); + + test('measure text + measure: colorizing', () => { + const options = getPrepareFunctionArgs( + measureTextAndMeasure as unknown as Partial, + ); + const result = preparePieData(options); + const items = result.graphs[0].data?.map((item) => ({ + colorValue: item.colorValue, + color: item.color, + })); + + expect(items).toEqual([ + {color: '#4DA2F1', colorValue: '2'}, + {color: '#FF3D64', colorValue: '1'}, + ]); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/pie/gravity-charts.ts b/src/server/modes/charts/plugins/datalens/preparers/pie/gravity-charts.ts new file mode 100644 index 0000000..614a70d --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/pie/gravity-charts.ts @@ -0,0 +1,146 @@ +import type {ChartData, PieSeries, PieSeriesData} from '@gravity-ui/chartkit/gravity-charts'; +import merge from 'lodash/merge'; + +import type {SeriesExportSettings} from '../../../../../../../shared'; +import { + WizardVisualizationId, + formatNumber, + getFormatOptions, + isMeasureValue, +} from '../../../../../../../shared'; +import {getFakeTitleOrTitle} from '../../../../../../../shared/modules/fields'; +import {isHtmlField, isMarkdownField, isMarkupField} from '../../../../../../../shared/types/index'; +import {getBaseChartConfig} from '../../gravity-charts/utils'; +import {getFieldFormatOptions} from '../../gravity-charts/utils/format'; +import {getExportColumnSettings} from '../../utils/export-helpers'; +import {getLegendColorScale} from '../helpers/legend'; +import type {PiePoint, PrepareFunctionArgs} from '../types'; + +import preparePieData from './prepare-pie-data'; +import {getFormattedValue, isColoringByMeasure} from './utils'; + +type ExtendedPieSeriesData = Omit & { + drillDownFilterValue?: string; + formattedValue: string | null; + percentage: number; + label?: PiePoint['label']; +}; + +type ExtendedPieSeries = Omit & { + custom?: { + totals?: string; + exportSettings?: SeriesExportSettings; + }; + data: ExtendedPieSeriesData[]; +}; + +export function prepareD3Pie(args: PrepareFunctionArgs) { + const {shared, labels, visualizationId, placeholders, ChartEditor, colorsConfig, idToDataType} = + args; + const {graphs, label, measure, totals, color, dimension} = preparePieData(args); + const isLabelsEnabled = Boolean(labels?.length && label && measure?.hideLabelMode !== 'hide'); + + const shouldUseHtmlForLabels = + isMarkupField(label) || isHtmlField(label) || isMarkdownField(label); + const labelField = isMeasureValue(label) ? measure : label; + + let data: ExtendedPieSeries[] = []; + + if (measure && graphs.length > 0) { + const graph = graphs[0]; + const total = graph.data?.reduce((sum, d) => sum + (d.y || 0), 0) ?? 0; + const labelFormatting = labelField ? getFormatOptions(labelField) : undefined; + const seriesConfig: ExtendedPieSeries = { + type: 'pie', + minRadius: '50%', + dataLabels: { + enabled: isLabelsEnabled, + html: shouldUseHtmlForLabels, + format: isLabelsEnabled ? getFieldFormatOptions({field: labelField}) : undefined, + }, + data: + graph.data?.map((item) => { + const percentage = item.y / total; + return { + ...item, + value: item.y, + color: item.color, + formattedValue: getFormattedValue(String(item.y), { + ...measure, + data_type: idToDataType[measure.guid], + }), + percentage, + label: labelFormatting?.labelMode === 'percent' ? percentage : item.label, + }; + }) ?? [], + }; + + seriesConfig.custom = { + exportSettings: { + columns: [ + { + id: 'categories', + name: ChartEditor.getTranslation('chartkit.data-provider', 'categories'), + field: 'name', + }, + getExportColumnSettings({path: 'value', field: measure}), + ], + }, + }; + + if (visualizationId === WizardVisualizationId.Donut) { + seriesConfig.innerRadius = '50%'; + + if (measure && totals) { + seriesConfig.custom = { + ...seriesConfig.custom, + totals: formatNumber(Number(totals), getFormatOptions(measure)), + }; + } + } + + data.push(seriesConfig); + } else { + data = []; + } + + let legend: ChartData['legend'] = {}; + if (graphs.length && isColoringByMeasure(args)) { + const points = graphs + .map((graph) => (graph.data ?? []).map((d) => ({colorValue: d.colorValue as unknown}))) + .flat(2); + + const colorScale = getLegendColorScale({ + colorsConfig, + points, + }); + + legend = { + enabled: true, + type: 'continuous', + title: {text: getFakeTitleOrTitle(measure), style: {fontWeight: '500'}}, + colorScale, + }; + } else { + const shouldUseHtmlForLegend = [dimension, color].some(isHtmlField); + if (shouldUseHtmlForLegend) { + legend.html = true; + } + + const isLegendEnabled = shared?.extraSettings?.legendMode !== 'hide'; + if (isLegendEnabled) { + legend.enabled = true; + } + } + + return merge(getBaseChartConfig({shared, visualization: {placeholders, id: visualizationId}}), { + chart: { + margin: {top: 20, left: 12, right: 12, bottom: 20}, + zoom: {enabled: false}, + }, + series: { + data: data.filter((s) => s.data.length), + }, + legend, + }); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/pie/highcharts.ts b/src/server/modes/charts/plugins/datalens/preparers/pie/highcharts.ts new file mode 100644 index 0000000..b1ca69f --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/pie/highcharts.ts @@ -0,0 +1,71 @@ +import set from 'lodash/set'; + +import {getFakeTitleOrTitle, isHtmlField} from '../../../../../../../shared'; +import {getHighchartsGradientStops} from '../../utils/get-gradient-stops'; +import {isLegendEnabled} from '../../utils/misc-helpers'; +import type {PrepareFunctionArgs} from '../types'; + +import preparePieData from './prepare-pie-data'; +import {isColoringByMeasure} from './utils'; + +export function prepareHighchartsPie(args: PrepareFunctionArgs) { + const {ChartEditor, colorsConfig, labels, shared} = args; + const {graphs, totals, measure, label, color, dimension} = preparePieData(args); + + const labelsLength = labels && labels.length; + const isHideLabel = measure?.hideLabelMode === 'hide'; + + const customConfig: Record = { + plotOptions: { + series: { + dataLabels: { + enabled: Boolean(labelsLength && label && !isHideLabel), + }, + }, + }, + }; + + const pie = graphs[0]; + + if (pie && pie.data) { + if (isColoringByMeasure(args)) { + pie.showInLegend = false; + + const colorValues = pie.data.map((point) => Number(point.colorValue)); + const points = pie.data as unknown as Highcharts.PointOptionsObject[]; + + const minColorValue = Math.min(...colorValues); + const maxColorValue = Math.max(...colorValues); + + customConfig.colorAxis = { + startOnTick: false, + endOnTick: false, + min: minColorValue, + max: maxColorValue, + stops: getHighchartsGradientStops({ + colorsConfig, + points, + minColorValue, + maxColorValue, + }), + }; + + customConfig.legend = { + title: { + text: getFakeTitleOrTitle(color), + }, + enabled: isLegendEnabled(shared.extraSettings), + symbolWidth: null, + }; + } + } + + const shouldUseHtmlForLegend = [dimension, color].some(isHtmlField); + if (shouldUseHtmlForLegend) { + set(customConfig, 'legend.useHTML', true); + } + + ChartEditor.updateHighchartsConfig(customConfig); + + return {graphs, totals}; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/pie/index.ts b/src/server/modes/charts/plugins/datalens/preparers/pie/index.ts new file mode 100644 index 0000000..35f5127 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/pie/index.ts @@ -0,0 +1,2 @@ +export * from './gravity-charts'; +export * from './highcharts'; diff --git a/src/server/modes/charts/plugins/datalens/preparers/pie/prepare-pie-data.ts b/src/server/modes/charts/plugins/datalens/preparers/pie/prepare-pie-data.ts new file mode 100644 index 0000000..d1eef65 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/pie/prepare-pie-data.ts @@ -0,0 +1,353 @@ +import type {ExtendedSeriesLineOptions, WrappedHTML} from '../../../../../../../shared'; +import { + DATASET_FIELD_TYPES, + MINIMUM_FRACTION_DIGITS, + PlaceholderId, + getDistinctValue, + getFakeTitleOrTitle, + getFormatOptions, + isFieldHierarchy, + isHtmlField, + isMarkdownField, + isMarkupField, + isMeasureValue, + isNumberField, + isPseudoField, + wrapMarkupValue, +} from '../../../../../../../shared'; +import {wrapMarkdownValue} from '../../../../../../../shared/utils/markdown'; +import {wrapHtml} from '../../../../../../../shared/utils/ui-sandbox'; +import {getColorsSettings} from '../../../helpers/color-palettes'; +import type {ChartColorsConfig} from '../../types'; +import type {ColorValue} from '../../utils/color-helpers'; +import {getColorsByMeasureField, getThresholdValues} from '../../utils/color-helpers'; +import {getColor, getMountedColor} from '../../utils/constants'; +import {findIndexInOrder} from '../../utils/misc-helpers'; +import {addActionParamValue} from '../helpers/action-params'; +import type {PiePoint, PrepareFunctionArgs} from '../types'; + +import {getFormattedValue, isColoringByMeasure} from './utils'; + +export type PieConfig = { + name: string; + dataLabels: any; + tooltip: any; + data?: (PiePoint & ExtendedSeriesLineOptions)[]; + showInLegend?: boolean; + pointConflict?: boolean; +}; + +function mapAndColorizePieByGradient( + points: (PiePoint & ExtendedSeriesLineOptions)[], + colorsConfig: ChartColorsConfig, +) { + const colorValues = points.map((point) => Number(point.colorValue) as ColorValue); + + const gradientThresholdValues = getThresholdValues(colorsConfig, colorValues); + const gradientColors = getColorsByMeasureField({ + values: colorValues, + colorsConfig, + gradientThresholdValues, + }); + + points.forEach((point) => { + const pointColorValue = Number(point.colorValue); + + if (gradientColors[pointColorValue]) { + point.color = gradientColors[pointColorValue]; + } + }); + + return points; +} + +function getPieSegmentColor({ + colorValue, + usedColors, + colors, + mountedColors, +}: { + colorValue: string | undefined; + usedColors: Map; + colors: string[]; + mountedColors: Record; +}) { + if (!usedColors.has(colorValue)) { + usedColors.set(colorValue, getColor(usedColors.size, colors)); + } + + if (colorValue && mountedColors[colorValue]) { + return getMountedColor({ + mountedColors, + colors, + value: colorValue, + }); + } + + return usedColors.get(colorValue); +} + +// eslint-disable-next-line complexity +export function preparePieData(args: PrepareFunctionArgs) { + const { + placeholders, + resultData, + sort, + labels, + colorsConfig, + idToTitle, + idToDataType, + ChartEditor, + disableDefaultSorting = false, + shared, + defaultColorPaletteId, + } = args; + const {data, order, totals} = resultData; + const widgetConfig = ChartEditor.getWidgetConfig(); + + const measure = placeholders.find((p) => p.id === PlaceholderId.Measures)?.items[0]; + let colorField = placeholders.find((p) => p.id === PlaceholderId.Colors)?.items[0]; + if (isFieldHierarchy(colorField)) { + const drillDownLevel = shared.sharedData?.drillDownData?.level || 0; + colorField = colorField.fields[Math.min(drillDownLevel, colorField.fields.length - 1)]; + } + + if (colorField) { + colorField = { + ...colorField, + data_type: idToDataType[colorField.guid], + }; + } + const isHtmlColor = isHtmlField(colorField); + + let dimensionField = placeholders.find((p) => p.id === PlaceholderId.Dimensions)?.items[0]; + if (dimensionField) { + dimensionField = { + ...dimensionField, + data_type: idToDataType[dimensionField.guid], + }; + } + const isHtmlDimension = isHtmlField(dimensionField); + + if (!measure) { + return {graphs: []}; + } + + const colorIndex = colorField + ? findIndexInOrder(order, colorField, idToTitle[colorField.guid]) + : -1; + const shouldUseGradient = isColoringByMeasure(args); + const dimensionIndex = dimensionField + ? findIndexInOrder(order, dimensionField, idToTitle[dimensionField.guid]) + : -1; + + const labelItem = labels?.[0]; + const labelField = labelItem + ? {...labelItem, data_type: idToDataType[labelItem.guid]} + : labelItem; + const labelIndex = labelField + ? findIndexInOrder(order, labelField, idToTitle[labelField.guid]) + : -1; + const isMarkdownLabel = isMarkdownField(labelItem); + const isMarkupLabel = isMarkupField(labelItem); + const isHtmlLabel = isHtmlField(labelItem); + + const measureIndex = findIndexInOrder(order, measure, idToTitle[measure.guid]); + const measureDataType = idToDataType[measure.guid] || measure.data_type; + + if (measureIndex === -1) { + return {graphs: []}; + } + + const title = idToTitle[measure.guid]; + const name = + title.includes(measure.guid) && measure.originalTitle ? measure.originalTitle : title; + const measureFormatting = getFormatOptions(measure); + const labelFormatting = isMeasureValue(labelField) + ? measureFormatting + : getFormatOptions(labelField); + const labelFinalDataType = isPseudoField(labelField) ? measureDataType : labelField?.data_type; + + const pie: PieConfig = { + name, + tooltip: + measureFormatting && Object.keys(measureFormatting).length + ? { + chartKitFormatting: true, + chartKitPrecision: measureFormatting.precision, + chartKitPrefix: measureFormatting.prefix, + chartKitPostfix: measureFormatting.postfix, + chartKitUnit: measureFormatting.unit, + chartKitFormat: measureFormatting.format, + chartKitLabelMode: measureFormatting.labelMode, + chartKitShowRankDelimiter: measureFormatting.showRankDelimiter, + } + : { + chartKitFormatting: true, + chartKitPrecision: measureDataType === 'float' ? MINIMUM_FRACTION_DIGITS : 0, + }, + dataLabels: { + ...(labelFormatting && Object.keys(labelFormatting).length + ? { + // need to reset dataLabels.format to use dataLabels.formatter + format: null, + chartKitFormatting: true, + chartKitPrecision: labelFormatting.precision, + chartKitPrefix: labelFormatting.prefix, + chartKitPostfix: labelFormatting.postfix, + chartKitUnit: labelFormatting.unit, + chartKitLabelMode: labelFormatting.labelMode, + chartKitFormat: labelFormatting.format, + chartKitShowRankDelimiter: labelFormatting.showRankDelimiter, + } + : { + chartKitFormatting: true, + chartKitPrecision: + labelFinalDataType === DATASET_FIELD_TYPES.FLOAT + ? MINIMUM_FRACTION_DIGITS + : 0, + }), + useHTML: (isMarkdownLabel || isMarkupLabel || isHtmlLabel) ?? undefined, + }, + }; + + // eslint-disable-next-line complexity + const pieData = data.reduce((acc, values) => { + const dimensionValue = values[dimensionIndex]; + const measureValue = values[measureIndex]; + const colorFieldValue = values[colorIndex]; + const labelValue = values[labelIndex]; + + let colorValue: string | number = name; + const legendParts: string[] = []; + const formattedNameParts: Array = []; + + if (colorField && typeof colorFieldValue !== 'undefined') { + if (shouldUseGradient) { + colorValue = Number(colorFieldValue); + } else { + colorValue = getDistinctValue(colorFieldValue); + legendParts.push(String(colorFieldValue)); + formattedNameParts.push(String(getFormattedValue(colorFieldValue, colorField))); + } + } + + if (dimensionField) { + legendParts.push(String(dimensionValue)); + formattedNameParts.push(String(getFormattedValue(dimensionValue, dimensionField))); + } + + const pointName = legendParts.length + ? legendParts.join(': ') + : getFakeTitleOrTitle(measure); + const drillDownFilterValue = pointName; + + let formattedName: string | WrappedHTML = formattedNameParts.join(': '); + if (isHtmlColor || isHtmlDimension) { + formattedName = wrapHtml(formattedName); + } + + const shouldWrapPointName = isHtmlColor || isHtmlDimension; + const point: PiePoint = { + name: shouldWrapPointName ? wrapHtml(pointName) : pointName, + formattedName, + drillDownFilterValue, + y: Number(measureValue), + colorGuid: colorField?.guid, + colorValue, + }; + + if (labelField) { + if (isPseudoField(labelField)) { + point.label = isMeasureValue(labelField) ? Number(measureValue) : formattedName; + } else if (isNumberField(labelField)) { + // The value will be formatted using dataLabels.chartKitFormatting + point.label = Number(labelValue); + } else if (labelValue && isMarkdownLabel) { + point.label = wrapMarkdownValue(labelValue); + } else if (labelValue && isMarkupLabel) { + point.label = wrapMarkupValue(labelValue); + } else if (labelValue && isHtmlLabel) { + point.label = wrapHtml(labelValue); + } else { + point.label = getFormattedValue(labelValue, { + ...labelField, + data_type: idToDataType[labelField.guid], + }); + } + } + + if (widgetConfig?.actionParams?.enable) { + const actionParams: Record = {}; + addActionParamValue(actionParams, dimensionField, dimensionValue); + addActionParamValue(actionParams, colorField, colorValue); + + point.custom = { + actionParams, + }; + } + + if (acc.get(pointName)) { + pie.pointConflict = true; + } + + acc.set(pointName, point); + + return acc; + }, new Map()); + + pie.data = Array.from(pieData.values()) + // We remove negative values, since pie does not know how to display them + .filter((point) => point.y > 0) as (PiePoint & ExtendedSeriesLineOptions)[]; + + if (!disableDefaultSorting && (!sort || !sort.length)) { + pie.data.sort((a, b) => { + return a.y > b.y ? -1 : a.y < b.y ? 1 : 0; + }); + } + + if (shouldUseGradient) { + pie.data = mapAndColorizePieByGradient(pie.data, colorsConfig); + } else { + const {mountedColors, colors} = getColorsSettings({ + field: colorField, + colorsConfig: colorsConfig, + defaultColorPaletteId, + availablePalettes: colorsConfig.availablePalettes, + customColorPalettes: colorsConfig.loadedColorPalettes, + }); + + const usedColors = new Map(); + pie.data.forEach((d) => { + d.color = getPieSegmentColor({ + colorValue: d.colorValue, + colors, + usedColors, + mountedColors, + }); + }); + } + + if (isMarkdownLabel) { + ChartEditor.updateConfig({useMarkdown: true}); + } + + if (isMarkupLabel) { + ChartEditor.updateConfig({useMarkup: true}); + } + + if (isHtmlColor || isHtmlDimension || [labelField].some(isHtmlField)) { + ChartEditor.updateConfig({useHtml: true}); + } + + return { + graphs: [pie], + totals: totals.find((value) => value), + label: labelField, + measure, + color: colorField, + dimension: dimensionField, + }; +} + +export default preparePieData; diff --git a/src/server/modes/charts/plugins/datalens/preparers/pie/utils.ts b/src/server/modes/charts/plugins/datalens/preparers/pie/utils.ts new file mode 100644 index 0000000..ddb9ab3 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/pie/utils.ts @@ -0,0 +1,51 @@ +import type {ServerField} from '../../../../../../../shared'; +import { + PlaceholderId, + getFormatOptions, + isDateField, + isNumberField, +} from '../../../../../../../shared'; +import { + chartKitFormatNumberWrapper, + formatDate, + isGradientMode, + isNumericalDataType, +} from '../../utils/misc-helpers'; +import type {PrepareFunctionArgs} from '../types'; + +export function getFormattedValue(value: string | null, field: ServerField) { + if (value === null) { + return null; + } + + if (isDateField(field)) { + return formatDate({ + valueType: field.data_type, + value, + format: field.format, + }); + } + + if (isNumberField(field)) { + return chartKitFormatNumberWrapper(Number(value), { + lang: 'ru', + ...getFormatOptions(field), + }); + } + + return String(value); +} + +export function isColoringByMeasure(args: PrepareFunctionArgs) { + const {colorsConfig, placeholders, idToDataType} = args; + const colorField = placeholders.find((p) => p.id === PlaceholderId.Colors)?.items[0]; + + if (!colorField) { + return false; + } + + const colorFieldDataType = idToDataType[colorField.guid] || colorField.data_type; + const gradientMode = isGradientMode({colorField, colorFieldDataType, colorsConfig}); + + return isNumericalDataType(colorFieldDataType) && Boolean(gradientMode); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/polyline.ts b/src/server/modes/charts/plugins/datalens/preparers/polyline.ts new file mode 100644 index 0000000..c076d93 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/polyline.ts @@ -0,0 +1,375 @@ +import {ZoomMode, isDateField} from '../../../../../../shared'; +import {GEO_MAP_LAYERS_LEVEL} from '../utils/constants'; +import { + colorizeGeoByGradient, + colorizeGeoByPalette, + getLayerAlpha, + getMapBounds, + getMapState, +} from '../utils/geo-helpers'; +import {findIndexInOrder, formatDate, isGradientMode} from '../utils/misc-helpers'; + +import type {PrepareFunctionArgs, PrepareFunctionDataRow, ResultDataOrder} from './types'; + +type YMapItemConfig = { + feature: { + geometry: { + type: string; + coordinates: string[] | string[][]; + radius?: number; + }; + properties?: { + radius?: number; + value?: string; + data?: { + color?: string; + text?: string; + }[]; + }; + }; + options?: { + geoObjectId?: string; + strokeWidth?: number; + opacity?: number | string; + fillColor?: string; + strokeColor?: string; + iconColor?: string; + preset?: string; + zIndex?: number; + }; +}; + +const DEFAULT_GROUP = 'DEFAULT_GROUP'; + +type FieldData = { + title: string; + value: string | null; +}; + +type CoordsData = { + title: string; + value: string[]; +}; + +type MappedRowData = { + coords: CoordsData; + measures: FieldData[]; + sort?: FieldData; + color?: FieldData; +}; + +const getFieldData = ( + fields: { + title: string; + guid: string; + datasetId: string; + }[], + dataRow: PrepareFunctionDataRow, + order: ResultDataOrder, + idToTitle: Record, +): FieldData[] => { + return fields.reduce((acc: FieldData[], measureField) => { + const labelTitle = measureField.title || idToTitle[measureField.guid]; + const index = findIndexInOrder(order, measureField, labelTitle); + + if (index !== -1) { + acc.push({ + title: labelTitle, + value: dataRow[index], + }); + } + + return acc; + }, []); +}; + +const preparePolyline = (options: PrepareFunctionArgs) => { + const {shared, defaultColorPaletteId, ChartEditor} = options; + const i18n = (key: string, params?: Record) => + ChartEditor.getTranslation('wizard.prepares', key, params); + + const {idToDataType} = options; + + const {data, order} = options.resultData; + const [color] = options.colors; + const colorsConfig = options.colorsConfig; + const colorFieldDataType = color ? idToDataType[color.guid] : null; + + const gradientMode = + color && + colorFieldDataType && + isGradientMode({colorField: color, colorFieldDataType, colorsConfig}); + + const ALPHA = getLayerAlpha(options.layerSettings || {}); + let leftBot: undefined | [number, number]; + let rightTop: undefined | [number, number]; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const groupingFields = options.placeholders.find( + (placeholder) => placeholder.id === 'grouping', + )!.items; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const measures = options.placeholders.find( + (placeholder) => placeholder.id === 'measures', + )!.items; + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const polylinePlaceholder = options.placeholders.find( + (placeholder) => placeholder.id === 'polyline', + )!; + const [polylineField] = polylinePlaceholder.items; + + const sort = options.sort; + const [sortField] = sort; + + const showPoints = + (polylinePlaceholder.settings as Record).polylinePoints === 'on'; + + const groups = data.reduce( + (acc: Record, dataRow: PrepareFunctionDataRow) => { + const point = JSON.parse(dataRow[0] as string); + if (point === null) { + return acc; + } + + const groupingObjects = getFieldData(groupingFields, dataRow, order, options.idToTitle); + [leftBot, rightTop] = getMapBounds({ + leftBot, + rightTop, + current: point, + }); + + const group = groupingObjects.map((item) => `${item.title}: ${item.value}`).join(', '); + + if (!acc[group || DEFAULT_GROUP]) { + acc[group || DEFAULT_GROUP] = []; + } + + acc[group || DEFAULT_GROUP].push(dataRow); + + return acc; + }, + {}, + ); + + const children: YMapItemConfig[] = []; + + let colorData: Record | undefined; + + if (color) { + // CHARTS-1961 + + const hashTable = data.reduce( + (acc: any, dataRow: PrepareFunctionDataRow, valuesIndex: number) => { + const colorsObjects = getFieldData( + options.colors, + dataRow, + order, + options.idToTitle, + ); + + acc[`points-${valuesIndex}`] = colorsObjects.map((el) => { + return { + [String(el.value)]: el.value, + }; + }); + + return acc; + }, + {}, + ); + + if (gradientMode) { + colorData = colorizeGeoByGradient(hashTable, colorsConfig).colorData; + } else { + colorData = colorizeGeoByPalette({ + data: hashTable, + colorsConfig, + colorField: color, + defaultColorPaletteId, + }).colorData; + } + } + + const mapOptions: Record = { + opacity: ALPHA, + }; + + if (options.layerSettings.id) { + mapOptions.geoObjectId = options.layerSettings.id; + } + + Object.entries(groups).forEach(([groupName, groupDataRows]) => { + const mappedRowData = groupDataRows.reduce( + (acc: MappedRowData[], row: PrepareFunctionDataRow) => { + const coords = getFieldData([polylineField], row, order, options.idToTitle)[0]; + let sortData; + if (sortField) { + sortData = getFieldData(sort, row, order, options.idToTitle)[0]; + + if (isDateField(sortField)) { + sortData.value = formatDate({ + valueType: sortField.data_type, + value: sortData.value, + format: sortField.format, + }); + } + } + const colorsObjects = getFieldData(options.colors, row, order, options.idToTitle); + + acc.push({ + coords: { + title: coords.title, + value: JSON.parse(String(coords.value)), + }, + measures: getFieldData(measures, row, order, options.idToTitle), + sort: sortData, + color: colorsObjects[0], + }); + + return acc; + }, + [], + ); + + for (let i = 1; i < mappedRowData.length; i++) { + const prev = mappedRowData[i - 1]; + const current = mappedRowData[i]; + const color = + colorData && prev.color && colorData[String(prev.color.value)].backgroundColor; + + const tooltipData = []; + + if (groupName !== DEFAULT_GROUP) { + tooltipData.push({ + text: i18n('label_line', {value: groupName}), + }); + } + + tooltipData.push({ + text: i18n('label_segment', { + value: `${prev.coords.value} → ${current.coords.value}`, + }), + }); + + if (prev.sort && current.sort) { + tooltipData.push({ + text: i18n('label_order', { + value: `${prev.sort.value} → ${current.sort.value}`, + }), + }); + } + + if (color && prev.color) { + tooltipData.push({ + color, + text: i18n('label_color', { + value: `${prev.color.title}: ${prev.color.value}`, + }), + }); + } + + tooltipData.push( + ...prev.measures.map((measure, index) => { + const currentMeasure = current.measures[index]; + return { + text: `${measure.title}: ${measure.value} → ${currentMeasure.value}`, + }; + }), + ); + + children.push({ + feature: { + geometry: { + type: 'LineString', + coordinates: [prev.coords.value, current.coords.value], + }, + properties: { + data: tooltipData, + }, + }, + options: { + strokeWidth: 6, + strokeColor: + colorData && + prev.color && + colorData[String(prev.color.value)].backgroundColor, + zIndex: GEO_MAP_LAYERS_LEVEL.POLYLINE, + }, + }); + } + + if (showPoints) { + mappedRowData.forEach((item) => { + const tooltipData = []; + + tooltipData.push({ + text: i18n('label_point', {value: item.coords.value}), + }); + + if (groupName !== DEFAULT_GROUP) { + tooltipData.push({ + text: i18n('label_line', {value: groupName}), + }); + } + + if (colorData && item.color) { + tooltipData.push({ + color: colorData[String(item.color.value)].backgroundColor, + text: i18n('label_color', { + value: `${item.color.title}: ${item.color.value}`, + }), + }); + } + + tooltipData.push( + ...item.measures.map((item) => { + return { + text: `${item.title}: ${item.value}`, + }; + }), + ); + + children.push({ + feature: { + geometry: { + type: 'Point', + coordinates: item.coords.value, + }, + properties: { + radius: 1.5, + data: tooltipData, + }, + }, + options: { + preset: 'chartkit#chips', + iconColor: + colorData && + item.color && + colorData[String(item.color.value)].backgroundColor, + }, + }); + }); + } + }); + + const shouldSetBounds = + shared?.extraSettings?.zoomMode !== ZoomMode.Manual && + shared?.extraSettings?.mapCenterMode !== ZoomMode.Manual; + const {zoom, center} = getMapState(shared, [leftBot, rightTop]); + + ChartEditor?.updateHighchartsConfig({state: {zoom, center}}); + + return [ + { + collection: { + children, + }, + options: mapOptions, + bounds: shouldSetBounds ? [leftBot, rightTop] : undefined, + }, + ]; +}; + +export default preparePolyline; diff --git a/src/server/modes/charts/plugins/datalens/preparers/scatter/__tests__/mocks/scatter.mock.ts b/src/server/modes/charts/plugins/datalens/preparers/scatter/__tests__/mocks/scatter.mock.ts new file mode 100644 index 0000000..89973d6 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/scatter/__tests__/mocks/scatter.mock.ts @@ -0,0 +1,295 @@ +import type { + IChartEditor, + ServerColor, + ServerField, + ServerShape, +} from '../../../../../../../../../shared'; +import {AxisMode, DATASET_FIELD_TYPES} from '../../../../../../../../../shared'; + +const chartEditorMock = { + getLang: () => { + return 'en'; + }, + updateHighchartsConfig: () => {}, + updateConfig: () => {}, + getWidgetConfig: () => {}, +}; + +const datasetId = 'j43msj9o23ge9'; + +const xField = { + datasetId: datasetId, + title: 'XField', + guid: 'cddd9cad-52a2-4232-8898-ade9a972c864', + data_type: DATASET_FIELD_TYPES.GENERICDATETIME, +} as ServerField; + +const yField = { + datasetId: datasetId, + title: 'YField', + guid: 'a6b94410-e219-11e9-a279-0b30c0a74ab7', + data_type: DATASET_FIELD_TYPES.FLOAT, +} as ServerField; + +export const colorField = { + datasetId: datasetId, + title: 'ColorField', + guid: '38e0b1f4-d46b-48a0-8905-6a6d1e61900d', + data_type: DATASET_FIELD_TYPES.STRING, +} as ServerColor; + +export const shapeField = { + datasetId: datasetId, + title: 'ShapeField', + guid: '45bdfbb6-1dd9-41bd-9871-b011efd8ec6b', + data_type: DATASET_FIELD_TYPES.STRING, +} as ServerShape; + +export const scatterPrepareBaseArgs = { + ChartEditor: { + getWidgetConfig: () => {}, + } as IChartEditor, + colors: [], + colorsConfig: { + loadedColorPalettes: {}, + colors: ['blue', 'red', 'orange'], + availablePalettes: {custom: {id: 'custom', scheme: ['blue', 'red', 'orange']}}, + palette: 'custom', + gradientColors: [], + }, + datasets: [], + fields: [], + shapes: [], + visualizationId: 'scatter', + idToDataType: { + [xField.guid]: xField.data_type, + [yField.guid]: yField.data_type, + [colorField.guid]: colorField.data_type, + [shapeField.guid]: shapeField.data_type, + } as Record, + idToTitle: { + [xField.guid]: xField.title, + [yField.guid]: yField.title, + [colorField.guid]: colorField.title, + [shapeField.guid]: shapeField.title, + }, + placeholders: [ + { + id: 'x', + items: [xField], + }, + { + id: 'y', + items: [yField], + }, + { + id: 'points', + items: [], + }, + { + id: 'size', + items: [], + }, + ], + resultData: { + data: [ + ['2023-05-11T00:00:00', '10', 'Shape-1', 'Color-1'], + ['2023-05-12T00:00:00', '11', 'Shape-1', 'Color-2'], + ['2023-05-13T00:00:00', '15', 'Shape-2', 'Color-1'], + ['2023-05-14T00:00:00', '100', 'Shape-2', 'Color-2'], + ], + order: [xField, yField, shapeField, colorField], + totals: [], + }, + features: [], + shared: {}, +}; + +export const scatterPrepareForQLArgs = { + placeholders: [ + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'x', + type: 'x', + title: 'section_x', + iconProps: {}, + items: [ + { + guid: 'built_year-0', + title: 'built_year', + datasetId: 'ql-mocked-dataset', + data_type: 'string', + cast: 'string', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + required: true, + capacity: 1, + settings: { + scale: 'auto', + scaleValue: 'min-max', + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + holidays: 'off', + axisFormatMode: 'auto', + axisModeMap: {'built_year-0': AxisMode.Discrete}, + }, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'y', + type: 'y', + title: 'section_y', + iconProps: {}, + items: [ + { + guid: 'iznos-1', + title: 'iznos', + datasetId: 'ql-mocked-dataset', + data_type: 'float', + cast: 'float', + type: 'DIMENSION', + calc_mode: 'direct', + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }, + ], + required: true, + capacity: 1, + settings: { + scale: 'auto', + scaleValue: 'min-max', + title: 'off', + titleValue: '', + type: 'linear', + grid: 'on', + gridStep: 'auto', + gridStepValue: 50, + hideLabels: 'no', + labelsView: 'auto', + axisFormatMode: 'auto', + }, + }, + { + allowedTypes: {}, + allowedDataTypes: {}, + id: 'points', + type: 'points', + title: 'section_points', + iconProps: {}, + items: [], + capacity: 1, + }, + { + allowedTypes: {}, + allowedFinalTypes: {}, + allowedDataTypes: {}, + id: 'size', + type: 'measures', + title: 'section_points_size', + iconProps: {}, + items: [], + capacity: 1, + }, + ], + fields: [], + colors: [], + colorsConfig: { + colors: [ + '#4DA2F1', + '#FF3D64', + '#8AD554', + '#FFC636', + '#FFB9DD', + '#84D1EE', + '#FF91A1', + '#54A520', + '#DB9100', + '#BA74B3', + '#1F68A9', + '#ED65A9', + '#0FA08D', + '#FF7E00', + '#E8B0A4', + '#52A6C5', + '#BE2443', + '#70C1AF', + '#FFB46C', + '#DCA3D7', + ], + gradientColors: ['#0044A3', '#8CCBFF'], + loadedColorPalettes: {}, + }, + sort: [], + visualizationId: 'scatter', + labels: [], + tooltips: [], + datasets: [], + resultData: { + data: [ + [null, 67], + ['1976', 100], + ['1970', 70], + [null, 40], + [null, null], + [null, null], + [null, null], + ['1950', 78], + ], + order: [ + {datasetId: 'ql-mocked-dataset', title: 'built_year'}, + {datasetId: 'ql-mocked-dataset', title: 'iznos'}, + {datasetId: 'ql-mocked-dataset', title: 'Column Names'}, + ], + totals: [], + }, + idToTitle: {'built_year-0': 'built_year', 'iznos-1': 'iznos', '': 'Column Names'}, + idToDataType: {'built_year-0': 'string', 'iznos-1': 'float', '': 'string'}, + shared: {}, + ChartEditor: chartEditorMock, + shapes: [], + shapesConfig: {}, + segments: [], + disableDefaultSorting: false, + features: [], +}; + +export const scatterPrepareForQLResult = { + graphs: [ + { + data: [ + {xLabel: '1976', yLabel: '100,00', x: 0, y: 100, marker: {}}, + {xLabel: '1970', yLabel: '70,00', x: 1, y: 70, marker: {}}, + {xLabel: '1950', yLabel: '78,00', x: 2, y: 78, marker: {}}, + ], + color: '#4DA2F1', + marker: {symbol: 'circle'}, + keys: ['y'], + custom: { + tooltipOptions: { + colorTitle: '', + pointTitle: '', + shapeTitle: '', + sizeTitle: '', + xTitle: 'built_year', + yTitle: 'iznos', + }, + }, + }, + ], + categories: ['1976', '1970', '1950'], +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/scatter/__tests__/scatter.test.ts b/src/server/modes/charts/plugins/datalens/preparers/scatter/__tests__/scatter.test.ts new file mode 100644 index 0000000..f02604e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/scatter/__tests__/scatter.test.ts @@ -0,0 +1,158 @@ +import cloneDeep from 'lodash/cloneDeep'; +import merge from 'lodash/merge'; + +import type {PrepareFunctionArgs} from '../../types'; +import {prepareHighchartsScatter} from '../highcharts'; +import {prepareScatter} from '../prepare-scatter'; + +import { + colorField, + scatterPrepareBaseArgs, + scatterPrepareForQLArgs, + scatterPrepareForQLResult, + shapeField, +} from './mocks/scatter.mock'; + +function getPrepareFunctionArgs(options: Partial = {}) { + return merge(cloneDeep(scatterPrepareBaseArgs), options) as unknown as PrepareFunctionArgs; +} + +describe('prepareScatter', () => { + test('should set default marker symbol and color when Shapes and Colors sections is empty', () => { + const options = getPrepareFunctionArgs(); + const result = prepareScatter(options); + const items = result.graphs.map((item) => ({marker: item.marker, color: item.color})); + + expect(items).toEqual([ + { + color: 'blue', + marker: { + symbol: 'circle', + }, + }, + ]); + }); + + test("should set default marker symbols and different colors when Shapes is empty and Colors isn't empty", () => { + const options = getPrepareFunctionArgs({ + colors: [colorField], + }); + const result = prepareScatter(options); + const items = result.graphs.map((item) => ({marker: item.marker, color: item.color})); + + expect(items).toEqual([ + { + color: 'blue', + marker: { + symbol: 'circle', + }, + }, + { + color: 'red', + marker: { + symbol: 'circle', + }, + }, + ]); + }); + + test("should set different marker symbols and default color when Colors is empty and Shapes isn't empty", () => { + const options = getPrepareFunctionArgs({ + shapes: [shapeField], + }); + const result = prepareScatter(options); + const items = result.graphs.map((item) => ({marker: item.marker, color: item.color})); + + expect(items).toEqual([ + { + color: 'blue', + marker: { + symbol: 'circle', + }, + }, + { + color: 'blue', + marker: { + symbol: 'diamond', + }, + }, + ]); + }); + + test("should set different marker symbols and colors when Colors and Shapes aren't empty", () => { + const options = getPrepareFunctionArgs({ + colors: [colorField], + shapes: [shapeField], + }); + const result = prepareScatter(options); + const items = result.graphs.map((item) => ({marker: item.marker, color: item.color})); + + expect(items).toEqual([ + { + color: 'blue', + marker: { + symbol: 'circle', + }, + }, + { + color: 'blue', + marker: { + symbol: 'diamond', + }, + }, + { + color: 'red', + marker: { + symbol: 'circle', + }, + }, + { + color: 'red', + marker: { + symbol: 'diamond', + }, + }, + ]); + }); + + test("should set mounted marker symbols when shapeConfig isn't empty", () => { + const options = getPrepareFunctionArgs({ + shapes: [shapeField], + shapesConfig: { + mountedShapes: { + 'Shape-1': 'square', + 'Shape-2': 'triangle-down', + }, + }, + }); + const result = prepareScatter(options); + const items = result.graphs.map((item) => ({marker: item.marker, name: item.name})); + + expect(items).toEqual([ + { + name: 'Shape-1', + marker: { + symbol: 'square', + }, + }, + { + name: 'Shape-2', + marker: { + symbol: 'triangle-down', + }, + }, + ]); + }); +}); + +describe('prepareHighchartsScatter', () => { + describe('ql', () => { + test('should render simple scatter correctly', () => { + const result = prepareHighchartsScatter( + scatterPrepareForQLArgs as unknown as PrepareFunctionArgs, + ); + + expect(result).toEqual(scatterPrepareForQLResult); + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/preparers/scatter/gravity-charts.ts b/src/server/modes/charts/plugins/datalens/preparers/scatter/gravity-charts.ts new file mode 100644 index 0000000..e8e8bfa --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/scatter/gravity-charts.ts @@ -0,0 +1,258 @@ +import type { + ChartData, + ScatterSeries, + ScatterSeriesData, +} from '@gravity-ui/chartkit/gravity-charts'; +import merge from 'lodash/merge'; + +import type {SeriesExportSettings} from '../../../../../../../shared'; +import { + PlaceholderId, + getFakeTitleOrTitle, + getXAxisMode, + isDateField, + isHtmlField, + isMarkdownField, + isMarkupField, + isNumberField, +} from '../../../../../../../shared'; +import type { + PointCustomData, + ScatterSeriesCustomData, +} from '../../../../../../../shared/types/chartkit'; +import {getBaseChartConfig} from '../../gravity-charts/utils'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import {getExportColumnSettings} from '../../utils/export-helpers'; +import {isGradientMode} from '../../utils/misc-helpers'; +import {getAxisFormatting, getAxisType} from '../helpers/axis'; +import {getLegendColorScale} from '../helpers/legend'; +import type {PrepareFunctionArgs} from '../types'; + +import type {ScatterGraph} from './prepare-scatter'; +import {prepareScatter} from './prepare-scatter'; + +type MapScatterSeriesArgs = { + xAxisType?: string; + yAxisType?: string; + graph: ScatterGraph; +}; + +function mapScatterSeries(args: MapScatterSeriesArgs): ScatterSeries { + const {xAxisType, yAxisType, graph} = args; + + const series = { + type: 'scatter', + name: graph.name || '', + color: typeof graph.color === 'string' ? graph.color : undefined, + data: + graph.data?.map((item, index) => { + const point = item; + const pointData: ScatterSeriesData = { + radius: point.marker?.radius, + custom: { + ...item.custom, + name: point.name, + xLabel: point.xLabel, + yLabel: point.yLabel, + colorValue: point.colorValue, + cLabel: point.cLabel, + shapeValue: point.shapeValue, + sLabel: point.sLabel, + sizeValue: point.sizeValue, + sizeLabel: point.sizeLabel, + }, + color: typeof point.color === 'string' ? point.color : undefined, + }; + + if (xAxisType === 'category') { + pointData.x = typeof item.x === 'number' ? item.x : index; + } else { + pointData.x = item.x; + } + + if (yAxisType === 'category') { + pointData.y = typeof item.y === 'number' ? item.y : index; + } else { + pointData.y = item.y; + } + + return pointData; + }) || [], + // @ts-ignore + custom: graph.custom, + } as ScatterSeries; + + if (graph.marker?.symbol) { + series.symbolType = graph.marker?.symbol as ScatterSeries['symbolType']; + } + + return series; +} + +// eslint-disable-next-line complexity +export function prepareGravityChartsScatter(args: PrepareFunctionArgs): ChartData { + const {shared, idToDataType, placeholders, colors, colorsConfig, shapes, visualizationId} = + args; + const { + categories: preparedXCategories, + graphs, + x, + y, + z, + color, + shape, + size, + } = prepareScatter(args); + const xCategories = preparedXCategories; + + const exportSettings: SeriesExportSettings = { + columns: [ + getExportColumnSettings({path: 'x', field: x}), + getExportColumnSettings({path: 'y', field: y}), + ], + }; + + if (z) { + exportSettings.columns.push(getExportColumnSettings({path: 'custom.name', field: z})); + } + + if (size) { + exportSettings.columns.push( + getExportColumnSettings({path: 'custom.sizeValue', field: size}), + ); + } + + const colorItem = colors[0]; + if (colorItem) { + exportSettings.columns.push( + getExportColumnSettings({path: 'custom.colorValue', field: colorItem}), + ); + } + + const shapeItem = shapes[0]; + if (shapeItem) { + exportSettings.columns.push( + getExportColumnSettings({path: 'custom.shapeValue', field: shapeItem}), + ); + } + + const seriesCustomData: ScatterSeriesCustomData = { + xTitle: getFakeTitleOrTitle(x), + yTitle: getFakeTitleOrTitle(y), + pointTitle: getFakeTitleOrTitle(z), + colorTitle: getFakeTitleOrTitle(color), + sizeTitle: getFakeTitleOrTitle(size), + shapeTitle: getFakeTitleOrTitle(shape), + exportSettings, + }; + + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + const xAxisMode = getXAxisMode({config: chartConfig}); + const xPlaceholder = placeholders.find((p) => p.id === PlaceholderId.X); + const xAxisType = getAxisType({ + field: x, + settings: xPlaceholder?.settings, + axisMode: xAxisMode, + }); + const yPlaceholder = placeholders.find((p) => p.id === PlaceholderId.Y); + const yAxisType = getAxisType({ + field: y, + settings: yPlaceholder?.settings, + }); + + let xAxis: ChartData['xAxis'] = {}; + if (xAxisType === 'category' && xCategories?.length) { + xAxis = { + type: 'category', + // @ts-ignore There may be a type mismatch due to the wrapper over html, markup and markdown + categories: xCategories, + labels: { + html: isHtmlField(x) || isMarkdownField(x) || isMarkupField(x), + }, + }; + } else { + if (isDateField(x)) { + xAxis.type = 'datetime'; + } + + if (isNumberField(x)) { + xAxis.type = xPlaceholder?.settings?.type === 'logarithmic' ? 'logarithmic' : 'linear'; + } + + const xAxisLabelNumberFormat = xPlaceholder + ? getAxisFormatting({ + placeholder: xPlaceholder, + visualizationId, + }) + : undefined; + + if (xAxisLabelNumberFormat) { + xAxis.labels = {numberFormat: xAxisLabelNumberFormat}; + } + } + + const colorFieldDataType = color ? idToDataType[color.guid] : null; + const gradientMode = + color && + colorFieldDataType && + isGradientMode({colorField: color, colorFieldDataType, colorsConfig}); + + let legend: ChartData['legend'] = { + html: [x, y, z, color].some( + (field) => isHtmlField(field) || isMarkdownField(field) || isMarkupField(field), + ), + }; + + if (graphs.length && gradientMode) { + const points = graphs + .map((graph) => (graph.data ?? []).map((d) => ({colorValue: d.colorValue}))) + .flat(2); + const colorScale = getLegendColorScale({ + colorsConfig, + points, + }); + + legend = { + enabled: true, + type: 'continuous', + title: {text: getFakeTitleOrTitle(color), style: {fontWeight: '500'}}, + colorScale, + }; + } else if (graphs.length <= 1) { + legend.enabled = false; + } + + const axisLabelNumberFormat = yPlaceholder + ? getAxisFormatting({ + placeholder: yPlaceholder, + visualizationId, + }) + : undefined; + + const config: ChartData = { + xAxis, + yAxis: [ + { + labels: { + numberFormat: axisLabelNumberFormat ?? undefined, + html: + yAxisType === 'category' && + (isHtmlField(y) || isMarkdownField(y) || isMarkupField(y)), + }, + maxPadding: 0, + }, + ], + series: { + data: graphs.map((graph) => ({ + ...mapScatterSeries({graph, xAxisType, yAxisType}), + custom: seriesCustomData, + })), + }, + legend, + }; + + return merge( + getBaseChartConfig({shared, visualization: {placeholders, id: visualizationId}}), + config, + ); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/scatter/helpers/shape.ts b/src/server/modes/charts/plugins/datalens/preparers/scatter/helpers/shape.ts new file mode 100644 index 0000000..a4dabf3 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/scatter/helpers/shape.ts @@ -0,0 +1,79 @@ +import {groupBy} from 'lodash'; + +import type {MarkupItem, ServerField, ServerShapesConfig} from '../../../../../../../../shared'; +import { + POINT_SHAPES_IN_ORDER, + isMarkupField, + markupToRawString, +} from '../../../../../../../../shared'; +import type {ExtendedSeriesScatterOptions} from '../../../utils/color-helpers'; + +function getShapeByFieldValueSelector(shapeConfig: ServerShapesConfig | undefined) { + const shapeSymbolsByValue = new Map(); + + return (shapeValue: string) => { + const mountedShape = shapeConfig?.mountedShapes?.[shapeValue]; + if (mountedShape && mountedShape !== 'auto') { + return mountedShape; + } + + if (!shapeSymbolsByValue.has(shapeValue)) { + const shapeSymbol = + POINT_SHAPES_IN_ORDER[shapeSymbolsByValue.size % POINT_SHAPES_IN_ORDER.length]; + shapeSymbolsByValue.set(shapeValue, shapeSymbol); + } + + return shapeSymbolsByValue.get(shapeValue); + }; +} + +export function mapPointsByShape({ + graphs, + shapesConfig, + field, +}: { + graphs: ExtendedSeriesScatterOptions[]; + field: ServerField; + shapesConfig?: ServerShapesConfig; +}): ExtendedSeriesScatterOptions[] { + const result: ExtendedSeriesScatterOptions[] = []; + const getShapeByFieldValue = getShapeByFieldValueSelector(shapesConfig); + + const isMarkupShape = isMarkupField(field); + + graphs.forEach((graph) => { + const graphData = graph.data || []; + + const groups = groupBy(graphData, (point) => { + const value = point.shapeValue || ''; + if (isMarkupShape) { + return markupToRawString(value as unknown as MarkupItem); + } + + return value; + }); + + Object.keys(groups) + .sort() + .forEach((shapeValue) => { + const points = groups[shapeValue]; + const colorValue = points[0].colorValue; + + let name = shapeValue; + if (colorValue && colorValue !== shapeValue) { + name = `${colorValue}: ${shapeValue}`; + } + + result.push({ + ...graph, + name, + data: points, + marker: { + symbol: getShapeByFieldValue(shapeValue), + }, + }); + }); + }); + + return result; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/scatter/helpers/tooltip.ts b/src/server/modes/charts/plugins/datalens/preparers/scatter/helpers/tooltip.ts new file mode 100644 index 0000000..c3b42f6 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/scatter/helpers/tooltip.ts @@ -0,0 +1,28 @@ +import type {ChartsConfig, ServerPlaceholder} from '../../../../../../../../shared'; +import {PlaceholderId, getFakeTitleOrTitle} from '../../../../../../../../shared'; +import {getFieldTitle} from '../../../utils/misc-helpers'; + +type GetScatterTooltipOptionsArgs = { + placeholders: ServerPlaceholder[]; + shared: ChartsConfig; +}; + +export function getScatterTooltipOptions(args: GetScatterTooltipOptionsArgs) { + const {shared, placeholders} = args; + + const xField = placeholders[0]?.items?.[0]; + const yField = placeholders[1]?.items?.[0]; + const pointField = placeholders[2]?.items?.[0]; + const colorField = shared.colors?.[0]; + const shapeField = shared.shapes?.[0]; + const sizeField = placeholders.find((pl) => pl.id === PlaceholderId.Size)?.items?.[0]; + + return { + pointTitle: getFakeTitleOrTitle(pointField), + colorTitle: getFieldTitle(colorField), + shapeTitle: getFieldTitle(shapeField), + sizeTitle: getFieldTitle(sizeField), + xTitle: getFieldTitle(xField), + yTitle: getFieldTitle(yField), + }; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/scatter/highcharts.ts b/src/server/modes/charts/plugins/datalens/preparers/scatter/highcharts.ts new file mode 100644 index 0000000..b88bfe6 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/scatter/highcharts.ts @@ -0,0 +1,174 @@ +import type {Highcharts} from '@gravity-ui/chartkit/highcharts'; +import set from 'lodash/set'; + +import type {ExtendedExportingCsvOptions} from '../../../../../../../shared'; +import { + ChartkitHandlers, + LegendDisplayMode, + isDateField, + isHtmlField, + isMarkdownField, +} from '../../../../../../../shared'; +import {getConfigWithActualFieldTypes} from '../../utils/config-helpers'; +import {getFieldExportingOptions, getFieldsExportingOptions} from '../../utils/export-helpers'; +import {getHighchartsGradientStops} from '../../utils/get-gradient-stops'; +import {isGradientMode, isNumericalDataType} from '../../utils/misc-helpers'; +import {addAxisFormatter, addAxisFormatting} from '../helpers/axis'; +import type {ChartKitFormatSettings, PrepareFunctionArgs} from '../types'; + +import {prepareScatter} from './prepare-scatter'; + +// eslint-disable-next-line complexity +export function prepareHighchartsScatter(options: PrepareFunctionArgs) { + const {ChartEditor, shared, placeholders, idToTitle, idToDataType, visualizationId} = options; + const { + graphs, + categories, + x, + y, + z, + color, + shape, + minColorValue, + maxColorValue, + colorsConfig, + size, + } = prepareScatter(options); + + const colorFieldDataType = color ? idToDataType[color.guid] : null; + + const gradientMode = + color && + colorFieldDataType && + colorsConfig && + isGradientMode({colorField: color, colorFieldDataType, colorsConfig}); + + const points = graphs.map((graph) => graph.data).flat(2) as Highcharts.PointOptionsObject[]; + + if (!x || !y) { + return { + graphs, + }; + } + + const legendIsHidden = shared.extraSettings?.legendMode === LegendDisplayMode.Hide; + + if (ChartEditor) { + const xPlaceholder = placeholders.find((placeholder) => placeholder.id === 'x')!; + const yPlaceholder = placeholders.find((placeholder) => placeholder.id === 'y')!; + + const xPlaceholderSettings = xPlaceholder.settings; + const yPlaceholderSettings = yPlaceholder.settings; + const chartConfig = getConfigWithActualFieldTypes({config: shared, idToDataType}); + + const customConfig: Omit, 'exporting' | 'xAxis' | 'yAxis'> & { + axesFormatting: { + xAxis: (ChartKitFormatSettings | undefined)[]; + yAxis: (ChartKitFormatSettings | undefined)[]; + }; + xAxis: Record; + yAxis: Record; + exporting: {csv: ExtendedExportingCsvOptions}; + } = { + axesFormatting: {xAxis: [], yAxis: []}, + xAxis: {}, + yAxis: {}, + exporting: { + csv: { + custom: { + columnHeaderMap: getFieldsExportingOptions({ + x, + y, + sizeValue: size, + colorValue: color, + }), + categoryHeader: getFieldExportingOptions(z || x), + }, + columnHeaderFormatter: ChartkitHandlers.WizardExportColumnNamesFormatter, + }, + }, + }; + + if (xPlaceholderSettings?.title === 'auto') { + customConfig.xAxis = { + title: { + text: x.fakeTitle || idToTitle[x.guid], + }, + }; + } + + if (yPlaceholderSettings?.title === 'auto') { + const yDataType = idToDataType[y.guid]; + const yIsDate = isDateField({data_type: yDataType}); + + customConfig.yAxis = { + type: yIsDate ? 'datetime' : undefined, + title: { + text: y.fakeTitle || idToTitle[y.guid], + }, + }; + } + addAxisFormatting(customConfig.axesFormatting.xAxis, visualizationId, xPlaceholder); + addAxisFormatting(customConfig.axesFormatting.yAxis, visualizationId, yPlaceholder); + addAxisFormatter({ + axisConfig: customConfig.xAxis, + placeholder: xPlaceholder, + chartConfig, + }); + addAxisFormatter({ + axisConfig: customConfig.yAxis, + placeholder: yPlaceholder, + }); + + if (gradientMode) { + if ( + colorsConfig && + typeof minColorValue === 'number' && + typeof maxColorValue === 'number' + ) { + customConfig.colorAxis = { + min: minColorValue, + max: maxColorValue, + stops: getHighchartsGradientStops({ + colorsConfig, + points, + minColorValue, + maxColorValue, + }), + } as Highcharts.ColorAxisOptions; + } + + if (!legendIsHidden && isNumericalDataType(color.data_type)) { + customConfig.legend = { + enabled: true, + }; + } + } + + const shouldUseHtmlForXAxis = isHtmlField(x) || isMarkdownField(x); + if (shouldUseHtmlForXAxis) { + set(customConfig, 'xAxis.labels.useHTML', true); + } + + const shouldUseHtmlForYAxis = isHtmlField(y) || isMarkdownField(y); + if (shouldUseHtmlForYAxis) { + set(customConfig, 'yAxis.labels.useHTML', true); + } + + const shouldUseHtmlForLegend = + isMarkdownField(color) || + isHtmlField(color) || + isMarkdownField(shape) || + isHtmlField(shape); + if (shouldUseHtmlForLegend) { + set(customConfig, 'legend.useHTML', true); + } + + ChartEditor.updateHighchartsConfig(customConfig); + } + + return { + graphs, + categories, + }; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/scatter/index.ts b/src/server/modes/charts/plugins/datalens/preparers/scatter/index.ts new file mode 100644 index 0000000..35f5127 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/scatter/index.ts @@ -0,0 +1,2 @@ +export * from './gravity-charts'; +export * from './highcharts'; diff --git a/src/server/modes/charts/plugins/datalens/preparers/scatter/prepare-scatter.ts b/src/server/modes/charts/plugins/datalens/preparers/scatter/prepare-scatter.ts new file mode 100644 index 0000000..3fec428 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/scatter/prepare-scatter.ts @@ -0,0 +1,511 @@ +import type {Highcharts} from '@gravity-ui/chartkit/highcharts'; + +import type { + PointSizeConfig, + ServerField, + WrappedHTML, + WrappedMarkup, +} from '../../../../../../../shared'; +import { + MINIMUM_FRACTION_DIGITS, + POINT_SHAPES_IN_ORDER, + getFakeTitleOrTitle, + getFormatOptions, + isDateField, + isHtmlField, + isMarkdownField, + isMarkupField, + isStringField, + wrapMarkupValue, +} from '../../../../../../../shared'; +import type {WrappedMarkdown} from '../../../../../../../shared/utils/markdown'; +import {wrapMarkdownValue} from '../../../../../../../shared/utils/markdown'; +import {wrapHtml} from '../../../../../../../shared/utils/ui-sandbox'; +import type {ChartColorsConfig} from '../../types'; +import type {ExtendedSeriesScatterOptions} from '../../utils/color-helpers'; +import { + mapAndColorizePointsByGradient, + mapAndColorizePointsByPalette, +} from '../../utils/color-helpers'; +import {getMountedColor} from '../../utils/constants'; +import {getExtremeValues} from '../../utils/geo-helpers'; +import { + chartKitFormatNumberWrapper, + findIndexInOrder, + formatDate, + getCategoryFormatter, + getPointRadius, + getTimezoneOffsettedTime, + isGradientMode, + isNumericalDataType, +} from '../../utils/misc-helpers'; +import {addActionParamValue} from '../helpers/action-params'; +import type {PrepareFunctionArgs} from '../types'; + +import {mapPointsByShape} from './helpers/shape'; +import {getScatterTooltipOptions} from './helpers/tooltip'; +import type {ScatterPoint} from './types'; + +export type ScatterGraph = { + name?: string | WrappedMarkdown; + color?: string; + data?: ScatterPoint[]; + marker?: { + symbol?: string; + }; + custom?: any; +}; + +export type PrepareScatterResult = { + graphs: ScatterGraph[]; + categories?: (string | number | WrappedMarkdown | WrappedHTML)[]; + x?: ServerField; + y?: ServerField; + z?: ServerField; + color?: ServerField; + shape?: ServerField; + size?: ServerField; + minColorValue?: number; + maxColorValue?: number; + colorsConfig?: ChartColorsConfig; +}; + +// eslint-disable-next-line complexity +export function prepareScatter(options: PrepareFunctionArgs): PrepareScatterResult { + const geopointsConfig = (options.geopointsConfig || {}) as PointSizeConfig; + const { + placeholders, + resultData, + colors, + colorsConfig, + idToTitle, + idToDataType, + shapes, + shapesConfig, + ChartEditor, + shared, + defaultColorPaletteId, + } = options; + const widgetConfig = ChartEditor.getWidgetConfig(); + const isActionParamsEnable = widgetConfig?.actionParams?.enable; + const {data, order} = resultData; + + const x = placeholders[0].items[0]; + if (!x) { + return {graphs: []}; + } + + const y = placeholders[1].items[0]; + if (!y) { + return {graphs: []}; + } + + const z = placeholders[2].items[0]; + const size = placeholders[3]?.items[0]; + + const color = colors && colors[0]; + const colorFieldDataType = color ? idToDataType[color.guid] : null; + + const gradientMode = + color && + colorFieldDataType && + isGradientMode({colorField: color, colorFieldDataType, colorsConfig}); + + const shape = shapes?.[0]; + const shapesConfigured = Object.keys(shapesConfig?.mountedShapes || {}).length > 0; + + const xDataType = idToDataType[x.guid]; + const xIsNumber = isNumericalDataType(xDataType); + const xIsDate = isDateField({data_type: xDataType}); + + const yDataType = idToDataType[y.guid]; + const yIsNumber = isNumericalDataType(yDataType); + const yIsDate = isDateField({data_type: yDataType}); + + const cDataType = color && idToDataType[color.guid]; + + const points: ScatterPoint[] = []; + const xCategories: (string | number)[] = []; + const yCategories: (string | number | null)[] = []; + + let minColorValue = Infinity; + let maxColorValue = -Infinity; + + let sizeMinValue: number, sizeMaxValue: number; + + if (size) { + data.forEach((values) => { + const sizeTitle = idToTitle[size.guid]; + const i = findIndexInOrder(order, size, sizeTitle); + const pointValue = Number(values[i]); + [sizeMinValue, sizeMaxValue] = getExtremeValues({ + value: pointValue, + min: sizeMinValue, + max: sizeMaxValue, + }); + }); + } + + const keys = new Set(['x', 'y']); + + const xFormatting = getFormatOptions(x); + const yFormatting = getFormatOptions(y); + + // eslint-disable-next-line complexity + data.forEach((values) => { + const xTitle = idToTitle[x.guid]; + const xi = findIndexInOrder(order, x, xTitle); + const xValueRaw: string | null | undefined = values[xi]; + let xValue: string | number | Date; + let zValueRaw: string | null | undefined; + const point: ScatterPoint = {}; + + if (xValueRaw === null || xValueRaw === undefined) { + return; + } + + xValue = xValueRaw; + + if (xIsNumber) { + xValue = Number(xValueRaw); + } else if (xIsDate) { + xValue = new Date(xValueRaw); + + if (xDataType) { + xValue.setTime(getTimezoneOffsettedTime(xValue)); + } + + xValue = xValue.getTime(); + } + + if (xIsDate) { + point.xLabel = formatDate({ + valueType: xDataType, + value: xValue as string, + format: x.format, + utc: true, + }); + } else if (xIsNumber) { + point.xLabel = chartKitFormatNumberWrapper(xValue as number, { + lang: 'ru', + ...(xFormatting ?? { + precision: xDataType === 'float' ? MINIMUM_FRACTION_DIGITS : 0, + }), + }); + } else { + let value: string | WrappedMarkdown | WrappedHTML = xValue as string; + if (isMarkdownField(x)) { + value = wrapMarkdownValue(value); + } else if (isHtmlField(x)) { + value = wrapHtml(value); + } + + point.xLabel = value; + } + + let indexOfXValue = xCategories.indexOf(xValue); + + if (indexOfXValue === -1) { + xCategories.push(xValue); + indexOfXValue = xCategories.length - 1; + } + + if (!xIsNumber && !xIsDate) { + xValue = indexOfXValue; + } + + const yTitle = idToTitle[y.guid]; + const yi = findIndexInOrder(order, y, yTitle); + const yValueRaw: string | null = values[yi]; + let yValue: number | string | null = yValueRaw; + + if (yIsNumber) { + yValue = Number(yValueRaw); + } else if (yIsDate) { + const yValueDate = new Date(String(yValueRaw)); + + if (y.data_type === 'datetime' || y.data_type === 'genericdatetime') { + yValueDate.setTime(getTimezoneOffsettedTime(yValueDate)); + } + + yValue = yValueDate.getTime(); + } + + if (yIsDate) { + point.yLabel = formatDate({ + valueType: yDataType, + value: yValue as string, + format: y.format, + utc: true, + }); + } else if (yIsNumber) { + point.yLabel = chartKitFormatNumberWrapper(yValue as number, { + lang: 'ru', + ...(yFormatting ?? { + precision: yDataType === 'float' ? MINIMUM_FRACTION_DIGITS : 0, + }), + }); + } else { + let yLabel: string | WrappedMarkdown | WrappedHTML = String(yValue as string); + if (isMarkdownField(y)) { + yLabel = wrapMarkdownValue(yLabel); + } else if (isHtmlField(y)) { + yLabel = wrapHtml(yLabel); + } + + point.yLabel = yLabel; + } + + let indexOfYValue = yCategories.indexOf(yValue); + + if (indexOfYValue === -1) { + yCategories.push(yValue); + indexOfYValue = yCategories.length - 1; + } + + if (!yIsNumber && !yIsDate) { + yValue = indexOfYValue; + } + + point.x = xValue as number; + point.y = yValue as number; + + if (z) { + const zTitle = idToTitle[z.guid]; + const zi = findIndexInOrder(order, z, zTitle); + zValueRaw = values[zi]; + let formattedZValue: string | null | WrappedMarkdown | WrappedMarkup | WrappedHTML = + zValueRaw; + + if (isNumericalDataType(z.data_type)) { + const formatting = getFormatOptions(z); + if (formatting) { + formattedZValue = chartKitFormatNumberWrapper(Number(formattedZValue), { + lang: 'ru', + ...formatting, + }); + } + } + + if (isStringField(z)) { + if (isMarkdownField(z)) { + formattedZValue = wrapMarkdownValue(zValueRaw as string); + } else if (isHtmlField(z)) { + formattedZValue = wrapHtml(zValueRaw as string); + } + } + + if (isMarkupField(z)) { + formattedZValue = wrapMarkupValue(zValueRaw); + } + + point.name = formattedZValue || ''; + } else { + delete point.name; + keys.delete('x'); + } + + let radius; + if (size) { + const sizeTitle = idToTitle[size.guid]; + keys.add('sizeValue'); + const i = findIndexInOrder(order, size, sizeTitle); + const pointValue = Number(values[i]); + point.sizeValue = pointValue; + point.sizeLabel = chartKitFormatNumberWrapper(pointValue, { + lang: 'ru', + ...getFormatOptions(size), + }); + radius = getPointRadius({ + current: pointValue, + min: sizeMinValue, + max: sizeMaxValue, + geopointsConfig, + }); + } else { + radius = geopointsConfig?.radius; + } + point.marker = { + radius, + }; + + if (color) { + const cTitle = idToTitle[color.guid]; + const i = findIndexInOrder(order, color, cTitle); + const colorValue = values[i]; + let colorLabel: string | null | WrappedMarkdown | WrappedHTML = colorValue; + if (isMarkdownField(color)) { + colorLabel = wrapMarkdownValue(String(colorValue)); + } else if (isHtmlField(color)) { + colorLabel = wrapHtml(String(colorValue)); + } + + if (gradientMode) { + const numberColorValue = Number(colorValue); + + if (numberColorValue < minColorValue) { + minColorValue = numberColorValue; + } + + if (numberColorValue > maxColorValue) { + maxColorValue = numberColorValue; + } + + const formatting = getFormatOptions(color); + point.cLabel = chartKitFormatNumberWrapper(numberColorValue, { + lang: 'ru', + ...(formatting ?? { + precision: cDataType === 'float' ? MINIMUM_FRACTION_DIGITS : 0, + }), + }); + point.value = numberColorValue; + point.colorValue = numberColorValue as unknown as string; + keys.add('colorValue'); + } else { + point.value = colorValue as unknown as number; + point.colorValue = colorValue; + point.cLabel = colorLabel; + point.colorGuid = colors[0] ? colors[0].guid : undefined; + } + } + + if (shape) { + const cTitle = idToTitle[shape.guid]; + const i = findIndexInOrder(order, shape, cTitle); + const shapeValue = values[i] ?? ''; + let shapeLabel: WrappedMarkdown | string | WrappedHTML | WrappedMarkup = shapeValue; + if (isMarkdownField(shape)) { + shapeLabel = wrapMarkdownValue(shapeValue); + } else if (isHtmlField(shape)) { + shapeLabel = wrapHtml(shapeValue); + } else if (isMarkupField(shape)) { + shapeLabel = wrapMarkupValue(shapeValue); + } + + point.shapeValue = shapeValue; + point.sLabel = shapeLabel; + } else if (shapesConfigured) { + const shapeValue = yTitle; + + point.shapeValue = shapeValue; + point.sLabel = shapeValue; + } + + if (isActionParamsEnable) { + const actionParams: Record = {}; + addActionParamValue(actionParams, x, xValueRaw); + addActionParamValue(actionParams, y, yValueRaw); + addActionParamValue(actionParams, z, zValueRaw); + + point.custom = { + ...point.custom, + actionParams, + }; + } + + points.push(point); + }); + + let graphs: ExtendedSeriesScatterOptions[] = [{data: points}] as ExtendedSeriesScatterOptions[]; + + if (color) { + if (gradientMode) { + mapAndColorizePointsByGradient(points as Highcharts.PointOptionsObject[], colorsConfig); + } else { + graphs = mapAndColorizePointsByPalette({ + points, + colorsConfig, + defaultColorPaletteId, + colorField: color, + }); + } + + if (graphs.length) { + graphs[0].title = color.fakeTitle || idToTitle[color.guid]; + } + } else { + const yField = {...y, title: idToTitle[y.guid] ?? y.title}; + const value = getFakeTitleOrTitle(yField); + const colorFromConfig = getMountedColor({...colorsConfig, value}) || colorsConfig.colors[0]; + graphs.forEach((graph) => { + graph.color = colorFromConfig; + }); + } + + if (shape || shapesConfigured) { + graphs = mapPointsByShape({graphs, shapesConfig, field: shape}); + } else { + graphs.forEach((graph) => { + graph.marker = { + symbol: POINT_SHAPES_IN_ORDER[0], + }; + }); + } + + if (isMarkdownField(color) || isMarkdownField(shape)) { + graphs.forEach((g) => { + g.name = wrapMarkdownValue(g.name as string); + }); + } else if (isHtmlField(color) || isHtmlField(shape)) { + graphs.forEach((g) => { + g.name = wrapHtml(g.name as string); + }); + } + + let categories: (string | number | WrappedMarkdown | WrappedHTML)[] | undefined; + + if (!xIsNumber && !xIsDate) { + categories = xCategories; + const categoryField = x ? {...x, data_type: xDataType ?? x?.data_type} : undefined; + const categoriesFormatter = getCategoryFormatter({ + field: categoryField, + }); + + categories = categories?.map((c) => categoriesFormatter(String(c))); + } + + const hasMarkdown = [x, y, z, size, color, shape].some((field) => isMarkdownField(field)); + if (hasMarkdown) { + ChartEditor.updateConfig({useMarkdown: true}); + } + + const hasMarkup = [x, y, z, size, color, shape].some((field) => isMarkupField(field)); + if (hasMarkup) { + ChartEditor.updateConfig({useMarkup: true}); + } + + const hasHtml = [x, y, z, size, color, shape].some((field) => isHtmlField(field)); + if (hasHtml) { + ChartEditor.updateConfig({useHtml: true}); + } + + graphs.forEach((graph) => { + graph.keys = Array.from(keys); + graph.custom = { + ...graph.custom, + tooltipOptions: getScatterTooltipOptions({placeholders, shared}), + }; + + if (isActionParamsEnable) { + const actionParams: Record = {}; + addActionParamValue(actionParams, color, graph.data?.[0]?.colorValue); + addActionParamValue(actionParams, shape, graph.data?.[0]?.shapeValue); + + graph.custom.actionParams = actionParams; + } + }); + + return { + x, + y, + z, + color, + shape, + minColorValue, + maxColorValue, + colorsConfig, + size, + graphs: graphs as ScatterGraph[], + categories, + }; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/scatter/types.ts b/src/server/modes/charts/plugins/datalens/preparers/scatter/types.ts new file mode 100644 index 0000000..ddae54d --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/scatter/types.ts @@ -0,0 +1,19 @@ +import type {WrappedHTML, WrappedMarkup} from '../../../../../../../shared'; +import type {WrappedMarkdown} from '../../../../../../../shared/utils/markdown'; +import type {ExtendedPointOptionsObject} from '../../utils/color-helpers'; + +export type ScatterPoint = ExtendedPointOptionsObject & { + xLabel?: string | WrappedMarkdown | WrappedHTML; + yLabel?: string | WrappedMarkdown | WrappedHTML; + cLabel?: string | null | WrappedMarkdown | WrappedHTML; + sLabel?: string | WrappedMarkdown | WrappedHTML | WrappedMarkup; + sizeValue?: number | null; + sizeLabel?: string | null | WrappedMarkdown | WrappedHTML; + x?: number; + y?: number; + value?: number; + marker?: { + radius?: number; + }; + custom?: Record; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/treemap/gravity-charts.ts b/src/server/modes/charts/plugins/datalens/preparers/treemap/gravity-charts.ts new file mode 100644 index 0000000..7d673c1 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/treemap/gravity-charts.ts @@ -0,0 +1,291 @@ +import type { + ChartData, + TreemapSeries, + TreemapSeriesData, +} from '@gravity-ui/chartkit/gravity-charts'; +import merge from 'lodash/merge'; + +import type { + ColumnExportSettings, + SeriesExportSettings, + WrappedMarkdown, +} from '../../../../../../../shared'; +import { + MINIMUM_FRACTION_DIGITS, + PlaceholderId, + getFormatOptions, + isDateField, + isHtmlField, + isMarkdownField, +} from '../../../../../../../shared'; +import {wrapMarkdownValue} from '../../../../../../../shared/utils/markdown'; +import {wrapHtml} from '../../../../../../../shared/utils/ui-sandbox'; +import {getColorsSettings} from '../../../helpers/color-palettes'; +import {getBaseChartConfig} from '../../gravity-charts/utils'; +import { + mapAndColorizeHashTableByGradient, + mapAndColorizeHashTableByPalette, +} from '../../utils/color-helpers'; +import {getExportColumnSettings} from '../../utils/export-helpers'; +import { + chartKitFormatNumberWrapper, + findIndexInOrder, + formatDate, + isGradientMode, + isNumericalDataType, +} from '../../utils/misc-helpers'; +import type {PrepareFunctionArgs} from '../types'; + +type TreemapItemName = string | null | WrappedMarkdown; +type ExtendedTreemapSeriesData = Omit & { + name: TreemapItemName | TreemapItemName[]; + drillDownFilterValue: unknown; + label?: string; +}; + +type ExtendedTreemapSeries = TreemapSeries & { + custom?: { + exportSettings?: SeriesExportSettings; + }; +}; + +export function prepareD3Treemap({ + shared, + visualizationId, + placeholders, + resultData, + colors, + colorsConfig, + idToTitle, + idToDataType, + ChartEditor, + defaultColorPaletteId, +}: PrepareFunctionArgs): Partial { + const dimensions = placeholders.find((p) => p.id === PlaceholderId.Dimensions)?.items ?? []; + const dTypes = dimensions.map((item) => item.data_type); + const useMarkdown = dimensions?.some(isMarkdownField); + const useHtml = dimensions?.some(isHtmlField); + + const measures = placeholders.find((p) => p.id === PlaceholderId.Measures)?.items ?? []; + + const color = colors[0]; + const colorFieldDataType = color ? idToDataType[color.guid] : null; + + const gradientMode = + color && + colorFieldDataType && + isGradientMode({colorField: color, colorFieldDataType, colorsConfig}); + + const {data, order} = resultData; + + let treemap: ExtendedTreemapSeriesData[] = []; + const treemapIds: string[] = []; + const hashTable: Record = {}; + const valuesForColorData: Record & {colorGuid?: string} = {}; + const isFloat = measures[0] && measures[0].data_type === 'float'; + let colorData: Record = {}; + + if (color) { + // We make the property non-enumerable so that it does not participate in the formation of the palette + Object.defineProperty(valuesForColorData, 'colorGuid', { + enumerable: false, + value: color.guid, + }); + } + + data.forEach((values) => { + let colorByDimension: string | null; + if (color && color.type === 'DIMENSION') { + const actualTitle = idToTitle[color.guid]; + const i = findIndexInOrder(order, color, actualTitle); + const colorValue = values[i]; + + colorByDimension = colorValue; + } + + const dPath: (string | null)[] = []; + let lastDimensionItem: ExtendedTreemapSeriesData | undefined; + dimensions.forEach((item, level) => { + if (item.type === 'PSEUDO') { + return; + } + + const actualTitle = idToTitle[item.guid]; + + const i = findIndexInOrder(order, item, actualTitle); + + const rawValue = values[i]; + let value: string | null; + + if (isDateField({data_type: dTypes[level]})) { + value = formatDate({ + valueType: dTypes[level], + value: rawValue, + format: item.format, + }); + } else if (isNumericalDataType(dTypes[level])) { + const formatting = getFormatOptions(item); + value = chartKitFormatNumberWrapper(rawValue as unknown as number, { + lang: 'ru', + ...formatting, + }); + } else { + value = rawValue; + } + + const treemapId = + dPath.length >= 1 ? `id_${dPath[0]}/${value}` : `id_${dPath.join()}${value}`; + + const name = isMarkdownField(item) ? wrapMarkdownValue(value as string) : value; + + const treemapItem: ExtendedTreemapSeriesData = { + id: treemapId, + name, + drillDownFilterValue: value, + }; + + if (dPath.length) { + treemapItem.parentId = `id_${dPath.join('/')}`; + } + + dPath.push(value); + + treemapItem.id = `id_${dPath.join('/')}`; + + if (level === dimensions.length - 1) { + lastDimensionItem = treemapItem; + } else if (!treemapIds.includes(treemapItem.id)) { + treemap.push(treemapItem); + treemapIds.push(treemapItem.id); + } + }); + + const key = `id_${dPath.join('/')}`; + measures.forEach((measureItem) => { + const actualTitle = idToTitle[measureItem.guid]; + const i = findIndexInOrder(order, measureItem, actualTitle); + const value = values[i]; + const formatting = getFormatOptions(measureItem); + const label = chartKitFormatNumberWrapper(Number(value), { + lang: 'ru', + ...(formatting ?? {precision: isFloat ? MINIMUM_FRACTION_DIGITS : 0}), + }); + + hashTable[key] = {value: value, label}; + + if (color) { + if (gradientMode) { + const colorTitle = idToTitle[color.guid]; + const i = findIndexInOrder(order, color, colorTitle); + const colorValue = values[i]; + + valuesForColorData[key] = colorValue as unknown as number; + } else { + valuesForColorData[key] = colorByDimension as unknown as number; + } + } + }); + + if (lastDimensionItem) { + lastDimensionItem.value = Number(hashTable[key]?.value); + lastDimensionItem.label = hashTable[key]?.label; + let name: any[] = dPath; + if (useMarkdown) { + name = dPath.map((item) => (item ? wrapMarkdownValue(item) : item)); + } else if (useHtml) { + name = dPath.map((item) => (item ? wrapHtml(item) : item)); + } + lastDimensionItem.name = name as any; + + treemap.push(lastDimensionItem); + } + }); + + if (color) { + if (gradientMode) { + colorData = mapAndColorizeHashTableByGradient( + valuesForColorData, + colorsConfig, + ).colorData; + } else { + const {mountedColors, colors: paletteColors} = getColorsSettings({ + field: color, + colorsConfig: shared.colorsConfig, + defaultColorPaletteId, + availablePalettes: colorsConfig.availablePalettes, + customColorPalettes: colorsConfig.loadedColorPalettes, + }); + + colorData = mapAndColorizeHashTableByPalette({ + hashTable: valuesForColorData, + colors: paletteColors, + mountedColors, + }); + } + + treemap = treemap.map((obj) => { + const item = {...obj}; + + const colorDataValue = obj.id ? colorData[obj.id] : null; + if (colorDataValue) { + item.color = colorDataValue.backgroundColor; + } + + return item; + }); + } + + const dimensionsSize = dimensions.length; + const maxPadding = 5; + const levels: TreemapSeries['levels'] = new Array(dimensionsSize) + .fill(null) + .map((_, index) => ({ + index: index + 1, + padding: Math.min(maxPadding, (dimensionsSize - index) * 2 - 1), + })); + + if (useMarkdown) { + ChartEditor.updateConfig({useMarkdown: true}); + } + + if (useHtml) { + ChartEditor.updateConfig({useHtml: true}); + } + + const exportSettingsCols = dimensions.map((field, index) => { + return getExportColumnSettings({path: `name.${index}`, field}); + }); + exportSettingsCols.push(getExportColumnSettings({path: `value`, field: measures[0]})); + + const series: ExtendedTreemapSeries = { + type: 'treemap', + name: '', + layoutAlgorithm: 'squarify' as TreemapSeries['layoutAlgorithm'], + dataLabels: { + enabled: true, + html: useMarkdown || useHtml, + }, + levels, + data: treemap as TreemapSeriesData[], + sorting: { + enabled: true, + }, + custom: { + exportSettings: { + columns: exportSettingsCols, + }, + }, + }; + + return merge(getBaseChartConfig({shared, visualization: {placeholders, id: visualizationId}}), { + series: { + data: [series], + }, + legend: { + enabled: false, + }, + chart: { + zoom: {enabled: false}, + }, + }); +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/treemap/highcharts.ts b/src/server/modes/charts/plugins/datalens/preparers/treemap/highcharts.ts new file mode 100644 index 0000000..29e0ba8 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/treemap/highcharts.ts @@ -0,0 +1,326 @@ +import { + MINIMUM_FRACTION_DIGITS, + getFormatOptions, + isDateField, + isHtmlField, + isMarkdownField, +} from '../../../../../../../shared'; +import type {WrappedMarkdown} from '../../../../../../../shared/utils/markdown'; +import {wrapMarkdownValue} from '../../../../../../../shared/utils/markdown'; +import {wrapHtml} from '../../../../../../../shared/utils/ui-sandbox'; +import {getColorsSettings} from '../../../helpers/color-palettes'; +import { + mapAndColorizeHashTableByGradient, + mapAndColorizeHashTableByPalette, +} from '../../utils/color-helpers'; +import { + chartKitFormatNumberWrapper, + findIndexInOrder, + formatDate, + isGradientMode, + isNumericalDataType, +} from '../../utils/misc-helpers'; +import type {PrepareFunctionArgs} from '../types'; + +type TreemapItemName = string | null | WrappedMarkdown; + +type TreemapItem = { + id: string; + name: TreemapItemName | TreemapItemName[]; + parent?: string; + label?: string; + value?: number; + drillDownFilterValue?: string | null; + color?: string; + custom?: object; +}; + +export function prepareHighchartsTreemap({ + placeholders, + resultData, + colors, + colorsConfig, + idToTitle, + idToDataType, + ChartEditor, + defaultColorPaletteId, + shared, +}: PrepareFunctionArgs) { + // Dimensions + const d = placeholders[0].items; + const dTypes = d.map((item) => item.data_type); + const useMarkdown = d?.some(isMarkdownField); + const useHtml = d?.some(isHtmlField); + + // Measures + const m = placeholders[1].items; + + const color = colors[0]; + const colorFieldDataType = color ? idToDataType[color.guid] : null; + + const gradientMode = + color && + colorFieldDataType && + isGradientMode({colorField: color, colorFieldDataType, colorsConfig}); + + const {data, order} = resultData; + + let treemap: TreemapItem[] = []; + const treemapIds: string[] = []; + const hashTable: Record = {}; + const valuesForColorData: Record & {colorGuid?: string} = {}; + const isFloat = m[0] && m[0].data_type === 'float'; + let multimeasure = false; + let measureNamesLevel: number; + let colorData: Record = {}; + + if (color) { + // We make the property non-enumerable so that it does not participate in the formation of the palette + Object.defineProperty(valuesForColorData, 'colorGuid', { + enumerable: false, + value: color.guid, + }); + } + + const measureNames = m.map((measureItem) => idToTitle[measureItem.guid]); + + // TODO: think about why. After all, you can put only one field in the measures (Size) (treemap.tsx) + if (measureNames.length > 1) { + multimeasure = true; + + d.some((item, level) => { + if (item.type === 'PSEUDO') { + measureNamesLevel = level; + + return true; + } else { + return false; + } + }); + } + + data.forEach((values) => { + let colorByDimension: string | null; + if (color && color.type === 'DIMENSION') { + const actualTitle = idToTitle[color.guid]; + const i = findIndexInOrder(order, color, actualTitle); + const colorValue = values[i]; + + colorByDimension = colorValue; + } + + const dPath: (string | null)[] = []; + let lastDimensionItem: TreemapItem | undefined; + d.forEach((item, level) => { + if (item.type === 'PSEUDO') { + return; + } + + const actualTitle = idToTitle[item.guid]; + + const i = findIndexInOrder(order, item, actualTitle); + + const rawValue = values[i]; + let value: string | null; + + if (isDateField({data_type: dTypes[level]})) { + value = formatDate({ + valueType: dTypes[level], + value: rawValue, + format: item.format, + }); + } else if (isNumericalDataType(dTypes[level])) { + const formatting = getFormatOptions(item); + value = chartKitFormatNumberWrapper(rawValue as unknown as number, { + lang: 'ru', + ...formatting, + }); + } else { + value = rawValue; + } + + const treemapId = + dPath.length >= 1 ? `id_${dPath[0]}/${value}` : `id_${dPath.join()}${value}`; + + const name = isMarkdownField(item) ? wrapMarkdownValue(value as string) : value; + + const treemapItem: TreemapItem = { + id: treemapId, + name, + drillDownFilterValue: value, + }; + + if (dPath.length) { + treemapItem.parent = `id_${dPath.join('/')}`; + } + + dPath.push(value); + + treemapItem.id = `id_${dPath.join('/')}`; + + if (level === d.length - 1) { + lastDimensionItem = treemapItem; + } else if (!treemapIds.includes(treemapItem.id)) { + treemap.push(treemapItem); + treemapIds.push(treemapItem.id); + } + }); + + const key = `id_${dPath.join('/')}`; + m.forEach((measureItem) => { + const actualTitle = idToTitle[measureItem.guid]; + const i = findIndexInOrder(order, measureItem, actualTitle); + const value = values[i]; + const formatting = getFormatOptions(measureItem); + const label = chartKitFormatNumberWrapper(Number(value), { + lang: 'ru', + ...(formatting ?? {precision: isFloat ? MINIMUM_FRACTION_DIGITS : 0}), + }); + + if (multimeasure) { + const dPathSpecial = [...dPath]; + + dPathSpecial.splice(measureNamesLevel, 0, actualTitle); + + const specialKey = `${dPathSpecial.join('/')}`; + + hashTable[specialKey] = {value: value, label}; + } else { + hashTable[key] = {value: value, label}; + } + + if (color) { + if (gradientMode) { + const colorTitle = idToTitle[color.guid]; + const i = findIndexInOrder(order, color, colorTitle); + const colorValue = values[i]; + + valuesForColorData[key] = colorValue as unknown as number; + } else { + valuesForColorData[key] = colorByDimension as unknown as number; + } + } + }); + + if (lastDimensionItem) { + lastDimensionItem.value = Number(hashTable[key]?.value); + lastDimensionItem.label = hashTable[key]?.label; + let name: any[] = dPath; + if (useMarkdown) { + name = dPath.map((item) => (item ? wrapMarkdownValue(item) : item)); + } else if (useHtml) { + name = dPath.map((item) => (item ? wrapHtml(item) : item)); + } + lastDimensionItem.name = name as any; + + treemap.push(lastDimensionItem); + } + }); + + if (color) { + if (gradientMode) { + colorData = mapAndColorizeHashTableByGradient( + valuesForColorData, + colorsConfig, + ).colorData; + } else { + const {mountedColors, colors: paletteColors} = getColorsSettings({ + field: color, + colorsConfig: shared.colorsConfig, + defaultColorPaletteId, + availablePalettes: colorsConfig.availablePalettes, + customColorPalettes: colorsConfig.loadedColorPalettes, + }); + + colorData = mapAndColorizeHashTableByPalette({ + hashTable: valuesForColorData, + colors: paletteColors, + mountedColors, + }); + } + + treemap = treemap.map((obj) => { + const item = {...obj}; + + const colorDataValue = colorData[obj.id]; + if (colorDataValue) { + item.color = colorDataValue.backgroundColor; + } + + return item; + }); + } + + let levels; + + if (d.length === 1) { + levels = [ + { + level: 1, + borderWidth: 1, + }, + ]; + } else if (d.length === 2) { + levels = [ + { + level: 1, + borderWidth: 3, + }, + { + level: 2, + borderWidth: 1, + }, + ]; + } else { + levels = [ + { + level: 1, + borderWidth: 5, + }, + { + level: 2, + borderWidth: 3, + }, + { + level: 3, + borderWidth: 1, + }, + ]; + } + + if (useMarkdown) { + ChartEditor.updateConfig({useMarkdown: true}); + } + + if (useHtml) { + ChartEditor.updateConfig({useHtml: true}); + } + + const graphs = [ + { + type: 'treemap', + layoutAlgorithm: 'squarified', + allowTraversingTree: true, + interactByLeaf: true, + tooltip: { + ...(isFloat && {valueDecimals: MINIMUM_FRACTION_DIGITS}), + }, + dataLabels: { + enabled: true, + align: 'left', + verticalAlign: 'top', + style: { + cursor: 'pointer', + }, + ...((useMarkdown || useHtml) && { + useHTML: true, + }), + }, + levelIsConstant: false, + levels, + data: treemap, + }, + ]; + + return {graphs}; +} diff --git a/src/server/modes/charts/plugins/datalens/preparers/treemap/index.ts b/src/server/modes/charts/plugins/datalens/preparers/treemap/index.ts new file mode 100644 index 0000000..35f5127 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/treemap/index.ts @@ -0,0 +1,2 @@ +export * from './gravity-charts'; +export * from './highcharts'; diff --git a/src/server/modes/charts/plugins/datalens/preparers/types.ts b/src/server/modes/charts/plugins/datalens/preparers/types.ts new file mode 100644 index 0000000..6391ff3 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/types.ts @@ -0,0 +1,101 @@ +import type { + DATASET_FIELD_TYPES, + FeatureConfig, + IChartEditor, + ServerChartsConfig, + ServerColor, + ServerField, + ServerLabel, + ServerLayerSettings, + ServerPlaceholder, + ServerPointSizeConfig, + ServerShape, + ServerShapesConfig, + ServerSort, + ServerTooltip, + ServerTooltipConfig, + WrappedHTML, + WrappedMarkup, +} from '../../../../../../shared'; +import type {ApiV2RequestField} from '../../../../../../shared/types/bi-api/v2'; +import type {WrappedMarkdown} from '../../../../../../shared/utils/markdown'; +import type {ChartColorsConfig} from '../types'; + +export type PrepareFunction = (args: PrepareFunctionArgs) => any; + +export type PrepareFunctionArgs = { + placeholders: ServerPlaceholder[]; + resultData: PrepareFunctionResultData; + fields: ApiV2RequestField[]; + colors: ServerColor[]; + sort: ServerSort[]; + idToTitle: {[key: string]: string}; + shared: ServerChartsConfig; + layerSettings: ServerLayerSettings; + ChartEditor: IChartEditor; + colorsConfig: ChartColorsConfig; + geopointsConfig?: ServerPointSizeConfig; + tooltips: ServerTooltip[]; + tooltipConfig?: ServerTooltipConfig; + labels: ServerLabel[]; + idToDataType: Record; + + visualizationId: string; + shapes: ServerShape[]; + shapesConfig: ServerShapesConfig | undefined; + segments: ServerField[]; + datasets: string[]; + layerChartMeta?: LayerChartMeta; + usedColors?: (string | undefined)[]; + disableDefaultSorting?: boolean; + features: FeatureConfig; + defaultColorPaletteId: string; +}; + +export type PrepareFunctionDataRow = (string | null)[]; + +export type PrepareFunctionResultData = { + data: PrepareFunctionDataRow[]; + legend?: number[][]; + order: ResultDataOrder; + totals: (string | null)[]; +}; + +export type ChartKitFormatSettings = { + format?: null; + chartKitFormatting?: boolean; + chartKitPrecision?: number; + chartKitPrefix?: string; + chartKitPostfix?: string; + chartKitUnit?: string; + chartKitFormat?: string; + chartKitLabelMode?: string; + chartKitShowRankDelimiter?: boolean; +}; + +export type ResultDataOrder = (ResultDataOrderItem | ResultDataOrderItem[])[]; + +export type ResultDataOrderItem = { + datasetId: string; + title: string; + dataType?: string; + legendItemId?: number; +}; + +export type Categories = (string | number)[]; + +export type LayerChartMeta = { + isCategoriesSortAvailable?: boolean; +}; + +export type PiePoint = { + name: string | WrappedHTML; + formattedName: string | WrappedHTML; + drillDownFilterValue: string; + y: number; + color?: string; + colorGuid?: string; + colorValue?: string | number; + label?: string | number | null | WrappedMarkdown | WrappedMarkup | WrappedHTML; + custom?: object; +}; diff --git a/src/server/modes/charts/plugins/datalens/preparers/utils.ts b/src/server/modes/charts/plugins/datalens/preparers/utils.ts new file mode 100644 index 0000000..15a1304 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/preparers/utils.ts @@ -0,0 +1,43 @@ +import type {ValueFormat} from '@gravity-ui/chartkit/gravity-charts'; + +import type {ServerField} from '../../../../../../shared'; +import {isMeasureValue, isNumberField} from '../../../../../../shared'; +import type {FormatNumberOptions} from '../../../../../../shared/modules/format-units/types'; +import {getFieldFormatOptions} from '../gravity-charts/utils/format'; + +import type {ChartKitFormatSettings} from './types'; + +export function mapToGravityChartValueFormat({ + field, + formatSettings, +}: { + field: ServerField; + formatSettings: ChartKitFormatSettings; +}) { + const isNumber = isNumberField(field) || isMeasureValue(field); + if (isNumber && formatSettings) { + return mapChartkitFormatSettingsToGravityChartValueFormat({ + chartkitFormatSettings: formatSettings, + }); + } + + return getFieldFormatOptions({field}); +} + +export function mapChartkitFormatSettingsToGravityChartValueFormat(args: { + type?: ValueFormat['type']; + chartkitFormatSettings?: ChartKitFormatSettings; +}): ValueFormat { + const {chartkitFormatSettings = {}, type = 'number'} = args; + + return { + format: chartkitFormatSettings.chartKitFormat as FormatNumberOptions['format'], + labelMode: chartkitFormatSettings.chartKitLabelMode, + postfix: chartkitFormatSettings.chartKitPostfix, + precision: chartkitFormatSettings.chartKitPrecision, + prefix: chartkitFormatSettings.chartKitPrefix, + showRankDelimiter: chartkitFormatSettings.chartKitShowRankDelimiter, + type, + unit: chartkitFormatSettings.chartKitUnit as FormatNumberOptions['unit'], + }; +} diff --git a/src/server/modes/charts/plugins/datalens/private-module.ts b/src/server/modes/charts/plugins/datalens/private-module.ts new file mode 100644 index 0000000..9d97c4a --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/private-module.ts @@ -0,0 +1,13 @@ +import {buildChartsConfigPrivate} from './config'; +import {buildHighchartsConfigPrivate} from './highcharts'; +import {buildGraphPrivate} from './js/js'; +import {buildSourcesPrivate} from './url/build-sources'; +import {setConsole} from './utils/misc-helpers'; + +export const datalensModule = { + buildHighchartsConfig: buildHighchartsConfigPrivate, + buildSources: buildSourcesPrivate, + buildGraph: buildGraphPrivate, + buildChartsConfig: buildChartsConfigPrivate, + setConsole, +}; diff --git a/src/server/modes/charts/plugins/datalens/types.ts b/src/server/modes/charts/plugins/datalens/types.ts new file mode 100644 index 0000000..a9559ad --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/types.ts @@ -0,0 +1,113 @@ +import type { + ColorPalette, + ColumnSettings, + Palette, + ParameterDefaultValue, + PayloadFilter, + ServerColorsConfig, + ServerField, + ServerFieldFormatting, + TableBarsSettings, + Update, +} from '../../../../../shared'; +import type {WrappedHTML} from '../../../../../shared/types/charts'; +import type {TableFieldBackgroundSettings} from '../../../../../shared/types/wizard/background-settings'; + +export type PayloadParameter = { + id: string; + value: ParameterDefaultValue; +}; + +export type BaseUrlPayload = { + columns: string[]; + where?: PayloadFilter[]; + order_by?: { + direction: string; + column: string; + }[]; + updates?: Update[]; + ignore_nonexistent_filters?: boolean; + disable_group_by?: boolean; + limit?: number; + offset?: number; + with_totals: boolean; + parameters?: PayloadParameter[]; +}; + +export type ApiVersion = '1.5' | '2'; + +export type ChartPlugin = 'highcharts' | 'gravity-charts'; + +export type ServerFieldWithBackgroundSettings = ServerField & { + backgroundSettings: TableFieldBackgroundSettings; +}; + +export type ServerFieldWithBarsSettings = ServerField & { + barsSettings: TableBarsSettings; +}; + +export type ServerFieldWithColumnWidthSettings = ServerField & { + columnSettings: ColumnSettings; +}; + +export type BackendPivotTableCellCustom = { + // Role needs to make difference between total column/row and any other + role: 'data' | 'total'; + // Current view direction. It needs for rendering icon and calc next direction + currentSortDirection: 'asc' | 'desc' | null; + // Next direction which will be sent to backend + nextSortDirection: 'asc' | 'desc' | undefined; + // path represents the nesting of the column values + path: string[]; + // needs when sorting table with multiple measures + // when building request + measureGuid?: string; + // needs to compare field order in ui section + // when building request + fieldOrder: string[]; +}; + +export interface ChartColorsConfig extends ServerColorsConfig { + colors: string[]; + gradientColors: string[]; + loadedColorPalettes: Record; + availablePalettes: Record; +} + +// Extended Highcharts.AxisOptions +export type AxisOptions = { + type?: string; + visible?: boolean; + opposite?: boolean; + top?: string; + height?: string; + offset?: number; + min?: number; + max?: number; + endOnTick?: boolean; + tickPixelInterval?: number; + lineWidth?: number; + gridLineWidth?: number; + minorGridLineWidth?: number; + title?: { + text: string | WrappedHTML; + align?: string; + textAlign?: string; + offset?: number; + rotation?: number; + style?: React.CSSProperties; + useHTML?: boolean; + }; + labels?: any[]; +}; + +export type DatasetMeta = { + id: string; + fields: Record; + fieldsList: { + title: string; + guid: string; + dataType: string; + formatting?: ServerFieldFormatting; + }[]; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/__tests__/default-request/buildTotalRequestFields.test.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/__tests__/default-request/buildTotalRequestFields.test.ts new file mode 100644 index 0000000..9fbe58d --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/__tests__/default-request/buildTotalRequestFields.test.ts @@ -0,0 +1,106 @@ +import {buildTotalRequestFields} from '../../default-request'; + +import { + COLUMNS, + COLUMNS_FOR_DATASET_ID_1, + COLUMNS_FOR_DATASET_ID_2, + COLUMNS_FOR_ORDER_TEST, + DATASET_ID_1, + DATASET_ID_2, + FIELDS, + FIELDS_FOR_ORDER_TEST, + FIELDS_WITH_DIFFERENT_DATASET_ID_TEST, +} from './mocks/buildTotalRequests.mock'; + +describe('buildTotalRequestFields', () => { + it('Returns fields for totals: A stub for the measurement and a field with the total role for the measure', () => { + const totalsColumns = buildTotalRequestFields({ + fields: FIELDS, + datasetId: DATASET_ID_1, + columns: COLUMNS, + }); + + expect(totalsColumns).toHaveLength(2); + + const dimensionPlaceholderColumn = totalsColumns[0]; + const measureTotalColumn = totalsColumns[1]; + + expect(dimensionPlaceholderColumn.role_spec?.role).toEqual('template'); + expect(dimensionPlaceholderColumn.role_spec?.template).toEqual(''); + expect(dimensionPlaceholderColumn.ref.type).toEqual('placeholder'); + + expect(measureTotalColumn.ref.type).toEqual('id'); + + const measureFieldGuid = FIELDS[1].guid; + + // @ts-ignore + expect(measureTotalColumn.ref.id).toEqual(measureFieldGuid); + expect(measureTotalColumn.role_spec?.role).toEqual('total'); + }); + + it('The order in the returned array depends on the order of fields and columns', () => { + const totalsColumns = buildTotalRequestFields({ + fields: FIELDS_FOR_ORDER_TEST, + columns: COLUMNS_FOR_ORDER_TEST, + datasetId: DATASET_ID_1, + }); + + expect(totalsColumns).toHaveLength(3); + + const measureField_1 = FIELDS_FOR_ORDER_TEST[0]; + const measureField_2 = FIELDS_FOR_ORDER_TEST[2]; + + expect(totalsColumns[0].ref.type).toEqual('id'); + // @ts-ignore + expect(totalsColumns[0].ref.id).toEqual(measureField_1.guid); + + expect(totalsColumns[1].ref.type).toEqual('placeholder'); + expect(totalsColumns[1].role_spec?.role).toEqual('template'); + expect(totalsColumns[1].role_spec?.template).toEqual(''); + + expect(totalsColumns[2].ref.type).toEqual('id'); + // @ts-ignore + expect(totalsColumns[2].ref.id).toEqual(measureField_2.guid); + }); + + it('Only fields with the datasetId used to build the query get into the totals query', () => { + const totalsColumns = buildTotalRequestFields({ + fields: FIELDS_WITH_DIFFERENT_DATASET_ID_TEST, + columns: COLUMNS_FOR_DATASET_ID_1, + datasetId: DATASET_ID_1, + }); + + expect(totalsColumns).toHaveLength(3); + + expect(totalsColumns[0].ref.type).toEqual('placeholder'); + + expect(totalsColumns[1].ref.type).toEqual('id'); + // @ts-ignore + expect(totalsColumns[1].ref.id).toEqual(FIELDS_WITH_DIFFERENT_DATASET_ID_TEST[1].guid); + expect(totalsColumns[1].role_spec?.role).toEqual('total'); + + expect(totalsColumns[2].ref.type).toEqual('id'); + // @ts-ignore + expect(totalsColumns[2].ref.id).toEqual(FIELDS_WITH_DIFFERENT_DATASET_ID_TEST[2].guid); + expect(totalsColumns[2].role_spec?.role).toEqual('total'); + }); + + it('If there are columns in columns that are not in fields, then they are added to the end of the array of totals in the form of placeholder', () => { + const totalsColumns = buildTotalRequestFields({ + fields: FIELDS_WITH_DIFFERENT_DATASET_ID_TEST, + columns: COLUMNS_FOR_DATASET_ID_2, + datasetId: DATASET_ID_2, + }); + + expect(totalsColumns).toHaveLength(2); + + expect(totalsColumns[0].ref.type).toEqual('id'); + //@ts-ignore + expect(totalsColumns[0].ref.id).toEqual(FIELDS_WITH_DIFFERENT_DATASET_ID_TEST[3].guid); + expect(totalsColumns[0].role_spec?.role).toEqual('total'); + + expect(totalsColumns[1].ref.type).toEqual('placeholder'); + expect(totalsColumns[1].role_spec?.role).toEqual('template'); + expect(totalsColumns[1].role_spec?.template).toEqual(''); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/__tests__/default-request/mocks/buildTotalRequests.mock.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/__tests__/default-request/mocks/buildTotalRequests.mock.ts new file mode 100644 index 0000000..872209e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/__tests__/default-request/mocks/buildTotalRequests.mock.ts @@ -0,0 +1,84 @@ +import type {ServerField} from '../../../../../../../../../../shared'; + +export const FIELDS = [ + { + type: 'DIMENSION', + guid: 'ac8dd226-3372-4212-bde7-17afc792a728', + title: 'Category', + datasetId: '4fnqsvsxtmcix', + }, + { + type: 'MEASURE', + guid: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + title: 'Profit', + datasetId: '4fnqsvsxtmcix', + }, +] as ServerField[]; +export const FIELDS_FOR_ORDER_TEST = [ + { + type: 'MEASURE', + guid: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + title: 'Profit', + datasetId: '4fnqsvsxtmcix', + }, + { + type: 'DIMENSION', + guid: 'ac8dd226-3372-4212-bde7-17afc792a728', + title: 'Category', + datasetId: '4fnqsvsxtmcix', + }, + { + type: 'MEASURE', + guid: '809d03e1-15b7-4bdc-b108-653603a27001', + title: 'Sales', + datasetId: '4fnqsvsxtmcix', + }, +] as ServerField[]; +export const FIELDS_WITH_DIFFERENT_DATASET_ID_TEST = [ + { + type: 'DIMENSION', + guid: 'ac8dd226-3372-4212-bde7-17afc792a728', + title: 'Category', + datasetId: '4fnqsvsxtmcix', + }, + { + type: 'MEASURE', + guid: '4b6463d1-30e1-425e-b7d2-77205bc75de6', + title: 'Profit', + datasetId: '4fnqsvsxtmcix', + }, + { + type: 'MEASURE', + guid: '809d03e1-15b7-4bdc-b108-653603a27001', + title: 'Sales', + datasetId: '4fnqsvsxtmcix', + }, + { + type: 'MEASURE', + guid: '21eaad10-0cc2-11ed-9fc7-c54699cfb56e', + title: 'Height (1)', + datasetId: 'wz12arsqpvuup', + }, +] as ServerField[]; + +export const COLUMNS = [ + 'ac8dd226-3372-4212-bde7-17afc792a728', + '4b6463d1-30e1-425e-b7d2-77205bc75de6', +]; +export const COLUMNS_FOR_ORDER_TEST = [ + '4b6463d1-30e1-425e-b7d2-77205bc75de6', + 'ac8dd226-3372-4212-bde7-17afc792a728', + '809d03e1-15b7-4bdc-b108-653603a27001', +]; +export const COLUMNS_FOR_DATASET_ID_1 = [ + 'ac8dd226-3372-4212-bde7-17afc792a728', + '4b6463d1-30e1-425e-b7d2-77205bc75de6', + '809d03e1-15b7-4bdc-b108-653603a27001', +]; +export const COLUMNS_FOR_DATASET_ID_2 = [ + '21eaad10-0cc2-11ed-9fc7-c54699cfb56e', + 'c5eb54f9-1fe6-436d-8d47-7529de3857af', +]; + +export const DATASET_ID_1 = '4fnqsvsxtmcix'; +export const DATASET_ID_2 = 'wz12arsqpvuup'; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/constants.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/constants.ts new file mode 100644 index 0000000..5d22314 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/constants.ts @@ -0,0 +1 @@ +export const RESERVED_PARAM_KEYS = new Set(['__datasetId']); diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/default-request.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/default-request.ts new file mode 100644 index 0000000..3af73c0 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/default-request.ts @@ -0,0 +1,273 @@ +import type { + ApiV2Filter, + ApiV2OrderBy, + ApiV2Parameter, + ApiV2RequestBody, + ApiV2RequestField, + ServerField, + StringParams, +} from '../../../../../../../shared'; +import {isDimensionField, isTreeField} from '../../../../../../../shared'; +import type {ApiVersion, BaseUrlPayload} from '../../types'; +import {getTreeState, mapParameterToRequestFormat} from '../helpers'; + +const TREE_ROOT = '[]'; + +type GetRequestFieldsPayload = {columns: string[]}; + +const getRequestFields = (payload: GetRequestFieldsPayload): ApiV2RequestField[] => { + return payload.columns.map((fieldId) => ({ + ref: { + type: 'id', + id: fieldId, + }, + block_id: 0, + })); +}; + +type GetTreeRequestFieldsPayload = { + columns: string[]; + fields: ServerField[]; + params: StringParams; +}; + +const getTreeRequestFields = ({columns, fields, params}: GetTreeRequestFieldsPayload) => { + const requestFields: ApiV2RequestField[] = []; + + columns.forEach((fieldId) => { + const field = fields.find((field) => field.guid === fieldId); + + if (field && isTreeField(field)) { + const treeState = getTreeState(params); + + const initialLegendItemId = 1; + let legendItemId = initialLegendItemId + 1; + + const legendDict: Record = { + [TREE_ROOT]: initialLegendItemId, + }; + + requestFields.push({ + ref: { + type: 'id', + id: fieldId, + }, + legend_item_id: initialLegendItemId, + role_spec: { + prefix: '[]', + dimension_values: [], + role: 'tree', + level: 1, + }, + block_id: 0, + }); + + treeState.forEach((prefix, index2) => { + legendItemId++; + + const parsedPrefix = JSON.parse(prefix); + + const currentLegendKey = JSON.stringify(parsedPrefix); + const prevLegendKey = JSON.stringify(parsedPrefix.slice(0, -1)); + + legendDict[currentLegendKey] = legendItemId; + + const prevLefend = legendDict[prevLegendKey]; + + requestFields.push({ + ref: { + type: 'id', + id: fieldId, + }, + legend_item_id: legendItemId, + role_spec: { + prefix: prefix, + dimension_values: !parsedPrefix.length + ? [] + : [{legend_item_id: prevLefend, value: prefix}], + role: 'tree', + level: parsedPrefix.length + 1, + }, + block_id: 1 + index2, + }); + }); + + return; + } + + requestFields.push({ + ref: { + type: 'id', + id: fieldId, + }, + }); + }); + + return requestFields; +}; + +export type BuildDefaultRequestArgs = { + payload: BaseUrlPayload; + fields: ServerField[]; + apiVersion: ApiVersion; + params: StringParams; + revisionId?: string; + datasetId: string; + + allMeasuresMap: Record; +}; + +export const buildDefaultRequest = ({ + payload, + fields, + apiVersion, + params, + datasetId, + revisionId, + allMeasuresMap, +}: BuildDefaultRequestArgs): ApiV2RequestBody => { + const columns = payload.columns; + const where = payload.where || []; + const parameters = payload.parameters || []; + + const isTreeRequest = + apiVersion === '2' && + columns.some((fieldId) => { + const field = fields.find((field) => field.guid === fieldId); + + return field && isTreeField(field); + }); + + let requestFields: ApiV2RequestField[]; + + if (isTreeRequest) { + requestFields = getTreeRequestFields({ + columns, + fields, + params, + }); + } else { + requestFields = getRequestFields({ + columns, + }); + } + + if (apiVersion === '2' && payload.with_totals && fields) { + requestFields.push( + ...buildTotalRequestFields({fields, columns: payload.columns, datasetId}), + ); + } + + const filters: ApiV2Filter[] = where.map((filter) => ({ + ref: {type: 'id', id: filter.column || ''}, + operation: filter.operation, + values: filter.values, + })); + + const order_by: ApiV2OrderBy[] = (payload.order_by || []).map(({direction, column}) => ({ + ref: {type: 'id', id: column}, + direction: direction.toLowerCase(), + block_id: isTreeRequest ? undefined : 0, + })); + + const parameter_values: ApiV2Parameter[] = parameters.map(mapParameterToRequestFormat); + + requestFields = requestFields.map((field) => { + if ( + !field.role_spec?.role && + (('id' in field.ref && allMeasuresMap[field.ref.id]) || + ('title' in field.ref && allMeasuresMap[field.ref.title])) + ) { + return { + ...field, + role_spec: {role: 'measure'}, + }; + } + + return field; + }); + + return { + fields: requestFields, + filters, + order_by, + parameter_values, + dataset_revision_id: revisionId, + + updates: payload.updates, + ignore_nonexistent_filters: payload.ignore_nonexistent_filters, + limit: payload.limit, + offset: payload.offset, + disable_group_by: payload.disable_group_by, + with_totals: payload.with_totals, + autofill_legend: true, + }; +}; + +type BuildTotalRequestFieldsArgs = { + fields: ServerField[]; + columns?: string[]; + datasetId: string; +}; + +export const buildTotalRequestFields = ({ + fields, + columns = [], + datasetId, +}: BuildTotalRequestFieldsArgs): ApiV2RequestField[] => { + const totalsColumns: ApiV2RequestField[] = []; + + fields + .filter((field) => datasetId === field.datasetId) + .forEach((field) => { + if (isDimensionField(field)) { + totalsColumns.push({ + ref: { + type: 'placeholder', + }, + role_spec: { + role: 'template', + template: '', + }, + block_id: 1, + }); + + return; + } + + totalsColumns.push({ + ref: { + type: 'id', + id: field.guid, + }, + role_spec: { + role: 'total', + }, + block_id: 1, + }); + }); + + const guidsDict = fields.reduce((acc: Record, item) => { + acc[item.guid] = true; + return acc; + }, {}); + + columns + .filter((column) => { + return !guidsDict[column]; + }) + .forEach(() => { + totalsColumns.push({ + ref: { + type: 'placeholder', + }, + role_spec: { + role: 'template', + template: '', + }, + block_id: 1, + }); + }); + + return totalsColumns; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/index.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/index.ts new file mode 100644 index 0000000..b8ebc29 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/index.ts @@ -0,0 +1,768 @@ +/* eslint-disable camelcase */ + +import {dateTimeParse} from '@gravity-ui/date-utils'; + +import type { + ApiV2RequestBody, + DatasetFieldCalcMode, + DrillDownData, + IntervalPart, + Link, + ParameterDefaultValue, + PayloadFilter, + ServerChartsConfig, + ServerDatasetField, + ServerField, + ServerVisualizationLayer, + Shared, + SharedData, + StringParams, + V4Layer, +} from '../../../../../../../shared'; +import { + Operations, + WizardVisualizationId, + filterUpdatesByDatasetId, + getItemLinkWithDatasets, + isDateField, + isMeasureField, + isParameter, + prepareFilterValues, + prepareFilterValuesWithOperations, + resolveRelativeDate, + splitParamsToParametersAndFilters, + transformParamsToUrlParams, + transformUrlParamsToParams, +} from '../../../../../../../shared'; +import type {ApiVersion, BaseUrlPayload, PayloadParameter} from '../../types'; +import {mapChartsConfigToServerConfig} from '../../utils/config-helpers'; +import {DEFAULT_DATETIME_FORMAT, DEFAULT_DATE_FORMAT, SORT_ORDER} from '../../utils/constants'; +import {preprocessHierarchies} from '../../utils/hierarchy-helpers'; +import {getAllPlaceholderItems, getServerDateFormat, log} from '../../utils/misc-helpers'; +import { + getBackgroundColorFieldsIds, + getParametersMap, + isParamValid, + isRawParamValid, + mapItemToPayloadParameter, + prepareColumns, + prepareParameterForPayload, + prepareParameters, +} from '../helpers'; +import {getMergedChartAndParamsFilters} from '../helpers/filters'; +import {getPayloadWithCommonTableSettings} from '../helpers/table-settings'; + +import {RESERVED_PARAM_KEYS} from './constants'; +import {buildDefaultRequest} from './default-request'; +import {buildPivotRequest} from './pivot-request'; + +function formatParamsFilters({ + datasetSchema, + links, + params, + datasetId, +}: { + datasetId: string; + datasetSchema: (ServerDatasetField | ServerField)[]; + links: Link[]; + params: StringParams; +}) { + if (!params) { + return []; + } + + const resultSchema = datasetSchema; + const paramsFilters: PayloadFilter[] = []; + + Object.keys(params) + .filter((param) => !RESERVED_PARAM_KEYS.has(param)) + .filter((param) => { + const paramValue = params[param]; + return isRawParamValid(paramValue); + }) + .forEach((param) => { + const paramValue = params[param]; + + if (!isParamValid(paramValue)) { + return; + } + + // We take into account the possibility to accept an array of values and a single value + const values = Array.isArray(paramValue) ? paramValue : [paramValue]; + + const foundField = resultSchema.find( + (item) => item.guid === param || item.title === param, + ); + + const valuesWithOperations = prepareFilterValuesWithOperations({ + values, + field: foundField as ServerField, + }); + + let guid = param; + + // Crutch: if it's not Guid who came, but the title of the field + if (guid.length !== 36 && foundField?.guid) { + guid = foundField.guid; + } + + if ( + links.length && + !resultSchema.some((field) => { + return field.guid === guid; + }) + ) { + const targetLink = links.find((link) => { + const datasetsIdsInLink = Object.keys(link.fields); + + if (datasetsIdsInLink.indexOf(datasetId) === -1) { + return false; + } + + return datasetsIdsInLink.some((someDatasetId) => { + return link.fields[someDatasetId].field.guid === param; + }); + }); + + if (targetLink) { + guid = targetLink.fields[datasetId].field.guid; + } + } + + const existingFilterIndex = paramsFilters.findIndex((existingFilter) => { + if (existingFilter.column === guid) { + const operation = existingFilter.operation; + if (operation === Operations.IN || operation === Operations.NIN) { + existingFilter.values.forEach((filterValue) => { + valuesWithOperations.values.push(filterValue); + valuesWithOperations.operations.push( + existingFilter.operation as Operations, + ); + }); + } + return true; + } + + return false; + }); + + if (existingFilterIndex > -1) { + paramsFilters.splice(existingFilterIndex, 1); + } + + // Returning the payload for the API + + paramsFilters.push(...getJoinedParamsFilters(guid, valuesWithOperations)); + + paramsFilters.push(...getSeparateParamsValues(guid, valuesWithOperations)); + }); + + return paramsFilters; +} + +export function getJoinedParamsFilters( + guid: string, + { + operations, + values, + }: { + operations: Operations[]; + values: (string | string[])[]; + }, +): PayloadFilter[] { + const joinedFilters: PayloadFilter[] = []; + + const groupedOperations: Record = { + [Operations.IN]: [], + [Operations.NIN]: [], + }; + + operations.forEach((operation, index) => { + if (operation === Operations.IN || operation === Operations.NIN) { + groupedOperations[operation].push(values[index] as string); + } + }); + + for (const [operation, operationValues] of Object.entries(groupedOperations)) { + if (operationValues.length) { + joinedFilters.push({ + column: guid, + operation, + values: operationValues, + }); + } + } + + return joinedFilters; +} + +export function getSeparateParamsValues( + guid: string, + { + operations, + values, + }: { + operations: Operations[]; + values: (string | string[])[]; + }, +): PayloadFilter[] { + const separateParamsFilters: PayloadFilter[] = []; + + values.forEach((value, index) => { + if (operations[index] !== Operations.IN && operations[index] !== Operations.NIN) { + separateParamsFilters.push({ + column: guid, + operation: operations[index], + values: Array.isArray(value) ? value : [value], + }); + } + }); + + return separateParamsFilters; +} + +function formatFilters({ + filters, + links, + datasetId, + datasetSchema, + filterParams, + drillDownData, +}: { + filters: ServerChartsConfig['filters']; + links: Link[]; + datasetId: string; + datasetSchema: ServerDatasetField[]; + filterParams: StringParams; + drillDownData?: DrillDownData; +}): PayloadFilter[] | undefined { + let chartFilters: PayloadFilter[] = []; + + let paramsFilters = formatParamsFilters({ + datasetSchema, + datasetId, + links, + params: filterParams, + }); + + if (drillDownData) { + const filtersToApply = drillDownData.filters.slice(0, drillDownData.level); + + const drillDownParamsFilters: StringParams = filtersToApply.reduce( + (acc: StringParams, el, index) => { + if (el) { + const field = drillDownData.fields[index]; + + if (isDateField(field)) { + const clientFormat = + field.data_type === 'genericdatetime' + ? DEFAULT_DATETIME_FORMAT + : DEFAULT_DATE_FORMAT; + + const serverFormat = getServerDateFormat(field.data_type); + + acc[field.guid] = + dateTimeParse(el, {format: clientFormat})?.format(serverFormat) || el; + } else { + acc[field.guid] = el; + } + } + + return acc; + }, + {}, + ); + + const formattedDrillDownParams = formatParamsFilters({ + datasetSchema, + datasetId, + links, + params: drillDownParamsFilters, + }).map((drillDownFilter) => ({...drillDownFilter, isDrillDown: true})); + + paramsFilters = [...paramsFilters, ...formattedDrillDownParams]; + } + + if (filters.length) { + chartFilters = filters + .filter((filter) => { + if (filter.disabled) { + return false; + } + + if (filter.datasetId === datasetId) { + return true; + } + + const linkExists = links.some((link) => { + return ( + filter.datasetId && link.fields[filter.datasetId] && link.fields[datasetId] + ); + }); + + return linkExists; + }) + .map((filter) => { + if (!filter.filter) { + return null; + } + + const operation = filter.filter.operation.code; + const value = filter.filter.value; + + if ((typeof value === 'number' && isNaN(value)) || !value) { + return null; + } + + let values: string[] = Array.isArray(value) ? value : [value]; + let valuesValidationFailed = false; + + if (values.length === 1) { + values = ([] as string[]).concat(...prepareFilterValues({values})); + } else { + values = values + .map((entry, i) => { + if (/^__relative/.test(entry)) { + let intervalPart: IntervalPart | undefined = undefined; // eslint-disable-line no-undef-init + if (operation === 'BETWEEN') { + intervalPart = i === 0 ? 'start' : 'end'; + } + + const resolved = resolveRelativeDate(entry, intervalPart); + + if (resolved === null) { + valuesValidationFailed = true; + return null; + } + + return resolved; + } else { + return entry; + } + }) + .filter((value): value is string => value !== null); + } + + if (valuesValidationFailed) { + return null; + } + + let {guid} = filter; + if (filter.datasetId !== datasetId) { + const targetLink = getItemLinkWithDatasets(filter, datasetId, links); + + if (targetLink) { + guid = targetLink.fields[datasetId].field.guid; + } + } + + const result: PayloadFilter = { + column: guid, + operation, + values, + }; + + return result; + }) + .filter((filter): filter is PayloadFilter => filter !== null); + } + + const resultFilters = getMergedChartAndParamsFilters({chartFilters, paramsFilters}).filter( + (filter) => { + if (filter.operation === Operations.NO_SELECTED_VALUES) { + return false; + } + + const filterField = datasetSchema.find((field) => field.guid === filter.column); + + if ( + !filter.isDrillDown && + filterField && + (Object.hasOwnProperty.call(filterParams, filterField.guid) || + Object.hasOwnProperty.call(filterParams, filterField.title)) + ) { + const paramFilter = + filterParams[filterField.guid] || filterParams[filterField.title]; + + return Array.isArray(paramFilter) ? paramFilter[0] !== '' : paramFilter !== ''; + } + + return true; + }, + ); + + return resultFilters.length ? resultFilters : undefined; +} + +// This function prepares a request for fields and data for a single dataset and layer +export function prepareSingleRequest({ + apiVersion, + datasetSchema, + datasetId, + links = [], + params, + visualization, + placeholders, + filters = [], + colors = [], + shapes = [], + sort = [], + labels = [], + tooltips = [], + updates = [], + segments = [], + extraSettings, + sharedData, + revisionId, +}: { + apiVersion: ApiVersion; + datasetId: string; + datasetSchema: ServerDatasetField[]; + links: Link[]; + params: StringParams; + visualization: ServerChartsConfig['visualization']; + placeholders: ServerChartsConfig['visualization']['placeholders']; + filters: ServerChartsConfig['filters']; + colors: ServerChartsConfig['colors']; + shapes?: ServerChartsConfig['shapes']; + segments: ServerChartsConfig['segments']; + sort: ServerChartsConfig['sort']; + labels: ServerChartsConfig['labels']; + tooltips: ServerChartsConfig['tooltips']; + updates: ServerChartsConfig['updates']; + layerId: string | undefined; + extraSettings?: ServerChartsConfig['extraSettings']; + sharedData: SharedData; + revisionId?: string; +}): ApiV2RequestBody { + preprocessHierarchies({ + visualizationId: visualization.id, + placeholders, + params, + sharedData, + colors, + shapes, + segments, + }); + + const items = getAllPlaceholderItems(placeholders); + + const fields = items.filter((item: ServerField) => !isParameter(item)); + const itemsParameters = items.filter((item) => isParameter(item)); + + const allItems = [...items, ...colors, ...sort, ...labels, ...tooltips, ...shapes, ...segments]; + + const {allItemsIds, allMeasuresMap} = allItems.reduce( + (acc, item) => { + acc.allItemsIds[item.guid] = true; + + if (isMeasureField(item)) { + acc.allMeasuresMap[item.guid] = true; + acc.allMeasuresMap[item.title] = true; + } + + return acc; + }, + {allMeasuresMap: {}, allItemsIds: {}} as { + allItemsIds: Record; + allMeasuresMap: Record; + }, + ); + + const isMeasureInFields = fields.some(isMeasureField); + + const withTotals = + (visualization.id === WizardVisualizationId.FlatTable || + visualization.id === WizardVisualizationId.Donut || + visualization.id === WizardVisualizationId.PivotTable) && + extraSettings?.totals === 'on' && + isMeasureInFields; + + const backgroundColorsFieldsIds = getBackgroundColorFieldsIds( + fields, + datasetId, + visualization.id as WizardVisualizationId, + ); + + const parameters: PayloadParameter[] = prepareParameterForPayload(itemsParameters, datasetId); + let payload: BaseUrlPayload = { + with_totals: withTotals, + columns: prepareColumns({ + fields, + datasetId, + backgroundColorsFieldsIds, + parameters: itemsParameters, + }), + }; + + const parametersMap = getParametersMap(parameters); + + // We form all fields for which connections are made in columns + links.forEach((link) => { + const fieldFromCurrentDataset = link.fields[datasetId]; + if (fieldFromCurrentDataset) { + const {guid} = fieldFromCurrentDataset.field; + + if ( + allItems.some((item) => { + const linkForOtherDataset = link.fields[item.datasetId]; + + return linkForOtherDataset && linkForOtherDataset.field.guid === item.guid; + }) + ) { + if (payload.columns.indexOf(guid) === -1) { + payload.columns.push(guid); + } + } + } + }); + + // Forming colors, labels, tooltips in columns + [colors, labels, tooltips, shapes, segments].forEach((container) => { + if (container && container.length) { + container.forEach( + (item: { + datasetId: string; + guid: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + }) => { + if (item.datasetId === datasetId) { + const itemGuid = item.guid; + if (isParameter(item) && !parametersMap[itemGuid]) { + parametersMap[itemGuid] = true; + parameters.push(mapItemToPayloadParameter(item)); + } else if (payload.columns.indexOf(itemGuid) === -1) { + payload.columns.push(itemGuid); + } + } + }, + ); + } + }); + + // Forming and passing order_by + if (sort && sort.length) { + const sortItems = sort + .map((item) => { + if (item.datasetId === datasetId) { + return item; + } else { + const targetLink = getItemLinkWithDatasets(item, datasetId, links); + + if (targetLink) { + const targetFieldInfo = targetLink.fields[datasetId].field; + return { + guid: targetFieldInfo.guid, + datasetId, + direction: item.direction, + }; + } else { + return item; + } + } + }) + .filter((item) => item.datasetId === datasetId) + .map((item) => { + const itemGuid = item.guid; + if (isParameter(item) && !parametersMap[itemGuid]) { + parametersMap[itemGuid] = true; + parameters.push(mapItemToPayloadParameter(item)); + } else if (!payload.columns.includes(itemGuid)) { + payload.columns.push(item.guid); + } + + return { + direction: item.direction || SORT_ORDER.DESCENDING.STR, + column: item.guid, + }; + }); + + payload.order_by = sortItems; + } + + // We form and transmit updates + if (updates && updates.length) { + payload.updates = filterUpdatesByDatasetId(updates, datasetId); + } + + // We pass the flag so that the backend does not swear 400 on non-existent fields in where + payload.ignore_nonexistent_filters = true; + + // We pass the flag according to the settings for disabling grouping + if (visualization.id === WizardVisualizationId.FlatTable) { + const {settings} = placeholders[0]; + + if ( + settings && + 'groupping' in settings && + (settings.groupping === 'disabled' || settings.groupping === 'off') + ) { + payload.disable_group_by = true; + } + } + + if ( + visualization.id === WizardVisualizationId.PivotTable || + visualization.id === WizardVisualizationId.FlatTable + ) { + payload = getPayloadWithCommonTableSettings(payload, { + extraSettings, + params, + datasetId, + allItemsIds, + visualization, + fields, + }); + } + + let resultRequest: ApiV2RequestBody; + + const pivotFallbackEnabled = extraSettings?.pivotFallback === 'on'; + + const urlSearchParams = transformParamsToUrlParams(params); + const {filtersParams, parametersParams} = splitParamsToParametersAndFilters( + urlSearchParams, + datasetSchema, + ); + const transformedFilterParams = transformUrlParamsToParams(filtersParams); + const transformedParameterParams = transformUrlParamsToParams(parametersParams); + + const formattedFilters = formatFilters({ + filters, + links, + datasetSchema, + datasetId, + filterParams: transformedFilterParams, + drillDownData: sharedData.drillDownData, + }); + + if (formattedFilters) { + payload.where = formattedFilters; + } + + payload.parameters = prepareParameters( + parameters, + transformedParameterParams as Record, + datasetSchema, + ); + + if (visualization.id === WizardVisualizationId.PivotTable && !pivotFallbackEnabled) { + resultRequest = buildPivotRequest({ + apiVersion, + placeholders, + payload, + colors, + params, + fields: [], + datasetId, + revisionId, + backgroundColorsFieldsIds, + }); + } else { + // Forming a POST data request + resultRequest = buildDefaultRequest({ + payload, + fields, + apiVersion, + params, + datasetId, + revisionId, + allMeasuresMap, + }); + } + return resultRequest; +} + +export const getUrlsRequestBody = (args: { + params: StringParams; + shared: Shared; + apiVersion?: ApiVersion; + datasetFields: ServerDatasetField[]; + datasetId: string; + layerId: string; + revisionId?: string; +}): ApiV2RequestBody => { + const {params, shared, datasetId, layerId, revisionId} = args; + + const apiVersion = args.apiVersion || '1.5'; + + const config = mapChartsConfigToServerConfig(shared); + + shared.sharedData = config.sharedData; + shared.links = config.links; + + const visualization = config.visualization; + const layers = config.visualization.layers || []; + + const currentLayer = + layers.find((layer) => layer.layerSettings.id === layerId) || visualization; + + const placeholders = currentLayer.placeholders; + const currentDatasetIndex = config.datasetsIds.findIndex((value) => value === datasetId); + const currentLocalFields = + currentDatasetIndex >= 0 ? config.datasetsPartialFields?.[currentDatasetIndex] : []; + + const datasetSchema = [...args.datasetFields, ...(currentLocalFields ?? [])]; + + const links = config.links; + const segments = config.segments; + const sort = + (currentLayer as ServerVisualizationLayer)?.commonPlaceholders?.sort ?? config.sort; + const updates = config.updates; + const extraSettings = config.extraSettings; + const sharedData = shared.sharedData; + + let filters, colors, labels, tooltips, shapes; + + if (layerId) { + const commonPlaceholders = (currentLayer as V4Layer).commonPlaceholders; + filters = [...commonPlaceholders.filters, ...config.filters]; + colors = commonPlaceholders.colors; + labels = commonPlaceholders.labels; + tooltips = commonPlaceholders.tooltips; + shapes = commonPlaceholders.shapes; + } else { + filters = config.filters; + colors = config.colors; + labels = config.labels; + tooltips = config.tooltips; + shapes = config.shapes; + } + + const request = prepareSingleRequest({ + apiVersion, + datasetSchema, + datasetId, + params, + links, + visualization, + placeholders, + filters, + colors, + shapes, + sort, + labels, + tooltips, + updates, + extraSettings, + sharedData, + layerId, + revisionId, + segments, + }); + + log(`REQUEST`); + log(`datasetId=${datasetId}:`); + if (layerId) { + log(`layerId=${layerId}`); + log(`layerName=${(currentLayer as V4Layer)?.layerSettings?.name}`); + } + + log(request); + + log('UPDATES:'); + log(request.updates || []); + + return request; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getAnnotations.test.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getAnnotations.test.ts new file mode 100644 index 0000000..ef2e774 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getAnnotations.test.ts @@ -0,0 +1,110 @@ +import {getAnnotations} from '../helpers'; + +import {ORDER_BY_MAP} from './mocks/common.mock'; +import { + BACKGROUND_COLORS, + COLORS, + DIMENSION_BACKGROUND_COLORS, + MEASURE_NAME_COLORS, + USED_FIELDS_MAP, + extendUsedFieldsMap, +} from './mocks/getAnnotations.mock'; + +describe('getAnnotations', () => { + it('Should turn colors into annotations and put them first in an array', () => { + const result = getAnnotations({ + colors: COLORS, + legendItemCounter: {legendItemIdIndex: 0}, + orderByMap: {}, + backgroundColors: BACKGROUND_COLORS, + usedFieldsMap: USED_FIELDS_MAP, + }); + + expect(result).toHaveLength(2); + expect(result[0]).toEqual({ + legend_item_id: 0, + block_id: 0, + role_spec: { + annotation_type: 'color', + role: 'pivot_annotation', + direction: 'asc', + }, + ref: {type: 'id', id: '1899d37a-ff15-4bbc-b9f1-9df1e5f715ee'}, + }); + }); + + it('Must put direction, depending on the value in order_by', () => { + const result = getAnnotations({ + colors: COLORS, + legendItemCounter: {legendItemIdIndex: 0}, + orderByMap: ORDER_BY_MAP, + backgroundColors: [], + usedFieldsMap: {}, + }); + + expect(result[0].role_spec!.direction).toEqual('desc'); + }); + + it('Must filter Measure Names', () => { + const result = getAnnotations({ + orderByMap: {}, + colors: MEASURE_NAME_COLORS, + legendItemCounter: {legendItemIdIndex: 0}, + backgroundColors: [], + usedFieldsMap: {}, + }); + + expect(result[0]).toBeUndefined(); + }); + + it('Must increase legend_item_id by one for each field in turn: [colors, backgroundColors]', () => { + const result = getAnnotations({ + colors: COLORS, + legendItemCounter: {legendItemIdIndex: 0}, + orderByMap: {}, + backgroundColors: BACKGROUND_COLORS, + usedFieldsMap: USED_FIELDS_MAP, + }); + + expect(result[0].legend_item_id).toEqual(0); + expect(result[1].legend_item_id).toEqual(1); + }); + + it('Must add the legend_item_id from measuresReq to target_legend_item_ids if we request an annotation for the measure', () => { + const result = getAnnotations({ + colors: [], + legendItemCounter: {legendItemIdIndex: 0}, + orderByMap: {}, + backgroundColors: BACKGROUND_COLORS, + usedFieldsMap: USED_FIELDS_MAP, + }); + + expect(result[0].role_spec!.target_legend_item_ids).toBeDefined(); + expect(result[0].role_spec!.target_legend_item_ids).toEqual([1]); + }); + + it('Should filter out dimensions that are not represented in the fields query for backgroundSettings annotation', () => { + const result = getAnnotations({ + colors: [], + legendItemCounter: {legendItemIdIndex: 0}, + orderByMap: {}, + backgroundColors: DIMENSION_BACKGROUND_COLORS, + usedFieldsMap: USED_FIELDS_MAP, + }); + expect(result).toHaveLength(0); + }); + + it('Must unlock the dimensions that are presented in the fields query for backgroundSettings annotation', () => { + const result = getAnnotations({ + colors: [], + legendItemCounter: {legendItemIdIndex: 0}, + orderByMap: {}, + backgroundColors: DIMENSION_BACKGROUND_COLORS, + usedFieldsMap: extendUsedFieldsMap({ + 'asdha91-2131njda-12424jjd-23s-21eh12h': {legendItemId: 2, role: 'pivot_column'}, + }), + }); + + expect(result).toHaveLength(1); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getRegularFields.test.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getRegularFields.test.ts new file mode 100644 index 0000000..42eb4dc --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getRegularFields.test.ts @@ -0,0 +1,159 @@ +import {getRegularFields} from '../helpers'; + +import {ORDER_BY_MAP} from './mocks/common.mock'; +import { + COLUMNS, + COLUMNS_MEASURE_NAME, + MEASURES, + ROWS, + ROWS_MEASURE_NAME_AND_MEASURE_VALUE, +} from './mocks/getRegularFields.mock'; + +describe('getRegularFields', () => { + it('Should return a stacked array of columns', () => { + const {columnsReq} = getRegularFields({ + columns: COLUMNS, + orderByMap: {}, + legendItemCounter: {legendItemIdIndex: 0}, + rows: [], + measures: [], + }); + + expect(columnsReq).toEqual([ + { + legend_item_id: 0, + block_id: 0, + role_spec: { + role: 'pivot_column', + direction: 'asc', + }, + ref: {type: 'id', id: '9aa48a4f-cace-4256-bd59-55fc7aa43c4f'}, + }, + ]); + }); + + it('Must return the accumulated array of strings', () => { + const {rowsReq} = getRegularFields({ + columns: [], + orderByMap: {}, + legendItemCounter: {legendItemIdIndex: 0}, + rows: ROWS, + measures: [], + }); + + expect(rowsReq).toEqual([ + { + legend_item_id: 0, + block_id: 0, + role_spec: { + role: 'pivot_row', + direction: 'asc', + }, + ref: {type: 'id', id: '56185375-6b39-4ca2-aec2-f0971b7332bc'}, + }, + ]); + }); + + it('Must return the accumulated array of measures', () => { + const {measuresReq} = getRegularFields({ + columns: [], + orderByMap: {}, + legendItemCounter: {legendItemIdIndex: 0}, + rows: [], + measures: MEASURES, + }); + + expect(measuresReq).toEqual([ + { + legend_item_id: 0, + block_id: 0, + role_spec: { + role: 'pivot_measure', + direction: 'asc', + }, + ref: {type: 'id', id: '9781c180-fe55-11ea-be64-078ac452d479'}, + }, + ]); + }); + + it('Should return a stacked array of columns if Measure Names is there', () => { + const {columnsReq} = getRegularFields({ + columns: COLUMNS_MEASURE_NAME, + orderByMap: {}, + legendItemCounter: {legendItemIdIndex: 0}, + rows: [], + measures: [], + }); + + expect(columnsReq).toEqual([ + { + legend_item_id: 0, + block_id: 0, + role_spec: { + role: 'pivot_column', + direction: 'asc', + }, + ref: {type: 'measure_name'}, + }, + ]); + }); + + it('Should return the accumulated array of strings if there is a Measure Names or Measure Values', () => { + const {rowsReq} = getRegularFields({ + columns: [], + orderByMap: {}, + legendItemCounter: {legendItemIdIndex: 0}, + rows: ROWS_MEASURE_NAME_AND_MEASURE_VALUE, + measures: [], + }); + + expect(rowsReq).toEqual([ + { + legend_item_id: 0, + block_id: 0, + role_spec: { + role: 'pivot_row', + direction: 'asc', + }, + ref: {type: 'measure_name'}, + }, + { + legend_item_id: 1, + block_id: 0, + role_spec: { + role: 'pivot_row', + direction: 'asc', + }, + ref: {type: 'measure_name'}, + }, + ]); + }); + + it('Must put direction, depending on the orderByMap object', () => { + const {columnsReq, rowsReq, measuresReq} = getRegularFields({ + columns: COLUMNS, + orderByMap: ORDER_BY_MAP, + legendItemCounter: {legendItemIdIndex: 0}, + rows: ROWS, + measures: MEASURES, + }); + + expect(columnsReq[0].role_spec?.direction).toEqual('desc'); + expect(rowsReq[0].role_spec?.direction).toEqual('asc'); + expect(measuresReq[0].role_spec?.direction).toEqual('desc'); + }); + + it('Must increment legend_item_id by one for each field in turn: [columnsReq, rowsReq, measuresReq, colorsReq]', () => { + const {columnsReq, rowsReq, measuresReq} = getRegularFields({ + columns: COLUMNS, + rows: ROWS, + measures: MEASURES, + legendItemCounter: {legendItemIdIndex: 0}, + orderByMap: {}, + }); + + expect(columnsReq[0].legend_item_id).toEqual(0); + expect(rowsReq[0].legend_item_id).toEqual(1); + expect(measuresReq[0].legend_item_id).toEqual(2); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getTotalsForPivot.test.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getTotalsForPivot.test.ts new file mode 100644 index 0000000..c704113 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/getTotalsForPivot.test.ts @@ -0,0 +1,58 @@ +import type {ApiV2RequestPivotTotals, ServerField} from '../../../../../../../../../shared'; +import {getTotalsForPivot} from '../helpers/totals'; + +import {MOCKED_DIMENSION_FIELD} from './mocks/common.mock'; +import {MOCKED_DIMENSION_FIELD_WITH_SUB_TOTALS_SETTING} from './mocks/getTotalsForPivot.mock'; + +jest.mock('../../../../../../../../registry', () => ({ + registry: { + getApp() { + return {nodekit: {ctx: {config: {features: {}}}}}; + }, + }, +})); + +describe('getTotalsForPivot', () => { + it.each([ + ['columns', {key: 'columnsFields', field: MOCKED_DIMENSION_FIELD_WITH_SUB_TOTALS_SETTING}], + ['rows', {key: 'rowsFields', field: MOCKED_DIMENSION_FIELD_WITH_SUB_TOTALS_SETTING}], + ] as ['columns' | 'rows', {key: string; field: ServerField}][])( + 'Should return totals with %s level: 0, when grand totals disabled, but sub-totals for first field exists', + (type, {key, field}) => { + const args = { + isGrandTotalsEnabled: false, + columnsFields: [], + rowsFields: [], + [key]: [field], + }; + const {settings: result} = getTotalsForPivot(args) as { + settings: {totals: ApiV2RequestPivotTotals}; + }; + expect(result.totals[type]).toEqual([{level: 0}]); + }, + ); + + it('Should set level, that equal index in array, when field has enabled sub-totals', () => { + const args = { + isGrandTotalsEnabled: false, + columnsFields: [ + MOCKED_DIMENSION_FIELD_WITH_SUB_TOTALS_SETTING, + MOCKED_DIMENSION_FIELD, + MOCKED_DIMENSION_FIELD_WITH_SUB_TOTALS_SETTING, + ], + rowsFields: [ + MOCKED_DIMENSION_FIELD, + MOCKED_DIMENSION_FIELD_WITH_SUB_TOTALS_SETTING, + MOCKED_DIMENSION_FIELD, + MOCKED_DIMENSION_FIELD_WITH_SUB_TOTALS_SETTING, + ], + }; + + const {settings: result} = getTotalsForPivot(args) as { + settings: {totals: ApiV2RequestPivotTotals}; + }; + + expect(result.totals.columns).toEqual([{level: 0}, {level: 2}]); + expect(result.totals.rows).toEqual([{level: 1}, {level: 3}]); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/common.mock.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/common.mock.ts new file mode 100644 index 0000000..293344f --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/common.mock.ts @@ -0,0 +1,21 @@ +import type {ServerField} from '../../../../../../../../../../shared'; + +export const ORDER_BY_MAP = { + '9aa48a4f-cace-4256-bd59-55fc7aa43c4f': 'desc', + '56185375-6b39-4ca2-aec2-f0971b7332bc': 'asc', + '9781c180-fe55-11ea-be64-078ac452d479': 'desc', + '1899d37a-ff15-4bbc-b9f1-9df1e5f715ee': 'desc', +}; + +export const MOCKED_DIMENSION_FIELD = { + guid: 'f7a2f8b0-d556-11ed-99ad-75d0706437d7', + type: 'DIMENSION', +} as ServerField; +export const MOCKED_MEASURE_FIELD = { + guid: '7d67c220-f490-11e8-8706-c9ca730e1cff', + type: 'MEASURE', +} as ServerField; +export const MOCKED_MEASURE_NAMES_FIELD = { + title: 'Measure Names', + type: 'PSEUDO', +}; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getAnnotations.mock.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getAnnotations.mock.ts new file mode 100644 index 0000000..26f3bd2 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getAnnotations.mock.ts @@ -0,0 +1,33 @@ +import type {ApiV2BackgroundSettingsGuids, ServerField} from '../../../../../../../../../../shared'; + +export const COLORS = [{guid: '1899d37a-ff15-4bbc-b9f1-9df1e5f715ee'}] as ServerField[]; + +export const BACKGROUND_COLORS = [ + { + colorFieldGuid: 'bfccec5c-941a-45aa-89d6-8cc7a09b19ca', + targetFieldGuid: 'fdddd3b0-5639-11eb-9c8e-41e84ec800f0', + isContinuous: true, + }, +] as ApiV2BackgroundSettingsGuids[]; + +export const DIMENSION_BACKGROUND_COLORS = [ + { + colorFieldGuid: 'asdha91-2131njda-12424jjd-23s-21eh12h', + targetFieldGuid: 'fdddd3b0-5639-11eb-9c8e-41e84ec800f0', + isContinuous: false, + }, +] as ApiV2BackgroundSettingsGuids[]; + +export const USED_FIELDS_MAP = { + 'fdddd3b0-5639-11eb-9c8e-41e84ec800f0': {legendItemId: 1, role: 'pivot_measure'}, +}; +export const MEASURE_NAME_COLORS = [{title: 'Measure Names', type: 'PSEUDO'}] as ServerField[]; + +export const extendUsedFieldsMap = ( + extend: Record, +) => { + return { + ...USED_FIELDS_MAP, + ...extend, + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getRegularFields.mock.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getRegularFields.mock.ts new file mode 100644 index 0000000..501c2dd --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getRegularFields.mock.ts @@ -0,0 +1,14 @@ +import type {ServerField} from '../../../../../../../../../../shared'; + +export const COLUMNS = [{guid: '9aa48a4f-cace-4256-bd59-55fc7aa43c4f'}] as ServerField[]; + +export const ROWS = [{guid: '56185375-6b39-4ca2-aec2-f0971b7332bc'}] as ServerField[]; + +export const MEASURES = [{guid: '9781c180-fe55-11ea-be64-078ac452d479'}] as ServerField[]; + +export const COLUMNS_MEASURE_NAME = [{title: 'Measure Names', type: 'PSEUDO'}] as ServerField[]; + +export const ROWS_MEASURE_NAME_AND_MEASURE_VALUE = [ + {title: 'Measure Names', type: 'PSEUDO'}, + {title: 'Measure Values', type: 'PSEUDO'}, +] as ServerField[]; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getTotalsForPivot.mock.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getTotalsForPivot.mock.ts new file mode 100644 index 0000000..b021b37 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/__tests__/mocks/getTotalsForPivot.mock.ts @@ -0,0 +1,8 @@ +import type {ServerField} from '../../../../../../../../../../shared'; + +import {MOCKED_DIMENSION_FIELD} from './common.mock'; + +export const MOCKED_DIMENSION_FIELD_WITH_SUB_TOTALS_SETTING = { + ...MOCKED_DIMENSION_FIELD, + subTotalsSettings: {enabled: true}, +} as ServerField; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/index.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/index.ts new file mode 100644 index 0000000..6041e6d --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/index.ts @@ -0,0 +1,300 @@ +// 0. -> block_id=0: +import isEmpty from 'lodash/isEmpty'; + +import type { + ApiV2PivotRequestStructure, + ApiV2RequestField, + ApiV2RequestFieldSorting, + ApiV2RequestPivot, + ApiV2RequestPivotRoleSpec, +} from '../../../../../../../../../shared'; +import { + ApiV2Annotations, + PseudoFieldTitle, + getObjectValueByPossibleKeys, + isMeasureName, + isMeasureValue, +} from '../../../../../../../../../shared'; +import {getSortParams} from '../../../../../../../../components/charts-engine/components/processor/paramsUtils'; +import type {BackendPivotTableCellCustom} from '../../../../types'; + +import type { + GetAnnotationsArgs, + GetPivotStructureArgs, + GetRegularFieldsArgs, + GetStructureWithSortingFromFieldArgs, +} from './types'; + +export const getRegularFields = ({ + columns, + rows, + measures, + legendItemCounter, + orderByMap, +}: GetRegularFieldsArgs) => { + const columnsReq: ApiV2RequestField[] = columns.map((el) => { + const direction = getObjectValueByPossibleKeys([el.guid, el.title], orderByMap) || 'asc'; + if (isMeasureName(el)) { + return { + legend_item_id: legendItemCounter.legendItemIdIndex++, + block_id: 0, + role_spec: { + role: 'pivot_column', + direction, + }, + ref: {type: 'measure_name'}, + }; + } + + return { + legend_item_id: legendItemCounter.legendItemIdIndex++, + block_id: 0, + role_spec: { + role: 'pivot_column', + direction, + }, + ref: {type: 'id', id: el.guid}, + }; + }); + + const rowsReq: ApiV2RequestField[] = rows.map((el) => { + const direction = getObjectValueByPossibleKeys([el.guid, el.title], orderByMap) || 'asc'; + // In the old summary, it was possible to put Measure Values in the lines. Visually, the chart did not change. + // Therefore, in order not to break the old summary on dashboards and in preview. We send it to the back as if it were a Measure Names + if (isMeasureName(el) || isMeasureValue(el)) { + return { + legend_item_id: legendItemCounter.legendItemIdIndex++, + block_id: 0, + role_spec: { + role: 'pivot_row', + direction, + }, + ref: {type: 'measure_name'}, + }; + } + + return { + legend_item_id: legendItemCounter.legendItemIdIndex++, + block_id: 0, + ref: {type: 'id', id: el.guid}, + role_spec: { + role: 'pivot_row', + direction, + }, + }; + }); + + const measuresReq: ApiV2RequestField[] = measures.map((el) => { + const direction = getObjectValueByPossibleKeys([el.guid, el.title], orderByMap) || 'asc'; + return { + legend_item_id: legendItemCounter.legendItemIdIndex++, + block_id: 0, + ref: {type: 'id', id: el.guid}, + role_spec: { + role: 'pivot_measure', + direction, + }, + }; + }); + + return {columnsReq, rowsReq, measuresReq}; +}; + +export const getAnnotations = ({ + colors, + backgroundColors, + legendItemCounter, + orderByMap, + usedFieldsMap, +}: GetAnnotationsArgs): ApiV2RequestField[] => { + const colorsAnnotations: ApiV2RequestField[] = colors + .filter((el) => !isMeasureName(el)) + .map((el) => { + const direction = + getObjectValueByPossibleKeys([el.guid, el.title], orderByMap) || 'asc'; + const legendItemId = + typeof usedFieldsMap[el.guid]?.legendItemId !== 'undefined' + ? usedFieldsMap[el.guid]?.legendItemId + : legendItemCounter.legendItemIdIndex++; + return { + legend_item_id: legendItemId, + block_id: 0, + ref: {type: 'id', id: el.guid}, + role_spec: { + annotation_type: ApiV2Annotations.Color, + role: 'pivot_annotation', + direction, + }, + }; + }); + + // The request is only for measuresReq, we do not request annotations for measurements. + const backgroundColorsAnnotations: ApiV2RequestField[] = backgroundColors + .filter(({targetFieldGuid, colorFieldGuid, isContinuous}) => { + const isDimensionFieldExistsInRequest = + isContinuous || typeof usedFieldsMap[colorFieldGuid] !== 'undefined'; + return ( + usedFieldsMap[targetFieldGuid]?.role === 'pivot_measure' && + isDimensionFieldExistsInRequest && + // TODO: CHARTS-7124 + // Now the backend fails with 500 when the helmet is in the measure names annotations. + // Therefore, we made the coloring on our own. After backend corrects, + // you will need to switch to a general approach and remove the filter here + colorFieldGuid !== PseudoFieldTitle.MeasureNames + ); + }) + .map(({colorFieldGuid, targetFieldGuid}) => { + const legendItemId = + typeof usedFieldsMap[colorFieldGuid]?.legendItemId !== 'undefined' + ? usedFieldsMap[colorFieldGuid]?.legendItemId + : legendItemCounter.legendItemIdIndex++; + return { + legend_item_id: legendItemId, + block_id: 0, + ref: {type: 'id', id: colorFieldGuid}, + role_spec: { + annotation_type: ApiV2Annotations.BackgroundColor, + role: 'pivot_annotation', + target_legend_item_ids: [usedFieldsMap[targetFieldGuid].legendItemId], + }, + }; + }); + + return [...colorsAnnotations, ...backgroundColorsAnnotations]; +}; + +const getStructureFromField = (field: ApiV2RequestField): ApiV2PivotRequestStructure => { + const role_spec: ApiV2RequestPivotRoleSpec | undefined = field.role_spec + ? { + role: field.role_spec.role, + direction: field.role_spec.direction, + annotation_type: field.role_spec.annotation_type, + target_legend_item_ids: field.role_spec.target_legend_item_ids, + } + : undefined; + return { + legend_item_ids: [field.legend_item_id!], + role_spec, + }; +}; + +const isFieldsChanged = (meta: BackendPivotTableCellCustom, fields: ApiV2RequestField[]) => { + const prevOrder = meta.fieldOrder || []; + const isInvisibleMeasureName = + prevOrder.length === 1 && fields.length === 0 && prevOrder[0] === 'measure_name'; + + if (prevOrder.length !== fields.length && !isInvisibleMeasureName) { + return true; + } + + return fields.some((field, index) => { + const guid = prevOrder[index]; + + switch (field.ref.type) { + case 'id': + return field.ref.id !== guid; + case 'measure_name': + return field.ref.type !== guid; + default: + return false; + } + }); +}; + +const isSortSupported = (meta: BackendPivotTableCellCustom, measures: number, guid: string) => { + if (measures > 1) { + return meta.measureGuid && meta.measureGuid === guid; + } else if (measures === 1 && meta.measureGuid) { + return meta.measureGuid === guid; + } + + return true; +}; + +const getStructureWithSortingFromField = (args: GetStructureWithSortingFromFieldArgs) => { + const {params, field, rowsReq, columnsReq, measuresReq} = args; + const sortParams = getSortParams(params); + const meta = sortParams.meta as + | {column: BackendPivotTableCellCustom; row: BackendPivotTableCellCustom} + | undefined; + + const structure = getStructureFromField(field); + + if (!meta || isEmpty(meta) || !('id' in field.ref)) { + return structure; + } + + const {column, row} = meta; + + const isColumnExists = !isEmpty(column); + const isRowExists = !isEmpty(row); + + const sorting: ApiV2RequestFieldSorting = {}; + + const isRowFieldsChanged = isFieldsChanged(row, rowsReq); + const isColumnFieldsChanged = isFieldsChanged(column, columnsReq); + + const isColumnSortSupported = Boolean( + isSortSupported(column, measuresReq.length, field.ref.id), + ); + const isRowSortSupported = Boolean(isSortSupported(row, measuresReq.length, field.ref.id)); + + if ( + !isColumnFieldsChanged && + isColumnExists && + column.nextSortDirection && + isColumnSortSupported + ) { + sorting.column = { + header_values: column.path.map((value) => ({value})), + role_spec: { + role: column.role, + }, + direction: column.nextSortDirection, + }; + } + + if (!isRowFieldsChanged && isRowExists && row.nextSortDirection && isRowSortSupported) { + sorting.row = { + header_values: row.path.map((value) => ({value})), + role_spec: { + role: row.role, + }, + direction: row.nextSortDirection, + }; + } + + structure.role_spec = { + ...structure.role_spec!, + sorting: isEmpty(sorting) ? undefined : sorting, + }; + + return structure; +}; + +export const getPivotStructure = ({ + columnsReq, + measuresReq, + rowsReq, + annotations, + params, + totals, +}: GetPivotStructureArgs): ApiV2RequestPivot['structure'] => { + // PivotStructure is a mapping of elements from columns, measures and rows + // legend_item_ids stores the field id from each block. + return [ + ...columnsReq.map(getStructureFromField), + ...rowsReq.map(getStructureFromField), + ...measuresReq.map((field) => { + return getStructureWithSortingFromField({ + field, + params, + rowsReq, + columnsReq, + measuresReq, + totals, + }); + }), + ...annotations.map(getStructureFromField), + ]; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/totals.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/totals.ts new file mode 100644 index 0000000..1630624 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/totals.ts @@ -0,0 +1,17 @@ +import type {ApiV2RequestPivotTotals} from '../../../../../../../../../shared'; +import {getPivotTableSubTotals} from '../../../../utils/pivot-table/totals'; + +import type {GetTotalsForPivotArgs} from './types'; + +export const getTotalsForPivot = ({ + rowsFields, + columnsFields, +}: GetTotalsForPivotArgs): { + settings: {totals: ApiV2RequestPivotTotals}; +} => { + return { + settings: { + totals: getPivotTableSubTotals({rowsFields, columnsFields}), + }, + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/types.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/types.ts new file mode 100644 index 0000000..320a176 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/helpers/types.ts @@ -0,0 +1,57 @@ +import type { + ApiV2BackgroundSettingsGuids, + ApiV2RequestField, + ApiV2RequestPivotTotals, + ServerColor, + ServerField, + StringParams, +} from '../../../../../../../../../shared'; + +export type GetRegularFieldsArgs = { + columns: ServerField[]; + rows: ServerField[]; + measures: ServerField[]; + orderByMap: Record; + legendItemCounter: { + legendItemIdIndex: number; + }; +}; + +export type GetPivotStructureArgs = { + columnsReq: ApiV2RequestField[]; + measuresReq: ApiV2RequestField[]; + rowsReq: ApiV2RequestField[]; + annotations: ApiV2RequestField[]; + params: StringParams; + totals?: ApiV2RequestPivotTotals; +}; + +export type GetAnnotationsArgs = { + colors: ServerColor[]; + backgroundColors: ApiV2BackgroundSettingsGuids[]; + legendItemCounter: { + legendItemIdIndex: number; + }; + orderByMap: Record; + usedFieldsMap: Record< + string, + { + legendItemId: number; + role: string; + } + >; +}; + +export type GetStructureWithSortingFromFieldArgs = { + field: ApiV2RequestField; + params: StringParams; + columnsReq: ApiV2RequestField[]; + rowsReq: ApiV2RequestField[]; + measuresReq: ApiV2RequestField[]; + totals?: ApiV2RequestPivotTotals; +}; + +export type GetTotalsForPivotArgs = { + columnsFields: ServerField[]; + rowsFields: ServerField[]; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/index.ts b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/index.ts new file mode 100644 index 0000000..decba0e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-request-body/pivot-request/index.ts @@ -0,0 +1,175 @@ +import type { + ApiV2BackgroundSettingsGuids, + ApiV2Filter, + ApiV2OrderBy, + ApiV2RequestBodyPivot, + ApiV2RequestField, + ApiV2RequestPivot, + ServerColor, + ServerPlaceholder, +} from '../../../../../../../../shared'; +import {PseudoFieldTitle} from '../../../../../../../../shared'; +import {mapParameterToRequestFormat} from '../../helpers'; +import type {BuildDefaultRequestArgs} from '../default-request'; + +import {getAnnotations, getPivotStructure, getRegularFields} from './helpers'; +import {getTotalsForPivot} from './helpers/totals'; + +export type BuildPivotResultRequestArgs = Omit & { + colors: ServerColor[]; + placeholders: ServerPlaceholder[]; + backgroundColorsFieldsIds: ApiV2BackgroundSettingsGuids[]; +}; + +export const buildPivotRequest = (args: BuildPivotResultRequestArgs): ApiV2RequestBodyPivot => { + const {placeholders, colors, payload, revisionId, backgroundColorsFieldsIds, params} = args; + + const columns = + placeholders.find((placeholder) => placeholder.id === 'pivot-table-columns')?.items || []; + const rows = placeholders.find((placeholder) => placeholder.id === 'rows')?.items || []; + const measures = placeholders.find((placeholder) => placeholder.id === 'measures')?.items || []; + + const orderBy = payload.order_by || []; + + const legendItemCounter = { + legendItemIdIndex: 0, + }; + + let fields: ApiV2RequestField[] = []; + + const orderByMap = orderBy.reduce( + (acc, orderByPayload) => { + return { + ...acc, + [orderByPayload.column]: orderByPayload.direction.toLowerCase(), + }; + }, + {} as Record, + ); + + const {columnsReq, rowsReq, measuresReq} = getRegularFields({ + columns, + rows, + measures, + legendItemCounter, + orderByMap, + }); + + const {usedFieldsMap, usedLegendItemIds} = [...measuresReq, ...columnsReq, ...rowsReq].reduce( + (acc, reqField) => { + if (typeof reqField.legend_item_id === 'undefined') { + return acc; + } + + switch (reqField.ref.type) { + case 'measure_name': + acc.usedFieldsMap[PseudoFieldTitle.MeasureNames] = { + legendItemId: reqField.legend_item_id, + role: reqField.role_spec?.role || '', + }; + break; + case 'id': + acc.usedFieldsMap[reqField.ref.id] = { + legendItemId: reqField.legend_item_id, + role: reqField.role_spec?.role || '', + }; + + break; + } + + acc.usedLegendItemIds[reqField.legend_item_id] = true; + + return acc; + }, + { + usedFieldsMap: {} as Record< + string, + { + legendItemId: number; + role: string; + } + >, + usedLegendItemIds: {} as Record, + }, + ); + + const annotations = getAnnotations({ + colors, + orderByMap, + legendItemCounter, + backgroundColors: backgroundColorsFieldsIds, + usedFieldsMap, + }); + + const annotationsForRequest = annotations.filter((annotation) => { + const isFieldMissedInRequest = + annotation.ref.type === 'id' && !usedFieldsMap[annotation.ref.id]; + const isLegendItemIdMissedInRequest = + typeof annotation.legend_item_id !== 'undefined' && + !usedLegendItemIds[annotation.legend_item_id]; + + return isFieldMissedInRequest && isLegendItemIdMissedInRequest; + }); + + fields = fields.concat(columnsReq, rowsReq, measuresReq, annotationsForRequest); + + const pivot_pagination = { + offset_rows: payload.offset, + limit_rows: payload.limit, + }; + + const {settings} = getTotalsForPivot({ + columnsFields: columns, + rowsFields: rows, + }); + + const pivot: ApiV2RequestPivot = { + structure: getPivotStructure({ + columnsReq, + rowsReq, + measuresReq, + annotations, + params, + }), + pagination: pivot_pagination, + ...settings, + }; + + const filters: ApiV2Filter[] = (payload.where || []).map((el) => { + return { + ref: {type: 'id', id: el.column || ''}, + operation: el.operation, + values: el.values, + }; + }); + + const order_by: ApiV2OrderBy[] = orderBy.map((el) => { + return { + ref: {type: 'id', id: el.column}, + direction: el.direction.toLowerCase(), + }; + }); + + const parameter_values = (payload.parameters || []).map(mapParameterToRequestFormat); + + fields.forEach((field) => { + if (field.role_spec?.role !== 'total' && field.role_spec?.role !== 'template') { + delete field.role_spec; + } + }); + + return { + fields, + pivot, + filters, + order_by, + parameter_values, + pivot_pagination, + dataset_revision_id: revisionId, + + updates: payload.updates, + ignore_nonexistent_filters: payload.ignore_nonexistent_filters, + disable_group_by: payload.disable_group_by, + autofill_legend: true, + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/build-sources.ts b/src/server/modes/charts/plugins/datalens/url/build-sources.ts new file mode 100644 index 0000000..9118d67 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/build-sources.ts @@ -0,0 +1,161 @@ +import type {Palette, ServerPlaceholder, V4Field} from '../../../../../../shared'; +import {DATASET_FIELD_TYPES} from '../../../../../../shared'; +import { + CHARTS_MIDDLEWARE_URL_TYPE, + REQUEST_WITH_DATASET_SOURCE_NAME, +} from '../../constants/middleware-urls'; +import {getColorPalettesRequests} from '../../helpers/color-palettes'; +import {getDatasetIdAndLayerIdRequestKey} from '../../helpers/misc'; +import {mapChartsConfigToServerConfig} from '../utils/config-helpers'; +import {DATASET_DATA_PIVOT_URL, DATASET_DATA_URL_V1, DATASET_DATA_URL_V2} from '../utils/constants'; +import {getAllPlaceholderItems, log} from '../utils/misc-helpers'; + +import {prepareFieldsForPayload} from './helpers'; +import type { + PrepareSingleSourceRequestArgs, + PrepareSourceRequestBody, + PrepareSourceRequestsArgs, + SourceRequest, + SourceRequests, + SourcesArgs, +} from './types'; + +const getAllPlaceholdersItemsForSourceRequest = (placeholders: ServerPlaceholder[]) => { + const placeholdersItems = getAllPlaceholderItems(placeholders); + + return placeholdersItems.reduce((acc, field) => { + if (field.data_type === DATASET_FIELD_TYPES.HIERARCHY) { + const items = field.fields || []; + return [...acc, ...items]; + } + return [...acc, field]; + }, [] as V4Field[]); +}; + +const prepareSourceRequestBody = (args: PrepareSourceRequestBody): SourceRequest => { + const {sourceArgs, isPivotRequest, datasetId, apiVersion} = args; + + let url; + if (isPivotRequest) { + url = DATASET_DATA_PIVOT_URL.replace('{id}', datasetId); + } else if (apiVersion === '2') { + url = DATASET_DATA_URL_V2.replace('{id}', datasetId); + } else { + url = DATASET_DATA_URL_V1.replace('{id}', datasetId); + } + + return { + url, + middlewareUrl: { + sourceName: REQUEST_WITH_DATASET_SOURCE_NAME, + middlewareType: CHARTS_MIDDLEWARE_URL_TYPE, + }, + method: 'POST', + sourceArgs, + }; +}; + +const prepareSingleSourceRequest = (args: PrepareSingleSourceRequestArgs): SourceRequests => { + const { + placeholders, + datasetsIds, + apiVersion, + layerId = '', + sourceArgs, + isPivotRequest, + links, + } = args; + + const placeholdersItems = getAllPlaceholdersItemsForSourceRequest(placeholders); + + return datasetsIds.reduce((acc, datasetId: string) => { + const payloadFields = prepareFieldsForPayload(placeholdersItems, datasetId, links); + + // If there are no fields that are necessary for visualization, then we do not request anything at all + if (!payloadFields.length) { + return acc; + } + + const key = getDatasetIdAndLayerIdRequestKey(datasetId, layerId); + + return { + ...acc, + [key]: prepareSourceRequestBody({apiVersion, sourceArgs, isPivotRequest, datasetId}), + }; + }, {} as SourceRequests); +}; + +const prepareSourceRequests = (args: PrepareSourceRequestsArgs): SourceRequests => { + const {datasetsIds, visualization, apiVersion, sourceArgs, extraSettings, links} = args; + + const isVisualizationWithLayers = + visualization.id === 'geolayer' || visualization.id === 'combined-chart'; + + const pivotFallbackEnabled = extraSettings?.pivotFallback === 'on'; + + const isPivotRequest = Boolean(visualization.id === 'pivotTable' && !pivotFallbackEnabled); + + if (isVisualizationWithLayers) { + const layers = visualization.layers || []; + return layers.reduce((acc, layer) => { + const {placeholders} = layer; + + const request = prepareSingleSourceRequest({ + placeholders, + apiVersion, + datasetsIds, + sourceArgs, + isPivotRequest, + layerId: layer.layerSettings.id, + links, + }); + + return { + ...acc, + ...request, + }; + }, {} as SourceRequests); + } else { + const placeholders = visualization.placeholders; + return prepareSingleSourceRequest({ + placeholders, + apiVersion, + datasetsIds, + sourceArgs, + isPivotRequest, + }); + } +}; + +export const buildSourcesPrivate = ( + args: SourcesArgs & {palettes: Record}, +): SourceRequests => { + const {palettes, ...sourceArgs} = args; + const {shared} = sourceArgs; + const apiVersion = args.apiVersion || '1.5'; + + const config = mapChartsConfigToServerConfig(shared); + + shared.sharedData = config.sharedData; + shared.links = config.links; + + const visualization = config.visualization; + const datasetsIds = config.datasetsIds; + const extraSettings = config.extraSettings; + + const requests = prepareSourceRequests({ + apiVersion, + visualization, + datasetsIds, + extraSettings, + sourceArgs: sourceArgs, + links: config.links, + }); + + Object.assign(requests, getColorPalettesRequests({config, palettes})); + + log('SOURCE REQUESTS:'); + log(requests); + + return requests; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/helpers/__tests__/filters.test.ts b/src/server/modes/charts/plugins/datalens/url/helpers/__tests__/filters.test.ts new file mode 100644 index 0000000..49b75c8 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/helpers/__tests__/filters.test.ts @@ -0,0 +1,112 @@ +import type {PayloadFilter} from '../../../../../../../../shared'; +import {getMergedChartAndParamsFilters} from '../filters'; + +describe('getMergedChartAndParamsFilters', () => { + it('should return list of filter parameters and chart filters without duplicates', () => { + const paramsFilters: PayloadFilter[] = [ + { + column: 'dataset-field-guid-1', + values: ['params-value'], + operation: 'IN', + }, + { + column: 'dataset-field-guid-2', + values: ['params-value-2'], + operation: 'IN', + }, + ]; + const chartFilters: PayloadFilter[] = [ + { + column: 'dataset-field-guid-1', + values: ['chart-value'], + operation: 'EQ', + }, + { + column: 'dataset-field-guid-3', + values: ['chart-value-3'], + operation: 'GTE', + }, + ]; + + const expectedResult = [ + { + column: 'dataset-field-guid-3', + values: ['chart-value-3'], + operation: 'GTE', + }, + { + column: 'dataset-field-guid-1', + values: ['params-value'], + operation: 'IN', + }, + { + column: 'dataset-field-guid-2', + values: ['params-value-2'], + operation: 'IN', + }, + ]; + + const result = getMergedChartAndParamsFilters({paramsFilters, chartFilters}); + + expect(result).toEqual(expectedResult); + }); + + it('should remove all duplicate filters of the chart if there is the same guid in the filter parameters', () => { + const paramsFilters: PayloadFilter[] = [ + { + column: 'dataset-field-guid-1', + values: ['params-value'], + operation: 'IN', + }, + ]; + const chartFilters: PayloadFilter[] = [ + { + column: 'dataset-field-guid-1', + values: ['chart-value-1'], + operation: 'EQ', + }, + { + column: 'dataset-field-guid-1', + values: ['chart-value-1-next'], + operation: 'GTE', + }, + ]; + + const expectedResult = [ + { + column: 'dataset-field-guid-1', + values: ['params-value'], + operation: 'IN', + }, + ]; + + const result = getMergedChartAndParamsFilters({chartFilters, paramsFilters}); + + expect(result).toEqual(expectedResult); + }); + + it('should return chart filters if there are no parameter filters.', () => { + const paramsFilters: PayloadFilter[] = []; + const chartFilters: PayloadFilter[] = [ + { + column: 'dataset-field-guid-1', + values: ['chart-value'], + operation: 'EQ', + }, + { + column: 'dataset-field-guid-2', + values: ['chart-value-2'], + operation: 'IN', + }, + { + column: 'dataset-field-guid-3', + values: ['chart-value-3'], + operation: 'GTE', + }, + ]; + + const result = getMergedChartAndParamsFilters({chartFilters, paramsFilters}); + + expect(result).toEqual(chartFilters); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/url/helpers/__tests__/parameters.test.ts b/src/server/modes/charts/plugins/datalens/url/helpers/__tests__/parameters.test.ts new file mode 100644 index 0000000..8dcd861 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/helpers/__tests__/parameters.test.ts @@ -0,0 +1,28 @@ +import type {ServerField} from '../../../../../../../../shared'; +import {mapItemToPayloadParameter} from '../parameters'; + +describe('mapItemToPayloadParameter', () => { + it('integer parameter with a default value of 0', () => { + const integerParameter = { + guid: 'guid', + default_value: 0, + } as ServerField; + const result = mapItemToPayloadParameter(integerParameter); + expect(result).toEqual({ + id: 'guid', + value: 0, + }); + }); + + it('boolean parameter with a default value of false', () => { + const integerParameter = { + guid: 'guid', + default_value: false, + } as ServerField; + const result = mapItemToPayloadParameter(integerParameter); + expect(result).toEqual({ + id: 'guid', + value: false, + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/url/helpers/fields.ts b/src/server/modes/charts/plugins/datalens/url/helpers/fields.ts new file mode 100644 index 0000000..78172a1 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/helpers/fields.ts @@ -0,0 +1,75 @@ +import type {ApiV2BackgroundSettingsGuids, Link, ServerField} from '../../../../../../../shared'; +import {WizardVisualizationId} from '../../../../../../../shared'; +import type {ServerFieldWithBackgroundSettings} from '../../types'; +import {isTableFieldBackgroundSettingsEnabled} from '../../utils/misc-helpers'; + +export const prepareFieldsForPayload = ( + fields: ServerField[], + datasetId: string, + links?: Link[], +): string[] => { + return fields + .filter((field) => { + const datasetsIdMatching = field.datasetId === datasetId; + + // Checking if this field is in needed dataset + if (datasetsIdMatching) { + return true; + } + + // Checking if this field is linked to other datasets + const linkForField = links?.find(({fields: linkFields}) => { + return ( + Boolean(linkFields[datasetId]) && + Object.keys(linkFields).some((linkedDatasetId) => { + return linkFields[linkedDatasetId].field.guid === field.guid; + }) + ); + }); + + return linkForField; + }) + .map((field) => field.guid); +}; + +export const getBackgroundColorFieldsIds = ( + fields: ServerField[], + datasetId: string, + visualizationId: WizardVisualizationId, +): ApiV2BackgroundSettingsGuids[] => { + let preparedFields = fields.filter( + (field): field is ServerFieldWithBackgroundSettings => + field.datasetId === datasetId && isTableFieldBackgroundSettingsEnabled(field), + ); + if (visualizationId === WizardVisualizationId.FlatTable) { + preparedFields = preparedFields.filter( + (field) => field.backgroundSettings.settings.isContinuous, + ); + } + return preparedFields.map((field) => ({ + colorFieldGuid: field.backgroundSettings.colorFieldGuid, + targetFieldGuid: field.guid, + isContinuous: field.backgroundSettings.settings.isContinuous, + })); +}; + +export const prepareColumns = ({ + fields, + datasetId, + parameters, + backgroundColorsFieldsIds, +}: { + fields: ServerField[]; + parameters: ServerField[]; + backgroundColorsFieldsIds: ApiV2BackgroundSettingsGuids[]; + datasetId: string; +}): string[] => { + const rawColumns = [ + ...prepareFieldsForPayload(fields, datasetId), + // The parameters that lie in the main sections need to be compared with the fields for rendering + ...prepareFieldsForPayload(parameters, datasetId), + ...backgroundColorsFieldsIds.map(({colorFieldGuid}) => colorFieldGuid), + ]; + + return Array.from(new Set(rawColumns)); +}; diff --git a/src/server/modes/charts/plugins/datalens/url/helpers/filters.ts b/src/server/modes/charts/plugins/datalens/url/helpers/filters.ts new file mode 100644 index 0000000..8e1fb6c --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/helpers/filters.ts @@ -0,0 +1,27 @@ +import type {PayloadFilter} from '../../../../../../../shared'; + +type GetMergedChartAndParamsFiltersArgs = { + paramsFilters: PayloadFilter[]; + chartFilters: PayloadFilter[]; +}; + +export const getMergedChartAndParamsFilters = ( + args: GetMergedChartAndParamsFiltersArgs, +): PayloadFilter[] => { + const {chartFilters, paramsFilters} = args; + + const paramsFiltersMap = paramsFilters.reduce( + (acc, paramFilter) => { + acc[paramFilter.column] = true; + + return acc; + }, + {} as Record, + ); + + const filteredChartFilters = chartFilters.filter((chartFilter) => { + return !paramsFiltersMap[chartFilter.column]; + }); + + return [...filteredChartFilters, ...paramsFilters]; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/helpers/index.ts b/src/server/modes/charts/plugins/datalens/url/helpers/index.ts new file mode 100644 index 0000000..fa6e45a --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/helpers/index.ts @@ -0,0 +1,3 @@ +export * from './parameters'; +export * from './misc'; +export * from './fields'; diff --git a/src/server/modes/charts/plugins/datalens/url/helpers/misc.ts b/src/server/modes/charts/plugins/datalens/url/helpers/misc.ts new file mode 100644 index 0000000..b7fecbc --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/helpers/misc.ts @@ -0,0 +1,45 @@ +import type {StringParams} from '../../../../../../../shared'; +import {getParam} from '../../../../../../components/charts-engine/components/processor/paramsUtils'; + +export const isParamValid = (value: unknown) => { + if (typeof value === 'undefined' || value === null) { + return false; + } + + if (typeof value === 'number' && isNaN(value)) { + return false; + } + + if ( + Array.isArray(value) && + (value.length === 0 || value.some((x: string | number | null) => x === null)) + ) { + return false; + } + + if (Array.isArray(value) && value.length === 1 && value[0] === '') { + return false; + } + + return true; +}; + +export const isRawParamValid = (paramValue: unknown) => { + if (paramValue === null) { + return false; + } + + if (paramValue === '') { + return false; + } + + if (Array.isArray(paramValue) && paramValue.length === 1 && paramValue[0] === '') { + return false; + } + + return true; +}; + +export const getTreeState = (params: StringParams) => { + return ([] as string[]).concat(getParam('treeState', params)).filter(Boolean); +}; diff --git a/src/server/modes/charts/plugins/datalens/url/helpers/parameters.ts b/src/server/modes/charts/plugins/datalens/url/helpers/parameters.ts new file mode 100644 index 0000000..418f307 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/helpers/parameters.ts @@ -0,0 +1,98 @@ +import type { + DatasetField, + ParameterDefaultValue, + ServerDatasetField, + ServerField, +} from '../../../../../../../shared'; +import type {ApiV2Parameter} from '../../../../../../../shared/types/bi-api/v2'; +import type {PayloadParameter} from '../../types'; + +import {isParamValid, isRawParamValid} from './misc'; + +export const mapItemToPayloadParameter = ( + field: Partial & {guid: string}, +): PayloadParameter => { + return { + id: field.guid, + value: field.default_value ?? '', + }; +}; +export const prepareParameterForPayload = ( + fields: ServerField[], + datasetId: string, +): PayloadParameter[] => { + return fields + .filter((field: ServerField) => field.datasetId === datasetId) + .map(mapItemToPayloadParameter); +}; +export const getParametersMap = (parameters: PayloadParameter[]) => { + return parameters.reduce( + (acc, curr) => { + return {...acc, [curr.id]: true}; + }, + {} as Record, + ); +}; +export const mapParameterToRequestFormat = (parameter: PayloadParameter): ApiV2Parameter => { + return { + ref: { + type: 'id', + id: parameter.id, + }, + value: parameter.value, + }; +}; + +type PrepareParamsParameters = { + params: Record | undefined; + resultSchema: DatasetField[] | ServerDatasetField[]; +}; + +export const prepareParamsParameters = ({params, resultSchema}: PrepareParamsParameters) => { + if (!params) { + return []; + } + + const filteredParamsKeys = Object.keys(params).filter((key) => { + const param = params[key]; + const isRawParameterValid = isRawParamValid(param); + + if (!isRawParameterValid) { + return false; + } + + return isParamValid(param); + }); + + const preparedParamsParameters: Array = filteredParamsKeys.map( + (paramKey) => { + const param = String(params[paramKey]); + + const datasetField = resultSchema.find( + (item) => item.guid === paramKey || item.title === paramKey, + ); + + const guid = paramKey.length !== 36 && datasetField ? datasetField.guid : paramKey; + + return mapItemToPayloadParameter({guid, default_value: param}); + }, + ); + + return preparedParamsParameters.filter((p): p is PayloadParameter => Boolean(p)); +}; + +export const prepareParameters = ( + parameters: PayloadParameter[], + params: Record | undefined, + resultSchema: DatasetField[] | ServerDatasetField[], +) => { + const paramsParameters = prepareParamsParameters({params, resultSchema}); + + const paramsParametersMap = getParametersMap(paramsParameters); + + const filteredWizardParameters = parameters.filter( + (parameter) => !paramsParametersMap[parameter.id], + ); + + return [...filteredWizardParameters, ...paramsParameters]; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/helpers/table-settings.ts b/src/server/modes/charts/plugins/datalens/url/helpers/table-settings.ts new file mode 100644 index 0000000..e078c92 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/helpers/table-settings.ts @@ -0,0 +1,158 @@ +import _unionBy from 'lodash/unionBy'; + +import type { + ServerCommonSharedExtraSettings, + ServerField, + ServerVisualization, + StringParams, +} from '../../../../../../../shared'; +import {PlaceholderId, WizardVisualizationId} from '../../../../../../../shared'; +import { + getCurrentPage, + getSortParams, +} from '../../../../../../components/charts-engine/components/processor/paramsUtils'; +import type {BaseUrlPayload} from '../../types'; +import {SORT_ORDER} from '../../utils/constants'; +import {getSortData} from '../../utils/misc-helpers'; + +type GetOrderByItemForColumnSortClickArgs = { + params: StringParams; + isPivotTable: boolean; + allItemsIds: Record; + visualization: ServerVisualization; +}; +const getOrderByItemForColumnClickSort = ({ + params, + allItemsIds, + isPivotTable, + visualization, +}: GetOrderByItemForColumnSortClickArgs) => { + const {columnId, order} = getSortData(getSortParams(params), isPivotTable); + + // If the data for sorting and the column exist, then + // We process tabular sorting, it is in priority + const isSortColumnExists = columnId && order && allItemsIds[columnId]; + const sortItem = isSortColumnExists + ? { + direction: order, + column: columnId, + } + : undefined; + + if (isPivotTable && columnId && sortItem) { + const columnsPlaceholder = (visualization.placeholders || []).find( + (p) => p.id === PlaceholderId.PivotTableRows, + ); + const fields = columnsPlaceholder?.items || []; + const guids = fields.reduce( + (acc, field) => ({...acc, [field.guid]: true}), + {} as Record, + ); + + // sorting by dimensions only for the pivot table is handled here + // this can only be if sorted by field from the row section + // therefore, we do not add to order_by if the field is from another section + return guids[columnId] ? sortItem : undefined; + } + + return sortItem; +}; + +type GetUpdatedOrderByForColumnClickSortArgs = { + params: StringParams; + isPivotTable: boolean; + allItemsIds: Record; + orderBy: BaseUrlPayload['order_by']; + dimensionsFromCurrentDataset: BaseUrlPayload['order_by']; + visualization: ServerVisualization; +}; +const getUpdatedOrderByForColumnClickSort = ({ + params, + allItemsIds, + isPivotTable, + orderBy, + dimensionsFromCurrentDataset, + visualization, +}: GetUpdatedOrderByForColumnClickSortArgs) => { + const columnSort = getOrderByItemForColumnClickSort({ + params, + allItemsIds, + isPivotTable, + visualization, + }); + + let updatedOrderBy = orderBy ? [...orderBy] : []; + + if (columnSort) { + updatedOrderBy.unshift(columnSort); + } + + // Adding all dimensions to order_by, excluding those that are in sort + // CHARTS-3421#5fda2052b806202d36837f7f + updatedOrderBy = _unionBy( + updatedOrderBy || [], + dimensionsFromCurrentDataset, + ({column}) => column, + ); + + return updatedOrderBy; +}; + +export const getPayloadWithCommonTableSettings = ( + payload: BaseUrlPayload, + options: { + extraSettings: ServerCommonSharedExtraSettings | undefined; + fields: ServerField[]; + params: StringParams; + allItemsIds: Record; + datasetId: string; + visualization: ServerVisualization; + }, +): BaseUrlPayload => { + const {fields, extraSettings, params, allItemsIds, datasetId, visualization} = options; + + const visualizationId = visualization.id; + + const isPivotTable = visualizationId === WizardVisualizationId.PivotTable; + const isFlatTable = visualizationId === WizardVisualizationId.FlatTable; + + const dimensionsFromCurrentDataset = fields + .filter((item) => item.type === 'DIMENSION' && item.datasetId === datasetId) + .map(({guid}) => ({ + direction: SORT_ORDER.ASCENDING.STR, + column: guid, + })); + + const updatedPayload: BaseUrlPayload = { + ...payload, + }; + + const isPivotFallbackEnabled = extraSettings?.pivotFallback === 'on'; + const isBackendPivotTable = isPivotTable && !isPivotFallbackEnabled; + const isPaginationAvailable = isBackendPivotTable || isFlatTable; + const isPaginationEnabled = Boolean( + isPaginationAvailable && extraSettings?.pagination === 'on', + ); + + // This check for length is used to be sure that user uses dimensions in graph + // without dimensions pagination makes no sense because offset will always be 0 + // and backend will be failed to process it + if (dimensionsFromCurrentDataset.length && isPaginationEnabled && extraSettings?.limit) { + updatedPayload.limit = extraSettings?.limit; + const page = getCurrentPage(params); + updatedPayload.offset = (page - 1) * extraSettings.limit; + } + + if (isBackendPivotTable || isPaginationEnabled) { + updatedPayload.order_by = getUpdatedOrderByForColumnClickSort({ + params, + allItemsIds, + isPivotTable, + orderBy: payload.order_by, + dimensionsFromCurrentDataset, + visualization, + }); + } + + return updatedPayload; +}; diff --git a/src/server/modes/charts/plugins/datalens/url/types.ts b/src/server/modes/charts/plugins/datalens/url/types.ts new file mode 100644 index 0000000..dae666a --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/url/types.ts @@ -0,0 +1,51 @@ +import type { + ApiV2Request, + Link, + ServerChartsConfig, + ServerPlaceholder, + ServerVisualization, + Shared, + StringParams, +} from '../../../../../../shared'; +import type {MiddlewareUrl} from '../../types'; +import type {ApiVersion} from '../types'; + +export type SourcesArgs = { + params: StringParams; + shared: Shared; + apiVersion?: ApiVersion; +}; + +export type PrepareSourceRequestsArgs = { + apiVersion: ApiVersion; + datasetsIds: string[]; + visualization: ServerVisualization; + extraSettings: ServerChartsConfig['extraSettings']; + sourceArgs: SourcesArgs; + links?: Link[]; +}; + +export type PrepareSingleSourceRequestArgs = { + apiVersion: ApiVersion; + datasetsIds: string[]; + layerId?: string; + placeholders: ServerPlaceholder[]; + sourceArgs: SourcesArgs; + isPivotRequest: boolean; + links?: Link[]; +}; + +export type SourceRequests = Record; + +export type SourceRequest = Omit & { + sourceArgs?: SourcesArgs; + middlewareUrl?: MiddlewareUrl; + hideInInspector?: boolean; +}; + +export type PrepareSourceRequestBody = { + datasetId: string; + apiVersion: ApiVersion; + sourceArgs: SourcesArgs; + isPivotRequest: boolean; +}; diff --git a/src/server/modes/charts/plugins/datalens/utils/__tests__/color-helpers.test.ts b/src/server/modes/charts/plugins/datalens/utils/__tests__/color-helpers.test.ts new file mode 100644 index 0000000..074ba43 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/__tests__/color-helpers.test.ts @@ -0,0 +1,59 @@ +import type {ChartColorsConfig} from '../../types'; +import {mapAndColorizeHashTableByGradient} from '../color-helpers'; + +describe('mapAndColorizeHashTableByGradient', () => { + const colorConfig = { + gradientColors: ['#ffffff', '#888888', '#000000'], + gradientMode: '3-point', + } as ChartColorsConfig; + + it('hashTable with number values', () => { + const hashTable = { + polygon_0_0: 10, + polygon_1_0: 0, + polygon_2_0: 100, + polygon_3_0: 110, + }; + + let result = mapAndColorizeHashTableByGradient(hashTable, colorConfig); + result = JSON.parse(JSON.stringify(result).replace(/(\\n +)/g, '')); + + expect(result).toEqual({ + colorData: { + polygon_0_0: {backgroundColor: 'rgb(244, 244, 244)', color: '#FFF', value: 10}, + polygon_1_0: {backgroundColor: 'rgb(255, 255, 255)', color: '#FFF', value: 0}, + polygon_2_0: {backgroundColor: 'rgb(80, 80, 80)', color: '#FFF', value: 100}, + polygon_3_0: {backgroundColor: 'rgb(0, 0, 0)', color: '#FFF', value: 110}, + }, + min: 0, + mid: 55, + max: 110, + }); + }); + + it('hashTable with string values', () => { + const hashTable = {}; + + Object.assign(hashTable, { + polygon_0_0: '10.0', + polygon_1_0: '0', + polygon_2_0: '100.0', + polygon_3_0: '110.0', + }); + + let result = mapAndColorizeHashTableByGradient(hashTable, colorConfig); + result = JSON.parse(JSON.stringify(result).replace(/(\\n +)/g, '')); + + expect(result).toEqual({ + colorData: { + polygon_0_0: {backgroundColor: 'rgb(244, 244, 244)', color: '#FFF', value: 10}, + polygon_1_0: {backgroundColor: 'rgb(255, 255, 255)', color: '#FFF', value: 0}, + polygon_2_0: {backgroundColor: 'rgb(80, 80, 80)', color: '#FFF', value: 100}, + polygon_3_0: {backgroundColor: 'rgb(0, 0, 0)', color: '#FFF', value: 110}, + }, + min: 0, + mid: 55, + max: 110, + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/utils/__tests__/geo-helpers.test.ts b/src/server/modes/charts/plugins/datalens/utils/__tests__/geo-helpers.test.ts new file mode 100644 index 0000000..0b4e889 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/__tests__/geo-helpers.test.ts @@ -0,0 +1,55 @@ +import type {Coordinate} from '../geo-helpers'; +import {getMapBounds} from '../geo-helpers'; + +describe('getMapBounds', () => { + it('It should return 2 arrays for the leftmost lower and rightmost upper points on the map', () => { + const coordinates: Coordinate[] = [ + [11, 120], + [-10, 25], + [-30, 10], + [40, -50], + ]; + + const expectedBounds = [ + [-30, -50], + [40, 120], + ]; + + let leftBounds: Coordinate | undefined, rightBounds: Coordinate | undefined; + + coordinates.forEach((currentCoords) => { + [leftBounds, rightBounds] = getMapBounds({ + current: currentCoords, + rightTop: rightBounds, + leftBot: leftBounds, + }); + }); + + expect([leftBounds, rightBounds]).toEqual(expectedBounds); + }); + + it('If there is a coordinate at the south pole and its latitude value is less than -85, then -85 is returned, since this is the last coordinate for Yandex Maps', () => { + const coordinates: Coordinate[] = [ + [-89, 132], + [21, 30], + [40, 65], + ]; + + const expectedBounds = [ + [-85, 30], + [40, 132], + ]; + + let leftBounds: Coordinate | undefined, rightBounds: Coordinate | undefined; + + coordinates.forEach((currentCoords) => { + [leftBounds, rightBounds] = getMapBounds({ + current: currentCoords, + rightTop: rightBounds, + leftBot: leftBounds, + }); + }); + + expect([leftBounds, rightBounds]).toEqual(expectedBounds); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/utils/__tests__/get-gradient-stops.test.ts b/src/server/modes/charts/plugins/datalens/utils/__tests__/get-gradient-stops.test.ts new file mode 100644 index 0000000..c512281 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/__tests__/get-gradient-stops.test.ts @@ -0,0 +1,307 @@ +import {GradientType} from '../../../../../../../shared'; +import type {ChartColorsConfig} from '../../types'; +import {getHighchartsGradientStops} from '../get-gradient-stops'; + +describe('getGradientStops', () => { + const points = [1, 2, 3, 4, 5]; + + describe('three point gradient, manual thresholds', () => { + const colorsConfig = { + colors: [], + gradientColors: ['#ffffff', '#808080', '#000000'], + gradientMode: GradientType.THREE_POINT, + availablePalettes: {}, + loadedColorPalettes: {}, + thresholdsMode: 'manual', + } as ChartColorsConfig; + const testsArgs = [ + { + title: 'min === minColorValue && max === maxColorValue', + colorsConfig: { + leftThreshold: '1', + middleThreshold: '3', + rightThreshold: '5', + }, + result: [ + [0, 'rgb(255, 255, 255)'], + [0.5, 'rgb(128, 128, 128)'], + [1, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min === minColorValue && max < maxColorValue', + colorsConfig: { + leftThreshold: '1', + middleThreshold: '3', + rightThreshold: '4', + }, + result: [ + [0, 'rgb(255, 255, 255)'], + [0.5, 'rgb(128, 128, 128)'], + [0.75, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min > minColorValue && max === maxColorValue', + colorsConfig: { + leftThreshold: '2', + middleThreshold: '3', + rightThreshold: '5', + }, + result: [ + [0.25, 'rgb(255, 255, 255)'], + [0.5, 'rgb(128, 128, 128)'], + [1, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min > minColorValue && max < maxColorValue', + colorsConfig: { + leftThreshold: '2', + middleThreshold: '3', + rightThreshold: '4', + }, + result: [ + [0.25, 'rgb(255, 255, 255)'], + [0.5, 'rgb(128, 128, 128)'], + [0.75, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min < minColorValue && max === maxColorValue', + colorsConfig: { + leftThreshold: '0', + middleThreshold: '3', + rightThreshold: '5', + }, + result: [ + [-0.25, 'rgb(255, 255, 255)'], + [0.5, 'rgb(128, 128, 128)'], + [1, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min === minColorValue && max > maxColorValue', + colorsConfig: { + leftThreshold: '1', + middleThreshold: '3', + rightThreshold: '6', + }, + result: [ + [0, 'rgb(255, 255, 255)'], + [0.5, 'rgb(128, 128, 128)'], + [1.25, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min < minColorValue && max > maxColorValue', + colorsConfig: { + leftThreshold: '0', + middleThreshold: '3', + rightThreshold: '6', + }, + result: [ + [-0.25, 'rgb(255, 255, 255)'], + [0.5, 'rgb(128, 128, 128)'], + [1.25, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min > maxColorValue && max > maxColorValue', + colorsConfig: { + leftThreshold: '6', + middleThreshold: '7', + rightThreshold: '8', + }, + result: [ + [1.25, 'rgb(255, 255, 255)'], + [1.5, 'rgb(128, 128, 128)'], + [1.75, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min < maxColorValue && max < maxColorValue', + colorsConfig: { + leftThreshold: '-2', + middleThreshold: '-1', + rightThreshold: '0', + }, + result: [ + [-0.75, 'rgb(255, 255, 255)'], + [-0.5, 'rgb(128, 128, 128)'], + [-0.25, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min === minColorValue && min < 0', + points: [-1, 0, 1, 2, 3], + colorsConfig: { + leftThreshold: '-1', + middleThreshold: '1', + rightThreshold: '3', + }, + result: [ + [0, 'rgb(255, 255, 255)'], + [0.5, 'rgb(128, 128, 128)'], + [1, 'rgb(0, 0, 0)'], + ], + }, + ]; + + testsArgs.forEach((testArgs) => { + const values = testArgs?.points ?? points; + const minColorValue = Math.min(...values); + const maxColorValue = Math.max(...values); + it(testArgs.title, () => { + const result = getHighchartsGradientStops({ + colorsConfig: {...colorsConfig, ...testArgs.colorsConfig}, + points: values.map((value) => ({colorValue: value})), + minColorValue, + maxColorValue, + }); + + expect(result).toEqual(testArgs.result); + }); + }); + }); + + describe('two point gradient, manual thresholds', () => { + const colorsConfig = { + colors: [], + gradientColors: ['#ffffff', '#000000'], + gradientMode: GradientType.TWO_POINT, + availablePalettes: {}, + loadedColorPalettes: {}, + thresholdsMode: 'manual', + } as ChartColorsConfig; + const testsArgs = [ + { + title: 'min === minColorValue && max === maxColorValue', + colorsConfig: { + leftThreshold: '1', + rightThreshold: '5', + }, + result: [ + [0, 'rgb(255, 255, 255)'], + [1, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min === minColorValue && max < maxColorValue', + colorsConfig: { + leftThreshold: '1', + rightThreshold: '4', + }, + result: [ + [0, 'rgb(255, 255, 255)'], + [0.75, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min > minColorValue && max === maxColorValue', + colorsConfig: { + leftThreshold: '2', + rightThreshold: '5', + }, + result: [ + [0.25, 'rgb(255, 255, 255)'], + [1, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min > minColorValue && max < maxColorValue', + colorsConfig: { + leftThreshold: '2', + rightThreshold: '4', + }, + result: [ + [0.25, 'rgb(255, 255, 255)'], + [0.75, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min < minColorValue && max === maxColorValue', + colorsConfig: { + leftThreshold: '0', + rightThreshold: '5', + }, + result: [ + [-0.25, 'rgb(255, 255, 255)'], + [1, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min === minColorValue && max > maxColorValue', + colorsConfig: { + leftThreshold: '1', + rightThreshold: '6', + }, + result: [ + [0, 'rgb(255, 255, 255)'], + [1.25, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min < minColorValue && max > maxColorValue', + colorsConfig: { + leftThreshold: '0', + rightThreshold: '6', + }, + result: [ + [-0.25, 'rgb(255, 255, 255)'], + [1.25, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min > maxColorValue && max > maxColorValue', + colorsConfig: { + leftThreshold: '6', + rightThreshold: '8', + }, + result: [ + [1.25, 'rgb(255, 255, 255)'], + [1.75, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min < maxColorValue && max < maxColorValue', + colorsConfig: { + leftThreshold: '-2', + rightThreshold: '0', + }, + result: [ + [-0.75, 'rgb(255, 255, 255)'], + [-0.25, 'rgb(0, 0, 0)'], + ], + }, + { + title: 'min === minColorValue && min < 0', + points: [-1, 0, 1, 2, 3], + colorsConfig: { + leftThreshold: '-1', + rightThreshold: '3', + }, + result: [ + [0, 'rgb(255, 255, 255)'], + [1, 'rgb(0, 0, 0)'], + ], + }, + ]; + + testsArgs.forEach((testArgs) => { + const values = testArgs?.points ?? points; + const minColorValue = Math.min(...values); + const maxColorValue = Math.max(...values); + it(testArgs.title, () => { + const result = getHighchartsGradientStops({ + colorsConfig: {...colorsConfig, ...testArgs.colorsConfig}, + points: values.map((value) => ({colorValue: value})), + minColorValue, + maxColorValue, + }); + + expect(result).toEqual(testArgs.result); + }); + }); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/utils/__tests__/misc-helpers.test.ts b/src/server/modes/charts/plugins/datalens/utils/__tests__/misc-helpers.test.ts new file mode 100644 index 0000000..06b016c --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/__tests__/misc-helpers.test.ts @@ -0,0 +1,176 @@ +import type { + DATASET_FIELD_TYPES, + Field, + ServerPlaceholder, + Shared, +} from '../../../../../../../shared'; +import {getSortedColumnId, isNeedToCalcClosestPointManually} from '../misc-helpers'; + +type ChartType = Shared['visualization']['id']; + +const MOCKED_GUID = 'MOCKED_GUID'; +const ANOTHER_MOCKED_GUID = 'ANOTHER_MOCKED_GUID'; +const ANY_VISUALIZATION_ID = 'line' as ChartType; +const COLUMN_VISUALIZATION_ID = 'column' as ChartType; +const BAR_VISUALIZATION_ID = 'bar' as ChartType; + +const MOCKED_COLOR = { + guid: MOCKED_GUID, +} as Field; + +const MOCKED_COLOR_WITH_ANOTHER_GUID = {guid: ANOTHER_MOCKED_GUID} as Field; + +const MOCKED_X_COLUMN_ITEM = { + guid: MOCKED_GUID, +} as Field; + +const MOCKED_Y_BAR_ITEM = { + guid: MOCKED_GUID, +} as Field; + +const MOCKED_X_PLACEHOLDER = { + items: [MOCKED_X_COLUMN_ITEM], + id: 'x', +} as ServerPlaceholder; + +const MOCKED_Y_PLACEHOLDER = { + items: [MOCKED_Y_BAR_ITEM], + id: 'y', + settings: {}, +} as ServerPlaceholder; + +describe('isNeedToCalcClosestPointManually', () => { + it.each([ + { + visualizationId: COLUMN_VISUALIZATION_ID, + placeholder: MOCKED_X_PLACEHOLDER, + colorItem: MOCKED_COLOR, + dataType: 'date', + }, + { + visualizationId: COLUMN_VISUALIZATION_ID, + placeholder: MOCKED_X_PLACEHOLDER, + colorItem: MOCKED_COLOR, + dataType: 'datetime', + }, + ])( + `should return true if the chart type is "column", and fields in Section "X" and "Colors" are equal, and type "X" is date or datetime`, + ({visualizationId, placeholder, colorItem, dataType}) => { + MOCKED_X_COLUMN_ITEM.data_type = dataType as DATASET_FIELD_TYPES; + expect( + isNeedToCalcClosestPointManually(visualizationId, [placeholder], [colorItem]), + ).toEqual(true); + }, + ); + + it.each([ + { + visualizationId: BAR_VISUALIZATION_ID, + placeholder: MOCKED_Y_PLACEHOLDER, + colorItem: MOCKED_COLOR, + dataType: 'date', + }, + { + visualizationId: BAR_VISUALIZATION_ID, + placeholder: MOCKED_Y_PLACEHOLDER, + colorItem: MOCKED_COLOR, + dataType: 'datetime', + }, + ])( + 'Should return true if chart type is "bar", and fields in Section "Y" and "Colors" are equal, and type "Y" is date or datetime', + ({visualizationId, placeholder, colorItem, dataType}) => { + MOCKED_Y_BAR_ITEM.data_type = dataType as DATASET_FIELD_TYPES; + expect( + isNeedToCalcClosestPointManually(visualizationId, [placeholder], [colorItem]), + ).toEqual(true); + }, + ); + + it('should return false, if visualizationId is not column or bar', () => { + MOCKED_X_COLUMN_ITEM.data_type = 'date' as DATASET_FIELD_TYPES; + expect( + isNeedToCalcClosestPointManually( + ANY_VISUALIZATION_ID, + [MOCKED_X_PLACEHOLDER], + [MOCKED_COLOR], + ), + ).toEqual(false); + }); + + it('should return false if placeholders = undefined', () => { + MOCKED_X_COLUMN_ITEM.data_type = 'date' as DATASET_FIELD_TYPES; + expect( + isNeedToCalcClosestPointManually(COLUMN_VISUALIZATION_ID, undefined, [MOCKED_COLOR]), + ).toEqual(false); + }); + + it('should return false, if data_type is not "date" or "datetime"', () => { + MOCKED_X_COLUMN_ITEM.data_type = 'integer' as DATASET_FIELD_TYPES; + expect( + isNeedToCalcClosestPointManually( + COLUMN_VISUALIZATION_ID, + [MOCKED_X_PLACEHOLDER], + [MOCKED_COLOR], + ), + ).toEqual(false); + }); + + it('should return false, if Columns Sections do not have a Section "X"', () => { + MOCKED_X_COLUMN_ITEM.data_type = 'date' as DATASET_FIELD_TYPES; + expect( + isNeedToCalcClosestPointManually( + COLUMN_VISUALIZATION_ID, + [MOCKED_Y_PLACEHOLDER], + [MOCKED_COLOR], + ), + ).toEqual(false); + }); + + it('should return false, if Columns Sections do not have a Section "Y"', () => { + MOCKED_Y_BAR_ITEM.data_type = 'date' as DATASET_FIELD_TYPES; + expect( + isNeedToCalcClosestPointManually( + BAR_VISUALIZATION_ID, + [MOCKED_X_PLACEHOLDER], + [MOCKED_COLOR], + ), + ).toEqual(false); + }); + + it('should return false, if colors is empty array', () => { + MOCKED_X_COLUMN_ITEM.data_type = 'date' as DATASET_FIELD_TYPES; + expect( + isNeedToCalcClosestPointManually(COLUMN_VISUALIZATION_ID, [MOCKED_X_PLACEHOLDER], []), + ).toEqual(false); + }); + + it('should return true, if Color and Field guid are not equal', () => { + MOCKED_X_COLUMN_ITEM.data_type = 'date' as DATASET_FIELD_TYPES; + expect( + isNeedToCalcClosestPointManually( + COLUMN_VISUALIZATION_ID, + [MOCKED_X_PLACEHOLDER], + [MOCKED_COLOR_WITH_ANOTHER_GUID], + ), + ).toEqual(true); + }); +}); + +describe('getSortedColumnId', () => { + const expectedFieldId = '5b58a23b-68a2-4979-bfb3-9ee13f16d24d'; + const mockedPivotTableSortColumnId = + '0_0_1_id=fieldId=5b58a23b-68a2-4979-bfb3-9ee13f16d24d__index=0_name=Average rating'; + const mockedSortColumnId = '0_0_1_id=5b58a23b-68a2-4979-bfb3-9ee13f16d24d_name=Average rating'; + + it('Should parse sorted column id from flat table columnId', () => { + const fieldId = getSortedColumnId(mockedSortColumnId); + + expect(fieldId).toEqual(expectedFieldId); + }); + + it('Should parse sorted column id from pivot table columnId', () => { + const fieldId = getSortedColumnId(mockedPivotTableSortColumnId, true); + + expect(fieldId).toEqual(expectedFieldId); + }); +}); diff --git a/src/server/modes/charts/plugins/datalens/utils/axis-helpers.ts b/src/server/modes/charts/plugins/datalens/utils/axis-helpers.ts new file mode 100644 index 0000000..910c83c --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/axis-helpers.ts @@ -0,0 +1,147 @@ +import type {Highcharts} from '@gravity-ui/chartkit/highcharts'; + +import type { + ServerField, + ServerPlaceholder, + ServerPlaceholderSettings, +} from '../../../../../../shared'; +import {AxisAutoScaleModes, isDateField} from '../../../../../../shared'; +import type {AxisOptions} from '../types'; + +import {getOriginalTitleOrTitle} from './misc-helpers'; + +export interface IgnoreProps { + title: boolean; +} + +export function getAxisTitle(placeholderSettings: ServerPlaceholderSettings, field?: ServerField) { + switch (placeholderSettings.title) { + case 'auto': { + if (field) { + return getOriginalTitleOrTitle(field); + } + + return undefined; + } + case 'manual': { + return placeholderSettings.titleValue; + } + case 'off': { + return null; + } + default: { + return undefined; + } + } +} + +export function isGridEnabled(placeholderSettings?: ServerPlaceholderSettings) { + return placeholderSettings?.grid !== 'off'; +} + +export function getTickPixelInterval(placeholderSettings: ServerPlaceholderSettings) { + if (placeholderSettings.grid === 'on' && placeholderSettings.gridStep === 'manual') { + return placeholderSettings.gridStepValue; + } + + return undefined; +} + +// eslint-disable-next-line complexity +export const applyPlaceholderSettingsToAxis = ( + placeholder: ServerPlaceholder | undefined, + axis: Highcharts.AxisOptions | AxisOptions, + ignore: IgnoreProps, +) => { + if (placeholder && placeholder.settings) { + // Setting 0-max for y1 + // min-max is already there by default! + // Including support for the old autoscale flag === false + if ( + placeholder.settings.autoscale === false || + (placeholder.settings.scale === 'auto' && + placeholder.settings.scaleValue === AxisAutoScaleModes.ZeroMax) + ) { + if (placeholder.settings.type === 'logarithmic') { + // Fallback to null for incorrect case + axis.min = null; + } else { + axis.min = 0; + } + } else if (placeholder.settings.scale === 'manual') { + axis.endOnTick = false; + + const minValue = Number((placeholder.settings.scaleValue as [string, string])[0]); + const maxValue = Number((placeholder.settings.scaleValue as [string, string])[1]); + + if (placeholder.settings.type !== 'logarithmic' || 0 < minValue) { + axis.min = minValue; + } + + if (placeholder.settings.type !== 'logarithmic' || 0 < maxValue) { + axis.max = maxValue; + } + } + + const axisTitle = getAxisTitle(placeholder.settings, placeholder.items[0]); + if (!ignore.title && typeof axisTitle !== 'undefined') { + axis.title = {text: axisTitle}; + } + + if (isDateField(placeholder.items[0])) { + axis.type = 'datetime'; + } else { + axis.type = placeholder.settings.type === 'logarithmic' ? 'logarithmic' : 'linear'; + } + + if (!isGridEnabled(placeholder.settings)) { + axis.gridLineWidth = 0; + axis.minorGridLineWidth = 0; + } + + if (placeholder.settings?.axisVisibility === 'hide') { + axis.visible = false; + } + + const tickPixelInterval = getTickPixelInterval(placeholder.settings); + if (tickPixelInterval) { + axis.tickPixelInterval = tickPixelInterval; + } + + // We put the logarithmic type for the y axis + // The linear type is there and so by default + if (placeholder.settings.hideLabels === 'yes') { + axis.labels = { + ...(axis.labels || {}), + enabled: false, + }; + } else if (placeholder.settings.hideLabels === 'no') { + axis.labels = { + ...(axis.labels || {}), + enabled: true, + }; + } + + // We put the logarithmic type for the y axis + // The linear type is there and so by default + if (placeholder.settings.labelsView === 'horizontal') { + axis.labels = { + ...(axis.labels || {}), + rotation: 0, + }; + } else if (placeholder.settings.labelsView === 'vertical') { + axis.labels = { + ...(axis.labels || {}), + rotation: 90, + x: -3, + }; + } else if (placeholder.settings.labelsView === 'angle') { + axis.labels = { + ...(axis.labels || {}), + rotation: 45, + y: 20, + x: -3, + }; + } + } +}; diff --git a/src/server/modes/charts/plugins/datalens/utils/color-helpers.ts b/src/server/modes/charts/plugins/datalens/utils/color-helpers.ts new file mode 100644 index 0000000..858f388 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/color-helpers.ts @@ -0,0 +1,568 @@ +import type {Highcharts} from '@gravity-ui/chartkit/highcharts'; +import isNumber from 'lodash/isNumber'; + +import type { + ExtendedSeriesLineOptions, + RGBColor, + RGBGradient, + ServerField, + TableCellsRow, + WrappedHTML, + WrappedMarkup, +} from '../../../../../../shared'; +import { + GradientType, + getRangeDelta, + getRgbColorValue, + transformHexToRgb, +} from '../../../../../../shared'; +import type {WrappedMarkdown} from '../../../../../../shared/utils/markdown'; +import {getColorsSettings} from '../../helpers/color-palettes'; +import {interpolateRgbBasis} from '../preparers/helpers/colors'; +import type {ChartColorsConfig} from '../types'; + +import {getColor, getMountedColor} from './constants'; + +type HashTable = Record & {colorGuid?: string}; + +export type ColorValue = number | null; + +export type ExtendedPointOptionsObject = Omit< + Highcharts.PointOptionsObject, + 'colorValue' | 'name' +> & { + colorValue?: string | null; + colorGuid?: string; + shapeValue?: string | null; + shapeLabel?: string | WrappedMarkdown | WrappedMarkup | WrappedHTML; + name?: string | WrappedMarkdown | WrappedMarkup | WrappedHTML; +}; + +export type ExtendedSeriesScatterOptions = Omit< + Highcharts.SeriesScatterOptions, + 'data' | 'type' | 'name' +> & { + title?: string; + data?: ExtendedPointOptionsObject[]; + name?: string | WrappedMarkdown | WrappedHTML; +}; + +interface GradientThresholdValues { + min: number; + mid: number; + max: number; + range: number; + rangeMiddle: number; +} + +function hexToRgb(hex: string) { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + + return result + ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + } + : {}; +} + +type GetGradientMapArgs = { + values: ColorValue[]; + colorsConfig: ChartColorsConfig; + gradientThresholdValues: GradientThresholdValues; + sameRangeDelta?: number | null; +}; + +export function getColorsByMeasureField(options: GetGradientMapArgs) { + const {values, colorsConfig, gradientThresholdValues, sameRangeDelta = 0.5} = options; + const {min, rangeMiddle, range} = gradientThresholdValues; + const rangeMiddleRatio = rangeMiddle / range; + const currentGradient = getCurrentGradient(colorsConfig); + const colors: RGBColor[] = getRgbColors(currentGradient.colors, Boolean(colorsConfig.reversed)); + + let deltas; + + if (range === 0) { + deltas = values.map(() => { + return sameRangeDelta; + }); + } else { + deltas = values.map((colorValue) => getRangeDelta(colorValue, min, range)); + } + + const rgbColors: Record = {}; + deltas.forEach((delta, i) => { + if (delta !== null) { + const key = String(values[i]); + rgbColors[key] = getRgbColorValue( + delta, + colorsConfig.gradientMode || '', + rangeMiddleRatio, + colors, + ); + } + }); + + return rgbColors; +} + +function mapAndColorizeTableCells(rows: TableCellsRow[], colorsConfig: ChartColorsConfig) { + const colorValues: ColorValue[][] = rows.map((row) => { + return row.cells.map((cell) => { + if (typeof cell === 'object' && cell && 'color' in cell) { + return typeof cell.color !== 'number' || Number.isNaN(cell.color) + ? null + : cell.color; + } + return null; + }); + }); + const maxColorValues: ColorValue[] = colorValues.map((row) => Math.max(...row.map(Number))); + + const gradientThresholdValues = getThresholdValues(colorsConfig, maxColorValues); + const gradientColors = getColorsByMeasureField({ + values: colorValues.flat(2), + colorsConfig, + gradientThresholdValues, + }); + + rows.forEach((row) => { + row.cells.forEach((cell) => { + if (typeof cell === 'object') { + const colorValue = + typeof cell.color !== 'number' || Number.isNaN(cell.color) ? null : cell.color; + const backgroundColor = + colorValue === null ? undefined : gradientColors[colorValue]; + + if (backgroundColor && !cell.css) { + cell.css = { + backgroundColor, + color: '#FFF', + }; + } + } + }); + }); +} + +function colorizePivotTableCell( + colorValue: number | null, + colorsConfig: ChartColorsConfig, + colorValuesForMaxMin: number[], +) { + const gradientColors = getColorsByMeasureField({ + values: [colorValue], + colorsConfig, + gradientThresholdValues: getThresholdValues(colorsConfig, colorValuesForMaxMin), + }); + const backgroundColor = gradientColors[String(colorValue)]; + if (backgroundColor) { + return { + backgroundColor, + color: '#FFF', + value: colorValue, + }; + } + + return undefined; +} + +function mapAndColorizeHashTableByGradient(hashTable: HashTable, colorsConfig: ChartColorsConfig) { + const colorValues = Object.values(hashTable).map((colorValue) => { + if (colorValue === null) { + return null; + } + const value = Number(colorValue); + return isNaN(value) ? null : value; + }); + + const colorValuesForMaxMin = colorValues.filter((value): value is number => value !== null); + + const gradientThresholdValues = getThresholdValues(colorsConfig, colorValuesForMaxMin); + const {min, rangeMiddle, max} = gradientThresholdValues; + const gradientColors = getColorsByMeasureField({ + values: colorValues, + colorsConfig, + gradientThresholdValues, + }); + + const acc: Record = {}; + const colorData = Object.entries(hashTable).reduce((acc, [key, value]) => { + const colorValue = Number(value); + const backgroundColor = gradientColors[colorValue]; + + if (backgroundColor) { + acc[key] = { + backgroundColor, + color: '#FFF', + value: Number(colorValue), + }; + } + + return acc; + }, acc); + + return {colorData, min, mid: min + rangeMiddle, max}; +} + +function mapAndColorizeHashTableByPalette({ + hashTable, + colors, + mountedColors, +}: { + hashTable: HashTable; + colors: string[]; + mountedColors: Record; +}) { + const knownValues: number[] = []; + const result: Record = {}; + + Object.keys(hashTable).forEach((key) => { + const value = hashTable[key]; + let colorIndex = knownValues.indexOf(value); + let color; + + if (colorIndex === -1) { + knownValues.push(value); + colorIndex = knownValues.length - 1; + } + + if (mountedColors && mountedColors[value]) { + color = getMountedColor({mountedColors, colors, value}); + } else { + color = getColor(colorIndex, colors); + } + + result[key] = {backgroundColor: color}; + }); + + return result; +} + +function mapAndColorizePointsByGradient( + points: Highcharts.PointOptionsObject[], + colorsConfig: ChartColorsConfig, +) { + const colorValues: ColorValue[] = points.map((point) => { + if (typeof point.colorValue === 'number') { + return point.colorValue; + } + return null; + }); + + const colorValuesForMaxMin = colorValues.filter((value): value is number => value !== null); + const gradientThresholdValues = getThresholdValues(colorsConfig, colorValuesForMaxMin); + + if (gradientThresholdValues.range !== 0) { + const gradientColors = getColorsByMeasureField({ + values: colorValues, + colorsConfig, + gradientThresholdValues, + }); + + points.forEach((point) => { + const colorValue = point.colorValue; + + if (typeof colorValue === 'number' && gradientColors[colorValue]) { + point.color = gradientColors[colorValue]; + } + }); + } +} + +function mapAndColorizeGraphsByGradient( + graphs: ExtendedSeriesLineOptions[], + colorsConfig: ChartColorsConfig, +) { + const colorValues = getColorValuesAmongSeries(graphs); + + const gradientThresholdValues = getThresholdValues(colorsConfig, colorValues); + const gradientColors = getColorsByMeasureField({ + values: colorValues, + colorsConfig, + gradientThresholdValues, + }); + + if (gradientThresholdValues.range !== 0) { + graphs.forEach((graph) => { + const points = graph.data as Highcharts.PointOptionsObject[]; + + points.forEach((point) => { + const pointColorValue = point.colorValue; + + if (typeof pointColorValue === 'number' && gradientColors[pointColorValue]) { + point.color = gradientColors[pointColorValue]; + } + }); + }); + } +} + +export function mapAndColorizePointsByPalette({ + points, + colorsConfig, + colorField, + defaultColorPaletteId, +}: { + points: ExtendedPointOptionsObject[]; + colorsConfig: ChartColorsConfig; + colorField: ServerField; + defaultColorPaletteId: string; +}): ExtendedSeriesScatterOptions[] { + const series: ExtendedSeriesScatterOptions[] = []; + const knownValues: (string | null | undefined)[] = []; + + const {mountedColors, colors} = getColorsSettings({ + field: colorField, + colorsConfig: colorsConfig, + defaultColorPaletteId, + availablePalettes: colorsConfig.availablePalettes, + customColorPalettes: colorsConfig.loadedColorPalettes, + }); + + points.forEach((point) => { + const value = point.colorValue; + let colorIndex = knownValues.indexOf(value); + if (colorIndex === -1) { + knownValues.push(value); + colorIndex = knownValues.length - 1; + let color; + + if (point.colorValue && mountedColors[point.colorValue]) { + color = getMountedColor({mountedColors, colors, value: point.colorValue}); + } else { + color = getColor(colorIndex, colors); + } + + series[colorIndex] = { + data: [point], + color, + name: value || '', + }; + } else { + const data = series[colorIndex].data; + if (data) { + data.push(point); + } + } + }); + + return series; +} + +type MapAndColorizeGraphsByPalette = { + graphs: ExtendedSeriesLineOptions[]; + colorsConfig: ChartColorsConfig; + isShapesItemExists?: boolean; + isColorsItemExists?: boolean; + isSegmentsExists?: boolean; + usedColors?: (string | undefined)[]; + colorField: ServerField | undefined; + defaultColorPaletteId: string; +}; + +function mapAndColorizeGraphsByPalette({ + graphs, + colorsConfig, + isColorsItemExists, + isShapesItemExists, + isSegmentsExists, + usedColors = [], + colorField, + defaultColorPaletteId, +}: MapAndColorizeGraphsByPalette) { + const {mountedColors, colors} = getColorsSettings({ + field: colorField, + colorsConfig, + defaultColorPaletteId, + availablePalettes: colorsConfig.availablePalettes, + customColorPalettes: colorsConfig.loadedColorPalettes, + }); + + // eslint-disable-next-line complexity + graphs.forEach((graph, i) => { + let colorKey; + const colorValue = graph.colorValue; + const shapeValue = graph.shapeValue; + const colorTitle = graph.colorKey || graph.legendTitle || graph.name; + if ((colorValue && colorValue === colorTitle) || (colorValue && shapeValue)) { + // ../technotes.md -> utils/colors-helpers p3 + colorKey = colorValue; + } else if (isColorsItemExists) { + // ../technotes.md -> utils/colors-helpers p2 + colorKey = (colorTitle || '').split(':')[0]; + } else { + colorKey = colorTitle; + } + + if (colorKey && mountedColors[colorKey]) { + graph.color = getMountedColor({mountedColors, colors, value: colorKey}); + } else { + let value = graph.colorValue; + + if (isColorsItemExists && !isShapesItemExists && graph.legendTitle) { + value = graph.legendTitle; + } + + let colorIndex; + if (isShapesItemExists && !isColorsItemExists) { + colorIndex = usedColors.indexOf(value); + } else { + // we use the index from forEach in the case of coloring the second y axis + colorIndex = graph.yAxis === 0 || isSegmentsExists ? usedColors.indexOf(value) : i; + } + + if (colorIndex === -1) { + usedColors.push(value); + colorIndex = usedColors.length - 1; + } + + graph.color = getColor(colorIndex, colors); + } + }); + + return graphs; +} + +function getCurrentGradient(colorsConfig: ChartColorsConfig): RGBGradient { + return { + id: colorsConfig.gradientPalette!, + colors: colorsConfig.gradientColors.map(transformHexToRgb), + }; +} + +function getThresholdValues( + colorsConfig: ChartColorsConfig, + colorValues: ColorValue[], +): GradientThresholdValues { + const list = colorValues.filter((d) => d !== null).map(Number); + const max = + colorsConfig.thresholdsMode === 'manual' && + typeof colorsConfig.rightThreshold !== 'undefined' + ? Number(colorsConfig.rightThreshold) + : Math.max(...list); + + const min = + colorsConfig.thresholdsMode === 'manual' && + typeof colorsConfig.leftThreshold !== 'undefined' + ? Number(colorsConfig.leftThreshold) + : Math.min(...list); + + const range = max - min; + + const mid = + colorsConfig.thresholdsMode === 'manual' && + colorsConfig.gradientMode === GradientType.THREE_POINT && + typeof colorsConfig.middleThreshold !== 'undefined' + ? Number(colorsConfig.middleThreshold) + : max - range / 2; + + const rangeMiddle = mid === range / 2 ? mid : mid - min; + + return {min, range, rangeMiddle, max, mid}; +} + +function getRgbColors(gradientColors: RGBColor[] | undefined, isReversed: boolean) { + if (!gradientColors) { + return []; + } + // Avoiding using the object by reference + const colors: RGBColor[] = [...gradientColors]; + if (isReversed) { + colors.reverse(); + } + return colors; +} + +function getColorValuesAmongSeries(graphs: ExtendedSeriesLineOptions[]) { + return graphs.reduce((acc, graph) => { + const colorValues = (graph.data as Highcharts.PointOptionsObject[]) + .filter((point) => typeof point.colorValue === 'number') + .map((point) => point.colorValue as ColorValue); + return [...acc, ...colorValues]; + }, [] as ColorValue[]); +} + +const MAX_COLOR_DELTA_VALUE = 1; + +function getColorFn(colors: RGBColor[]) { + if (colors.length > 2) { + const firstColors = interpolateRgbBasis(colors.slice(0, 2)); + const lastColors = interpolateRgbBasis(colors.slice(1)); + + return (colorValue: number) => + colorValue >= 0.5 ? lastColors((colorValue - 0.5) * 2) : firstColors(colorValue * 2); + } + + return interpolateRgbBasis(colors); +} + +export function colorizeByColorValues({ + colorValues, + colorsConfig, +}: { + colorValues: ColorValue[]; + colorsConfig: ChartColorsConfig; +}) { + const {min, mid, max} = getThresholdValues(colorsConfig, colorValues.filter(isNumber)); + const currentGradient = getCurrentGradient(colorsConfig); + const colors: RGBColor[] = getRgbColors(currentGradient.colors, Boolean(colorsConfig.reversed)); + const getRgbColor = getColorFn(colors); + + let deltas: (number | null)[]; + + if (min === max) { + // If all values are the same, then we paint in the maximum color. + deltas = colorValues.map((colorValue) => { + if (colorValue === null) { + return null; + } + + return MAX_COLOR_DELTA_VALUE; + }); + } else { + deltas = colorValues.map((colorValue) => { + if (colorValue === null) { + return null; + } + + if (colorValue <= min) { + return 0; + } + + if (colorValue >= max) { + return 1; + } + + return colorValue >= mid + ? getRangeDelta(colorValue, 2 * mid - max, 2 * (max - mid)) + : getRangeDelta(colorValue, min, 2 * (mid - min)); + }); + } + + return deltas.map((delta) => { + if (delta === null) { + return null; + } + + const {red, green, blue} = getRgbColor(delta); + return `rgb(${red}, ${green}, ${blue})`; + }); +} + +export { + hexToRgb, + mapAndColorizeTableCells, + mapAndColorizeHashTableByPalette, + mapAndColorizeHashTableByGradient, + mapAndColorizePointsByGradient, + mapAndColorizeGraphsByPalette, + mapAndColorizeGraphsByGradient, + getCurrentGradient, + getRgbColors, + colorizePivotTableCell, + getRangeDelta, + getRgbColorValue, + getThresholdValues, +}; diff --git a/src/server/modes/charts/plugins/datalens/utils/config-helpers.ts b/src/server/modes/charts/plugins/datalens/utils/config-helpers.ts new file mode 100644 index 0000000..bbc4308 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/config-helpers.ts @@ -0,0 +1,51 @@ +import cloneDeepWith from 'lodash/cloneDeepWith'; + +import type { + DATASET_FIELD_TYPES, + ExtendedChartsConfig, + ServerChartsConfig, + ServerField, +} from '../../../../../../shared'; +import {mapChartsConfigToLatestVersion} from '../../../../../../shared'; + +export type MapChartsConfigToServerConfigArgs = ExtendedChartsConfig & { + sharedData: ServerChartsConfig['sharedData']; +}; + +export const mapChartsConfigToServerConfig = ( + config: MapChartsConfigToServerConfigArgs, +): ServerChartsConfig => { + const latestConfig = mapChartsConfigToLatestVersion(config, { + sharedData: config.sharedData || {}, + }); + + const serverConfig: ServerChartsConfig = { + ...latestConfig, + sharedData: config.sharedData || {}, + // Fallback for old charts (where there are no links) + links: latestConfig.links || [], + }; + + return serverConfig; +}; + +function isField(value: unknown): value is ServerField { + return Boolean(value && typeof value === 'object' && 'guid' in value); +} + +export function getConfigWithActualFieldTypes(args: { + config: ServerChartsConfig; + idToDataType: Record; +}) { + const {config, idToDataType} = args; + + return cloneDeepWith(config, function (value: unknown) { + if (isField(value)) { + const dataType = idToDataType[value.guid] ?? value.data_type; + const field: ServerField = {...value, data_type: dataType}; + return field; + } + + return undefined; + }); +} diff --git a/src/server/modes/charts/plugins/datalens/utils/constants.ts b/src/server/modes/charts/plugins/datalens/utils/constants.ts new file mode 100644 index 0000000..a56c2b9 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/constants.ts @@ -0,0 +1,61 @@ +import {isTrueArg} from '../../../../../../shared'; + +export const LOG_TIMING = 'process' in globalThis && isTrueArg(process.env.SHOW_CHARTS_LOG_TIMING); +export const LOG_INFO = 'process' in globalThis && isTrueArg(process.env.SHOW_CHARTS_LOG); + +export const LAT = 0; +export const LONG = 1; + +export const DEFAULT_DATE_FORMAT = 'DD.MM.YYYY'; +export const DEFAULT_DATETIME_FORMAT = 'DD.MM.YYYY HH:mm:ss'; +export const DEFAULT_DATETIMETZ_FORMAT = 'DD.MM.YYYY HH:mm:ss Z'; + +export const SERVER_DATE_FORMAT = 'YYYY-MM-DD'; +export const SERVER_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss'; + +export const DATASET_DATA_URL_V1 = '/_bi/api/data/v1.5/datasets/{id}/result'; +export const DATASET_DATA_URL_V2 = '/_bi_datasets/{id}/result'; +export const DATASET_DATA_PIVOT_URL = '/_bi_datasets/{id}/pivot'; +export const getColor = (colorIndex: number, colors: string[]) => { + const index = colorIndex % colors.length; + return colors[index]; +}; + +export const getMountedColor = ({ + mountedColors = {}, + colors, + value, +}: { + mountedColors?: Record; + colors: string[]; + value: string | number; +}) => { + const color = mountedColors[value]; + + return isNaN(Number(color)) ? color : getColor(Number(color), colors); +}; + +export const SORT_ORDER = { + ASCENDING: { + NUM: 1, + STR: 'ASC', + }, + DESCENDING: { + NUM: -1, + STR: 'DESC', + }, +}; + +export const DEFAULT_MIN_POINT_RADIUS = 2; +export const DEFAULT_MAX_POINT_RADIUS = 8; + +// Heatmap does not support zIndex. Always located at the bottom; +export const GEO_MAP_LAYERS_LEVEL = { + GEOPOINT: 3, + POLYLINE: 2, + POLYGON: 1, +}; + +export const PSEUDO = 'PSEUDO'; + +export const COLOR_SHAPE_SEPARATOR = '__COLOR_SHAPE_SEPARATOR__'; diff --git a/src/server/modes/charts/plugins/datalens/utils/export-helpers.ts b/src/server/modes/charts/plugins/datalens/utils/export-helpers.ts new file mode 100644 index 0000000..dc396f7 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/export-helpers.ts @@ -0,0 +1,65 @@ +import type { + ColumnExportSettings, + ExportingColumnOptions, + ServerField, +} from '../../../../../../shared'; +import { + getFakeTitleOrTitle, + getFormatOptions, + isDateField, + isNumberField, +} from '../../../../../../shared'; + +import {getDefaultDateFormat} from './misc-helpers'; + +export function getFieldsExportingOptions(fields: Record) { + const result: Record = {}; + + return Object.keys(fields).reduce((res, key) => { + res[key] = getFieldExportingOptions(fields[key]); + return res; + }, result); +} + +export function getFieldExportingOptions(field?: ServerField): ExportingColumnOptions { + let format = field?.format; + + if (isDateField(field) && !format) { + format = getDefaultDateFormat(field?.data_type); + } + + return { + title: field ? getFakeTitleOrTitle(field) : undefined, + dataType: field?.data_type, + format, + }; +} + +export function getExportColumnSettings(args: { + path: string; + field?: ServerField; + id?: string; +}): ColumnExportSettings { + const {id, path, field} = args; + + let columnType: ColumnExportSettings['type'] = 'text'; + let formatter: ColumnExportSettings['formatter'] = {}; + let format: ColumnExportSettings['format']; + + if (isNumberField(field)) { + columnType = 'number'; + formatter = getFormatOptions(field); + } else if (isDateField(field)) { + columnType = field.data_type; + format = field.format ?? getDefaultDateFormat(field.data_type); + } + + return { + id: id ?? path, + name: getFakeTitleOrTitle(field), + formatter, + format, + field: path, + type: columnType, + }; +} diff --git a/src/server/modes/charts/plugins/datalens/utils/geo-helpers.ts b/src/server/modes/charts/plugins/datalens/utils/geo-helpers.ts new file mode 100644 index 0000000..55387ce --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/geo-helpers.ts @@ -0,0 +1,264 @@ +import { + MapCenterMode, + ZoomMode, + getSortedData, + mapStringToCoordinates, +} from '../../../../../../shared'; +import type {RGBGradient, ServerChartsConfig, ServerField} from '../../../../../../shared'; +import {getColorsSettings} from '../../helpers/color-palettes'; +import type {ChartColorsConfig} from '../types'; + +import {getCurrentGradient, getRgbColors, mapAndColorizeHashTableByGradient} from './color-helpers'; +import {LAT, LONG, getColor, getMountedColor} from './constants'; + +export type Coordinate = [number, number]; + +type GeoData = Record; + +interface GradientMapOptions { + mode: 'gradient'; + colorTitle: string; + gradientData: RGBGradient; + colorMinValue: number; + colorMaxValue: number; + colorMidValue?: number; +} + +function getMapBounds(points: { + current: Coordinate; + leftBot?: Coordinate; + rightTop?: Coordinate; +}): [Coordinate, Coordinate] { + const {current} = points; + const currentLat = (current[LAT] = current[LAT] < -90 ? current[LAT] + 180 : current[LAT]); + + // -168 - Bering Strait + const currentLong = (current[LONG] = + current[LONG] < -168 ? current[LONG] + 360 : current[LONG]); + + let leftBot: Coordinate, rightTop: Coordinate; + + if (points.leftBot) { + leftBot = [...points.leftBot]; + } else { + leftBot = [currentLat, currentLong]; + } + + if (points.rightTop) { + rightTop = [...points.rightTop]; + } else { + rightTop = [currentLat, currentLong]; + } + + if (currentLat < leftBot[LAT]) { + leftBot[LAT] = currentLat; + } + + if (currentLong < leftBot[LONG]) { + leftBot[LONG] = currentLong; + } + + if (currentLat > rightTop[LAT]) { + rightTop[LAT] = currentLat; + } + + if (currentLong > rightTop[LONG]) { + rightTop[LONG] = currentLong; + } + + // -85 is the maximum latitude value to which Yandex maps draw, then the south pole is displayed as a gray zone. + if (leftBot[LAT] < -85) { + leftBot[LAT] = -85; + } + + return [leftBot, rightTop]; +} + +function getExtremeValues(params: {min?: number; max?: number; value: number}) { + const {value} = params; + let min, max; + + if (params.min) { + min = params.min; + } else { + min = value; + } + + if (params.max) { + max = params.max; + } else { + max = value; + } + + if (value < min) { + min = value; + } + + if (value > max) { + max = value; + } + + return [min, max]; +} + +function getFlattenCoordinates(coordinates: unknown[][]) { + return coordinates.reduce((acc, val) => acc.concat(val), []); +} + +export type GradientOptions = { + min: number; + mid: number; + max: number; +}; + +export type ColorizedResult = GradientOptions & { + colorData: Record< + string, + { + backgroundColor: string; + color: string; + value: number | null; + } + >; +}; + +function colorizeGeoByGradient(data: GeoData, colorsConfig: ChartColorsConfig): ColorizedResult { + const preparedData = Object.entries(data).reduce((acc, [, points]) => { + points.forEach((point) => { + Object.assign(acc, point); + }); + + return acc; + }, {}); + + return mapAndColorizeHashTableByGradient(preparedData, colorsConfig); +} + +export function colorizeGeoByPalette({ + data, + colorsConfig, + colorField, + defaultColorPaletteId, +}: { + data: GeoData; + colorsConfig: ChartColorsConfig; + colorField: ServerField; + defaultColorPaletteId: string; +}) { + const preparedData: Record = Object.entries(data).reduce((acc, [, points]) => { + points.forEach((point) => { + if (typeof point === 'object' && point !== null) { + Object.assign(acc, point); + } + }); + + return acc; + }, {}); + + const knownValues: {point: string; value: string; backgroundColor?: string}[] = []; + + const colorData: Record = {}; + const colorDictionary: Record = {}; + + const {mountedColors, colors} = getColorsSettings({ + field: colorField, + colorsConfig, + defaultColorPaletteId, + availablePalettes: colorsConfig.availablePalettes, + customColorPalettes: colorsConfig.loadedColorPalettes, + }); + + // eslint-disable-next-line guard-for-in + for (const point in preparedData) { + const value = preparedData[point]; + colorData[point] = {}; + let colorIndex = knownValues.findIndex(({value: knownValue}) => knownValue === value); + + if (colorIndex === -1) { + knownValues.push({point, value}); + colorIndex = knownValues.length - 1; + + let color; + + if (mountedColors && mountedColors[value]) { + color = getMountedColor({mountedColors, colors, value}); + } else { + color = getColor(colorIndex, colors); + } + knownValues[knownValues.length - 1].backgroundColor = color; + colorData[point].backgroundColor = color; + + colorDictionary[value] = color; + } else { + colorData[point].backgroundColor = knownValues[colorIndex].backgroundColor; + } + + colorData[point].colorIndex = colorIndex; + } + + return {colorData, colorDictionary: getSortedData(colorDictionary)}; +} + +function getLayerAlpha(layerSettings: {alpha: number}) { + return layerSettings.alpha * 1e-2 || 0.8; +} + +function getGradientMapOptions( + colorsConfig: ChartColorsConfig, + colorTitle: string, + colorizedResult: GradientOptions, +) { + const rawGradientData = getCurrentGradient(colorsConfig); + const rgbColors = getRgbColors(rawGradientData.colors, Boolean(colorsConfig.reversed)); + const gradientData = { + ...rawGradientData, + colors: rgbColors, + }; + + const mapOptions: GradientMapOptions = { + mode: 'gradient', + colorTitle, + gradientData, + colorMinValue: colorizedResult.min, + colorMaxValue: colorizedResult.max, + }; + + if (colorsConfig.gradientMode === '3-point') { + mapOptions.colorMidValue = colorizedResult.mid; + } + + return mapOptions; +} + +export function getMapState(shared: ServerChartsConfig, bounds: (Coordinate | undefined)[]) { + const [leftBot, rightTop] = bounds; + const shouldSetBounds = + shared?.extraSettings?.zoomMode !== ZoomMode.Manual && + shared?.extraSettings?.mapCenterMode !== ZoomMode.Manual; + + let center: Coordinate = [55.76, 37.64]; + const centerValue = shared?.extraSettings?.mapCenterValue; + const mapCenterValue = centerValue ? (mapStringToCoordinates(centerValue) as Coordinate) : null; + + if (shared?.extraSettings?.mapCenterMode === MapCenterMode.Manual && mapCenterValue) { + center = mapCenterValue; + } else if (leftBot && rightTop && !shouldSetBounds) { + center = [leftBot[0] / 2 + rightTop[0] / 2, leftBot[1] / 2 + rightTop[1] / 2]; + } + + let zoom = 8; + if (shared?.extraSettings?.zoomMode === ZoomMode.Manual && shared?.extraSettings?.zoomValue) { + zoom = Math.max(1, shared.extraSettings.zoomValue); + } + + return {zoom, center}; +} + +export { + getMapBounds, + getExtremeValues, + getFlattenCoordinates, + colorizeGeoByGradient, + getLayerAlpha, + getGradientMapOptions, +}; diff --git a/src/server/modes/charts/plugins/datalens/utils/get-gradient-stops.ts b/src/server/modes/charts/plugins/datalens/utils/get-gradient-stops.ts new file mode 100644 index 0000000..514328e --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/get-gradient-stops.ts @@ -0,0 +1,48 @@ +import {GradientType} from '../../../../../../shared'; +import type {ChartColorsConfig} from '../types'; + +import {getCurrentGradient, getRgbColors, getThresholdValues} from './color-helpers'; + +type GetGradientStopsArgs = { + colorsConfig: ChartColorsConfig; + points: {colorValue?: unknown}[]; + minColorValue: number; + maxColorValue: number; +}; + +export function getGradientStops(args: GetGradientStopsArgs) { + const {colorsConfig, points, minColorValue, maxColorValue} = args; + const colorValues = points.map((point) => + typeof point.colorValue === 'number' ? point.colorValue : null, + ); + const {mid, min, max} = getThresholdValues(colorsConfig, colorValues); + const colorValueRange = maxColorValue - minColorValue; + + let stops: number[] = []; + if ( + colorsConfig.gradientMode === GradientType.TWO_POINT || + colorsConfig.gradientColors.length === 2 + ) { + stops = [(min - minColorValue) / colorValueRange, (max - minColorValue) / colorValueRange]; + } else { + stops = [ + (min - minColorValue) / colorValueRange, + (mid - minColorValue) / colorValueRange, + (max - minColorValue) / colorValueRange, + ]; + } + + return stops; +} + +export function getHighchartsGradientStops(args: GetGradientStopsArgs): [number, string][] { + const {colorsConfig, points, minColorValue, maxColorValue} = args; + + const stops = getGradientStops({colorsConfig, points, minColorValue, maxColorValue}); + const gradient = getCurrentGradient(colorsConfig); + const gradientColors = getRgbColors(gradient.colors, Boolean(colorsConfig.reversed)); + return gradientColors.map((color, i) => [ + stops[i], + `rgb(${color.red}, ${color.green}, ${color.blue})`, + ]); +} diff --git a/src/server/modes/charts/plugins/datalens/utils/hierarchy-helpers.ts b/src/server/modes/charts/plugins/datalens/utils/hierarchy-helpers.ts new file mode 100644 index 0000000..b3ef0fb --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/hierarchy-helpers.ts @@ -0,0 +1,159 @@ +import type { + HierarchyField, + ServerChartsConfig, + ServerField, + SharedData, + StringParams, +} from '../../../../../../shared'; +import { + DATASET_FIELD_TYPES, + PlaceholderId, + WizardVisualizationId, + isMarkupField, +} from '../../../../../../shared'; + +import {getDrillDownData} from './misc-helpers'; + +export function preprocessHierarchies({ + visualizationId, + placeholders, + params, + sharedData, + colors, + shapes, + segments, +}: { + visualizationId: string; + placeholders: ServerChartsConfig['visualization']['placeholders']; + params: StringParams; + sharedData: SharedData; + colors: ServerChartsConfig['colors']; + shapes: ServerChartsConfig['shapes']; + segments: ServerChartsConfig['segments']; +}): void { + const {drillDownFilters, drillDownLevel} = getDrillDownData(params); + + const hierarchiesWithParent: HierarchyWithParent[] = getHierarchiesTheirParent({ + placeholders: placeholders, + colors: colors, + shapes: shapes, + segments, + }); + + if (visualizationId !== 'flatTable') { + hierarchiesWithParent.forEach(({hierarchy}) => { + removeMarkupFieldsFromHierarchy(hierarchy); + }); + } + + hierarchiesWithParent.forEach( + ({hierarchy, hierachyContainer, hierarchyIndex, hierarchyContainerId}, index) => { + if (!index) { + sharedData.drillDownData = { + breadcrumbs: hierarchy.fields.map((el) => el.title), + filters: drillDownFilters || hierarchy.fields.map(() => ''), + level: drillDownLevel, + fields: hierarchy.fields, + isColorDrillDown: hierachyContainer === colors || hierachyContainer === shapes, + }; + } else if (sharedData.drillDownData) { + const notSplitByColors: string[] = [ + WizardVisualizationId.Pie, + WizardVisualizationId.Donut, + WizardVisualizationId.Treemap, + ]; + sharedData.drillDownData.isColorDrillDown = + !notSplitByColors.includes(visualizationId) && + (sharedData.drillDownData.isColorDrillDown || + hierachyContainer === colors || + hierachyContainer === shapes || + hierachyContainer === segments); + } + + const hierarchyFieldIndex = + hierarchy.fields.length < drillDownLevel + ? hierarchy.fields.length - 1 + : drillDownLevel; + hierachyContainer[hierarchyIndex] = hierarchy.fields[hierarchyFieldIndex]; + + if (!sharedData.metaHierarchy) { + sharedData.metaHierarchy = {}; + } + + sharedData.metaHierarchy[hierarchyContainerId] = {hierarchyIndex, hierarchyFieldIndex}; + }, + ); +} + +const isFieldHierarchy = (field: {data_type: string}) => + field.data_type === DATASET_FIELD_TYPES.HIERARCHY; + +type HierarchyWithParent = { + hierarchy: HierarchyField; + hierachyContainer: any; + hierarchyIndex: number; + + hierarchyContainerId: PlaceholderId; +}; + +function getHierarchiesTheirParent({ + placeholders, + colors, + shapes, + segments, +}: { + placeholders: ServerChartsConfig['visualization']['placeholders']; + colors: ServerChartsConfig['colors']; + shapes: ServerChartsConfig['shapes']; + segments: ServerChartsConfig['segments']; +}): HierarchyWithParent[] { + const placeholderHierarchies = placeholders.reduce( + (acc: HierarchyWithParent[], placeholder) => { + placeholder.items.forEach((field: ServerField, index: number) => { + if (isFieldHierarchy(field)) { + acc.push({ + hierarchy: field as HierarchyField, + hierachyContainer: placeholder.items, + hierarchyIndex: index, + hierarchyContainerId: placeholder.id as PlaceholderId, + }); + } + }); + + return acc; + }, + [], + ); + + const otherItems: [any[], PlaceholderId][] = [ + [colors, PlaceholderId.Colors], + [shapes, PlaceholderId.Shapes], + [segments, PlaceholderId.Segments], + ]; + const otherItemHierarchies = otherItems.map(([item, placeholderId]: [any[], PlaceholderId]) => { + return item.reduce((acc: HierarchyWithParent[], field, index) => { + if (isFieldHierarchy(field)) { + acc.push({ + hierarchy: field as HierarchyField, + hierachyContainer: item, + hierarchyIndex: index, + hierarchyContainerId: placeholderId, + }); + } + + return acc; + }, []); + }); + + const flattenOtherItemHierarchies = ([] as HierarchyWithParent[]).concat( + ...otherItemHierarchies, + ); + + return [...placeholderHierarchies, ...flattenOtherItemHierarchies]; +} + +function removeMarkupFieldsFromHierarchy(hierarchy: HierarchyField) { + hierarchy.fields = hierarchy.fields.filter((field) => { + return !isMarkupField(field); + }); +} diff --git a/src/server/modes/charts/plugins/datalens/utils/markup-helpers.ts b/src/server/modes/charts/plugins/datalens/utils/markup-helpers.ts new file mode 100644 index 0000000..1cfd976 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/markup-helpers.ts @@ -0,0 +1,34 @@ +export const prepareMetricObject = ({ + size, + title, + color, + value, +}: { + size: string; + title: string; + color: string; + value: string; +}) => { + return { + value: { + type: 'concat', + className: `markup-metric markup-metric_size_${size}`, + children: [ + { + className: 'markup-metric-title', + type: 'text', + content: title, + }, + { + type: 'color', + color, + content: { + className: 'markup-metric-value', + type: 'text', + content: value, + }, + }, + ], + }, + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/utils/misc-helpers.ts b/src/server/modes/charts/plugins/datalens/utils/misc-helpers.ts new file mode 100644 index 0000000..e4535eb --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/misc-helpers.ts @@ -0,0 +1,485 @@ +import isEmpty from 'lodash/isEmpty'; +import moment from 'moment'; + +import type { + CommonNumberFormattingOptions, + ServerColor, + ServerCommonSharedExtraSettings, + ServerField, + ServerPlaceholder, + ServerPointSizeConfig, + SortParams, + StringParams, + V9Color, + WrappedHTML, + WrappedMarkdown, +} from '../../../../../../shared'; +import { + ColorMode, + DATASET_FIELD_TYPES, + LegendDisplayMode, + MINIMUM_FRACTION_DIGITS, + formatNumber as chartKitFormatNumber, + isDateField, + isMeasureField, + isMeasureValue, + wrapMarkupValue, +} from '../../../../../../shared'; +import {isHtmlField, isMarkdownField} from '../../../../../../shared/types/index'; +import {wrapMarkdownValue} from '../../../../../../shared/utils/markdown'; +import {wrapHtml} from '../../../../../../shared/utils/ui-sandbox'; +import type {ChartKitFormatSettings, ResultDataOrder} from '../preparers/types'; +import type { + ChartColorsConfig, + ServerFieldWithBackgroundSettings, + ServerFieldWithBarsSettings, + ServerFieldWithColumnWidthSettings, +} from '../types'; + +import { + DEFAULT_DATETIME_FORMAT, + DEFAULT_DATE_FORMAT, + DEFAULT_MAX_POINT_RADIUS, + DEFAULT_MIN_POINT_RADIUS, + LOG_INFO, + LOG_TIMING, + SERVER_DATETIME_FORMAT, + SERVER_DATE_FORMAT, + SORT_ORDER, +} from './constants'; + +let currentConsole: {log: Function} = console; + +function setConsole(newConsole: {log: Function}) { + currentConsole = newConsole; +} + +function log(...data: unknown[]) { + return LOG_INFO && currentConsole.log(...data); +} + +function logTiming(...data: unknown[]) { + return LOG_TIMING && currentConsole.log(...data); +} + +const collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}); +const numericCollator = (a: number, b: number) => (a > b ? 1 : -1); +const numericStringCollator = (a: string, b: string) => { + return Number(a) > Number(b) ? 1 : -1; +}; + +function isNumericalDataType(dataType: string | null) { + return ( + dataType === DATASET_FIELD_TYPES.FLOAT || + dataType === DATASET_FIELD_TYPES.INTEGER || + dataType === DATASET_FIELD_TYPES.UINTEGER + ); +} + +function isFloatDataType(dataType: string) { + return dataType === DATASET_FIELD_TYPES.FLOAT; +} + +function isMarkupDataType(dataType: string) { + return dataType === DATASET_FIELD_TYPES.MARKUP; +} + +function getTimezoneOffsettedTime(value: Date) { + return value.getTime() - value.getTimezoneOffset() * 60 * 1000; +} + +export function getDefaultDateFormat(valueType?: string | null) { + return valueType === 'datetime' || valueType === 'genericdatetime' + ? DEFAULT_DATETIME_FORMAT + : DEFAULT_DATE_FORMAT; +} + +export function getServerDateFormat(fieldType: string) { + return fieldType === 'datetime' || fieldType === 'genericdatetime' + ? SERVER_DATETIME_FORMAT + : SERVER_DATE_FORMAT; +} + +function formatDate({ + valueType, + value, + format, + utc = false, +}: { + valueType?: string | null; + value: string | number | Date | null; + format?: string; + utc?: boolean; +}) { + const createDate = utc ? moment.utc : moment; + + if (format) { + return createDate(value).format(format.replace('hh', 'HH')); + } + + return createDate(value).format(getDefaultDateFormat(valueType)); +} + +function formatNumber(value: number, minimumFractionDigits = MINIMUM_FRACTION_DIGITS) { + const numberFormat = new Intl.NumberFormat('ru-RU', { + minimumFractionDigits, + maximumFractionDigits: minimumFractionDigits, + }); + + return numberFormat.format(value); +} + +// This function is needed in order to format numbers in the Russian locale +// since the environment in which this script works only knows about the EN locale +// TODO: ^ so it needs to be done at the front, not here +function customFormatNumber({ + value, + minimumFractionDigits, + locale = 'ru-RU', +}: { + value: number; + minimumFractionDigits: number; + locale: string; +}) { + switch (locale) { + case 'ru-RU': + default: { + const formattedValue = formatNumber(value, minimumFractionDigits); + + return formattedValue.replace(/,/g, ' ').replace(/\./g, ','); + } + } +} + +const chartKitFormatNumberWrapper: typeof chartKitFormatNumber = (value, options) => { + return chartKitFormatNumber(value, options); +}; + +function getTitleInOrder(order: ResultDataOrder, index: number, coordinates: ServerField[]) { + const orderItem = order[index]; + + if (Array.isArray(orderItem)) { + const orderItemIndex = orderItem.findIndex((orderItem) => { + return coordinates.find( + (field) => + (field.fakeTitle === orderItem.title || field.title === orderItem.title) && + orderItem.datasetId === field.datasetId, + ); + }); + + return orderItem[orderItemIndex === -1 ? 0 : orderItemIndex].title; + } + + return orderItem.title; +} + +function findIndexInOrder(order: ResultDataOrder, field: {datasetId: string}, title: string) { + return order.findIndex((entry) => { + if (Array.isArray(entry)) { + const neededEntry = entry.find( + (entryEntry) => entryEntry.datasetId === field.datasetId, + ); + + return neededEntry ? neededEntry.title === title : false; + } else { + return entry.title === title && entry.datasetId === field.datasetId; + } + }); +} + +function getAllPlaceholderItems(placeholders: ServerPlaceholder[]) { + let items: ServerPlaceholder['items'] = []; + + placeholders.forEach((placeholder) => { + items = [...items, ...placeholder.items]; + }); + + return items; +} + +export function getSortedColumnId(value: SortParams['columnId'], isPivotTable?: boolean) { + if (!value) { + return undefined; + } + + // eslint-disable-next-line security/detect-unsafe-regex + const columnId = value.match(/(?<=_id=)(.*?)(?=_name=)/)?.[0]; + + if (isPivotTable && columnId) { + // eslint-disable-next-line security/detect-unsafe-regex + return columnId.match(/(?<=fieldId=)(.*?)(?=__index)/)?.[0]; + } + + return columnId; +} + +function getSortOrder(value: SortParams['order']) { + if (!value) { + return undefined; + } + + return value === SORT_ORDER.ASCENDING.NUM + ? SORT_ORDER.ASCENDING.STR + : SORT_ORDER.DESCENDING.STR; +} + +function getSortData(params: SortParams, isPivotTable?: boolean) { + const columnId = getSortedColumnId(params.columnId, isPivotTable); + const order = getSortOrder(params.order); + + return {columnId, order}; +} + +function getDrillDownLevel(params: StringParams) { + return Number((params.drillDownLevel || ['0'])[0]); +} + +function getDrillDownFilters(params: StringParams): string[] | undefined { + let filters = params.drillDownFilters; + + if (!Array.isArray(filters)) { + return; + } + + filters = filters.map(String); + + if (filters.some(Boolean)) { + return filters; + } + + return; +} + +function getDrillDownData(params: StringParams) { + return { + drillDownLevel: getDrillDownLevel(params), + drillDownFilters: getDrillDownFilters(params), + }; +} + +function getPointRadius({ + current, + min, + max, + geopointsConfig, +}: { + current: number; + min: number; + max: number; + geopointsConfig: ServerPointSizeConfig; +}) { + const minRadius = geopointsConfig.minRadius || DEFAULT_MIN_POINT_RADIUS; + const maxRadius = geopointsConfig.maxRadius || DEFAULT_MAX_POINT_RADIUS; + const coeff = (maxRadius - minRadius) / (max - min || 1); + return minRadius + (current - min) * (Number.isNaN(coeff) ? 0 : coeff); +} + +function isNeedToCalcClosestPointManually( + visualizationId: string, + placeholders: ServerPlaceholder[] | undefined, + colors: ServerColor[], +) { + let placeholderId: string | undefined; + + switch (visualizationId) { + case 'column': + placeholderId = 'x'; + break; + case 'bar': + placeholderId = 'y'; + break; + } + + if (!placeholderId) { + return false; + } + const placeholderItem = placeholders?.find((pl) => pl.id === placeholderId)?.items?.[0]; + + if (!placeholderItem) { + return false; + } + + const colorItem = colors?.[0]; + + return Boolean(colorItem && isDateField(placeholderItem)); +} + +function getOriginalTitleOrTitle(field: {title: string; originalTitle?: string}) { + return field.originalTitle || field.title; +} + +function getFieldTitle(field: ServerField | undefined): string { + if (!field) { + return ''; + } + + return field.fakeTitle || field.originalTitle || field.title; +} + +function isTableBarsSettingsEnabled(field: ServerField): field is ServerFieldWithBarsSettings { + return Boolean(field.barsSettings?.enabled); +} + +function isTableFieldBackgroundSettingsEnabled( + field: ServerField | undefined, +): field is ServerFieldWithBackgroundSettings { + return Boolean(field && field.backgroundSettings?.enabled); +} + +function isColumnSettingsWidthEnabled( + field: ServerField, +): field is ServerFieldWithColumnWidthSettings { + return Boolean(field.columnSettings?.width); +} + +function getFormatOptionsFromFieldFormatting( + formatting: CommonNumberFormattingOptions | undefined, + dataType: string, + isAxisFormatting = false, +): ChartKitFormatSettings { + let chartKitPrecision = 0; + if (dataType === 'float' || isAxisFormatting) { + const minimumFractionDigits = isAxisFormatting ? 0 : MINIMUM_FRACTION_DIGITS; + chartKitPrecision = + typeof formatting?.precision === 'number' + ? formatting.precision + : minimumFractionDigits; + } + + return typeof formatting === 'undefined' || isEmpty(formatting) + ? { + chartKitFormatting: true, + chartKitPrecision, + } + : { + chartKitFormatting: true, + chartKitPrecision, + chartKitPrefix: formatting.prefix, + chartKitPostfix: formatting.postfix, + chartKitUnit: formatting.unit, + chartKitFormat: formatting.format, + chartKitLabelMode: formatting.labelMode, + chartKitShowRankDelimiter: formatting.showRankDelimiter, + }; +} + +function isGradientMode({ + colorField, + colorsConfig, + colorFieldDataType, +}: { + colorField: V9Color; + colorsConfig: ChartColorsConfig; + colorFieldDataType: string | null; +}) { + return ( + isMeasureField(colorField) || + isMeasureValue(colorField) || + (isNumericalDataType(colorFieldDataType) && colorsConfig.colorMode === ColorMode.GRADIENT) + ); +} + +export function isLegendEnabled(chartSetting?: ServerCommonSharedExtraSettings) { + return chartSetting?.legendMode !== LegendDisplayMode.Hide; +} + +export function getLabelValue( + value: undefined | string, + options: {isMarkdownLabel?: boolean; isMarkupLabel?: boolean; isHtmlLabel?: boolean} = {}, +) { + const {isMarkdownLabel, isMarkupLabel, isHtmlLabel} = options; + + if (value === undefined) { + return ''; + } + + if (isMarkdownLabel) { + return wrapMarkdownValue(value); + } + + if (isMarkupLabel) { + return wrapMarkupValue(value); + } + + if (isHtmlLabel) { + return wrapHtml(value); + } + + return value; +} + +type CategoryItem = string | number | WrappedHTML | WrappedMarkdown; + +export function getCategoryFormatter(args: { + field?: ServerField; +}): (value: string | number) => CategoryItem { + const {field} = args; + if (isDateField(field)) { + return (value: string | number) => { + return formatDate({ + valueType: field.data_type, + value, + format: field.format, + utc: true, + }); + }; + } + + if (isMarkdownField(field)) { + return (value: unknown) => wrapMarkdownValue(String(value)); + } + + if (isHtmlField(field)) { + return (value: unknown) => wrapHtml(String(value)); + } + + return (value: string | number) => { + if (value === '') { + return 'Null'; + } + + return String(value); + }; +} + +export function getSeriesTitleFormatter(args: {fields: (ServerField | undefined)[]}) { + const {fields} = args; + + if (fields.some(isHtmlField)) { + return (value?: string) => wrapHtml(String(value ?? '')); + } + + return (value?: string) => value ?? ''; +} + +export { + setConsole, + log, + logTiming, + collator, + numericCollator, + isGradientMode, + isNumericalDataType, + isFloatDataType, + isMarkupDataType, + getTimezoneOffsettedTime, + formatDate, + formatNumber, + customFormatNumber, + findIndexInOrder, + chartKitFormatNumberWrapper, + getAllPlaceholderItems, + getSortData, + getDrillDownData, + getPointRadius, + isNeedToCalcClosestPointManually, + getTitleInOrder, + getOriginalTitleOrTitle, + isTableBarsSettingsEnabled, + numericStringCollator, + getFormatOptionsFromFieldFormatting, + isTableFieldBackgroundSettingsEnabled, + isColumnSettingsWidthEnabled, + getSortOrder, + getFieldTitle, +}; diff --git a/src/server/modes/charts/plugins/datalens/utils/pivot-table/totals.ts b/src/server/modes/charts/plugins/datalens/utils/pivot-table/totals.ts new file mode 100644 index 0000000..48a6522 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/pivot-table/totals.ts @@ -0,0 +1,33 @@ +import type {ServerField} from '../../../../../../../shared'; + +type PivotDataTotal = { + level: number; +}; + +const getSubTotalsFromFields = (fields: ServerField[]): PivotDataTotal[] => { + return fields + .map((field, index) => { + const isSubTotalsEnabled = field.subTotalsSettings?.enabled; + + if (isSubTotalsEnabled) { + return { + level: index, + }; + } + + return undefined; + }) + .filter((setting) => Boolean(setting)) as PivotDataTotal[]; +}; + +type GetPivotTableSubTotals = { + columnsFields: ServerField[]; + rowsFields: ServerField[]; +}; + +export const getPivotTableSubTotals = ({rowsFields, columnsFields}: GetPivotTableSubTotals) => { + return { + rows: getSubTotalsFromFields(rowsFields), + columns: getSubTotalsFromFields(columnsFields), + }; +}; diff --git a/src/server/modes/charts/plugins/datalens/utils/shape-helpers.ts b/src/server/modes/charts/plugins/datalens/utils/shape-helpers.ts new file mode 100644 index 0000000..0e0d2b5 --- /dev/null +++ b/src/server/modes/charts/plugins/datalens/utils/shape-helpers.ts @@ -0,0 +1,69 @@ +import type {Highcharts} from '@gravity-ui/chartkit/highcharts'; + +import type {ExtendedSeriesLineOptions, ServerShapesConfig} from '../../../../../../shared'; +import {getServerShapesOrder} from '../../../../../../shared'; + +const SHAPES_IN_ORDER = getServerShapesOrder(); + +type MapAndShapeGraphArgs = { + graphs: ExtendedSeriesLineOptions[]; + shapesConfig: ServerShapesConfig | undefined; + isSegmentsExists: boolean; + isShapesDefault: boolean; +}; + +export const mapAndShapeGraph = ({ + graphs, + isSegmentsExists, + shapesConfig, + isShapesDefault, +}: MapAndShapeGraphArgs) => { + const knownValues: (string | undefined)[] = []; + const defaultLineWidth = shapesConfig?.commonLineSettings?.lineWidth; + const linecap = shapesConfig?.commonLineSettings?.linecap; + const linejoin = shapesConfig?.commonLineSettings?.linejoin; + + graphs.forEach((graph, i) => { + const value = graph.shapeValue; + const title = value || graph.legendTitle || graph.name; + + const currentLineWidth = title ? shapesConfig?.lineSettings?.[title]?.lineWidth : 'auto'; + if (currentLineWidth && currentLineWidth !== 'auto') { + graph.lineWidth = currentLineWidth; + } else if (defaultLineWidth && defaultLineWidth !== 'auto') { + graph.lineWidth = defaultLineWidth; + } + + if (linecap) { + graph.linecap = linecap; + } + + if (linejoin) { + // @ts-ignore + graph.linejoin = linejoin; + } + + if ( + shapesConfig?.mountedShapes && + title && + shapesConfig.mountedShapes[title] && + shapesConfig.mountedShapes[title] !== 'auto' + ) { + graph.dashStyle = shapesConfig.mountedShapes[title] as Highcharts.DashStyleValue; + } else if (isShapesDefault) { + graph.dashStyle = SHAPES_IN_ORDER[0]; + } else { + let shapeIndex = + graph.yAxis === 0 || !graph.shapeValue || isSegmentsExists + ? knownValues.indexOf(value) + : i; + + if (shapeIndex === -1) { + knownValues.push(value); + shapeIndex = knownValues.length - 1; + } + + graph.dashStyle = SHAPES_IN_ORDER[shapeIndex % SHAPES_IN_ORDER.length]; + } + }); +}; diff --git a/src/server/modes/charts/plugins/dataset/v2.ts b/src/server/modes/charts/plugins/dataset/v2.ts new file mode 100644 index 0000000..3e89f16 --- /dev/null +++ b/src/server/modes/charts/plugins/dataset/v2.ts @@ -0,0 +1,281 @@ +import {isObject} from 'lodash'; + +import type {IChartEditor, Update} from '../../../../../shared'; +import type { + ApiV2Filter, + ApiV2OrderBy, + ApiV2Parameter, + ApiV2Ref, + ApiV2Request, + ApiV2RequestField, + ApiV2ResponseField, + ApiV2ResultData, + ApiV2ResultDataRow, +} from '../../../../../shared/types/bi-api/v2'; + +const OPERATIONS = { + ISNULL: 'ISNULL', + ISNOTNULL: 'ISNOTNULL', + GT: 'GT', + LT: 'LT', + GTE: 'GTE', + LTE: 'LTE', + EQ: 'EQ', + NE: 'NE', + STARTSWITH: 'STARTSWITH', + ISTARTSWITH: 'ISTARTSWITH', + ENDSWITH: 'ENDSWITH', + IENDSWITH: 'IENDSWITH', + CONTAINS: 'CONTAINS', + ICONTAINS: 'ICONTAINS', + NOTCONTAINS: 'NOTCONTAINS', + NOTICONTAINS: 'NOTICONTAINS', + IN: 'IN', + NIN: 'NIN', + BETWEEN: 'BETWEEN', +}; + +const ORDERS = { + DESC: 'DESC', + ASC: 'ASC', +}; + +type Options = {utc?: boolean; disableProcessDates?: boolean}; + +function getTimezoneOffsettedTime(value: Date) { + return value.getTime() - value.getTimezoneOffset() * 60 * 1000; +} + +function convertSimpleType(data: null | string, dataType: string, options: Options) { + if (data === null) { + return null; + } + + switch (dataType) { + case 'integer': + case 'uinteger': + case 'float': + return Number(data); + case 'date': + case 'datetime': + case 'genericdatetime': + case 'datetimetz': { + if (options.disableProcessDates) { + return data; + } + const date = new Date(data); + + if (!options.utc) { + date.setTime(getTimezoneOffsettedTime(date)); + } + + return date; + } + case 'string': + default: + return data; + } +} + +function getTypedData(data: string | null, dataType: string, options: Options) { + return convertSimpleType(data, dataType, options); +} + +function getResultRows( + data: ApiV2ResultData['result_data'], + fields: ApiV2ResponseField[], + options: Options, +) { + const fieldsDict = fields.reduce((acc: Record, field) => { + acc[field.legend_item_id!] = field; + return acc; + }, {}); + + const rows = data[0]; + + return rows.rows.map((item: ApiV2ResultDataRow) => { + const rowHash: Record = {}; + const rowData = item.data; + const rowLegend = item.legend; + + rowData.forEach((rowDataItem, rowDataItemIndex) => { + const rowField = fieldsDict[rowLegend[rowDataItemIndex]]!; + + const columnId = rowField.title; + const columnType = rowField.data_type; + + rowHash[columnId] = getTypedData(rowDataItem, columnType, options); + }); + + return rowHash; + }); +} + +function processTableData( + resultData: ApiV2ResultData['result_data'], + fields: ApiV2ResponseField[], + options = {}, +) { + return getResultRows(resultData, fields, options); +} + +const mapColumnsToRequestFields = (columns: string[]): ApiV2RequestField[] => { + return columns.map((fieldId) => ({ + ref: { + type: 'title', + title: fieldId, + }, + block_id: 0, + })); +}; + +type BuildSourcePayload = { + id?: string; + datasetId?: string; + columns: string[]; + where?: { + column: string; + type?: 'title' | 'id'; + operation: string; + values: string[]; + }[]; + order_by?: { + direction: string; + column: string; + }[]; + cache: number; + disable_group_by: boolean; + limit?: number; + offset?: number; + updates?: Update[]; + ui: boolean; + parameters?: {id: string; value: string}[]; +}; + +function buildSource(payload: BuildSourcePayload) { + const fields = mapColumnsToRequestFields(payload.columns); + + const filters: ApiV2Filter[] | undefined = payload.where?.map((filter) => { + const type = filter.type || 'title'; + const value = filter.column || ''; + + const ref: ApiV2Ref = + type === 'title' + ? { + type: 'title', + title: value, + } + : { + type: 'id', + id: value, + }; + + return { + ref, + operation: filter.operation, + values: filter.values, + }; + }); + + const order_by: ApiV2OrderBy[] | undefined = payload.order_by?.map(({direction, column}) => ({ + ref: {type: 'title', title: column}, + direction: direction.toLowerCase(), + })); + + const parameter_values: ApiV2Parameter[] | undefined = payload.parameters?.map((parameter) => { + return { + ref: { + type: 'id', + id: parameter.id, + }, + value: parameter.value, + }; + }); + + let disable_group_by; + + if (payload.disable_group_by && typeof payload.disable_group_by !== 'undefined') { + disable_group_by = true; + } + + const requestData: ApiV2Request['data'] = { + fields, + filters, + order_by, + updates: payload.updates, + parameter_values, + limit: payload.limit, + offset: payload.offset, + disable_group_by, + autofill_legend: true, + }; + + Object.keys(requestData).forEach((key) => { + if (typeof (requestData as Record)[key] === 'undefined') { + delete (requestData as Record)[key]; + } + }); + + return { + datasetId: String(payload.id || payload.datasetId), + path: 'result', + data: requestData, + ui: payload.ui, + cache: payload.cache, + }; +} + +function processData( + data: Record, + field = 'dataset', + ChartEditor: IChartEditor, + options = {}, +) { + const dataResult = data[field]!; + + if (ChartEditor) { + // we put the information for the inspector in ChartKit + const query = dataResult.blocks.map((block) => block.query).join('\n'); + ChartEditor.setDataSourceInfo(field, {query}); + + if (dataResult.data_export_forbidden) { + ChartEditor.setExtra?.('dataExportForbidden', true); + } + } + + return processTableData(dataResult.result_data, dataResult.fields, options); +} + +function getDatasetRows(params: {datasetName: string}) { + if (!isObject(params)) { + throw new Error('Params should be an object'); + } + + const {datasetName} = params; + if (!datasetName) { + throw new Error('datasetName is not defined in params'); + } + + const EditorAPI = (globalThis as unknown as {Editor: IChartEditor}).Editor; + + if (!EditorAPI) { + throw new Error('EditorAPI is not defined'); + } + + const data = EditorAPI.getLoadedData(); + + if (!data[datasetName]) { + throw new Error(`Dataset "${datasetName}" is not defined`); + } + + return processData(data, datasetName, EditorAPI, {disableProcessDates: true}); +} + +export default { + buildSource, + processTableData, + processData, + getDatasetRows, + OPERATIONS, + ORDERS, +}; diff --git a/src/server/modes/charts/plugins/helpers/color-palettes.ts b/src/server/modes/charts/plugins/helpers/color-palettes.ts new file mode 100644 index 0000000..56772cc --- /dev/null +++ b/src/server/modes/charts/plugins/helpers/color-palettes.ts @@ -0,0 +1,163 @@ +import {isEmpty} from 'lodash'; + +import type { + ColorPalette, + Palette, + ServerChartsConfig, + ServerColorsConfig, + ServerField, +} from '../../../../../shared'; +import { + getFieldUISettings, + isEntryId, + isSystemGradientPaletteId, + isSystemPaletteId, + isVisualizationWithLayers, +} from '../../../../../shared'; +import {selectServerPalette} from '../../../../constants'; +import type {SourceRequest} from '../datalens/url/types'; + +type GetColorPalettesRequestArgs = { + config: ServerChartsConfig; + palettes: Record; +}; + +export function isCustomColorPaletteId(value: string, systemPalettes: Record) { + const isSystem = isSystemGradientPaletteId(value) || isSystemPaletteId(value, systemPalettes); + return !isSystem && isEntryId(value); +} + +export function addColorPaletteRequest({ + colorPaletteId, + palettes, + result, +}: { + colorPaletteId: string; + palettes: Record; + result: Record; +}) { + if (isCustomColorPaletteId(colorPaletteId, palettes)) { + // eslint-disable-next-line no-param-reassign + result[`colorPalettes_${colorPaletteId}`] = { + method: 'GET', + url: `/_us_color_palettes/${colorPaletteId}`, + hideInInspector: true, + }; + } +} + +export function getColorPalettesRequests(args: GetColorPalettesRequestArgs) { + const {config, palettes} = args; + const visualization = config.visualization; + const result: Record = {}; + + const colorPalettes = new Set(); + if (isVisualizationWithLayers(visualization)) { + visualization.layers.forEach((layer) => { + const colorConfig = layer.commonPlaceholders.colorsConfig; + colorPalettes.add(colorConfig?.palette || colorConfig?.gradientPalette || ''); + }); + } else { + const colorPaletteId = + config.colorsConfig?.palette || + config.colorsConfig?.gradientPalette || + config.extraSettings?.metricFontColorPalette || + ''; + colorPalettes.add(colorPaletteId); + } + + colorPalettes.forEach((colorPaletteId) => + addColorPaletteRequest({colorPaletteId, palettes, result}), + ); + + visualization.placeholders?.forEach((placeholder) => { + placeholder.items.forEach((item) => { + const {backgroundSettings, barsSettings} = item; + + if (backgroundSettings && backgroundSettings.enabled) { + const gradientPaletteId = + backgroundSettings.settings?.gradientState?.gradientPalette; + + if (gradientPaletteId) { + addColorPaletteRequest({colorPaletteId: gradientPaletteId, palettes, result}); + } + + const regularPaletteId = backgroundSettings.settings?.paletteState?.palette; + if (regularPaletteId) { + addColorPaletteRequest({colorPaletteId: regularPaletteId, palettes, result}); + } + } + + if (barsSettings && barsSettings.enabled) { + const gradientPaletteId = barsSettings.colorSettings.settings?.palette; + + if (gradientPaletteId) { + addColorPaletteRequest({colorPaletteId: gradientPaletteId, palettes, result}); + } + } + }); + }); + + return result; +} + +export function extractColorPalettesFromData(data: {[key: string]: any}) { + const palettes: Record = {}; + const loadedData: Record = {}; + + Object.keys(data).forEach((key) => { + if (key.includes('colorPalettes_')) { + const paletteId = key.replace('colorPalettes_', ''); + + palettes[paletteId] = data[key][0]; + } else { + loadedData[key] = data[key]; + } + }); + + return {colorPalettes: palettes, loadedData}; +} + +export function getColorsSettings({ + defaultColorPaletteId, + colorsConfig, + field, + customColorPalettes, + availablePalettes, +}: { + defaultColorPaletteId: string; + colorsConfig: ServerColorsConfig | undefined; + field: ServerField | undefined; + customColorPalettes: Record; + availablePalettes: Record; +}) { + let mountedColors: Record = {}; + let colors: string[] = []; + + if ( + colorsConfig?.mountedColors && + (field?.guid === colorsConfig.fieldGuid || colorsConfig.coloredByMeasure) + ) { + mountedColors = colorsConfig.mountedColors ?? {}; + } else if (field) { + const fieldSettings = getFieldUISettings({field}); + mountedColors = fieldSettings?.colors ?? {}; + colors = selectServerPalette({ + palette: fieldSettings?.palette, + availablePalettes, + customColorPalettes, + defaultColorPaletteId, + }); + } + + if (isEmpty(colors)) { + colors = selectServerPalette({ + palette: colorsConfig?.palette, + availablePalettes, + customColorPalettes, + defaultColorPaletteId, + }); + } + + return {mountedColors, colors}; +} diff --git a/src/server/modes/charts/plugins/helpers/misc.ts b/src/server/modes/charts/plugins/helpers/misc.ts new file mode 100644 index 0000000..9794284 --- /dev/null +++ b/src/server/modes/charts/plugins/helpers/misc.ts @@ -0,0 +1,55 @@ +import {type ServerField, type ServerPlaceholder, getFormatOptions} from '../../../../../shared'; +import { + DATASET_ID_KEY_PLACEHOLDER, + DATASET_ID_KEY_TEMPLATE, + DATASET_ID_LAYER_ID_KEY_TEMPLATE, + DATASET_ID_LAYER_ID_SEPARATOR, + LAYER_ID_KEY_PLACEHOLDER, + LAYER_ID_KEY_TEMPLATE, +} from '../constants/misc'; + +export const getDatasetIdRequestKey = (datasetId: string) => { + return DATASET_ID_KEY_TEMPLATE.replace(DATASET_ID_KEY_TEMPLATE, datasetId); +}; + +export const getLayerIdRequestKey = (layerId: string) => { + return LAYER_ID_KEY_TEMPLATE.replace(LAYER_ID_KEY_TEMPLATE, layerId); +}; + +export const getDatasetIdAndLayerIdFromKey = (key: string): [string, string] => { + const [datasetIdArgument, layerIdArgument = ''] = key.split(DATASET_ID_LAYER_ID_SEPARATOR); + + return [datasetIdArgument, layerIdArgument]; +}; + +export const getDatasetIdAndLayerIdRequestKey = (datasetId: string, layerId: string): string => { + const datasetIdKey = getDatasetIdRequestKey(datasetId); + + const key = DATASET_ID_LAYER_ID_KEY_TEMPLATE.replace(DATASET_ID_KEY_PLACEHOLDER, datasetIdKey); + + if (layerId) { + const layerIdKey = getLayerIdRequestKey(layerId); + return key.replace(LAYER_ID_KEY_PLACEHOLDER, layerIdKey); + } + + return key.replace(LAYER_ID_KEY_PLACEHOLDER, ''); +}; + +export function getFieldList(datasetFields: ServerField[], placeholders: ServerPlaceholder[]) { + const placeholdersFields = placeholders.map((p) => p.items || []).flat(2); + return datasetFields.map((field) => { + const fieldId = field.guid || (field as unknown as {id: string}).id; + const placeholdersField = placeholdersFields.find(({guid}) => guid === fieldId); + const fakeTitle = placeholdersField?.fakeTitle; + + return { + title: field.title, + guid: fieldId, + dataType: field.data_type, + formatting: placeholdersField + ? getFormatOptions({...field, ...placeholdersField}) + : undefined, + ...(fakeTitle ? {fakeTitle} : {}), + }; + }); +} diff --git a/src/server/modes/charts/plugins/logins-blacklist.ts b/src/server/modes/charts/plugins/logins-blacklist.ts new file mode 100644 index 0000000..4558fcc --- /dev/null +++ b/src/server/modes/charts/plugins/logins-blacklist.ts @@ -0,0 +1,27 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; + +import {MiddlewareStage} from '../../../components/charts-engine/types'; + +const {CHARTS_LOGIN_BLACKLIST} = process.env; + +const blacklist = CHARTS_LOGIN_BLACKLIST ? new Set(CHARTS_LOGIN_BLACKLIST.split(',')) : null; + +export const plugin = { + middlewares: [ + { + stage: MiddlewareStage.AfterAuth, + fn: (req: Request, res: Response, next: NextFunction) => { + const {login} = req.blackbox || {}; + + if (login && blacklist && blacklist.has(login)) { + res.status(429).send({ + message: 'Your login is blacklisted', + }); + return; + } + + next(); + }, + }, + ], +}; diff --git a/src/server/modes/charts/plugins/ql/index.ts b/src/server/modes/charts/plugins/ql/index.ts new file mode 100644 index 0000000..34ea6fc --- /dev/null +++ b/src/server/modes/charts/plugins/ql/index.ts @@ -0,0 +1,28 @@ +import {QL_TYPE} from '../../../../../shared'; +import type {Plugin} from '../../../../components/charts-engine/types'; + +import {runQlChart} from './ql-chart-runner'; + +export const plugin: Plugin = { + runners: [ + { + name: 'ql', + trigger: new Set([ + QL_TYPE.TIMESERIES_QL_NODE, + QL_TYPE.GRAPH_QL_NODE, + QL_TYPE.D3_QL_NODE, + QL_TYPE.TABLE_QL_NODE, + QL_TYPE.YMAP_QL_NODE, + QL_TYPE.METRIC_QL_NODE, + QL_TYPE.MARKUP_QL_NODE, + + QL_TYPE.LEGACY_GRAPH_QL_NODE, + QL_TYPE.LEGACY_TABLE_QL_NODE, + QL_TYPE.LEGACY_YMAP_QL_NODE, + QL_TYPE.LEGACY_METRIC_QL_NODE, + ]), + safeConfig: true, + handler: runQlChart, + }, + ], +}; diff --git a/src/server/modes/charts/plugins/ql/module/chart-editor-module.ts b/src/server/modes/charts/plugins/ql/module/chart-editor-module.ts new file mode 100644 index 0000000..7e4b2e1 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/chart-editor-module.ts @@ -0,0 +1,58 @@ +import type { + ConnectorType, + FeatureConfig, + IChartEditor, + Palette, + QlConfig, +} from '../../../../../../shared'; + +import privateModule from './private-module'; + +declare const __features: FeatureConfig; +declare const __palettes: Record; +declare const __qlConnectionTypeMap: Record; +declare const __defaultColorPaletteId: string; + +const buildLibraryConfig = ({ + shared, + ChartEditor, +}: { + shared: QlConfig; + ChartEditor: IChartEditor; +}) => { + return privateModule.buildLibraryConfig({shared, ChartEditor, features: __features}); +}; + +const buildSources = ({shared, ChartEditor}: {shared: QlConfig; ChartEditor: IChartEditor}) => { + return privateModule.buildSources({ + shared, + ChartEditor, + palettes: __palettes, + qlConnectionTypeMap: __qlConnectionTypeMap, + features: __features, + }); +}; + +const buildGraph = ({shared, ChartEditor}: {shared: QlConfig; ChartEditor: IChartEditor}) => { + return privateModule.buildGraph({ + shared, + ChartEditor, + features: __features, + palettes: __palettes, + qlConnectionTypeMap: __qlConnectionTypeMap, + defaultColorPaletteId: __defaultColorPaletteId, + }); +}; + +const buildChartConfig = ({shared, ChartEditor}: {shared: QlConfig; ChartEditor: IChartEditor}) => { + return privateModule.buildChartConfig({shared, ChartEditor, features: __features}); +}; + +export default { + buildLibraryConfig, + buildSources, + buildGraph, + buildChartConfig, + buildD3Config: () => {}, + setConsole: privateModule.setConsole, +}; diff --git a/src/server/modes/charts/plugins/ql/module/config/build-chart-config.ts b/src/server/modes/charts/plugins/ql/module/config/build-chart-config.ts new file mode 100644 index 0000000..94e2de4 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/config/build-chart-config.ts @@ -0,0 +1,85 @@ +import type {HighchartsWidgetData} from '@gravity-ui/chartkit/highcharts'; +import set from 'lodash/set'; + +import type {FeatureConfig, IChartEditor, QlConfig} from '../../../../../../../shared'; +import { + DEFAULT_CHART_LINES_LIMIT, + Feature, + WizardVisualizationId, +} from '../../../../../../../shared'; +import {ChartkitHandlers} from '../../../../../../../shared/constants/chartkit-handlers'; +import {mapQlConfigToLatestVersion} from '../../../../../../../shared/modules/config/ql'; +import type {DashWidgetConfig} from '../../../../../../../shared/types/charts'; +import {log} from '../../utils/misc-helpers'; + +type BuildChartsConfigArgs = { + shared: QlConfig; + ChartEditor: IChartEditor; + features: FeatureConfig; + widgetConfig?: DashWidgetConfig['widgetConfig']; +}; +type QlChartConfig = Pick< + HighchartsWidgetData['config'], + 'title' | 'hideHolidaysBands' | 'linesLimit' | 'tooltip' | 'enableSum' +> & { + enableGPTInsights?: boolean; + manageTooltipConfig?: ChartkitHandlers.WizardManageTooltipConfig; +}; + +export function buildChartConfig(args: BuildChartsConfigArgs) { + const {shared, ChartEditor, features, widgetConfig} = args; + const qlConfig = mapQlConfigToLatestVersion(shared, { + i18n: ChartEditor.getTranslation, + }); + + const config: Partial = { + tooltip: {pin: {altKey: true}, sort: {enabled: true}}, + }; + + if (qlConfig.extraSettings) { + const {title, titleMode, tooltipSum, enableGPTInsights} = qlConfig.extraSettings; + + if (title && titleMode === 'show') { + config.title = title; + } + + if (typeof tooltipSum === 'undefined' || tooltipSum === 'on') { + config.enableSum = true; + } + + config.enableGPTInsights = enableGPTInsights; + } + + const visualizationId = qlConfig?.visualization?.id; + const isTableWidget = visualizationId === WizardVisualizationId.FlatTable; + const isIndicatorWidget = visualizationId === WizardVisualizationId.Metric; + const isScatterWidget = visualizationId === WizardVisualizationId.Scatter; + const isTreemapWidget = visualizationId === WizardVisualizationId.Treemap; + + const isSetManageTooltipConfig = + !isIndicatorWidget && !isTableWidget && !isScatterWidget && !isTreemapWidget; + + if (isTableWidget) { + const size = widgetConfig?.size ?? shared?.extraSettings?.size; + if (size) { + set(config, 'size', size); + } + + set(config, 'settings.width', 'max-content'); + + if (shared?.extraSettings?.preserveWhiteSpace) { + set(config, 'preserveWhiteSpace', true); + } + } + + config.hideHolidaysBands = !features[Feature.HolidaysOnChart]; + config.linesLimit = DEFAULT_CHART_LINES_LIMIT; + + if (isSetManageTooltipConfig) { + config.manageTooltipConfig = ChartkitHandlers.WizardManageTooltipConfig; + } + + log('CONFIG:', config); + + return config; +} diff --git a/src/server/modes/charts/plugins/ql/module/config/index.ts b/src/server/modes/charts/plugins/ql/module/config/index.ts new file mode 100644 index 0000000..8251471 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/config/index.ts @@ -0,0 +1,13 @@ +import type {IChartEditor} from '../../../../../../../shared'; +import {getServerFeatures} from '../../../../../../../shared'; +import type {QlConfig} from '../../../../../../../shared/types/config/ql'; +import {registry} from '../../../../../../registry'; + +import {buildChartConfig} from './build-chart-config'; + +export default ({shared, ChartEditor}: {shared: QlConfig; ChartEditor: IChartEditor}) => { + const app = registry.getApp(); + const features = getServerFeatures(app.nodekit.ctx); + + return buildChartConfig({shared, ChartEditor, features}); +}; diff --git a/src/server/modes/charts/plugins/ql/module/highcharts.ts b/src/server/modes/charts/plugins/ql/module/highcharts.ts new file mode 100644 index 0000000..25b5c34 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/highcharts.ts @@ -0,0 +1,110 @@ +import type {IChartEditor} from '../../../../../../shared'; +import {mapQlConfigToLatestVersion} from '../../../../../../shared/modules/config/ql'; +import type {QlConfig} from '../../../../../../shared/types/config/ql'; + +// eslint-disable-next-line complexity +export default ({shared, ChartEditor}: {shared: QlConfig; ChartEditor: IChartEditor}) => { + const config = mapQlConfigToLatestVersion(shared, {i18n: ChartEditor.getTranslation}); + + const xAxis: Highcharts.Options['xAxis'] = { + endOnTick: false, + }; + const yAxis: Highcharts.Options['yAxis'] = {}; + + const chart: Highcharts.ChartOptions = { + type: config.visualization.highchartsId || config.visualization.id, + zoomType: 'x', + }; + + const plotOptions: Highcharts.PlotOptions = {}; + + const visualizationId = config.visualization.id; + + // We apply settings that are unique for each type of visualization + switch (visualizationId) { + case 'line': + chart.zoomType = 'xy'; + + break; + + case 'area': + plotOptions.area = { + stacking: 'normal', + }; + + break; + + case 'area100p': + plotOptions.area = { + stacking: 'percent', + }; + + break; + + case 'column': + case 'column100p': + case 'bar': + case 'bar100p': + chart.zoomType = 'xy'; + + plotOptions.column = { + dataGrouping: { + enabled: false, + }, + maxPointWidth: 50, + }; + + plotOptions.bar = {}; + + // In Highcharts typings, null is not allowed, but it is allowed by the dock + // https://github.com/highcharts/highcharts/issues/14710 + // @ts-ignore + plotOptions.bar.pointWidth = null; + + switch (visualizationId) { + case 'column': + plotOptions.column.stacking = 'normal'; + break; + + case 'bar': + plotOptions.bar.stacking = 'normal'; + break; + + case 'column100p': + plotOptions.column.stacking = 'percent'; + break; + + case 'bar100p': + plotOptions.bar.stacking = 'percent'; + break; + } + break; + + case 'pie': + plotOptions.pie = { + dataLabels: { + enabled: true, + }, + }; + break; + case 'donut': + plotOptions.pie = { + dataLabels: { + enabled: true, + }, + innerSize: '50%', + }; + } + + const result: Highcharts.Options = { + chart, + xAxis, + yAxis, + plotOptions, + time: { + useUTC: true, + }, + }; + + return result; +}; diff --git a/src/server/modes/charts/plugins/ql/module/js/build-graph.ts b/src/server/modes/charts/plugins/ql/module/js/build-graph.ts new file mode 100644 index 0000000..a6628f7 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/js/build-graph.ts @@ -0,0 +1,738 @@ +import type { + FeatureConfig, + Field, + IChartEditor, + Palette, + QlConfig, + ServerChartsConfig, + ServerVisualization, +} from '../../../../../../../shared'; +import { + AxisMode, + DATASET_FIELD_TYPES, + DatasetFieldType, + Feature, + PlaceholderId, + VISUALIZATION_IDS, + isMonitoringOrPrometheusChart, +} from '../../../../../../../shared'; +import {isChartSupportMultipleColors} from '../../../../../../../shared/modules/colors/common-helpers'; +import {mapQlConfigToLatestVersion} from '../../../../../../../shared/modules/config/ql'; +import type {QlConfigResultEntryMetadataDataColumn} from '../../../../../../../shared/types/config/ql'; +import type {PrepareSingleResultArgs} from '../../../datalens/js/helpers/misc/prepare-single-result'; +import prepareSingleResult from '../../../datalens/js/helpers/misc/prepare-single-result'; +import type {ChartPlugin} from '../../../datalens/types'; +import {extractColorPalettesFromData} from '../../../helpers/color-palettes'; +import {getFieldList} from '../../../helpers/misc'; +import type {QLConnectionTypeMap} from '../../utils/connection'; +import { + doesQueryContainOrderBy, + getColumnsAndRows, + log, + visualizationCanHaveContinuousAxis, +} from '../../utils/misc-helpers'; + +import prepareLine from './../../preparers/line'; +import prepareLineTime from './../../preparers/line-time'; +import prepareMetric from './../../preparers/metric'; +import preparePie from './../../preparers/pie'; +import preparePreviewTable from './../../preparers/preview-table'; +import prepareTable from './../../preparers/table'; +import {LINEAR_VISUALIZATIONS, PIE_VISUALIZATIONS} from './../../utils/constants'; +import { + mapItems, + mapVisualizationPlaceholdersItems, + migrateOrAutofillVisualization, +} from './../../utils/visualization-utils'; + +function getVisualizationTemplate(visualizationId: string): ServerVisualization | null { + switch (visualizationId) { + case VISUALIZATION_IDS.LINE: + return { + id: VISUALIZATION_IDS.LINE, + placeholders: [ + {id: PlaceholderId.X, items: [], settings: {}}, + {id: PlaceholderId.Y, items: [], settings: {}}, + ], + } as ServerVisualization; + + case VISUALIZATION_IDS.AREA: + return { + id: VISUALIZATION_IDS.AREA, + placeholders: [ + {id: PlaceholderId.X, items: [], settings: {}}, + {id: PlaceholderId.Y, items: [], settings: {}}, + ], + } as ServerVisualization; + + case VISUALIZATION_IDS.AREA_100P: + return { + id: VISUALIZATION_IDS.AREA_100P, + placeholders: [ + {id: PlaceholderId.X, items: [], settings: {}}, + {id: PlaceholderId.Y, items: [], settings: {}}, + ], + } as ServerVisualization; + + case VISUALIZATION_IDS.COLUMN: + return { + id: VISUALIZATION_IDS.COLUMN, + placeholders: [ + {id: PlaceholderId.X, items: [], settings: {}}, + {id: PlaceholderId.Y, items: [], settings: {}}, + ], + } as ServerVisualization; + + case VISUALIZATION_IDS.COLUMN_100P: + return { + id: VISUALIZATION_IDS.COLUMN_100P, + placeholders: [ + {id: PlaceholderId.X, items: [], settings: {}}, + {id: PlaceholderId.Y, items: [], settings: {}}, + ], + } as ServerVisualization; + + case VISUALIZATION_IDS.PIE: + return { + id: VISUALIZATION_IDS.PIE, + placeholders: [ + {id: PlaceholderId.Colors, items: [], settings: {}}, + {id: PlaceholderId.Measures, items: [], settings: {}}, + ], + } as ServerVisualization; + + case VISUALIZATION_IDS.DONUT: + return { + id: VISUALIZATION_IDS.DONUT, + placeholders: [ + {id: PlaceholderId.Colors, items: [], settings: {}}, + {id: PlaceholderId.Measures, items: [], settings: {}}, + ], + } as ServerVisualization; + + default: + return null; + } +} + +function getRowsLimitForVisualization(visualizationId: string): number { + switch (visualizationId) { + case VISUALIZATION_IDS.PIE: + case VISUALIZATION_IDS.DONUT: + return 1000; + default: + return 75000; + } +} + +function prepareDataForGravityCharts({ + columns, + rows, +}: { + columns: QlConfigResultEntryMetadataDataColumn[]; + rows: string[][]; +}) { + const datasetId = 'ql-mocked-dataset'; + + const order: { + datasetId: string; + title: string; + }[] = []; + + const resultData = { + data: rows, + order, + totals: [], + }; + + const orderedColumns = [...columns].sort((columnA, columnB) => { + return columnA.name > columnB.name ? 1 : -1; + }); + + const columnNames = new Set(); + const fields = columns.map((column) => { + const guessedType = (column.biType || DATASET_FIELD_TYPES.STRING) as DATASET_FIELD_TYPES; + + let fieldGuid; + + if (columnNames.has(column.name)) { + const orderedIndex = orderedColumns.findIndex( + (orderedColumn) => orderedColumn.name === column.name, + ); + + fieldGuid = `${column.name}-${orderedIndex}`; + } else { + columnNames.add(column.name); + + fieldGuid = column.name; + } + + return { + guid: fieldGuid, + title: column.name, + datasetId, + data_type: guessedType, + cast: guessedType, + type: DatasetFieldType.Dimension, + calc_mode: 'direct', + + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }; + }) as unknown as Field[]; + + if ( + fields.length > 1 && + fields.findIndex(({type}) => type === DatasetFieldType.Pseudo) === -1 + ) { + fields.push({ + title: 'Column Names', + type: DatasetFieldType.Pseudo, + data_type: DATASET_FIELD_TYPES.STRING, + + inspectHidden: true, + formulaHidden: true, + noEdit: true, + guid: '', + datasetId: '', + cast: DATASET_FIELD_TYPES.STRING, + calc_mode: 'direct', + } as unknown as Field); + } + + const distincts: Set[] = []; + + rows.forEach((row) => { + row.forEach((value, j) => { + if (!distincts[j]) { + distincts[j] = new Set(); + } + + if (!distincts[j].has(value)) { + distincts[j].add(String(value)); + } + }); + }); + + const resultDistincts: Record = {}; + + fields.forEach((column, i) => { + if (distincts[i]) { + resultDistincts[column.guid] = Array.from(distincts[i]).sort(); + } + }); + + const idToTitle: Record = {}; + const idToDataType: Record = {}; + + fields.forEach((column) => { + idToTitle[column.guid] = column.title; + idToDataType[column.guid] = column.data_type; + order.push({ + datasetId, + title: column.title, + }); + }); + + return { + fields, + resultData, + idToTitle, + idToDataType, + resultDistincts, + datasetId, + }; +} + +type BuildGraphArgs = { + shared: QlConfig; + ChartEditor: IChartEditor; + features: FeatureConfig; + palettes: Record; + qlConnectionTypeMap: QLConnectionTypeMap; + plugin?: ChartPlugin; + defaultColorPaletteId: string; +}; + +// eslint-disable-next-line complexity +export function buildGraph(args: BuildGraphArgs) { + const { + shared, + ChartEditor, + features, + palettes, + qlConnectionTypeMap, + plugin, + defaultColorPaletteId, + } = args; + const data = ChartEditor.getLoadedData(); + + log('LOADED DATA:', data); + + let tablePreviewData; + let prepare; + let result; + + const config = mapQlConfigToLatestVersion(shared, {i18n: ChartEditor.getTranslation}); + const {colorPalettes: loadedColorPalettes, loadedData} = extractColorPalettesFromData(data); + + const {columns, rows} = getColumnsAndRows({ + chartType: config.chartType, + ChartEditor, + queries: config.queries, + connectionType: config.connection.type, + data: loadedData, + qlConnectionTypeMap, + }); + + if ( + typeof columns === 'undefined' || + columns.length === 0 || + typeof rows === 'undefined' || + rows.length === 0 + ) { + return {}; + } + + log('RECOGNIZED COLUMNS:', columns); + log('RECOGNIZED ROWS:', rows); + + if (config.connection.dataExportForbidden) { + // Hiding the data export button in the ChartKit menu + ChartEditor.setExtra?.('dataExportForbidden', true); + } + + const sharedVisualization = config.visualization as ServerVisualization; + const { + colors: sharedColors, + labels: sharedLabels, + shapes: sharedShapes, + order: sharedOrder, + } = config; + + if (sharedVisualization?.placeholders) { + // Branch for actual ql charts + + const order: { + datasetId: string; + title: string; + }[] = []; + + const resultData = { + data: rows, + order, + totals: [], + }; + + const datasetId = 'ql-mocked-dataset'; + + const orderedColumns = [...columns].sort((columnA, columnB) => { + return columnA.name > columnB.name ? 1 : -1; + }); + + const columnNames = new Set(); + + // Converting dashsql columns to wizard fields + const fields = columns.map((column) => { + const guessedType = (column.biType || + DATASET_FIELD_TYPES.STRING) as DATASET_FIELD_TYPES; + + let fieldGuid; + + if (columnNames.has(column.name)) { + const orderedIndex = orderedColumns.findIndex( + (orderedColumn) => orderedColumn.name === column.name, + ); + + fieldGuid = `${column.name}-${orderedIndex}`; + } else { + columnNames.add(column.name); + + fieldGuid = column.name; + } + + return { + guid: fieldGuid, + title: column.name, + datasetId, + data_type: guessedType, + cast: guessedType, + type: DatasetFieldType.Dimension, + calc_mode: 'direct', + + inspectHidden: true, + formulaHidden: true, + noEdit: true, + }; + }) as unknown as Field[]; + + // Adding pseudo column named "Column names" + if ( + fields.length > 1 && + fields.findIndex(({type}) => type === DatasetFieldType.Pseudo) === -1 + ) { + fields.push({ + title: 'Column Names', + type: DatasetFieldType.Pseudo, + data_type: DATASET_FIELD_TYPES.STRING, + + inspectHidden: true, + formulaHidden: true, + noEdit: true, + guid: '', + datasetId: '', + cast: DATASET_FIELD_TYPES.STRING, + calc_mode: 'direct', + } as unknown as Field); + } + + const distincts: Set[] = []; + + // Generating distincts from data + rows.forEach((row) => { + row.forEach((value, j) => { + if (!distincts[j]) { + distincts[j] = new Set(); + } + + if (!distincts[j].has(value)) { + distincts[j].add(String(value)); + } + }); + }); + + const resultDistincts: Record = {}; + + fields.forEach((column, i) => { + if (distincts[i]) { + resultDistincts[column.guid] = Array.from(distincts[i]).sort(); + } + }); + + const idToTitle: Record = {}; + const idToDataType: Record = {}; + const datasetsIds: string[] = []; + + fields.forEach((column) => { + idToTitle[column.guid] = column.title; + idToDataType[column.guid] = column.data_type; + order.push({ + datasetId, + title: column.title, + }); + }); + + let newColors = sharedColors; + let newLabels = sharedLabels; + let newShapes = sharedShapes; + let newVisualization: ServerVisualization = sharedVisualization; + + const visualizationIsEmpty = sharedVisualization.placeholders.every( + (placeholder) => placeholder.items.length === 0, + ); + + if (visualizationIsEmpty) { + const isMultipleDistinctsAvailable = + features[Feature.MultipleColorsInVisualization] && + isChartSupportMultipleColors(config.chartType, sharedVisualization.id); + // Visualization is empty, so we need to autofill it + const {colors, visualization} = migrateOrAutofillVisualization({ + visualization: sharedVisualization, + fields, + rows, + order: sharedOrder, + colors: sharedColors, + distinctsMap: isMultipleDistinctsAvailable ? resultDistincts : undefined, + }); + + if (colors) { + newColors = colors; + } + + if (visualization) { + newVisualization = visualization; + } + } else { + newVisualization = mapVisualizationPlaceholdersItems({ + visualization: sharedVisualization, + fields, + }); + + newColors = mapItems({ + fields, + items: sharedColors, + }); + + newLabels = mapItems({ + fields, + items: sharedLabels, + }); + + newShapes = mapItems({ + fields, + items: sharedShapes, + }); + } + + const available = [...(fields as unknown as Field[])]; + + const disableDefaultSorting = doesQueryContainOrderBy(shared.queryValue); + + const prepareSingleResultArgs: PrepareSingleResultArgs = { + resultData, + shared: { + ...config, + available, + colors: newColors, + labels: newLabels, + shapes: newShapes, + sort: [], + sharedData: {}, + } as unknown as ServerChartsConfig, + visualization: newVisualization, + idToTitle, + idToDataType, + ChartEditor, + datasetsIds, + loadedColorPalettes, + disableDefaultSorting, + palettes, + features, + plugin, + defaultColorPaletteId, + }; + + result = prepareSingleResult(prepareSingleResultArgs); + + if (config.preview) { + result.tablePreviewData = preparePreviewTable({ + shared: config, + columns, + rows, + ChartEditor, + }); + } + + if (visualizationCanHaveContinuousAxis(newVisualization)) { + const targetPlaceholderId = [ + VISUALIZATION_IDS.BAR, + VISUALIZATION_IDS.BAR_100P, + ].includes(newVisualization.id) + ? PlaceholderId.Y + : PlaceholderId.X; + + const targetPlaceholder = newVisualization.placeholders.find( + ({id}) => id === targetPlaceholderId, + ); + + if (targetPlaceholder && targetPlaceholder.items[0]) { + if (disableDefaultSorting) { + targetPlaceholder.settings = { + axisModeMap: { + [targetPlaceholder.items[0].guid]: AxisMode.Discrete, + }, + disableAxisMode: true, + }; + } else { + targetPlaceholder.settings = { + disableAxisMode: false, + }; + } + } + } + + if (Array.isArray(result) && result[0]) { + result[0].metadata = { + visualization: newVisualization, + available, + colors: newColors, + labels: newLabels, + shapes: newShapes, + distincts: resultDistincts, + }; + } else { + result.metadata = { + visualization: newVisualization, + available, + colors: newColors, + labels: newLabels, + shapes: newShapes, + distincts: resultDistincts, + }; + + if (result.graphs) { + const pointConflict = result.graphs.some((graph: any) => graph.pointConflict); + + if (pointConflict) { + result.metadata.pointConflict = pointConflict; + } + } + } + + ChartEditor.setExtra('datasets', [ + { + id: datasetId, + fieldsList: getFieldList(fields, newVisualization.placeholders), + }, + ]); + + log('RESULT:', result); + + return result; + } else if (isMonitoringOrPrometheusChart(config.chartType)) { + // Branch for older ql charts of promql type + // Deprecated + // Works only for old-saved charts from dashboards + + if (config.preview) { + tablePreviewData = preparePreviewTable({shared: config, columns, rows, ChartEditor}); + } + + const {id} = config.visualization; + if (LINEAR_VISUALIZATIONS.has(id)) { + prepare = prepareLineTime; + } else if (PIE_VISUALIZATIONS.has(id)) { + prepare = preparePie; + } else if (id === VISUALIZATION_IDS.METRIC) { + prepare = prepareMetric; + } else if (id === VISUALIZATION_IDS.TABLE) { + prepare = prepareTable; + } + } else { + // Branch for older ql charts of sql type + // Deprecated + // Works only for old-saved charts from dashboards + + const {id} = config.visualization; + + if (plugin === 'gravity-charts') { + const visualization = getVisualizationTemplate(id); + + if (visualization) { + const rowsLimit = getRowsLimitForVisualization(id); + + if (rows.length > rowsLimit) { + ChartEditor._setError({ + code: 'ERR.CHARTS.ROWS_NUMBER_OVERSIZE', + details: { + rowsLength: rows.length, + rowsLimit: rowsLimit, + }, + }); + + return {}; + } + + const {fields, resultData, idToTitle, idToDataType, resultDistincts} = + prepareDataForGravityCharts({columns, rows}); + const {colors: newColors, visualization: newVisualization} = + migrateOrAutofillVisualization({ + visualization, + fields, + rows, + order: config.order, + colors: [], + }); + const available = [...fields]; + const disableDefaultSorting = doesQueryContainOrderBy(shared.queryValue); + const datasetsIds: string[] = []; + const prepareSingleResultArgs: PrepareSingleResultArgs = { + resultData, + shared: { + ...config, + available, + colors: newColors, + labels: [], + shapes: [], + sort: [], + sharedData: {}, + } as unknown as ServerChartsConfig, + visualization: newVisualization, + idToTitle, + idToDataType, + ChartEditor, + datasetsIds, + loadedColorPalettes, + disableDefaultSorting, + palettes, + features, + plugin, + defaultColorPaletteId, + }; + + result = prepareSingleResult(prepareSingleResultArgs); + result.metadata = { + visualization: newVisualization, + available, + colors: newColors, + labels: [], + shapes: [], + distincts: resultDistincts, + }; + + log('RESULT:', result); + + return result; + } + } + + if (config.preview) { + tablePreviewData = preparePreviewTable({shared: config, columns, rows, ChartEditor}); + } + + let rowsLimit; + + switch (id) { + case VISUALIZATION_IDS.LINE: + case VISUALIZATION_IDS.AREA: + case VISUALIZATION_IDS.AREA_100P: + case VISUALIZATION_IDS.COLUMN: + case VISUALIZATION_IDS.COLUMN_100P: + case VISUALIZATION_IDS.BAR: + case VISUALIZATION_IDS.BAR_100P: + prepare = prepareLine; + rowsLimit = 75000; + break; + + case VISUALIZATION_IDS.PIE: + case VISUALIZATION_IDS.DONUT: + prepare = preparePie; + rowsLimit = 1000; + break; + + case VISUALIZATION_IDS.METRIC: + prepare = prepareMetric; + rowsLimit = 1000; + break; + + case VISUALIZATION_IDS.TABLE: + prepare = prepareTable; + rowsLimit = 100000; + break; + + default: + return {}; + } + + if (rows.length > rowsLimit) { + ChartEditor._setError({ + code: 'ERR.CHARTS.ROWS_NUMBER_OVERSIZE', + details: { + rowsLength: rows.length, + rowsLimit: rowsLimit, + }, + }); + + return {}; + } + } + + if (prepare) { + result = prepare({shared: config, columns, rows, ChartEditor, tablePreviewData}); + } + + log('RESULT:', result); + + return result; +} diff --git a/src/server/modes/charts/plugins/ql/module/library-config/build-library-config.ts b/src/server/modes/charts/plugins/ql/module/library-config/build-library-config.ts new file mode 100644 index 0000000..135d44a --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/library-config/build-library-config.ts @@ -0,0 +1,61 @@ +import type { + FeatureConfig, + IChartEditor, + QlConfig, + ServerChartsConfig, + ServerVisualization, +} from '../../../../../../../shared'; +import {ChartsConfigVersion, isYAGRVisualization} from '../../../../../../../shared'; +import {mapQlConfigToLatestVersion} from '../../../../../../../shared/modules/config/ql'; +import {buildHighchartsConfigPrivate as buildHighchartsConfigWizard} from '../../../datalens/highcharts'; +import {log} from '../../utils/misc-helpers'; +import buildYagrConfig from '../../yagr'; +import buildHighchartsConfig from '../highcharts'; + +type BuildLibraryConfigArgs = { + shared: QlConfig; + ChartEditor: IChartEditor; + features: FeatureConfig; +}; + +export function buildLibraryConfig({shared, ChartEditor, features}: BuildLibraryConfigArgs) { + const config = mapQlConfigToLatestVersion(shared, {i18n: ChartEditor.getTranslation}); + + const visualization = config.visualization as ServerVisualization; + + if (isYAGRVisualization(config.chartType, visualization.id)) { + const result = buildYagrConfig({shared, ChartEditor}); + + log('LIBRARY CONFIG (YAGR):'); + log(result); + + return result; + } else if (visualization?.placeholders) { + const result = buildHighchartsConfigWizard({ + // @ts-ignore we are passing empty arrays just as a stub + shared: { + ...config, + filters: [], + hierarchies: [], + links: [], + updates: [], + version: ChartsConfigVersion.V12, + datasetsIds: [], + datasetsPartialFields: [], + } as ServerChartsConfig, + features, + }); + + log('LIBRARY CONFIG (WIZARD HC):'); + log(result); + + return result; + } else { + const result = buildHighchartsConfig({shared: config, ChartEditor}); + + log('LIBRARY CONFIG (HC):'); + log(result); + + return result; + } +} diff --git a/src/server/modes/charts/plugins/ql/module/library-config/index.ts b/src/server/modes/charts/plugins/ql/module/library-config/index.ts new file mode 100644 index 0000000..5d1c6f7 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/library-config/index.ts @@ -0,0 +1,11 @@ +import type {IChartEditor, QlConfig} from '../../../../../../../shared'; +import {getServerFeatures} from '../../../../../../../shared'; +import {registry} from '../../../../../../registry'; + +import {buildLibraryConfig} from './build-library-config'; + +export default ({shared, ChartEditor}: {shared: QlConfig; ChartEditor: IChartEditor}) => { + const app = registry.getApp(); + const features = getServerFeatures(app.nodekit.ctx); + return buildLibraryConfig({shared, ChartEditor, features}); +}; diff --git a/src/server/modes/charts/plugins/ql/module/private-module.ts b/src/server/modes/charts/plugins/ql/module/private-module.ts new file mode 100644 index 0000000..9f68c9a --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/private-module.ts @@ -0,0 +1,14 @@ +import {setConsole} from '../utils/misc-helpers'; + +import {buildChartConfig} from './config/build-chart-config'; +import {buildGraph} from './js/build-graph'; +import {buildLibraryConfig} from './library-config/build-library-config'; +import {buildSources} from './url/build-sources'; + +export default { + buildLibraryConfig, + buildSources, + buildGraph, + buildChartConfig, + setConsole, +}; diff --git a/src/server/modes/charts/plugins/ql/module/url/build-sources.ts b/src/server/modes/charts/plugins/ql/module/url/build-sources.ts new file mode 100644 index 0000000..eebe9f0 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/url/build-sources.ts @@ -0,0 +1,234 @@ +import type {Dictionary, ServerChartsConfig, StringParams} from '../../../../../../../shared'; +import { + ConnectorType, + QLParamType, + isMonitoringOrPrometheusChart, + resolveIntervalDate, + resolveOperation, + resolveRelativeDate, +} from '../../../../../../../shared'; +import {mapQlConfigToLatestVersion} from '../../../../../../../shared/modules/config/ql'; +import {getColorPalettesRequests} from '../../../helpers/color-palettes'; +import { + buildSource, + iterateThroughVisibleQueries, + log, + prepareQuery, +} from '../../utils/misc-helpers'; + +import type {BuildSourcesArgs} from './types'; + +const resolveUrlParameter = (urlParamValue: string | string[]) => { + if (Array.isArray(urlParamValue)) { + return urlParamValue.map((value) => { + const resolvedValueAndOperation = resolveOperation(value); + + return resolvedValueAndOperation?.value || ''; + }); + } else { + const resolvedValueAndOperation = resolveOperation(urlParamValue); + + return resolvedValueAndOperation?.value || ''; + } +}; + +const prepareDefaultDate = (date: string) => { + const resolvedDate = resolveRelativeDate(date); + + return resolvedDate || date; +}; + +export function buildSources(args: BuildSourcesArgs) { + const {shared, ChartEditor, palettes, qlConnectionTypeMap, features} = args; + const config = mapQlConfigToLatestVersion(shared, {i18n: ChartEditor.getTranslation}); + + const urlParams = ChartEditor.getParams(); + + let params: StringParams = {}; + + // Measure the values of the parameters: + // 1) default + // 2) override - which in ui ql overwritten the default ones (this is for ui) + // 3) from the url (this is for dashboards) + if (config.params) { + const chartParams: StringParams = config.params.reduce( + // eslint-disable-next-line complexity + (accumulated: StringParams, param) => { + const paramIsInterval = + param.type === QLParamType.DateInterval || + param.type === QLParamType.DatetimeInterval; + + // If the parameter is interval + if (paramIsInterval && !Array.isArray(param.defaultValue)) { + const fromKey = `${param.name}_from`; + const toKey = `${param.name}_to`; + + // If there is in the url + if (urlParams[param.name] && urlParams[param.name][0] !== '') { + // We take from the url + const resolvedValueAndOperation = resolveOperation( + urlParams[param.name][0], + ); + + if (resolvedValueAndOperation) { + const resolvedInterval = resolveIntervalDate( + resolvedValueAndOperation.value, + ); + + if (resolvedInterval !== null) { + accumulated[fromKey] = resolvedInterval.from; + accumulated[toKey] = resolvedInterval.to; + } + } + } else if ( + typeof param.overridenValue === 'object' && + param.overridenValue !== null && + !Array.isArray(param.overridenValue) && + typeof param.overridenValue.from !== 'undefined' && + typeof param.overridenValue.to !== 'undefined' + ) { + // Otherwise, we take override (if there is) + accumulated[fromKey] = param.overridenValue.from; + accumulated[toKey] = param.overridenValue.to; + } else if (typeof param.defaultValue === 'object') { + // And if there is no override, then we take default + if (typeof param.defaultValue?.from !== 'undefined') { + accumulated[fromKey] = prepareDefaultDate(param.defaultValue?.from); + } + + if (typeof param.defaultValue?.to !== 'undefined') { + accumulated[toKey] = prepareDefaultDate(param.defaultValue?.to); + } + } + } else if (typeof param.defaultValue === 'string') { + // The parameter is the usual value + + // If there is in the url, we take from the url + if (urlParams[param.name] && urlParams[param.name][0] !== '') { + accumulated[param.name] = resolveUrlParameter(urlParams[param.name]); + } else if ( + (typeof param.overridenValue === 'string' && param.overridenValue !== '') || + Array.isArray(param.overridenValue) + ) { + // Otherwise, we take override (if there is) + accumulated[param.name] = param.overridenValue; + } else if ( + param.type === QLParamType.Date || + param.type === QLParamType.Datetime + ) { + accumulated[param.name] = prepareDefaultDate(param.defaultValue); + } else { + accumulated[param.name] = param.defaultValue; + } + } else if (Array.isArray(param.defaultValue)) { + // Parameter - array + + // If there is in the url, we take from the url + if (urlParams[param.name] && urlParams[param.name][0] !== '') { + accumulated[param.name] = resolveUrlParameter(urlParams[param.name]); + } else if ( + (typeof param.overridenValue === 'string' && param.overridenValue !== '') || + Array.isArray(param.overridenValue) + ) { + // Otherwise, we take override (if there is) + accumulated[param.name] = param.overridenValue; + } else { + // Otherwise we take default + accumulated[param.name] = param.defaultValue; + } + } + + return accumulated; + }, + {}, + ); + + params = {...urlParams, ...chartParams}; + } + + const { + connection: {entryId: connectionEntryId, type: connectionType}, + } = config; + + let sources: Dictionary = {}; + try { + if (isMonitoringOrPrometheusChart(config.chartType)) { + if (params.interval) { + const operation = ChartEditor.resolveOperation(params.interval[0]); + + if (operation) { + const intervalValues = ChartEditor.resolveInterval(operation.value); + + if (intervalValues && intervalValues.from && intervalValues.to) { + params.from = intervalValues?.from; + params.to = intervalValues?.to; + } + } + } + + iterateThroughVisibleQueries( + config.queries, + ({value: queryValue, params: queryParams = []}, i) => { + const localParams = {...params}; + + queryParams.forEach((queryParam) => { + if (typeof queryParam.defaultValue === 'string') { + localParams[queryParam.name] = queryParam.defaultValue; + } + }); + + const localParamsDescription = [...config.params, ...queryParams]; + + const source = buildSource({ + // specify the desired connection id + id: connectionEntryId, + + connectionType: connectionType || ConnectorType.Clickhouse, + + // requesting a query + query: prepareQuery(queryValue), + + params: localParams, + paramsDescription: localParamsDescription, + qlConnectionTypeMap, + features, + }); + + sources[`ql_${i}`] = source; + }, + ); + } else { + sources = { + sql: buildSource({ + // specify the desired connection id + id: connectionEntryId, + + connectionType: connectionType || ConnectorType.Clickhouse, + + // requesting a query + query: prepareQuery(config.queryValue), + + params, + paramsDescription: config.params, + qlConnectionTypeMap, + features, + }), + }; + } + + Object.assign( + sources, + getColorPalettesRequests({config: config as unknown as ServerChartsConfig, palettes}), + ); + } catch (error) { + ChartEditor._setError({ + code: 'ERR.CK.PROCESSING_ERROR', + }); + } + + log('SOURCES:', sources); + + return { + ...sources, + }; +} diff --git a/src/server/modes/charts/plugins/ql/module/url/index.ts b/src/server/modes/charts/plugins/ql/module/url/index.ts new file mode 100644 index 0000000..6703695 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/url/index.ts @@ -0,0 +1,20 @@ +import type {FeatureConfig, IChartEditor, QlConfig} from '../../../../../../../shared'; +import {registry} from '../../../../../../registry'; + +import {buildSources} from './build-sources'; + +export default function ({ + shared, + ChartEditor, + features, +}: { + shared: QlConfig; + ChartEditor: IChartEditor; + features: FeatureConfig; +}) { + const {getAvailablePalettesMap} = registry.common.functions.getAll(); + const palettes = getAvailablePalettesMap(); + const qlConnectionTypeMap = registry.getQLConnectionTypeMap(); + + return buildSources({shared, ChartEditor, palettes, qlConnectionTypeMap, features}); +} diff --git a/src/server/modes/charts/plugins/ql/module/url/types.ts b/src/server/modes/charts/plugins/ql/module/url/types.ts new file mode 100644 index 0000000..91fffd2 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/module/url/types.ts @@ -0,0 +1,10 @@ +import type {FeatureConfig, IChartEditor, Palette, QlConfig} from '../../../../../../../shared'; +import type {QLConnectionTypeMap} from '../../utils/connection'; + +export type BuildSourcesArgs = { + shared: QlConfig; + ChartEditor: IChartEditor; + palettes: Record; + qlConnectionTypeMap: QLConnectionTypeMap; + features: FeatureConfig; +}; diff --git a/src/server/modes/charts/plugins/ql/preparers/line-time.ts b/src/server/modes/charts/plugins/ql/preparers/line-time.ts new file mode 100644 index 0000000..d1dfb14 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/preparers/line-time.ts @@ -0,0 +1,436 @@ +import {DATALENS_QL_TYPES, getUtcDateTime} from '../../../../../../shared'; +import type { + IChartEditor, + QlConfig, + QlConfigPreviewTableData, + QlConfigResultEntryMetadataDataColumn, + QlConfigResultEntryMetadataDataColumnOrGroup, + QlConfigResultEntryMetadataDataGroup, +} from '../../../../../../shared'; +import {isLegendEnabled} from '../../datalens/utils/misc-helpers'; +import type {QLRenderResultYagr, QLRenderResultYagrGraph, QLValue} from '../utils/misc-helpers'; +import { + formatUnknownTypeValue, + isGroup, + parseNumberValue, + renderValue, +} from '../utils/misc-helpers'; + +const {getColorsForNames} = require('../utils/colors'); + +const collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}); + +// eslint-disable-next-line complexity +export default ({ + shared, + columns, + rows, + tablePreviewData, + ChartEditor, +}: { + shared: QlConfig; + columns: QlConfigResultEntryMetadataDataColumn[]; + rows: string[][]; + tablePreviewData?: QlConfigPreviewTableData; + ChartEditor: IChartEditor; +}) => { + if (columns === null) { + return {}; + } + + const columnTypes = columns.map((column) => column.typeName); + + const xGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'X', + group: true, + undragable: true, + capacity: 1, + size: 0, + }; + + const yGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Y', + group: true, + undragable: true, + allowedTypes: [DATALENS_QL_TYPES.NUMBER], + size: 0, + }; + + const colorGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Colors', + group: true, + undragable: true, + size: 0, + }; + + const availableGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Available', + group: true, + undragable: true, + size: 0, + }; + + const order: QlConfigResultEntryMetadataDataColumnOrGroup[] = [ + xGroup, + yGroup, + colorGroup, + availableGroup, + ]; + + let xIndex = -1; + let yIndexes: number[] = []; + let colorIndexes: number[] = []; + const availableIndexes: number[] = []; + + if (shared.order && shared.order.length) { + let collectingX = false; + let collectingY = false; + let collectingColor = false; + let collectingAvailable = false; + + let draggedX = false; + let draggedY = false; + + shared.order.forEach((item: QlConfigResultEntryMetadataDataColumnOrGroup) => { + const itemIsGroup = isGroup(item); + + if (itemIsGroup && item.name === 'X') { + collectingX = true; + return; + } + + if (itemIsGroup && item.name === 'Y') { + collectingX = false; + collectingY = true; + return; + } + + if (itemIsGroup && item.name === 'Colors') { + collectingY = false; + collectingColor = true; + return; + } + + if (itemIsGroup && item.name === 'Available') { + collectingX = false; + collectingY = false; + collectingColor = false; + collectingAvailable = true; + return; + } + + if (collectingX && !itemIsGroup) { + xIndex = columns.findIndex((column) => column.name === item.name); + draggedX = true; + } + + if (collectingY && !itemIsGroup) { + const yIndex = columns.findIndex((column) => column.name === item.name); + + if (yIndex > -1) { + yIndexes.push(yIndex); + } + + draggedY = true; + } + + if (collectingColor && !itemIsGroup) { + colorIndexes.push(columns.findIndex((column) => column.name === item.name)); + } + + if (collectingAvailable) { + availableIndexes.push(columns.findIndex((column) => column.name === item.name)); + } + }); + + if (draggedY) { + const findNewYIndex = () => + columnTypes.findIndex( + (columnType, index) => + columnType === DATALENS_QL_TYPES.NUMBER && + index !== xIndex && + !colorIndexes.includes(index) && + !yIndexes.includes(index) && + !availableIndexes.includes(index), + ); + + let newFoundYIndex = findNewYIndex(); + while (newFoundYIndex > -1) { + yIndexes.push(newFoundYIndex); + + newFoundYIndex = findNewYIndex(); + } + } + + if (draggedX && xIndex === -1) { + xIndex = columns.findIndex( + (_column, index) => !yIndexes.includes(index) && !colorIndexes.includes(index), + ); + } + } else { + const findNewYIndex = () => + columnTypes.findIndex( + (columnType, index) => + columnType === DATALENS_QL_TYPES.NUMBER && !yIndexes.includes(index), + ); + + let newFoundYIndex = findNewYIndex(); + while (newFoundYIndex > -1) { + yIndexes.push(newFoundYIndex); + + newFoundYIndex = findNewYIndex(); + } + + xIndex = columns.findIndex((_column, index) => !yIndexes.includes(index)); + + const homogeneousValues: Set[] = []; + const iToHomogeneity: Boolean[] = []; + rows.forEach((row) => { + row.forEach((value, i) => { + if (typeof homogeneousValues[i] === 'undefined') { + homogeneousValues[i] = new Set([value]); + iToHomogeneity[i] = true; + } else if (iToHomogeneity[i] === false) { + return; + } else if (!homogeneousValues[i].has(value)) { + iToHomogeneity[i] = false; + } + }); + }); + + iToHomogeneity.forEach((homogeneity, i) => { + if (!homogeneity && xIndex !== i && !yIndexes.includes(i)) { + colorIndexes.push(i); + } + }); + + colorIndexes.sort((colorIndex1: number, colorIndex2: number) => { + if (columns[colorIndex1] && columns[colorIndex2]) { + return columns[colorIndex1].name > columns[colorIndex2].name ? 1 : -1; + } else { + return 0; + } + }); + } + + colorIndexes = colorIndexes.filter((colorIndex) => colorIndex > -1); + yIndexes = yIndexes.filter((yIndex) => yIndex > -1); + + let inserted = 0; + if (columns[xIndex]) { + order.splice(1 + inserted, 0, columns[xIndex]); + ++inserted; + + xGroup.size = 1; + } + + yIndexes.forEach((yIndex) => { + if (columns[yIndex]) { + order.splice(2 + inserted, 0, columns[yIndex]); + ++inserted; + + if (typeof yGroup.size === 'number') { + yGroup.size += 1; + } else { + yGroup.size = 1; + } + } + }); + + if (yIndexes.length > 1) { + if (columns.findIndex(({pseudo}) => pseudo) === -1) { + columns.push({ + typeName: 'String', + name: 'Measure Names', + pseudo: true, + undragable: true, + }); + } + + colorGroup.size = 1; + + colorIndexes.push(columns.length - 1); + } + + if (colorIndexes.length > 0) { + colorIndexes.forEach((colorIndex) => { + order.splice(3 + inserted, 0, columns[colorIndex]); + ++inserted; + ++colorGroup.size; + }); + + if (isLegendEnabled(shared.extraSettings)) { + ChartEditor.updateLibraryConfig({ + legend: { + show: true, + }, + }); + } + } + + columns.forEach((column, index) => { + if (index !== xIndex && !yIndexes.includes(index) && !colorIndexes.includes(index)) { + order.push(column); + } + }); + + const result: QLRenderResultYagr = { + timeZone: 'UTC', + metadata: { + order, + }, + tablePreviewData, + }; + + const xIsDate = + columnTypes[xIndex] === DATALENS_QL_TYPES.DATE || + columnTypes[xIndex] === DATALENS_QL_TYPES.DATETIME; + + if (yIndexes.length > 0 && columns[xIndex]) { + let xValues: QLValue[] = []; + let colorValues: QLValue[] = []; + + const dataMatrix: Record> = + {}; + + rows.forEach((row) => { + let xValue: QLValue = row[xIndex]; + + if (xIsDate) { + // CHARTS-6632 - revision/study of yagr is necessary, after that moment.utc(xValue) is possible.valueOf(); + xValue = (getUtcDateTime(xValue)?.valueOf() || 0) / 1000; + } else if (columnTypes[xIndex] === DATALENS_QL_TYPES.UNKNOWN) { + xValue = formatUnknownTypeValue(xValue); + } + + xValues.push(xValue); + + yIndexes.forEach((yIndex) => { + const yValue: string | number = row[yIndex]; + + if (colorIndexes.length > 0) { + let colorValue = ''; + colorIndexes.forEach((colorIndex) => { + const colorValuePart: QLValue = columns[colorIndex].pseudo + ? columns[yIndex].name + : row[colorIndex]; + + colorValue = + colorValue.length > 0 + ? `${colorValue}; ${colorValuePart}` + : colorValuePart; + }); + + let dataCell = dataMatrix[String(xValue)] as Record< + string | number, + number | null + >; + + if (typeof dataCell === 'undefined') { + dataCell = dataMatrix[String(xValue)] = {}; + } + + if (typeof dataCell === 'object' && dataCell !== null) { + dataCell[String(colorValue)] = parseNumberValue(yValue); + } + + colorValues.push(colorValue); + } else { + dataMatrix[String(xValue)] = parseNumberValue(yValue); + } + }); + }); + + xValues = Array.from(new Set(xValues)).sort(); + colorValues = Array.from(new Set(colorValues)).sort(); + + result.timeline = xValues.map((value) => Number(value)); + + if (colorIndexes.length > 0) { + const graphs: QLRenderResultYagrGraph[] = colorValues.map((colorValue) => { + return { + id: renderValue(colorValue), + name: renderValue(colorValue), + color: 'rgb(0,127,0)', + data: [], + }; + }); + + xValues.forEach((xValue) => { + const dataCell = dataMatrix[String(xValue)] as Record; + if (typeof dataCell === 'object' && dataCell !== null) { + colorValues.forEach((colorValue: QLValue, i) => { + if (typeof dataCell[String(colorValue)] === 'undefined') { + graphs[i].data.push(null); + } else { + graphs[i].data.push(dataCell[String(colorValue)]); + } + }); + } + }); + + result.graphs = graphs; + } else { + result.graphs = []; + + yIndexes.forEach((yIndex) => { + const graph = { + id: columns[yIndex].name, + name: columns[yIndex].name, + data: xValues.map((xValue) => { + return dataMatrix[String(xValue)] as number; + }), + }; + + result.graphs?.push(graph); + }); + } + } else if (columns[xIndex]) { + result.graphs = [ + { + data: rows.map(() => { + return null; + }), + }, + ]; + } else { + result.graphs = []; + } + + result.graphs.sort((graph1, graph2) => { + if (graph1.name && graph2.name) { + return collator.compare(String(graph1.name), String(graph2.name)); + } else { + return 0; + } + }); + + let colors: string[]; + + if (shared.visualization.id === 'area' && result.graphs.length > 1) { + colors = getColorsForNames( + result.graphs.map(({name}) => name), + {type: 'gradient'}, + ); + } else { + colors = getColorsForNames(result.graphs.map(({name}) => name)); + } + + result.graphs.forEach((graph, i) => { + graph.color = colors[i]; + }); + + result.axes = [ + { + scale: 'x', + plotLines: [ + { + width: 3, + color: '#ffa0a0', + }, + ], + }, + ]; + + return result; +}; diff --git a/src/server/modes/charts/plugins/ql/preparers/line.ts b/src/server/modes/charts/plugins/ql/preparers/line.ts new file mode 100644 index 0000000..cafec27 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/preparers/line.ts @@ -0,0 +1,391 @@ +import moment from 'moment'; + +import type {IChartEditor, QlConfigPreviewTableData} from '../../../../../../shared'; +import {DATALENS_QL_TYPES} from '../../../../../../shared'; +import type { + QlConfig, + QlConfigResultEntryMetadataDataColumn, + QlConfigResultEntryMetadataDataColumnOrGroup, + QlConfigResultEntryMetadataDataGroup, +} from '../../../../../../shared/types/config/ql'; +import type {QLRenderResultHC, QLRenderResultHCGraph, QLValue} from '../utils/misc-helpers'; +import { + formatUnknownTypeValue, + isGroup, + parseNumberValue, + renderValue, +} from '../utils/misc-helpers'; + +// eslint-disable-next-line complexity +export default ({ + shared, + columns, + rows, + ChartEditor, + tablePreviewData, +}: { + shared: QlConfig; + columns: QlConfigResultEntryMetadataDataColumn[]; + rows: string[][]; + ChartEditor: IChartEditor; + tablePreviewData?: QlConfigPreviewTableData; +}) => { + if (columns === null) { + return {}; + } + + const columnTypes = columns.map((column) => column.typeName); + + const xGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'X', + group: true, + undragable: true, + capacity: 1, + size: 0, + }; + + const yGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Y', + group: true, + undragable: true, + allowedTypes: [DATALENS_QL_TYPES.NUMBER], + size: 0, + }; + + const colorGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Colors', + group: true, + undragable: true, + size: 0, + }; + + const availableGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Available', + group: true, + undragable: true, + size: 0, + }; + + const order: QlConfigResultEntryMetadataDataColumnOrGroup[] = [ + xGroup, + yGroup, + colorGroup, + availableGroup, + ]; + + let xIndex = -1; + let yIndexes: number[] = []; + let colorIndexes: number[] = []; + const availableIndexes: number[] = []; + + if (shared.order && shared.order.length) { + let collectingX = false; + let collectingY = false; + let collectingColor = false; + let collectingAvailable = false; + + let draggedX = false; + let draggedY = false; + + shared.order.forEach((item: QlConfigResultEntryMetadataDataColumnOrGroup) => { + const itemIsGroup = isGroup(item); + + if (itemIsGroup && item.name === 'X') { + collectingX = true; + return; + } + + if (itemIsGroup && item.name === 'Y') { + collectingX = false; + collectingY = true; + return; + } + + if (itemIsGroup && item.name === 'Colors') { + collectingY = false; + collectingColor = true; + return; + } + + if (itemIsGroup && item.name === 'Available') { + collectingX = false; + collectingY = false; + collectingColor = false; + collectingAvailable = true; + return; + } + + if (collectingX && !itemIsGroup) { + xIndex = columns.findIndex((column) => column.name === item.name); + draggedX = true; + } + + if (collectingY && !itemIsGroup) { + const yIndex = columns.findIndex((column) => column.name === item.name); + + if (yIndex > -1) { + yIndexes.push(yIndex); + } + + draggedY = true; + } + + if (collectingColor && !itemIsGroup) { + colorIndexes.push(columns.findIndex((column) => column.name === item.name)); + } + + if (collectingAvailable) { + availableIndexes.push(columns.findIndex((column) => column.name === item.name)); + } + }); + + if (draggedY) { + const findNewYIndex = () => + columnTypes.findIndex( + (columnType, index) => + columnType === DATALENS_QL_TYPES.NUMBER && + index !== xIndex && + !colorIndexes.includes(index) && + !yIndexes.includes(index) && + !availableIndexes.includes(index), + ); + + let newFoundYIndex = findNewYIndex(); + while (newFoundYIndex > -1) { + yIndexes.push(newFoundYIndex); + + newFoundYIndex = findNewYIndex(); + } + } + + if (draggedX && xIndex === -1) { + xIndex = columns.findIndex( + (_column, index) => !yIndexes.includes(index) && !colorIndexes.includes(index), + ); + } + } else { + const findNewYIndex = () => + columnTypes.findIndex( + (columnType, index) => + columnType === DATALENS_QL_TYPES.NUMBER && !yIndexes.includes(index), + ); + + let newFoundYIndex = findNewYIndex(); + while (newFoundYIndex > -1) { + yIndexes.push(newFoundYIndex); + + newFoundYIndex = findNewYIndex(); + } + + xIndex = columns.findIndex((_column, index) => !yIndexes.includes(index)); + + const homogeneousValues: Set[] = []; + const iToHomogeneity: Boolean[] = []; + rows.forEach((row) => { + row.forEach((value, i) => { + if (typeof homogeneousValues[i] === 'undefined') { + homogeneousValues[i] = new Set([value]); + iToHomogeneity[i] = true; + } else if (iToHomogeneity[i] === false) { + return; + } else if (!homogeneousValues[i].has(value)) { + iToHomogeneity[i] = false; + } + }); + }); + + iToHomogeneity.forEach((_homogeneity, i) => { + // Removed the uniformity check within CHARTS-5955 + // It may need to be returned in the future for more complex logic, but for now it is superfluous + if (xIndex !== i && !yIndexes.includes(i)) { + colorIndexes.push(i); + } + }); + } + + colorIndexes = colorIndexes.filter((colorIndex) => colorIndex > -1); + yIndexes = yIndexes.filter((yIndex) => yIndex > -1); + + let inserted = 0; + if (columns[xIndex]) { + order.splice(1 + inserted, 0, columns[xIndex]); + ++inserted; + + xGroup.size = 1; + } + + yIndexes.forEach((yIndex) => { + if (columns[yIndex]) { + order.splice(2 + inserted, 0, columns[yIndex]); + ++inserted; + + if (typeof yGroup.size === 'number') { + yGroup.size += 1; + } else { + yGroup.size = 1; + } + } + }); + + if (yIndexes.length > 1) { + if (columns.findIndex(({pseudo}) => pseudo) === -1) { + columns.push({ + typeName: 'String', + name: 'Measure Names', + pseudo: true, + undragable: true, + }); + } + + colorGroup.size = 1; + + colorIndexes.push(columns.length - 1); + } + + if (colorIndexes.length > 0) { + colorIndexes.forEach((colorIndex) => { + order.splice(3 + inserted, 0, columns[colorIndex]); + ++inserted; + ++colorGroup.size; + }); + } + + columns.forEach((column, index) => { + if (index !== xIndex && !yIndexes.includes(index) && !colorIndexes.includes(index)) { + order.push(column); + } + }); + + const result: QLRenderResultHC = { + metadata: { + order, + }, + tablePreviewData, + }; + + const xIsDate = + columnTypes[xIndex] === DATALENS_QL_TYPES.DATE || + columnTypes[xIndex] === DATALENS_QL_TYPES.DATETIME; + + if (xIsDate) { + ChartEditor.updateHighchartsConfig({ + xAxis: { + type: 'datetime', + }, + }); + } + + const xAxisName = xIsDate ? 'categories_ms' : 'categories'; + + if (yIndexes.length > 0 && columns[xIndex]) { + let xValues: (string | number)[] = []; + let colorValues: QLValue[] = []; + + const dataMatrix: Record> = + {}; + + rows.forEach((row) => { + let xValue: string | number = row[xIndex]; + + if (xIsDate) { + xValue = moment.utc(xValue).valueOf(); + } else if (columnTypes[xIndex] === DATALENS_QL_TYPES.UNKNOWN) { + xValue = formatUnknownTypeValue(xValue) as number | string; + } + + xValues.push(xValue); + + yIndexes.forEach((yIndex) => { + const yValue: string | number = row[yIndex]; + + if (colorIndexes.length > 0) { + let colorValue = ''; + colorIndexes.forEach((colorIndex) => { + const colorValuePart: QLValue = columns[colorIndex].pseudo + ? columns[yIndex].name + : row[colorIndex]; + + colorValue = + colorValue.length > 0 + ? `${colorValue}; ${colorValuePart}` + : colorValuePart; + }); + + let dataCell = dataMatrix[String(xValue)] as Record< + string | number, + number | null + >; + + if (typeof dataCell === 'undefined') { + dataCell = dataMatrix[String(xValue)] = {}; + } + + if (typeof dataCell === 'object' && dataCell !== null) { + dataCell[String(colorValue)] = parseNumberValue(yValue); + } + + colorValues.push(colorValue); + } else { + dataMatrix[String(xValue)] = parseNumberValue(yValue); + } + }); + }); + + xValues = Array.from(new Set(xValues)); + colorValues = Array.from(new Set(colorValues)); + + result[xAxisName] = xValues.map((value) => value); + + if (colorIndexes.length > 0) { + const graphs: QLRenderResultHCGraph[] = colorValues.map((colorValue) => { + return { + title: renderValue(colorValue), + data: [], + }; + }); + + xValues.forEach((xValue) => { + const dataCell = dataMatrix[String(xValue)] as Record< + string | number, + number | null + >; + if (typeof dataCell === 'object' && dataCell !== null) { + colorValues.forEach((colorValue: QLValue, i) => { + if (typeof dataCell[String(colorValue)] === 'undefined') { + graphs[i].data.push(null); + } else { + graphs[i].data.push(dataCell[String(colorValue)]); + } + }); + } + }); + + result.graphs = graphs; + } else { + result.graphs = []; + + yIndexes.forEach((yIndex) => { + const graph = { + title: columns[yIndex].name, + data: xValues.map((xValue) => { + return dataMatrix[String(xValue)] as {x: QLValue; y: QLValue}; + }), + }; + + result.graphs?.push(graph); + }); + } + } else if (columns[xIndex]) { + result.graphs = [ + { + data: rows.map(() => { + return null; + }), + }, + ]; + } else { + result.graphs = []; + } + + return result; +}; diff --git a/src/server/modes/charts/plugins/ql/preparers/metric.ts b/src/server/modes/charts/plugins/ql/preparers/metric.ts new file mode 100644 index 0000000..78bb6ca --- /dev/null +++ b/src/server/modes/charts/plugins/ql/preparers/metric.ts @@ -0,0 +1,116 @@ +import type {IChartEditor, QlConfigPreviewTableData} from '../../../../../../shared'; +import {DATALENS_QL_TYPES, formatNumber} from '../../../../../../shared'; +import type { + QlConfig, + QlConfigResultEntryMetadataDataColumn, + QlConfigResultEntryMetadataDataColumnOrGroup, + QlConfigResultEntryMetadataDataGroup, +} from '../../../../../../shared/types/config/ql'; +import {prepareMetricObject} from '../../datalens/utils/markup-helpers'; +import type {QLRenderResultMetric} from '../utils/misc-helpers'; +import {formatUnknownTypeValue, isGroup, parseNumberValue} from '../utils/misc-helpers'; + +export default ({ + shared, + columns, + rows, + ChartEditor: _ChartEditor, +}: { + shared: QlConfig; + columns: QlConfigResultEntryMetadataDataColumn[]; + rows: string[][]; + ChartEditor: IChartEditor; + tablePreviewData?: QlConfigPreviewTableData; +}) => { + if (columns === null) { + return {}; + } + + const columnTypes = columns.map((column) => column.typeName); + + const measureGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Measure', + group: true, + undragable: true, + capacity: 1, + size: 0, + }; + + const availableGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Available', + group: true, + undragable: true, + size: 0, + }; + + const order: QlConfigResultEntryMetadataDataColumnOrGroup[] = [measureGroup, availableGroup]; + + // Default x and y columns mapping + let measureIndex = -1; + + if (shared.order && shared.order.length) { + let collectingMeasure = false; + + shared.order.forEach((item: QlConfigResultEntryMetadataDataColumnOrGroup) => { + const itemIsGroup = isGroup(item); + + if (itemIsGroup && item.name === 'Measure') { + collectingMeasure = true; + return; + } + + if (itemIsGroup && item.name === 'Available') { + collectingMeasure = false; + return; + } + + if (collectingMeasure && !itemIsGroup) { + measureIndex = columns.findIndex((column) => column.name === item.name); + } + }); + } else { + measureIndex = 0; + } + + if (columns[measureIndex]) { + order.splice(1, 0, columns[measureIndex]); + + measureGroup.size = 1; + } + + columns.forEach((column, index) => { + if (index !== measureIndex) { + order.push(column); + } + }); + + let result: QLRenderResultMetric[] | {}; + + const measureColumn = columns[measureIndex]; + + if (measureColumn) { + let value: string | number | null = rows[0][measureIndex]; + + if (columnTypes[measureIndex] === DATALENS_QL_TYPES.UNKNOWN) { + value = formatUnknownTypeValue(value); + } else if (columnTypes[measureIndex] === DATALENS_QL_TYPES.NUMBER) { + value = parseNumberValue(value); + } + + const size = 'm'; + const color = 'rgb(77, 162, 241)'; + const title = measureColumn.name; + + let formattedValue = String(value); + + if (typeof value === 'number') { + formattedValue = formatNumber(value, {}); + } + + return prepareMetricObject({size, title, color, value: formattedValue}); + } else { + result = {}; + } + + return result; +}; diff --git a/src/server/modes/charts/plugins/ql/preparers/pie.ts b/src/server/modes/charts/plugins/ql/preparers/pie.ts new file mode 100644 index 0000000..1e1e664 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/preparers/pie.ts @@ -0,0 +1,153 @@ +import type {IChartEditor, QlConfigPreviewTableData} from '../../../../../../shared'; +import {DATALENS_QL_TYPES} from '../../../../../../shared'; +import type { + QlConfig, + QlConfigResultEntryMetadataDataColumn, + QlConfigResultEntryMetadataDataColumnOrGroup, + QlConfigResultEntryMetadataDataGroup, +} from '../../../../../../shared/types/config/ql'; +import type {QLRenderResultHC} from '../utils/misc-helpers'; +import {formatUnknownTypeValue, isGroup, parseNumberValue} from '../utils/misc-helpers'; + +export default ({ + shared, + columns, + rows, + tablePreviewData, +}: { + shared: QlConfig; + columns: QlConfigResultEntryMetadataDataColumn[]; + rows: string[][]; + ChartEditor: IChartEditor; + tablePreviewData?: QlConfigPreviewTableData; +}) => { + if (columns === null) { + return {}; + } + + const columnTypes = columns.map((column) => column.typeName); + + const colorGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Color', + group: true, + undragable: true, + capacity: 1, + size: 0, + }; + + const measureGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Measure', + group: true, + undragable: true, + capacity: 1, + size: 0, + }; + + const availableGroup: QlConfigResultEntryMetadataDataGroup = { + name: 'Available', + group: true, + undragable: true, + size: 0, + }; + + const order: QlConfigResultEntryMetadataDataColumnOrGroup[] = [ + colorGroup, + measureGroup, + availableGroup, + ]; + + // Default color and measure columns mapping + let colorIndex = -1; + let measureIndex = -1; + + if (shared.order && shared.order.length) { + let collectingColor = false; + let collectingMeasure = false; + + shared.order.forEach((item: QlConfigResultEntryMetadataDataColumnOrGroup) => { + const itemIsGroup = isGroup(item); + + if (itemIsGroup && item.name === 'Color') { + collectingColor = true; + return; + } + + if (itemIsGroup && item.name === 'Measure') { + collectingColor = false; + collectingMeasure = true; + return; + } + + if (itemIsGroup && item.name === 'Available') { + collectingColor = false; + collectingMeasure = false; + return; + } + + if (collectingColor && !itemIsGroup) { + colorIndex = columns.findIndex((column) => column.name === item.name); + } + + if (collectingMeasure && !itemIsGroup) { + measureIndex = columns.findIndex((column) => column.name === item.name); + } + }); + } else { + measureIndex = columnTypes.findIndex((columType) => columType === 'number'); + colorIndex = columns.findIndex((_column, index) => index !== measureIndex); + } + + let colorInserted = 0; + if (columns[colorIndex]) { + order.splice(1, 0, columns[colorIndex]); + colorInserted = 1; + colorGroup.size = 1; + } + + if (columns[measureIndex]) { + order.splice(2 + colorInserted, 0, columns[measureIndex]); + measureGroup.size = 1; + } + + columns.forEach((column, index) => { + if (index !== colorIndex && index !== measureIndex) { + order.push(column); + } + }); + + let result: QLRenderResultHC = { + metadata: { + order, + }, + tablePreviewData, + }; + + if (columns[measureIndex] && columns[colorIndex]) { + result.graphs = [ + { + data: rows.map((row: any[]) => { + let name = row[colorIndex]; + + if (columnTypes[colorIndex] === DATALENS_QL_TYPES.UNKNOWN) { + name = formatUnknownTypeValue(name); + } + + return { + y: parseNumberValue(row[measureIndex]), + name, + }; + }), + }, + ]; + } else { + result = { + graphs: [], + metadata: { + order, + }, + tablePreviewData, + }; + } + + return result; +}; diff --git a/src/server/modes/charts/plugins/ql/preparers/preview-table.ts b/src/server/modes/charts/plugins/ql/preparers/preview-table.ts new file mode 100644 index 0000000..84a972e --- /dev/null +++ b/src/server/modes/charts/plugins/ql/preparers/preview-table.ts @@ -0,0 +1,110 @@ +import type {IChartEditor, TableRow} from '../../../../../../shared'; +import {DATALENS_QL_TYPES} from '../../../../../../shared'; +import type { + QlConfig, + QlConfigResultEntryMetadataDataColumn, +} from '../../../../../../shared/types/config/ql'; +import {DEFAULT_DATETIME_FORMAT, DEFAULT_DATE_FORMAT} from '../utils/constants'; +import {formatUnknownTypeValue, parseNumberValueForTable} from '../utils/misc-helpers'; + +export default ({ + shared: _shared, + columns, + rows, + ChartEditor: _ChartEditor, +}: { + shared: QlConfig; + columns: QlConfigResultEntryMetadataDataColumn[]; + rows: string[][]; + ChartEditor: IChartEditor; +}) => { + if (columns === null) { + return {}; + } + + const columnTypes = columns.map((column) => column.typeName); + const knownColumnNames = new Set(); + + const head = columns.map((column: QlConfigResultEntryMetadataDataColumn, index) => { + const columnType = columnTypes[index]; + let tableColumnType; + let tableColumnFormat; + + if (columnType === DATALENS_QL_TYPES.NUMBER) { + tableColumnType = 'number'; + } else if ( + columnType === DATALENS_QL_TYPES.DATE || + columnType === DATALENS_QL_TYPES.DATETIME + ) { + if (columnType === DATALENS_QL_TYPES.DATETIME) { + tableColumnFormat = DEFAULT_DATETIME_FORMAT; + } else if (columnType === DATALENS_QL_TYPES.DATE) { + tableColumnFormat = DEFAULT_DATE_FORMAT; + } + + tableColumnType = 'date'; + } else { + tableColumnType = 'string'; + } + + let uniqueColumnName = column.name; + + while (knownColumnNames.has(uniqueColumnName)) { + if (/-(\d+)$/.test(uniqueColumnName)) { + uniqueColumnName = uniqueColumnName.replace( + /-(\d+)$/, + (_substring, uniqueIndex) => { + return `-${Number(uniqueIndex) + 1}`; + }, + ); + } else { + uniqueColumnName = `${uniqueColumnName}-1`; + } + } + + knownColumnNames.add(uniqueColumnName); + + return { + name: uniqueColumnName, + id: column.name, + type: tableColumnType, + format: tableColumnFormat, + }; + }); + + const result = { + columns: head, + data: rows.map((row: string[]) => { + const tableRow: TableRow = {cells: []}; + head.forEach(({name}, index) => { + let cellValue; + + const value = row[index]; + + if (columnTypes[index] === DATALENS_QL_TYPES.NUMBER) { + cellValue = parseNumberValueForTable(value); + } else if ( + columnTypes[index] === DATALENS_QL_TYPES.DATE || + columnTypes[index] === DATALENS_QL_TYPES.DATETIME + ) { + cellValue = value; + } else if ( + columnTypes[index] === DATALENS_QL_TYPES.UNKNOWN || + // We need to wait for BI-4892 + // Temporarily checking type missmatch + (value && typeof value === 'object' && columnTypes[index] === 'string') + ) { + cellValue = formatUnknownTypeValue(value); + } else { + cellValue = value; + } + + tableRow.cells.push({fieldId: name, value: cellValue}); + }); + + return tableRow; + }), + }; + + return result; +}; diff --git a/src/server/modes/charts/plugins/ql/preparers/table.ts b/src/server/modes/charts/plugins/ql/preparers/table.ts new file mode 100644 index 0000000..b26c2fe --- /dev/null +++ b/src/server/modes/charts/plugins/ql/preparers/table.ts @@ -0,0 +1,165 @@ +import moment from 'moment'; + +import type {IChartEditor, QlConfigPreviewTableData} from '../../../../../../shared'; +import {DATALENS_QL_TYPES} from '../../../../../../shared'; +import type { + QlConfig, + QlConfigResultEntryMetadataDataColumn, + QlConfigResultEntryMetadataDataColumnOrGroup, +} from '../../../../../../shared/types/config/ql'; +import {DEFAULT_DATETIME_FORMAT, DEFAULT_DATE_FORMAT} from '../utils/constants'; +import type {QLRenderResultTable} from '../utils/misc-helpers'; +import {formatUnknownTypeValue, isGroup, parseNumberValueForTable} from '../utils/misc-helpers'; + +export default ({ + shared, + columns, + rows, + tablePreviewData, +}: { + shared: QlConfig; + columns: QlConfigResultEntryMetadataDataColumn[]; + rows: string[][]; + ChartEditor: IChartEditor; + tablePreviewData?: QlConfigPreviewTableData; +}) => { + if (columns === null) { + return {}; + } + + const columnTypes = columns.map((column) => column.typeName); + + const orderColumns: QlConfigResultEntryMetadataDataColumn[] = []; + const orderAvailable: QlConfigResultEntryMetadataDataColumn[] = []; + + const result: QLRenderResultTable = { + head: [], + rows: [], + tablePreviewData, + }; + + const displayedColumnsIndices: number[] = []; + + if (shared.order && shared.order.length) { + let collectingColumns = false; + let collectingAvailable = false; + + shared.order.forEach((item: QlConfigResultEntryMetadataDataColumnOrGroup) => { + const itemIsGroup = isGroup(item); + + if (itemIsGroup && item.name === 'Columns') { + collectingColumns = true; + return; + } + + if (itemIsGroup && item.name === 'Available') { + collectingColumns = false; + collectingAvailable = true; + return; + } + + if (collectingColumns && !itemIsGroup) { + const itemIndex = columns.findIndex((column) => column.name === item.name); + + if (itemIndex > -1) { + orderColumns.push(columns[itemIndex]); + displayedColumnsIndices.push(itemIndex); + } + } + + if (collectingAvailable && !itemIsGroup) { + const itemIndex = columns.findIndex((column) => column.name === item.name); + + if (itemIndex > -1) { + orderAvailable.push(columns[itemIndex]); + } + } + }); + } else { + columns.forEach((column, index) => { + orderColumns.push(column); + displayedColumnsIndices.push(index); + }); + } + + columns.forEach((column) => { + if (orderAvailable.indexOf(column) === -1 && orderColumns.indexOf(column) === -1) { + orderAvailable.push(column); + } + }); + + result.head = displayedColumnsIndices.map((index) => { + let tableColumnType; + let tableColumnFormat; + + const columnType = columnTypes[index]; + + if (columnType === DATALENS_QL_TYPES.NUMBER) { + tableColumnType = 'number'; + } else if ( + columnType === DATALENS_QL_TYPES.DATETIME || + columnType === DATALENS_QL_TYPES.DATE + ) { + if (columnType === DATALENS_QL_TYPES.DATETIME) { + tableColumnFormat = DEFAULT_DATETIME_FORMAT; + } else if (columnType === DATALENS_QL_TYPES.DATE) { + tableColumnFormat = DEFAULT_DATE_FORMAT; + } + + tableColumnType = 'date'; + } else { + tableColumnType = 'string'; + } + + return { + name: columns[index].name, + type: tableColumnType, + format: tableColumnFormat, + }; + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + result.rows = rows.map((row: any[]) => { + return { + values: displayedColumnsIndices.map((index) => { + let cellValue; + + if (columnTypes[index] === DATALENS_QL_TYPES.NUMBER) { + cellValue = parseNumberValueForTable(row[index]); + } else if ( + columnTypes[index] === DATALENS_QL_TYPES.DATETIME || + columnTypes[index] === DATALENS_QL_TYPES.DATE + ) { + cellValue = moment.utc(row[index]).valueOf(); + } else if (columnTypes[index] === DATALENS_QL_TYPES.UNKNOWN) { + cellValue = formatUnknownTypeValue(row[index]); + } else { + cellValue = row[index]; + } + + return cellValue; + }), + }; + }); + + const order: QlConfigResultEntryMetadataDataColumnOrGroup[] = [ + { + name: 'Columns', + group: true, + undragable: true, + size: 0, + }, + ...orderColumns, + { + name: 'Available', + group: true, + undragable: true, + size: 0, + }, + ...orderAvailable, + ]; + + result.metadata = {order}; + + return result; +}; diff --git a/src/server/modes/charts/plugins/ql/ql-chart-runner.ts b/src/server/modes/charts/plugins/ql/ql-chart-runner.ts new file mode 100644 index 0000000..0f08f8f --- /dev/null +++ b/src/server/modes/charts/plugins/ql/ql-chart-runner.ts @@ -0,0 +1,51 @@ +import path from 'path'; + +import {type AppContext, USER_LANGUAGE_PARAM_NAME} from '@gravity-ui/nodekit'; +import type {Pool, Proxy, WorkerPoolOptions} from 'workerpool'; +import workerpool from 'workerpool'; + +import {getWizardChartBuilder} from '../../../../components/charts-engine/components/processor/worker-chart-builder'; +import type {ResolvedConfig} from '../../../../components/charts-engine/components/storage/types'; +import type {WizardWorker} from '../../../../components/charts-engine/components/wizard-worker/types'; +import type {RunnerHandler, RunnerHandlerProps} from '../../../../components/charts-engine/runners'; +import {runWorkerChart} from '../../../../components/charts-engine/runners/worker'; +import {registry} from '../../../../registry'; + +import type {QLAdditionalData} from './types'; + +let wizardWorkersPool: Pool | null = null; +async function getQlWorker(options?: WorkerPoolOptions): Promise> { + if (wizardWorkersPool === null) { + const scriptPath = path.resolve(__dirname, './worker'); + const additionalData: QLAdditionalData = { + qlConnectionTypeMap: registry.getQLConnectionTypeMap(), + }; + wizardWorkersPool = workerpool.pool(scriptPath, { + ...options, + workerThreadOpts: {workerData: additionalData}, + }); + } + + return wizardWorkersPool.proxy(); +} + +export const runQlChart: RunnerHandler = async (cx: AppContext, props: RunnerHandlerProps) => { + const {req, runnerLocals, config} = props; + const {widgetConfig} = req.body; + const timeouts = cx.config.runnerExecutionTimeouts?.qlChart; + + const chartBuilder = await getWizardChartBuilder({ + userLang: cx.get(USER_LANGUAGE_PARAM_NAME) || '', + userLogin: runnerLocals.login || '', + widgetConfig, + config: config as ResolvedConfig, + isScreenshoter: Boolean(req.headers['x-charts-scr']), + worker: await getQlWorker({ + maxWorkers: cx.config.chartsEngineConfig.maxWorkers ?? 1, + }), + timeouts, + tenantSettings: {defaultColorPaletteId: config.tenantSettings?.defaultColorPaletteId}, + }); + + return runWorkerChart(cx, {...props, chartBuilder, runnerType: 'Ql'}); +}; diff --git a/src/server/modes/charts/plugins/ql/types.ts b/src/server/modes/charts/plugins/ql/types.ts new file mode 100644 index 0000000..f19c3ce --- /dev/null +++ b/src/server/modes/charts/plugins/ql/types.ts @@ -0,0 +1,5 @@ +import type {QLConnectionTypeMap} from './utils/connection'; + +export type QLAdditionalData = { + qlConnectionTypeMap: QLConnectionTypeMap; +}; diff --git a/src/server/modes/charts/plugins/ql/utils/__tests__/misc-helpers.test.ts b/src/server/modes/charts/plugins/ql/utils/__tests__/misc-helpers.test.ts new file mode 100644 index 0000000..4847ed5 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/utils/__tests__/misc-helpers.test.ts @@ -0,0 +1,158 @@ +import type {QLConfigQuery} from '../../../../../../../shared'; +import {ConnectorType} from '../../../../../../../shared'; +import {convertConnectionType} from '../connection'; +import {buildSource, doesQueryContainOrderBy, iterateThroughVisibleQueries} from '../misc-helpers'; + +const MOCK_ID = 'MOCK_ID'; + +const commonBuildSourceArgsSet = { + id: MOCK_ID, + connectionType: 'postgres', + qlConnectionTypeMap: {postgres: ConnectorType.Postgres}, + features: {}, +}; + +const mockedBuildSourceArgsSet = { + ...commonBuildSourceArgsSet, + query: 'select built_year, iznos from public.sample where built_year in {{years}} limit 10', + params: {years: '1995'}, + paramsDescription: [ + {type: 'string', name: 'years', defaultValue: ['1990', '1995'], overridenValue: '1995'}, + ], +}; + +const expectedBuildSourceResultSet = { + url: `/_bi_connections/${MOCK_ID}/dashsql`, + method: 'post', + data: { + sql_query: + "select built_year, iznos from public.sample where built_year in (E'1995') limit 10", + params: {}, + }, +}; + +const mockedBuildSourceArgsSingle = { + ...commonBuildSourceArgsSet, + query: 'select built_year, iznos from public.sample where built_year = {{years}} limit 10', + params: {years: '1995'}, + paramsDescription: [ + {type: 'string', name: 'years', defaultValue: ['1990', '1995'], overridenValue: '1995'}, + ], +}; + +const expectedBuildSourceResultSingle = { + url: `/_bi_connections/${MOCK_ID}/dashsql`, + method: 'post', + data: { + sql_query: + "select built_year, iznos from public.sample where built_year = E'1995' limit 10", + params: {}, + }, +}; + +const mockedBuildSourceArgsPrewrapped = { + ...commonBuildSourceArgsSet, + query: 'select built_year, iznos from public.sample where built_year in ({{years}}) limit 10', + params: {years: '1995'}, + paramsDescription: [ + {type: 'string', name: 'years', defaultValue: ['1990', '1995'], overridenValue: '1995'}, + ], +}; + +const expectedBuildSourceResultPrewrapped = { + url: `/_bi_connections/${MOCK_ID}/dashsql`, + method: 'post', + data: { + sql_query: + "select built_year, iznos from public.sample where built_year in (E'1995') limit 10", + params: {}, + }, +}; + +jest.mock('../../../../../../registry', () => { + return { + registry: { + getConvertConnectorTypeToQLConnectionType: () => convertConnectionType, + }, + }; +}); + +describe('buildSource', () => { + it('should work correctly with parameter-set substitution', () => { + const buildSourceResult = buildSource(mockedBuildSourceArgsSet); + + expect(buildSourceResult).toEqual(expectedBuildSourceResultSet); + }); + + it('should work correctly with substitution of parameter "scalar" value', () => { + const buildSourceResult = buildSource(mockedBuildSourceArgsSingle); + + expect(buildSourceResult).toEqual(expectedBuildSourceResultSingle); + }); + + it('should work correctly with parameter-set substitution where value is already wrapped by user', () => { + const buildSourceResult = buildSource(mockedBuildSourceArgsPrewrapped); + + expect(buildSourceResult).toEqual(expectedBuildSourceResultPrewrapped); + }); +}); + +describe('iterateThroughVisibleQueries', () => { + it('should execute callback only for queries where hidden property is falsy', () => { + const cb = jest.fn(); + + const queries: QLConfigQuery[] = [ + {queryName: 'Query #1', value: 'sql-1', params: [], hidden: false}, + {queryName: 'Query #2', value: 'sql-2', params: [], hidden: true}, + {queryName: 'Query #3', value: 'sql-3', params: [], hidden: false}, + {queryName: 'Query #4', value: 'sql-4', params: [], hidden: false}, + {queryName: 'Query #5', value: 'sql-5', params: []}, + ]; + + iterateThroughVisibleQueries(queries, cb); + + expect(cb.mock.calls.length).toEqual(4); + expect( + cb.mock.calls.every((args) => { + const index = args[1]; + return index !== 1; + }), + ); + }); +}); + +describe('doesQueryContainOrderBy', () => { + it('should return true when order by is used normally', () => { + const queryContainsOrderBy = doesQueryContainOrderBy('select a from b order by a desc'); + + expect(queryContainsOrderBy).toEqual(true); + }); + + it('should return false when order by is not used', () => { + const queryContainsOrderBy = doesQueryContainOrderBy('select a from b'); + + expect(queryContainsOrderBy).toEqual(false); + }); + + it('should return false when order by is commented out', () => { + const queryContainsOrderBy = doesQueryContainOrderBy('select a from b -- order by a desc'); + + expect(queryContainsOrderBy).toEqual(false); + }); + + it('should return true when order by is used normally, but query contains -- in quotes', () => { + const queryContainsOrderBy = doesQueryContainOrderBy( + `select a from b where a like '%--%' order by a`, + ); + + expect(queryContainsOrderBy).toEqual(true); + }); + + it('should return false when order by is used as a string', () => { + const queryContainsOrderBy = doesQueryContainOrderBy( + `select a from b where a = 'order by'`, + ); + + expect(queryContainsOrderBy).toEqual(false); + }); +}); diff --git a/src/server/modes/charts/plugins/ql/utils/autofill-helpers.ts b/src/server/modes/charts/plugins/ql/utils/autofill-helpers.ts new file mode 100644 index 0000000..b0e6ce3 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/utils/autofill-helpers.ts @@ -0,0 +1,166 @@ +import type {Field} from '../../../../../../shared'; +import {DATASET_FIELD_TYPES, DatasetFieldType} from '../../../../../../shared'; + +import {getColorFieldsFromDistincts} from './colors'; + +export const autofillLineVisualization = ({ + fields, + distinctsMap, +}: { + fields: Field[]; + distinctsMap?: Record; +}): {xFields: Field[]; yFields: Field[]; colors?: Field[]} => { + const yIndexes: number[] = []; + const findNewYIndex = () => + fields.findIndex( + (column, index) => + (column.cast === DATASET_FIELD_TYPES.INTEGER || + column.cast === DATASET_FIELD_TYPES.FLOAT) && + !yIndexes.includes(index), + ); + + let newFoundYIndex = findNewYIndex(); + while (newFoundYIndex > -1) { + yIndexes.push(newFoundYIndex); + + newFoundYIndex = findNewYIndex(); + } + + const xIndex = fields.findIndex((_column, index) => !yIndexes.includes(index)); + + let xFields: Field[] = []; + + if (xIndex > -1) { + xFields = [fields[xIndex]]; + } + + const yFields: Field[] = []; + + yIndexes.forEach((index) => { + yFields.push(fields[index]); + }); + + return { + xFields, + yFields, + colors: getColorFieldsFromDistincts(distinctsMap, fields, [...xFields, ...yFields]), + }; +}; + +export const autofillScatterVisualization = ({fields}: {fields: Field[]}) => { + const xIndex = fields.findIndex( + (column) => + column.cast === DATASET_FIELD_TYPES.INTEGER || + column.cast === DATASET_FIELD_TYPES.FLOAT, + ); + + const yIndex = fields.findIndex( + (column, i) => + (column.cast === DATASET_FIELD_TYPES.INTEGER || + column.cast === DATASET_FIELD_TYPES.FLOAT) && + i !== xIndex, + ); + + const pointsIndex = fields.findIndex((_column, i) => i !== xIndex && i !== yIndex); + + let xFields: Field[] = []; + let yFields: Field[] = []; + let pointsFields: Field[] = []; + + if (xIndex > -1) { + xFields = [fields[xIndex]]; + } + + if (yIndex > -1) { + yFields = [fields[yIndex]]; + } + + if (pointsIndex > -1) { + pointsFields = [fields[pointsIndex]]; + } + + return { + xFields, + yFields, + pointsFields, + }; +}; + +export const autofillPieVisualization = ({fields}: {fields: Field[]}) => { + const measureIndex = fields.findIndex( + (field) => + field.cast === DATASET_FIELD_TYPES.INTEGER || field.cast === DATASET_FIELD_TYPES.FLOAT, + ); + const colorIndex = fields.findIndex((_field, index) => index !== measureIndex); + + let colorFields: Field[] = []; + let measureFields: Field[] = []; + + if (colorIndex > -1) { + colorFields = [fields[colorIndex]]; + } + + if (measureIndex > -1) { + measureFields = [fields[measureIndex]]; + } + + return { + colorFields, + measureFields, + }; +}; + +export const autofillMetricVisualization = ({fields}: {fields: Field[]}) => { + // First of all, we need to find some numeric value for metric + let measureIndex = fields.findIndex( + (field) => + field.cast === DATASET_FIELD_TYPES.INTEGER || field.cast === DATASET_FIELD_TYPES.FLOAT, + ); + + // If there is no such value, then we will use any other available value + if (measureIndex === -1 && fields.length > 0) { + measureIndex = 0; + } + + let measureFields: Field[] = []; + + if (measureIndex > -1) { + measureFields = [fields[measureIndex]]; + } + + return { + measureFields, + }; +}; + +export const autofillTreemapVisualization = ({fields}: {fields: Field[]}) => { + const sizeIndex = fields.findIndex( + (field) => + field.cast === DATASET_FIELD_TYPES.INTEGER || field.cast === DATASET_FIELD_TYPES.FLOAT, + ); + const dimensionIndex = fields.findIndex((_field, index) => index !== sizeIndex); + + let dimensionFields: Field[] = []; + let sizeFields: Field[] = []; + + if (dimensionIndex > -1) { + dimensionFields = [fields[dimensionIndex]]; + } + + if (sizeIndex > -1) { + sizeFields = [fields[sizeIndex]]; + } + + return { + dimensionFields, + sizeFields, + }; +}; + +export const autofillTableVisualization = ({fields}: {fields: Field[]}) => { + const columnFields = fields.filter((column) => column.type !== DatasetFieldType.Pseudo); + + return { + columnFields, + }; +}; diff --git a/src/server/modes/charts/plugins/ql/utils/colors.ts b/src/server/modes/charts/plugins/ql/utils/colors.ts new file mode 100644 index 0000000..6a34b46 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/utils/colors.ts @@ -0,0 +1,619 @@ +import _ from 'lodash'; + +import type {Field} from '../../../../../../shared'; + +import {QUERY_ALIAS_TITLE, QUERY_TITLE} from './constants'; + +interface RGB { + r: number; + g: number; + b: number; +} + +interface ColorScheme { + type: string; + + green?: string; + yellow?: string; + red?: string; + violet?: string; +} + +function isNumber(c: unknown) { + return !Number.isNaN(Number(c)); +} + +function isDigit(c: string) { + return c >= '0' && c <= '9'; +} + +function digitsPrefixLength(s: string, pos: number) { + let r = 0; + while (pos + r < s.length && isDigit(s.charAt(pos + r))) { + ++r; + } + return r; +} + +function compareImpl(s1: string, p1: number, s2: string, p2: number) { + while (p1 !== s1.length && p2 !== s2.length) { + const d1Len = Math.min(digitsPrefixLength(s1, p1), 17); + const d2Len = Math.min(digitsPrefixLength(s2, p2), 17); + if (d1Len > 0 && d2Len > 0) { + // lame + const d1 = parseInt(s1.substring(p1, p1 + d1Len), 10); + const d2 = parseInt(s2.substring(p2, p2 + d2Len), 10); + const c = d1 - d2; + if (c !== 0) { + return c; + } + p1 += d1Len; + p2 += d2Len; + continue; + } + + const d1 = s1.charCodeAt(p1); + const d2 = s2.charCodeAt(p2); + const c = d1 - d2; + if (c !== 0) { + return c; + } + ++p1; + ++p2; + } + + return Number(p1 !== s1.length) - Number(p2 !== s2.length); +} + +export function compareTextWithNumber(s1: string, s2: string) { + if (isNumber(s1) && isNumber(s2)) { + return Number(s1) - Number(s2); + } + + return compareImpl(s1, 0, s2, 0); +} + +const STARTING_HUE_POINTS = [80, 200, 50, 0, 160, 250]; +const REGION_HUE_POINTS = [90, 200, 50, 10, 160, 330]; + +function hue2rgb(p: number, q: number, t: number) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; +} + +function fixAttr(value: number) { + let absValue = value; + if (absValue > 255) { + absValue = 255; + } + if (absValue < 0) { + absValue = 0; + } + + return absValue.toFixed(); +} + +function formatRgb(r: number, g: number, b: number) { + return `rgb(${fixAttr(r)},${fixAttr(g)},${fixAttr(b)})`; +} + +function convertHslToRgb(h: number, s: number, l: number) { + let r; + let g; + let b; + + if (s === 0) { + r = g = b = l; // achromatic + } else { + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return formatRgb(r * 255, g * 255, b * 255); +} + +function lin(pos: number, rfrom: number, rto: number) { + const r = Math.floor(pos * (rto - rfrom) + rfrom); + + const rmin = Math.min(rfrom, rto); + const rmax = Math.max(rfrom, rto); + + if (r < rmin) { + return rmin; + } + + if (r > rmax) { + return rmax; + } + + return r; +} + +function lina(pos: number, rfrom: RGB, rto: RGB): RGB { + const r = lin(pos, rfrom.r, rto.r); + const g = lin(pos, rfrom.g, rto.g); + const b = lin(pos, rfrom.b, rto.b); + + return {r, g, b}; +} + +export function getGradientColor(pos: number, begin: RGB, end: RGB) { + const rgb = lina(pos, begin, end); + return formatRgb(rgb.r, rgb.g, rgb.b); +} + +/* ALEX PETROV COLOR */ + +export function getColorAlexPetrov(n: number) { + const regionSize = 40; + let hue; + let saturation = 100; + let lightness = 43; + const magic = 7; + + if (n < STARTING_HUE_POINTS.length) { + hue = STARTING_HUE_POINTS[n]; + } else { + const region = n % REGION_HUE_POINTS.length; + const iteration = Math.floor(n / REGION_HUE_POINTS.length); + + const hueShift = (magic * iteration) % regionSize; + + hue = (REGION_HUE_POINTS[region] + hueShift) % 360; + + // Indistinguishable green + if (hue > 120 && hue < 150) { + hue = (hue + 50) % 360; + } + // Purple-pink + if (hue > 270 && hue < 330) { + hue = (hue + 70) % 360; + } + // Indistinguishable blue + if (hue > 220 && hue < 250) { + hue = (hue + 40) % 360; + } + + const saturationShift = (13 * (iteration + region)) % 20; + saturation = 70 + saturationShift; + + const lightnessShift = (11 * (iteration + region)) % 25; + if ((region + iteration + 1) % 2 > 0) { + lightness = 45 - lightnessShift; + } else { + lightness = 45 + lightnessShift; + } + } + + return convertHslToRgb(hue / 360.0, saturation / 100.0, lightness / 100.0); +} + +/* SOLOMON SPECIFIED COLOR FUNCTIONS */ +enum ColorSchemeType { + Gradient = 'gradient', + Default = 'default', + Auto = 'auto', +} + +const GREEN = {r: 0, g: 127, b: 0}; +const YELLOW = {r: 255, g: 255, b: 0}; +const RED = {r: 255, g: 0, b: 0}; +const VIOLET = {r: 128, g: 0, b: 255}; + +const GREEN_YELLOW_ZONE_ID = 0; +const YELLOW_RED_ZONE_ID = 1; +const RED_VIOLET_ZONE_ID = 2; + +const GREEN_YELLOW_VALUE = {begin: GREEN, end: YELLOW}; +const YELLOW_RED_VALUE = {begin: YELLOW, end: RED}; +const RED_VIOLET_VALUE = {begin: RED, end: VIOLET}; + +const GradientZoneValues = [GREEN_YELLOW_VALUE, YELLOW_RED_VALUE, RED_VIOLET_VALUE]; + +class GradientColor { + gradientZone: number; + gradientPosition: number; + ordinal: number; + + constructor(gradientZone: number, gradientPosition: number, ordinal: number) { + this.gradientZone = gradientZone; + this.gradientPosition = gradientPosition; + this.ordinal = ordinal; + } +} + +const GradientColorIndexes = { + GREEN: 0, + YELLOW: 1, + RED: 2, + VIOLET: 3, +}; + +const GradientColorValues = [ + new GradientColor(GREEN_YELLOW_ZONE_ID, 0, 0), + new GradientColor(YELLOW_RED_ZONE_ID, 0, 1), + new GradientColor(RED_VIOLET_ZONE_ID, 0, 2), + new GradientColor(RED_VIOLET_ZONE_ID, 1, 3), +]; + +enum LineColorType { + Auto = 'auto', + Gradient = 'gradient', + Fixes = 'fixed', +} + +class LineColor { + type: LineColorType; + zone: number | null; + position: number | null; + + constructor(type: LineColorType, zone: number | null, position: number | null) { + this.type = type; + this.zone = zone; + this.position = position; + } +} + +const AUTO_COLOR = new LineColor(LineColorType.Auto, null, null); + +function gradientColor(zone: number | null, position: number | null) { + return new LineColor(LineColorType.Gradient, zone, position); +} + +const UNIQUE_GRADIENTED_LABELS_PATTERN = /^(|.*\D)(\d+)(\D*)$/; + +function putIfAbsent(map: Record, key: string | number, value: unknown) { + if (!_.has(map, key)) { + map[key] = value; + } +} + +function tryParseDecimalLong(s: string) { + const num = Number(s); + return !isNaN(num); +} + +function labelPrefixAndSuffixOrNull(label: string) { + const matcher = UNIQUE_GRADIENTED_LABELS_PATTERN.exec(label); + + if (matcher !== null && matcher.length === 4 && tryParseDecimalLong(matcher[2])) { + return [matcher[1], matcher[3]]; + } + + return null; +} + +function isUniqueGradientedLabels(labels: string[]) { + labels = labels.filter((label) => label !== 'inf'); + + if (labels.length === 0) { + return false; + } + + const firstLabel = labels[0]; + + const firstLabelPrefixOrSuffix = labelPrefixAndSuffixOrNull(firstLabel); + if (firstLabelPrefixOrSuffix === null) { + return false; + } + + for (let i = 1; i < labels.length; ++i) { + const currentLabel = labels[i]; + const currentLabelPrefixAndSuffix = labelPrefixAndSuffixOrNull(currentLabel); + if (!_.isEqual(firstLabelPrefixOrSuffix, currentLabelPrefixAndSuffix)) { + return false; + } + } + return true; +} + +function constructColorMap(colorIndexes: Record, labels: string[]) { + const colorMap: Record = {}; + + if (_.size(colorIndexes) > 0) { + const entryList = _.entries(colorIndexes); + entryList.sort((a, b) => a[1] - b[1]); + + let currentColorIndex = 0; + for (let i = 0; i < labels.length; ++i) { + while (currentColorIndex < entryList.length && i >= entryList[currentColorIndex][1]) { + ++currentColorIndex; + } + + let gradientPosition; + let zone; + + if (currentColorIndex < entryList.length && currentColorIndex > 0) { + const cidx = Number(entryList[currentColorIndex][0]); + const currentColor = GradientColorValues[cidx]; + const currentColorNumber = entryList[currentColorIndex][1]; + + const pidx = Number(entryList[currentColorIndex - 1][0]); + const previousColor = GradientColorValues[pidx]; + const previousColorNumber = entryList[currentColorIndex - 1][1]; + + gradientPosition = + (i - previousColorNumber) / (currentColorNumber - previousColorNumber); + + if (previousColor.ordinal < currentColor.ordinal) { + zone = previousColor.gradientZone; + } else { + zone = currentColor.gradientZone; + gradientPosition = 1 - gradientPosition; + } + } else if (currentColorIndex >= entryList.length) { + const cidx = Number(entryList[entryList.length - 1][0]); + const currentColor = GradientColorValues[cidx]; + zone = currentColor.gradientZone; + gradientPosition = currentColor.gradientPosition; + } else { + const cidx = Number(entryList[0][0]); + const currentColor = GradientColorValues[cidx]; + zone = currentColor.gradientZone; + gradientPosition = currentColor.gradientPosition; + } + + const colorN = labels[i]; + colorMap[colorN] = gradientColor(zone, gradientPosition); + } + } + + return colorMap; +} + +function getInputSetGradientColorMap(colorLabels: Record, uniqueValues: string[]) { + const colorNumbers: Record = {}; + + for (let i = 0; i < uniqueValues.length; ++i) { + const value = uniqueValues[i]; + const filteredEntries = _.entries(colorLabels).filter((e) => e[1] === value); + + if (filteredEntries.length > 0) { + const foundedColor = filteredEntries[0][0]; + colorNumbers[foundedColor] = i; + } + } + + return colorNumbers; +} + +function putColorLabelIfPresent( + colorLabels: Record, + color: number, + label?: string, +) { + if (label) { + colorLabels[color] = label; + } +} + +function getGradientColorLabels(colorSchemeParams: ColorScheme) { + const colorLabels = {}; + putColorLabelIfPresent(colorLabels, GradientColorIndexes.GREEN, colorSchemeParams.green); + putColorLabelIfPresent(colorLabels, GradientColorIndexes.YELLOW, colorSchemeParams.yellow); + putColorLabelIfPresent(colorLabels, GradientColorIndexes.RED, colorSchemeParams.red); + putColorLabelIfPresent(colorLabels, GradientColorIndexes.VIOLET, colorSchemeParams.violet); + return colorLabels; +} + +function getMapOrder(colorNumbers: Record) { + if (_.size(colorNumbers) > 1) { + const initialKeys = GradientColorValues.filter((color) => + _.has(colorNumbers, color.ordinal), + ); + + const firstColor = colorNumbers[initialKeys[0].ordinal]; + const lastColor = colorNumbers[initialKeys[initialKeys.length - 1].ordinal]; + return firstColor - lastColor; + } + + return 0; +} + +function addMissingPointsToGradientMap(colorNumbers: Record, setSize: number) { + const mapOrder = getMapOrder(colorNumbers); + const allKeys = GradientColorValues; + const firstKeyOrdinal = allKeys[0].ordinal; + const lastKeyOrdinal = allKeys[allKeys.length - 1].ordinal; + if (mapOrder > 0) { + putIfAbsent(colorNumbers, firstKeyOrdinal, setSize); + putIfAbsent(colorNumbers, lastKeyOrdinal, 0); + } else { + putIfAbsent(colorNumbers, firstKeyOrdinal, 0); + putIfAbsent(colorNumbers, lastKeyOrdinal, setSize); + } + + const keys = GradientColorValues.filter((color) => _.has(colorNumbers, color.ordinal)); + + const newColorNumber: Record = {}; + let i = 0; + let j = 0; + while (i < allKeys.length && j < keys.length) { + const presentKey = keys[j]; + const key = allKeys[i]; + + const order = presentKey.ordinal - key.ordinal; + if (order > 0) { + if (j > 0) { + const previousPresentKey = keys[j - 1]; + const pKeyOrdinal = previousPresentKey.ordinal; + const keyOrdinal = presentKey.ordinal; + const absentKeyOrdinal = key.ordinal; + const pKeyNumber = colorNumbers[pKeyOrdinal]; + const keyNumber = colorNumbers[keyOrdinal]; + const absentKeyNumber = Math.trunc( + ((keyNumber - pKeyNumber) * (absentKeyOrdinal - pKeyOrdinal)) / + (keyOrdinal - pKeyOrdinal) + + pKeyNumber, + ); + newColorNumber[key.ordinal] = absentKeyNumber; + } + i++; + } else if (order === 0) { + newColorNumber[presentKey.ordinal] = colorNumbers[presentKey.ordinal]; + i++; + j++; + } else { + throw new Error('we must not reach there'); + } + } + return newColorNumber; +} + +function getGradientColorsPositions(colorLabels: Record, uniqueValues: string[]) { + const colorIndexes = getInputSetGradientColorMap(colorLabels, uniqueValues); + return addMissingPointsToGradientMap(colorIndexes, uniqueValues.length); +} + +function getUniqueGradientedLabels(labels: string[], colorLabels: Record) { + const strings = []; + strings.push(...labels); + strings.push(..._.values(colorLabels).filter((s) => s)); + strings.sort(compareTextWithNumber); + + return _.uniqWith(strings, (a, b) => a === b); +} + +function gradientScheme(colorSchemeParams: ColorScheme, labelValues: string[]) { + const colorLabels = getGradientColorLabels(colorSchemeParams); + const uniqueValues = getUniqueGradientedLabels(labelValues, colorLabels); + const colorsIndexes = getGradientColorsPositions(colorLabels, uniqueValues); + return constructColorMap(colorsIndexes, uniqueValues); +} + +function autoScheme(colorSchemeParams: ColorScheme, labelValues: string[]) { + if (isUniqueGradientedLabels(labelValues)) { + return gradientScheme(colorSchemeParams, labelValues); + } + return {}; +} + +function constructColorScheme(colorSchemeParams: ColorScheme, labelValues: string[]) { + const type = colorSchemeParams.type || ColorSchemeType.Auto; + + switch (type) { + case 'gradient': + return gradientScheme(colorSchemeParams, labelValues); + case 'default': + return {}; + case 'automatic': + default: + return autoScheme(colorSchemeParams, labelValues); + } +} + +function getColorOrAutoColor(colorScheme: Record, label: string): LineColor { + if (_.has(colorScheme, label)) { + return colorScheme[label]; + } + + return AUTO_COLOR; +} + +function getAlexPetrovColorsForN(n: number) { + const result = []; + for (let i = 0; i < n; ++i) { + result[i] = getColorAlexPetrov(i); + } + return result; +} + +function getColorsForNamesImpl(names: string[], colorSchemeParams: ColorScheme) { + const colorScheme = constructColorScheme(colorSchemeParams, names); + + return names.map((name, index) => { + const lineColor = getColorOrAutoColor(colorScheme, name); + + if ( + lineColor.type === 'gradient' && + lineColor.zone !== null && + lineColor.position !== null + ) { + const zone = GradientZoneValues[lineColor.zone]; + return getGradientColor(lineColor.position, zone.begin, zone.end); + } + + return getColorAlexPetrov(index); + }); +} + +export function getColorsForNames(names: string[], colorSchemeParams?: ColorScheme) { + const uniqNames = new Set(names); + const namesForColors = + uniqNames.size <= 1 ? names.map((_, index) => `uniqName${index}`) : names; + + if (!colorSchemeParams) { + return getAlexPetrovColorsForN(namesForColors.length); + } + + try { + return getColorsForNamesImpl(namesForColors, colorSchemeParams); + } catch (e) { + return getAlexPetrovColorsForN(namesForColors.length); + } +} + +export function getColorFieldsFromDistincts( + distinctsMap: Record | undefined, + fields: Field[], + usedAxisFields: Field[], +): Field[] | undefined { + let colors: Field[] | undefined; + let colorIndex = fields.findIndex((column) => column.title === QUERY_ALIAS_TITLE); + + if (colorIndex === -1) { + colorIndex = fields.findIndex((column) => column.title === QUERY_TITLE); + } + // setup mandatory color fields + if (colorIndex > -1) { + colors = [fields[colorIndex]]; + } + + // distincts colorizing is optional and only supported for visualization with multiple color fields in section. + if (!distinctsMap) { + return colors; + } + + const usedFields = colors ? [...usedAxisFields, ...colors] : usedAxisFields; + + const generateGuidToFieldMap = (acc: Partial>, f: Field) => + Object.assign(acc, {[f.guid]: f}); + + const fieldByGuidMap = fields.reduce( + generateGuidToFieldMap, + {} as Partial>, + ); + + const usedFieldsByGuidMap = usedFields.reduce( + generateGuidToFieldMap, + {} as Partial>, + ); + + const optionalColors = Object.entries(distinctsMap) + // filter already used fields from colors array + // filter fields from sections Y and X since by default they are not participated in colorizing. + .filter(([fieldId]) => !usedFieldsByGuidMap[fieldId]) + .reduce((acc, [fieldId, distincts]) => { + const field = fieldByGuidMap[fieldId]; + return field && distincts.length > 1 ? Object.assign(acc, {[acc.length]: field}) : acc; + }, [] as Field[]); + + return colors ? colors.concat(optionalColors) : optionalColors; +} diff --git a/src/server/modes/charts/plugins/ql/utils/connection.ts b/src/server/modes/charts/plugins/ql/utils/connection.ts new file mode 100644 index 0000000..a8b2216 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/utils/connection.ts @@ -0,0 +1,40 @@ +import {ConnectorType, DATALENS_QL_CONNECTION_TYPES} from '../../../../../../shared'; + +export const CONNECTOR_TYPE_TO_QL_CONNECTION_TYPE_MAP: Record = { + [ConnectorType.Postgres]: DATALENS_QL_CONNECTION_TYPES.POSTGRESQL, + [ConnectorType.Greenplum]: DATALENS_QL_CONNECTION_TYPES.POSTGRESQL, + [ConnectorType.Clickhouse]: DATALENS_QL_CONNECTION_TYPES.CLICKHOUSE, + [ConnectorType.ChOverYt]: DATALENS_QL_CONNECTION_TYPES.CLICKHOUSE, + [ConnectorType.ChOverYtUserAuth]: DATALENS_QL_CONNECTION_TYPES.CLICKHOUSE, + [ConnectorType.Chydb]: DATALENS_QL_CONNECTION_TYPES.CLICKHOUSE, + [ConnectorType.ChFrozenDemo]: DATALENS_QL_CONNECTION_TYPES.CLICKHOUSE, + [ConnectorType.Chyt]: DATALENS_QL_CONNECTION_TYPES.CLICKHOUSE, + [ConnectorType.Mssql]: DATALENS_QL_CONNECTION_TYPES.MSSQL, + [ConnectorType.Mysql]: DATALENS_QL_CONNECTION_TYPES.MYSQL, + [ConnectorType.Oracle]: DATALENS_QL_CONNECTION_TYPES.ORACLE, + [ConnectorType.Trino]: DATALENS_QL_CONNECTION_TYPES.TRINO, + [ConnectorType.Ydb]: DATALENS_QL_CONNECTION_TYPES.YQL, + [ConnectorType.Yq]: DATALENS_QL_CONNECTION_TYPES.YQL, + [ConnectorType.Promql]: DATALENS_QL_CONNECTION_TYPES.PROMQL, + [ConnectorType.Monitoring]: DATALENS_QL_CONNECTION_TYPES.MONITORING, + [ConnectorType.MonitoringExt]: DATALENS_QL_CONNECTION_TYPES.MONITORING, +}; + +export function getConnectorToQlConnectionTypeMap() { + return CONNECTOR_TYPE_TO_QL_CONNECTION_TYPE_MAP; +} + +export type QLConnectionTypeMap = ReturnType; + +export function convertConnectionType( + connectionTypeMap: QLConnectionTypeMap, + connectionType: string, +) { + const mappedConnectionType = connectionTypeMap[connectionType]; + + if (!mappedConnectionType) { + throw new Error('Unsupported connection type'); + } + + return mappedConnectionType; +} diff --git a/src/server/modes/charts/plugins/ql/utils/constants.ts b/src/server/modes/charts/plugins/ql/utils/constants.ts new file mode 100644 index 0000000..e82cc18 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/utils/constants.ts @@ -0,0 +1,22 @@ +import {VISUALIZATION_IDS, isTrueArg} from '../../../../../../shared'; + +export const LOG_TIMING = 'process' in globalThis && isTrueArg(process.env.SHOW_CHARTS_LOG_TIMING); +export const LOG_INFO = 'process' in globalThis && isTrueArg(process.env.SHOW_CHARTS_LOG); + +export const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD'; +export const DEFAULT_DATETIME_FORMAT = 'YYYY-MM-DDTHH:mm:ss'; + +export const LINEAR_VISUALIZATIONS = new Set([ + VISUALIZATION_IDS.LINE, + VISUALIZATION_IDS.AREA, + VISUALIZATION_IDS.AREA_100P, + VISUALIZATION_IDS.COLUMN, + VISUALIZATION_IDS.COLUMN_100P, + VISUALIZATION_IDS.BAR, + VISUALIZATION_IDS.BAR_100P, +]); + +export const PIE_VISUALIZATIONS = new Set([VISUALIZATION_IDS.PIE, VISUALIZATION_IDS.DONUT]); + +export const QUERY_TITLE = 'query #'; +export const QUERY_ALIAS_TITLE = '_alias'; diff --git a/src/server/modes/charts/plugins/ql/utils/identify-params.ts b/src/server/modes/charts/plugins/ql/utils/identify-params.ts new file mode 100644 index 0000000..ddcbdbe --- /dev/null +++ b/src/server/modes/charts/plugins/ql/utils/identify-params.ts @@ -0,0 +1,40 @@ +import type {QlExtendedConfig, StringParams} from '../../../../../../shared'; +import {QLChartType} from '../../../../../../shared'; +import {mapQlConfigToLatestVersion} from '../../../../../../shared/modules/config/ql'; +import type {GetTranslationFn} from '../../../../../../shared/types/language'; + +export function identifyParams(args: {getTranslation: GetTranslationFn; chart: QlExtendedConfig}) { + const {chart, getTranslation} = args; + + const config = mapQlConfigToLatestVersion(chart, { + i18n: getTranslation, + }); + const {chartType, params} = config; + + const availableParams: StringParams = {}; + + if (params) { + params.forEach((param) => { + if ( + param.type.includes('interval') && + typeof param.defaultValue === 'object' && + param.defaultValue !== null + ) { + const fromName = `${param.name}_from`; + const toName = `${param.name}_to`; + + availableParams[`${param.name}`] = ''; + availableParams[fromName] = ''; + availableParams[toName] = ''; + } else { + availableParams[param.name] = ''; + } + }); + } + + if (chartType === QLChartType.Monitoringql) { + availableParams['interval'] = ''; + } + + return availableParams; +} diff --git a/src/server/modes/charts/plugins/ql/utils/migrate-helpers.ts b/src/server/modes/charts/plugins/ql/utils/migrate-helpers.ts new file mode 100644 index 0000000..52e059a --- /dev/null +++ b/src/server/modes/charts/plugins/ql/utils/migrate-helpers.ts @@ -0,0 +1,295 @@ +import type {Field} from '../../../../../../shared'; +import {DATASET_FIELD_TYPES} from '../../../../../../shared'; +import type {QlConfigResultEntryMetadataDataColumnOrGroup} from '../../../../../../shared/types/config/ql'; + +import {isGroup} from './misc-helpers'; + +const findIndexes = ({fields, forbiddenIndexes}: {fields: Field[]; forbiddenIndexes: number[]}) => + fields.reduce((result: number[], field: Field, fieldIndex: number) => { + if ( + (field.cast === DATASET_FIELD_TYPES.INTEGER || + field.cast === DATASET_FIELD_TYPES.FLOAT) && + !forbiddenIndexes.includes(fieldIndex) + ) { + return [...result, fieldIndex]; + } else { + return result; + } + }, []); + +export const migrateLineVisualization = ({ + order, + fields, + rows, +}: { + order: QlConfigResultEntryMetadataDataColumnOrGroup[]; + fields: Field[]; + rows: string[][]; +}) => { + let collectingX = false; + let collectingY = false; + let collectingColor = false; + + let xIndex = -1; + let xFields: Field[] = []; + let yIndexes: number[] = []; + let colorIndexes: number[] = []; + + let xDeclared = false; + let yDeclared = false; + + if (order.length) { + order.forEach((item: QlConfigResultEntryMetadataDataColumnOrGroup) => { + const itemIsGroup = isGroup(item); + + if (itemIsGroup && item.name === 'X') { + collectingX = true; + return; + } + + if (itemIsGroup && item.name === 'Y') { + collectingX = false; + collectingY = true; + return; + } + + if (itemIsGroup && item.name === 'Colors') { + collectingY = false; + collectingColor = true; + return; + } + + if (itemIsGroup && item.name === 'Available') { + collectingX = false; + collectingY = false; + collectingColor = false; + return; + } + + if (collectingX && !itemIsGroup) { + xIndex = fields.findIndex((column) => column.title === item.name); + + xDeclared = true; + } + + if (collectingY && !itemIsGroup) { + const yIndex = fields.findIndex((column) => column.title === item.name); + + if (yIndex > -1) { + yIndexes.push(yIndex); + } + + yDeclared = true; + } + + if (collectingColor && !itemIsGroup) { + colorIndexes.push(fields.findIndex((column) => column.title === item.name)); + } + }); + + if (xDeclared && xIndex === -1) { + xIndex = fields.findIndex( + (_column, index) => !yIndexes.includes(index) && !colorIndexes.includes(index), + ); + } + + if (yDeclared) { + const newYIndexes = findIndexes({ + fields, + forbiddenIndexes: [...yIndexes, xIndex, ...colorIndexes], + }); + + yIndexes = [...yIndexes, ...newYIndexes]; + } + } else { + yIndexes = findIndexes({ + fields, + forbiddenIndexes: [], + }); + + xIndex = fields.findIndex((_field, index) => !yIndexes.includes(index)); + + colorIndexes = fields + .map((_field, fieldIndex) => { + const homogeneousValues: Set = new Set(); + + const homogeneity = rows.every((row, rowIndex) => { + const value = row[fieldIndex]; + + if (rowIndex === 0) { + homogeneousValues.add(value); + + return true; + } + + if (homogeneousValues.has(value)) { + return true; + } else { + return false; + } + }); + + return homogeneity ? -1 : fieldIndex; + }) + .filter((index) => index !== -1 && xIndex !== index && !yIndexes.includes(index)); + + colorIndexes.sort((colorIndex1: number, colorIndex2: number) => { + if (fields[colorIndex1] && fields[colorIndex2]) { + return fields[colorIndex1].title > fields[colorIndex2].title ? 1 : -1; + } else { + return 0; + } + }); + } + + if (xIndex > -1) { + xFields = [fields[xIndex]]; + } + + const yFields: Field[] = []; + + yIndexes.forEach((index) => { + if (index > -1) { + yFields.push(fields[index]); + } + }); + + const colors: Field[] = []; + + colorIndexes.forEach((index) => { + if (index > -1) { + colors.push(fields[index]); + } + }); + + return {xFields, yFields, colors}; +}; + +export const migratePieVisualization = ({ + order, + fields, +}: { + order: QlConfigResultEntryMetadataDataColumnOrGroup[]; + fields: Field[]; +}) => { + let colorIndex = -1; + let measureIndex = -1; + + let collectingColor = false; + let collectingMeasure = false; + + order.forEach((item: QlConfigResultEntryMetadataDataColumnOrGroup) => { + const itemIsGroup = isGroup(item); + + if (itemIsGroup && item.name === 'Color') { + collectingColor = true; + return; + } + + if (itemIsGroup && item.name === 'Measure') { + collectingColor = false; + collectingMeasure = true; + return; + } + + if (itemIsGroup && item.name === 'Available') { + collectingMeasure = false; + collectingColor = false; + return; + } + + if (collectingColor && !itemIsGroup) { + colorIndex = fields.findIndex((column) => column.title === item.name); + } + + if (collectingMeasure && !itemIsGroup) { + measureIndex = fields.findIndex((column) => column.title === item.name); + } + }); + + let colorFields: Field[] = []; + let measureFields: Field[] = []; + + if (colorIndex > -1) { + colorFields = [fields[colorIndex]]; + } + + if (measureIndex > -1) { + measureFields = [fields[measureIndex]]; + } + + return {colorFields, measureFields}; +}; + +export const migrateMetricVisualization = ({ + order, + fields, +}: { + order: QlConfigResultEntryMetadataDataColumnOrGroup[]; + fields: Field[]; +}) => { + let collectingMeasure = false; + + let measureIndex = -1; + + order.forEach((item) => { + const itemIsGroup = isGroup(item); + + if (itemIsGroup && item.name === 'Measure') { + collectingMeasure = true; + return; + } + + if (itemIsGroup && item.name === 'Available') { + collectingMeasure = false; + return; + } + + if (collectingMeasure && !itemIsGroup) { + measureIndex = fields.findIndex((column) => column.title === item.name); + } + }); + + let measureFields: Field[] = []; + + if (measureIndex > -1) { + measureFields = [fields[measureIndex]]; + } + return {measureFields}; +}; + +export const migrateTableVisualization = ({ + order, + fields, +}: { + order: QlConfigResultEntryMetadataDataColumnOrGroup[]; + fields: Field[]; +}) => { + let collectingColumns = false; + + const columnFields: Field[] = []; + + order.forEach((item: QlConfigResultEntryMetadataDataColumnOrGroup) => { + const itemIsGroup = isGroup(item); + + if (itemIsGroup && item.name === 'Columns') { + collectingColumns = true; + return; + } + + if (itemIsGroup && item.name === 'Available') { + collectingColumns = false; + return; + } + + if (collectingColumns && !itemIsGroup) { + const itemIndex = fields.findIndex((column) => column.title === item.name); + + if (itemIndex > -1) { + columnFields.push(fields[itemIndex]); + } + } + }); + + return {columnFields}; +}; diff --git a/src/server/modes/charts/plugins/ql/utils/misc-helpers.ts b/src/server/modes/charts/plugins/ql/utils/misc-helpers.ts new file mode 100644 index 0000000..e6e595b --- /dev/null +++ b/src/server/modes/charts/plugins/ql/utils/misc-helpers.ts @@ -0,0 +1,757 @@ +import type {YagrConfig} from '@gravity-ui/chartkit/yagr'; +import {dateTimeParse} from '@gravity-ui/date-utils'; + +import type { + ApiV2DataExportField, + FeatureConfig, + IChartEditor, + QLChartType, + QlConfigPreviewTableData, + ServerVisualization, + StringParams, +} from '../../../../../../shared'; +import { + ConnectorType, + DATALENS_QL_CONNECTION_TYPES, + DATALENS_QL_TYPES, + QLParamType, + biToDatalensQL, + getDatalensQLTypeName, + getUtcDateTime, + isMonitoringOrPrometheusChart, +} from '../../../../../../shared'; +import type { + QLConfigQuery, + QlConfigParam, + QlConfigParamInterval, + QlConfigRequestParam, + QlConfigResultEntryMetadataDataColumn, + QlConfigResultEntryMetadataDataColumnOrGroup, + QlConfigResultEntryMetadataDataGroup, +} from '../../../../../../shared/types/config/ql'; +import {CONNECTIONS_DASHSQL, CONNECTION_ID_PLACEHOLDER} from '../../control/url/constants'; + +import type {QLConnectionTypeMap} from './connection'; +import {convertConnectionType} from './connection'; +import {LINEAR_VISUALIZATIONS, LOG_INFO, LOG_TIMING, QUERY_TITLE} from './constants'; + +export { + formatUnknownTypeValue, + renderValue, + parseNumberValue, + parseNumberValueForTable, +} from './value-helpers'; + +let currentConsole: {log: Function} = console; + +export function setConsole(newConsole: {log: Function}) { + currentConsole = newConsole; +} + +export function log(...data: unknown[]) { + return LOG_INFO && currentConsole.log(...data); +} + +export function logTiming(...data: unknown[]) { + return LOG_TIMING && currentConsole.log(...data); +} + +export interface QLResultEntryMetadata { + event: 'metadata'; + data: { + names: string[]; + driver_types: (string | number)[]; + postgresql_typnames?: string[]; + bi_types: string[]; + data_export: ApiV2DataExportField; + }; +} + +export interface QLResultEntry { + event: string; + data: unknown; +} + +export interface QLResultEntryData { + event: 'data'; + data: string[]; +} + +export interface QLResult { + [key: string]: QLResultEntry[]; +} + +export interface QLRenderResultTable { + head: {}; + rows: {}; + metadata?: { + order: QlConfigResultEntryMetadataDataColumnOrGroup[]; + }; + tablePreviewData?: QlConfigPreviewTableData; +} + +export interface QLRenderResultMetric { + content: { + current: { + value: string | number; + precision: number; + }; + }; + title: string; + metadata?: { + order: QlConfigResultEntryMetadataDataColumnOrGroup[]; + }; +} + +export type QLValue = string | number | null; + +export type QLRenderResultHCGraphData = ( + | number + | null + | { + name?: string; + y: QLValue; + } +)[]; + +export interface QLRenderResultHCGraph { + data: QLRenderResultHCGraphData; + title?: string | number; +} + +export interface QLRenderResultHC { + categories?: (string | number)[]; + categories_ms?: (string | number)[]; + metadata?: { + order: QlConfigResultEntryMetadataDataColumnOrGroup[]; + }; + graphs?: QLRenderResultHCGraph[]; + tablePreviewData?: QlConfigPreviewTableData; +} + +export interface QLRenderResultYagrGraph { + data: (number | null)[]; + id?: string | number; + name?: string | number; + color?: string; + sum?: number; + spanGaps?: boolean; +} + +export interface QLRenderResultYagr { + timeline?: number[]; + metadata?: { + order: QlConfigResultEntryMetadataDataColumnOrGroup[]; + }; + graphs?: QLRenderResultYagrGraph[]; + axes?: any[]; + tablePreviewData?: QlConfigPreviewTableData; + timeZone?: string; + tooltip?: YagrConfig['tooltip']; +} + +const clickhouseQuotemap: Record = { + // Unlike most databases, CH only works with binary strings + // (not unicode), and supports entering a 0 byte via \0. + '\b': '\\b', + '\f': '\\f', + '\r': '\\r', + '\n': '\\n', + '\t': '\\t', + '\0': '\\0', + '\\': '\\\\', + "'": "\\'", +}; + +function escapeSingleForClickhouse(input: string, type: string) { + const escapedValue = String(input).replace( + /([\b\f\r\n\t\0\\'])/g, + (match) => clickhouseQuotemap[match], + ); + switch (type) { + case QLParamType.String: + return `'${escapedValue}'`; + + case QLParamType.Number: + case QLParamType.Boolean: + return escapedValue; + + case QLParamType.Date: + case QLParamType.DateInterval: + // The time is already in UTC, so we do .utc() so it does not convert once again + return `toDate('${dateTimeParse(escapedValue, {timeZone: 'UTC'})?.format( + 'YYYY-MM-DD', + )}')`; + + case QLParamType.Datetime: + case QLParamType.DatetimeInterval: + // The time is already in UTC, so we do .utc() so it does not convert once again + return `toDateTime('${dateTimeParse(escapedValue, {timeZone: 'UTC'})?.format( + 'YYYY-MM-DD HH:mm:ss', + )}')`; + default: + throw new Error('Unsupported parameter type passed'); + } +} + +const quotemap: Record = { + // Common to most databases: quotas via backslash, with the exception of + // the 0 byte, which can only be cut. + '\b': '\\b', + '\f': '\\f', + '\r': '\\r', + '\n': '\\n', + '\t': '\\t', + '\0': '', + '\\': '\\\\', + "'": "\\'", +}; + +function escapeSingleForPostgreSQL(input: string, type: string) { + const escapedValue = String(input).replace(/([\b\f\r\n\t\0\\'])/g, (match) => quotemap[match]); + switch (type) { + case QLParamType.String: + return `E'${escapedValue}'`; + + case QLParamType.Number: + case QLParamType.Boolean: + return escapedValue; + + case QLParamType.Date: + case QLParamType.DateInterval: + // The time is already in UTC, so we do .utc() so it does not convert once again + return `'${dateTimeParse(escapedValue, {timeZone: 'UTC'})?.format( + 'YYYY-MM-DD', + )}'::date`; + + case QLParamType.Datetime: + case QLParamType.DatetimeInterval: + // The time is already in UTC, so we do .utc() so it does not convert once again + return `'${dateTimeParse(escapedValue, {timeZone: 'UTC'})?.format( + 'YYYY-MM-DDTHH:mm:ss', + )}'::timestamp`; + default: + throw new Error('Unsupported parameter type passed'); + } +} + +function escapeSingleOther(input: string, type: string) { + const escapedValue = String(input).replace(/([\b\f\r\n\t\0\\'])/g, (match) => quotemap[match]); + switch (type) { + case QLParamType.String: + return `'${escapedValue}'`; + + case QLParamType.Number: + case QLParamType.Boolean: + return escapedValue; + + case QLParamType.Date: + case QLParamType.DateInterval: + // The time is already in UTC, so we do .utc() so it does not convert once again + return `'${dateTimeParse(escapedValue, {timeZone: 'UTC'})?.format('YYYY-MM-DD')}'`; + + case QLParamType.Datetime: + case QLParamType.DatetimeInterval: + // The time is already in UTC, so we do .utc() so it does not convert once again + return `'${dateTimeParse(escapedValue, {timeZone: 'UTC'})?.format( + 'YYYY-MM-DDTHH:mm:ss', + )}'`; + default: + throw new Error('Unsupported parameter type passed'); + } +} + +function wrapQuotedValue(quotedValue: string, operation?: string) { + switch (operation?.toLowerCase()) { + case 'in': + return `(${quotedValue})`; + + case '=': + default: + return quotedValue; + } +} + +function escape( + input: string[] | string, + connectionType: string, + paramDescription?: QlConfigParam, + operation?: string, +) { + const type = paramDescription ? paramDescription.type.toLowerCase() : 'string'; + + let quoter = escapeSingleOther; + + if (connectionType === DATALENS_QL_CONNECTION_TYPES.CLICKHOUSE) { + quoter = escapeSingleForClickhouse; + } else if (connectionType === DATALENS_QL_CONNECTION_TYPES.POSTGRESQL) { + quoter = escapeSingleForPostgreSQL; + } + + if (Array.isArray(input)) { + if (input.length === 1) { + return wrapQuotedValue(quoter(String(input[0]), type), operation); + } else { + const result = input.map((inputEntry) => quoter(inputEntry, type)).join(', '); + + return `(${result})`; + } + } else { + return wrapQuotedValue(quoter(String(input), type), operation); + } +} + +function dumpReqParamValue(input: string, type: string, datalensQLConnectionType?: ConnectorType) { + let newValue; + + switch (type) { + case QLParamType.String: + case QLParamType.Number: + case QLParamType.Boolean: + newValue = input; + break; + + case QLParamType.Date: + case QLParamType.DateInterval: + if ( + datalensQLConnectionType === DATALENS_QL_CONNECTION_TYPES.MONITORING || + datalensQLConnectionType === DATALENS_QL_CONNECTION_TYPES.PROMQL + ) { + newValue = getUtcDateTime(input)?.toISOString(); + + if (!newValue) { + throw new Error('Invalid date passed'); + } + } else { + newValue = getUtcDateTime(input)?.format('YYYY-MM-DD'); + } + + if (!newValue) { + newValue = 'Invalid date'; + } + + break; + + case QLParamType.Datetime: + case QLParamType.DatetimeInterval: + if ( + datalensQLConnectionType === ConnectorType.Monitoring || + datalensQLConnectionType === ConnectorType.Promql + ) { + newValue = getUtcDateTime(input)?.toISOString(); + + if (!newValue) { + throw new Error('Invalid date passed'); + } + } else { + newValue = getUtcDateTime(input)?.format('YYYY-MM-DDTHH:mm:ss'); + } + + if (!newValue) { + newValue = 'Invalid date'; + } + + break; + + default: + throw new Error('Unsupported parameter type passed'); + } + + return newValue; +} + +function dumpReqParam( + input: string | string[], + paramDescription: QlConfigParam, + datalensQLConnectionType?: ConnectorType, +): QlConfigRequestParam { + const type = paramDescription.type.toLowerCase(); + let dumped: string | string[]; + + if (Array.isArray(input)) { + if (input.length === 1) { + dumped = dumpReqParamValue(String(input[0]), type, datalensQLConnectionType); + } else { + dumped = input.map((inputEntry) => + dumpReqParamValue(inputEntry, type, datalensQLConnectionType), + ); + } + } else { + dumped = dumpReqParamValue(String(input), type, datalensQLConnectionType); + } + let bi_type = type; + if (bi_type === 'number') { + bi_type = 'float'; + } + + return {type_name: bi_type, value: dumped}; +} + +export function buildSource({ + id, + connectionType, + query, + params, + paramsDescription, + qlConnectionTypeMap, +}: { + id: string; + connectionType: string; + query: string; + params: StringParams; + paramsDescription: QlConfigParam[]; + qlConnectionTypeMap: QLConnectionTypeMap; + features: FeatureConfig; +}) { + let sqlQuery = query; + + const datalensQLConnectionType = convertConnectionType(qlConnectionTypeMap, connectionType); + + const QLRequestParams: Record = {}; + if ( + datalensQLConnectionType === DATALENS_QL_CONNECTION_TYPES.POSTGRESQL || + datalensQLConnectionType === DATALENS_QL_CONNECTION_TYPES.CLICKHOUSE + ) { + // For PG/CH, we temporarily leave the old version, for greater compatibility. + if (params) { + // For correctness, the replacement should occur with a single call to `replace`, + // in order not to replace the excess in case the inserted new value + // contains any `{{key}}` + sqlQuery = sqlQuery.replace( + /(\s*)(=|in)?(\s*)\{\{([^}]+)\}\}/gi, + (_, spaceBefore, operation, spaceAfter, key) => { + const fixedKey = key.replace('_from', '').replace('_to', ''); + + const paramDescription = paramsDescription.find( + (param) => param.name === key || param.name === fixedKey, + ); + + let escapedValue; + if (paramDescription) { + escapedValue = escape( + params[key], + datalensQLConnectionType, + paramDescription, + operation, + ); + } else { + escapedValue = key; + } + + return `${ + operation + ? `${spaceBefore}${operation}${spaceAfter}` + : `${spaceBefore}${spaceAfter}` + }${escapedValue}`; + }, + ); + } + } else { + // For the rest - we leave the text as it is, we pass the parameters to the back. + Object.keys(params).forEach((key) => { + const paramDescription = paramsDescription.find((param) => param.name === key); + if (paramDescription?.defaultValue !== undefined) { + if ( + paramDescription.type === QLParamType.DateInterval || + paramDescription.type === QLParamType.DatetimeInterval + ) { + paramDescription.type = + paramDescription.type === QLParamType.DateInterval + ? QLParamType.Date + : QLParamType.Datetime; + + QLRequestParams[`${key}_from`] = dumpReqParam( + params[`${key}_from`], + { + ...paramDescription, + defaultValue: (paramDescription.defaultValue as QlConfigParamInterval) + .from, + }, + datalensQLConnectionType, + ); + + QLRequestParams[`${key}_to`] = dumpReqParam( + params[`${key}_to`], + { + ...paramDescription, + defaultValue: (paramDescription.defaultValue as QlConfigParamInterval) + .to, + }, + datalensQLConnectionType, + ); + } else { + QLRequestParams[key] = dumpReqParam( + params[key], + paramDescription, + datalensQLConnectionType, + ); + } + } + }); + } + + const payload = { + sql_query: sqlQuery, + params: QLRequestParams, + }; + + const connectionsUrl = CONNECTIONS_DASHSQL; + + return { + url: connectionsUrl.replace(CONNECTION_ID_PLACEHOLDER, id), + method: 'post', + data: payload, + }; +} + +export function getRows(data: QLResult, field = 'sql'): string[][] { + let rows: string[][] = []; + + rows = data[field] + .filter((entry: QLResultEntry) => entry.event === 'row') + .map((entry: QLResultEntry) => entry.data) as string[][]; + + return rows; +} + +export function getColumns(args: { + data: QLResult; + connectionType: string; + field?: string; + qlConnectionTypeMap: QLConnectionTypeMap; +}): QlConfigResultEntryMetadataDataColumn[] | null { + const {data, connectionType, field = 'sql', qlConnectionTypeMap} = args; + const row = data[field].find((entry: QLResultEntry) => entry.event === 'metadata'); + + const datalensQLConnectionType = convertConnectionType(qlConnectionTypeMap, connectionType); + + if (row) { + const metadataRow = row as QLResultEntryMetadata; + + return metadataRow.data.names.map((name, idx) => { + const bi_type = metadataRow.data.bi_types[idx]; + const driver_type = + datalensQLConnectionType === DATALENS_QL_CONNECTION_TYPES.POSTGRESQL + ? (metadataRow.data.postgresql_typnames || [])[idx] + : metadataRow.data.driver_types[idx]; + + return { + name: name, + + // typeName -- legacy type, incompatible with Field + // we can remove it once wizard ql common visualization will be enabled + typeName: + (bi_type + ? biToDatalensQL(bi_type) + : getDatalensQLTypeName(driver_type, datalensQLConnectionType)) || + DATALENS_QL_TYPES.UNKNOWN, + + // biType -- new type, compatable with Field + biType: bi_type, + }; + }); + } else { + return null; + } +} + +export function getColumnsAndRows({ + chartType, + ChartEditor, + queries, + connectionType, + data, + qlConnectionTypeMap, +}: { + chartType: QLChartType; + ChartEditor: IChartEditor; + queries: QLConfigQuery[]; + connectionType: string; + data: {[key: string]: any}; + qlConnectionTypeMap: QLConnectionTypeMap; +}) { + let columns: QlConfigResultEntryMetadataDataColumn[] | null = []; + + const columnsOrder: Record = {}; + const columnsByQuery: Record = {}; + + let rows: string[][] = []; + + if (isMonitoringOrPrometheusChart(chartType)) { + iterateThroughVisibleQueries(queries, (_query, i) => { + let localColumns: QlConfigResultEntryMetadataDataColumn[] = []; + + try { + const parsedColumns = getColumns({ + data, + connectionType, + field: `ql_${i}`, + qlConnectionTypeMap, + }); + + if (parsedColumns === null) { + return; + } + + localColumns = parsedColumns; + } catch (error) { + ChartEditor._setError({ + code: 'ERR.CK.PROCESSING_ERROR', + }); + } + + localColumns.push({ + biType: 'string', + name: QUERY_TITLE, + typeName: 'string', + }); + + if (columns && columns.length > 0) { + const uniqueLocalColumns = localColumns.filter((localColumn) => { + return columns?.find((column) => column.name === localColumn.name) === null; + }); + + columns = columns.concat(uniqueLocalColumns); + + columnsOrder[i] = uniqueLocalColumns.map((column) => { + return localColumns.findIndex((localColumn) => { + return localColumn.name === column.name; + }); + }); + + columnsByQuery[i] = localColumns; + } else { + columns = localColumns; + columnsOrder[i] = columns.map((_column, j) => j); + columnsByQuery[i] = localColumns; + } + }); + iterateThroughVisibleQueries(queries, (query, i) => { + let localRows; + + try { + localRows = getRows(data, `ql_${i}`); + } catch (error) { + ChartEditor._setError({ + code: 'ERR.CK.PROCESSING_ERROR', + }); + } + + if (!localRows) { + return; + } + + localRows.forEach((row) => { + row.push(query.queryName); + }); + + if (rows.length > 0) { + localRows.forEach((localRow) => { + const newRow: string[] = []; + + const localColumns = columnsByQuery[i]; + + columns?.forEach((column) => { + const targetColumnIndex = localColumns.findIndex( + (localColumn: QlConfigResultEntryMetadataDataColumn) => + localColumn.name === column.name, + ); + + if (targetColumnIndex > -1) { + newRow.push(localRow[targetColumnIndex]); + } else { + newRow.push('null'); + } + }); + + rows.push(newRow); + }); + } else { + rows = localRows; + } + }); + + if (rows.length === 0) { + return {}; + } + } else { + try { + columns = getColumns({ + data, + connectionType: connectionType || ConnectorType.Clickhouse, + qlConnectionTypeMap, + }); + + if (columns !== null) { + rows = getRows(data); + } + } catch (error) { + ChartEditor._setError({ + code: 'ERR.CK.PROCESSING_ERROR', + }); + } + + if (!columns || !rows || rows.length === 0) { + return {}; + } + } + + return {columns, rows}; +} + +export function isGroup( + item: QlConfigResultEntryMetadataDataColumnOrGroup, +): item is QlConfigResultEntryMetadataDataGroup { + return Boolean((item as QlConfigResultEntryMetadataDataGroup).group); +} + +export function iterateThroughVisibleQueries( + queries: QLConfigQuery[], + cb: (query: QLConfigQuery, index: number, array: QLConfigQuery[]) => void, +) { + queries.forEach((query, ...args) => { + if (query.hidden) { + return; + } + + cb(query, ...args); + }); +} + +export const prepareQuery = (query: string) => { + return query.trim().replace(/;+$/g, ''); +}; + +const removeComments = (query: string) => { + const result = query.replace( + /("(""|[^"])*")|('(''|[^'])*')|(--[^\n\r]*)|(\/\*[\w\W]*?(?=\*\/)\*\/)/gm, + (match) => { + if ( + (match[0] === '"' && match[match.length - 1] === '"') || + (match[0] === "'" && match[match.length - 1] === "'") + ) + return match; + + return ''; + }, + ); + + return result; +}; + +const removeQuotes = (query: string) => { + // eslint-disable-next-line security/detect-unsafe-regex + const result = query.replace(/(["'])(?:(?=(\\?))\2.)*?\1/gm, ''); + + return result; +}; + +export const doesQueryContainOrderBy = (query: string) => { + const queryWithoutComments = removeComments(removeQuotes(query)); + + return /order by/gim.test(queryWithoutComments); +}; + +export const visualizationCanHaveContinuousAxis = (visualization: ServerVisualization) => { + return LINEAR_VISUALIZATIONS.has(visualization.id); +}; diff --git a/src/server/modes/charts/plugins/ql/utils/value-helpers.ts b/src/server/modes/charts/plugins/ql/utils/value-helpers.ts new file mode 100644 index 0000000..e70446d --- /dev/null +++ b/src/server/modes/charts/plugins/ql/utils/value-helpers.ts @@ -0,0 +1,53 @@ +import type {QLValue} from './misc-helpers'; + +export function formatUnknownTypeValue(value: string | number | null) { + if (value === null) { + return null; + } + + return JSON.stringify(value); +} + +export function renderValue(value: QLValue) { + if (value === null) { + return 'null'; + } + + return value; +} + +export function parseNumberValue(value: number | string | null) { + let result; + + if (value === null) { + result = null; + } else if (value === '-inf') { + result = -Infinity; + } else if (value === 'inf') { + result = Infinity; + } else if (value === 'nan') { + result = NaN; + } else { + result = Number(value); + } + + return result; +} + +export function parseNumberValueForTable(value: number | string | null) { + let result; + + if (value === null) { + result = null; + } else if (value === '-inf') { + result = '-Infinity'; + } else if (value === 'inf') { + result = 'Infinity'; + } else if (value === 'nan') { + result = 'NaN'; + } else { + result = Number(value); + } + + return result; +} diff --git a/src/server/modes/charts/plugins/ql/utils/visualization-utils.ts b/src/server/modes/charts/plugins/ql/utils/visualization-utils.ts new file mode 100644 index 0000000..16ee902 --- /dev/null +++ b/src/server/modes/charts/plugins/ql/utils/visualization-utils.ts @@ -0,0 +1,224 @@ +import cloneDeep from 'lodash/cloneDeep'; + +import type {Field, ServerVisualization} from '../../../../../../shared'; +import {PlaceholderId, WizardVisualizationId} from '../../../../../../shared'; +import type {QlConfigResultEntryMetadataDataColumnOrGroup} from '../../../../../../shared/types/config/ql'; + +import { + autofillLineVisualization, + autofillMetricVisualization, + autofillPieVisualization, + autofillScatterVisualization, + autofillTableVisualization, + autofillTreemapVisualization, +} from './autofill-helpers'; +import { + migrateLineVisualization, + migrateMetricVisualization, + migratePieVisualization, + migrateTableVisualization, +} from './migrate-helpers'; + +export const migrateOrAutofillVisualization = ({ + visualization: originalVisualization, + fields, + rows, + order, + colors: originalColors, + distinctsMap, +}: { + visualization: ServerVisualization; + fields: Field[]; + rows: string[][]; + order?: QlConfigResultEntryMetadataDataColumnOrGroup[] | null; + colors?: Field[]; + // distincts are optional and is used only for visualization which supports multiple color fields in section + distinctsMap?: Record; +}) => { + const {id: visualizationId} = originalVisualization; + + const newVisualization = cloneDeep(originalVisualization); + let newColors: Field[] = originalColors || []; + + // Logic of autofill depends on visualization id + if ( + new Set([ + WizardVisualizationId.Line, + WizardVisualizationId.Area, + WizardVisualizationId.Area100p, + WizardVisualizationId.Column, + WizardVisualizationId.Column100p, + WizardVisualizationId.Bar, + WizardVisualizationId.Bar100p, + ]).has(visualizationId as WizardVisualizationId) + ) { + if (order) { + // Order is set, so we need to migrate old order to new structure + const { + xFields, + yFields, + colors: migratedColors, + } = migrateLineVisualization({ + order, + fields, + rows, + }); + + newVisualization.placeholders[0].items = xFields; + newVisualization.placeholders[1].items = yFields; + + newColors = migratedColors; + } else { + // Old order was not set, so we can do autofill + const {xFields, yFields, colors} = autofillLineVisualization({ + fields, + distinctsMap, + }); + + newVisualization.placeholders[0].items = xFields; + newVisualization.placeholders[1].items = yFields; + + if (colors) { + newColors = colors; + } + } + } else if (visualizationId === WizardVisualizationId.Scatter) { + // Scatter visualization was not present in older ql charts, + // so we don't need to migrate older order + const {xFields, yFields, pointsFields} = autofillScatterVisualization({fields}); + + newVisualization.placeholders[0].items = xFields; + newVisualization.placeholders[1].items = yFields; + newVisualization.placeholders[2].items = pointsFields; + } else if ( + new Set([WizardVisualizationId.Pie, WizardVisualizationId.Donut]).has( + visualizationId as WizardVisualizationId, + ) + ) { + // Checking if order is set (from older versions of ql charts) + const hasOrder = order && order.length > 0; + const {colorFields, measureFields} = hasOrder + ? // Order is set, so we need to migrate old order to new structure + migratePieVisualization({ + order: order, + fields, + }) + : // Old order was not set, so we can do autofill + autofillPieVisualization({ + fields, + }); + + const colorsPlaceholder = newVisualization.placeholders.find( + (p) => p.id === PlaceholderId.Colors, + ); + if (colorsPlaceholder) { + colorsPlaceholder.items = colorFields; + } + + const measurePlaceholder = newVisualization.placeholders.find( + (p) => p.id === PlaceholderId.Measures, + ); + if (measurePlaceholder) { + measurePlaceholder.items = measureFields; + } + } else if (visualizationId === WizardVisualizationId.Metric) { + // Checking if order is set (from older versions of ql charts) + if (order && order.length > 0) { + // Order is set, so we need to migrate old order to new structure + const {measureFields} = migrateMetricVisualization({ + order: order, + fields, + }); + + newVisualization.placeholders[0].items = measureFields; + } else { + // Old order was not set, so we can do autofill + const {measureFields} = autofillMetricVisualization({ + fields, + }); + + newVisualization.placeholders[0].items = measureFields; + } + } else if (visualizationId === WizardVisualizationId.Treemap) { + const {dimensionFields, sizeFields} = autofillTreemapVisualization({ + fields, + }); + + newVisualization.placeholders[0].items = dimensionFields; + newVisualization.placeholders[1].items = sizeFields; + } else if (visualizationId === WizardVisualizationId.FlatTable) { + // Checking if order is set (from older versions of ql charts) + if (order && order.length > 0) { + // Order is set, so we need to migrate old order to new structure + const {columnFields} = migrateTableVisualization({ + order: order, + fields, + }); + + newVisualization.placeholders[0].items = columnFields; + } else { + // Old order was not set, so we can do autofill + const {columnFields} = autofillTableVisualization({fields}); + + newVisualization.placeholders[0].items = columnFields; + } + } + + return { + colors: newColors, + visualization: newVisualization, + }; +}; + +export const mapItems = ({items, fields}: {items: Field[]; fields: Field[]}) => { + return items.map((item, i) => { + const exactField = fields.find( + (field) => field.guid === item.guid && field.data_type === item.data_type, + ); + + // Field does not need to be mapped + if (exactField) { + delete item.conflict; + + return item; + } + + const matchingField = fields.find((field) => field.title === item.title); + + // We need to use new field in placeholder + if (matchingField) { + if (item.data_type === matchingField.data_type) { + // eslint-disable-next-line no-param-reassign + return { + ...items[i], + ...matchingField, + }; + } else { + // eslint-disable-next-line no-param-reassign + return matchingField; + } + } + + return { + ...item, + conflict: 'not-existing-ql', + }; + }); +}; + +export const mapVisualizationPlaceholdersItems = ({ + visualization, + fields, +}: { + visualization: ServerVisualization; + fields: Field[]; +}) => { + const newVisualization = cloneDeep(visualization); + + // Visualization is not empty, we may need to map some new fields to existing fields in placeholders + newVisualization.placeholders.forEach((placeholder) => { + placeholder.items = mapItems({items: placeholder.items as Field[], fields}); + }); + + return newVisualization; +}; diff --git a/src/server/modes/charts/plugins/ql/worker.ts b/src/server/modes/charts/plugins/ql/worker.ts new file mode 100644 index 0000000..5ae2c0b --- /dev/null +++ b/src/server/modes/charts/plugins/ql/worker.ts @@ -0,0 +1,202 @@ +import {workerData} from 'worker_threads'; + +import workerPool from 'workerpool'; + +import type {QlConfig, QlExtendedConfig} from '../../../../../shared'; +import { + WizardVisualizationId, + isGravityChartsVisualization, + isYAGRVisualization, +} from '../../../../../shared'; +import {getTranslationFn} from '../../../../../shared/modules/language'; +import {Console} from '../../../../components/charts-engine'; +import type {GetChartApiContextArgs} from '../../../../components/charts-engine/components/processor/chart-api-context'; +import type { + BuildChartArgs, + BuildChartConfigArgs, + BuildLibraryConfigArgs, + BuildParamsArgs, + BuildSourceArgs, + WizardWorker, +} from '../../../../components/charts-engine/components/wizard-worker/types'; +import {getChartApiContext} from '../../../../components/charts-engine/components/wizard-worker/utils'; +import {createI18nInstance} from '../../../../utils/language'; + +import qlModule from './module/private-module'; +import type {QLAdditionalData} from './types'; +import {identifyParams} from './utils/identify-params'; + +function getQLAdditionalData(): QLAdditionalData { + return { + qlConnectionTypeMap: workerData?.qlConnectionTypeMap ?? {}, + }; +} + +const worker: WizardWorker = { + buildParams: async (args: BuildParamsArgs) => { + const {shared, userLang} = args; + const i18n = createI18nInstance({lang: userLang}); + const result = identifyParams({ + chart: shared as QlExtendedConfig, + getTranslation: getTranslationFn(i18n.getI18nServer()), + }); + + return { + exports: result, + runtimeMetadata: {}, + }; + }, + + buildSources: async (args: BuildSourceArgs) => { + const {shared, params, actionParams, widgetConfig, userLang, palettes, features} = args; + const context = getChartApiContext({ + name: 'Sources', + shared, + params, + actionParams, + widgetConfig, + userLang, + }); + + const console = new Console({}); + qlModule.setConsole(console); + + const {qlConnectionTypeMap} = getQLAdditionalData(); + return { + exports: qlModule.buildSources({ + shared: shared as QlConfig, + ChartEditor: context.ChartEditor, + palettes, + qlConnectionTypeMap, + features, + }), + runtimeMetadata: context.__runtimeMetadata, + logs: console.getLogs(), + }; + }, + buildLibraryConfig: async (args: BuildLibraryConfigArgs) => { + const {shared, params, actionParams, widgetConfig, userLang, features} = args; + const context = getChartApiContext({ + name: 'Highcharts', + shared, + params, + actionParams, + widgetConfig, + userLang, + }); + + const console = new Console({}); + qlModule.setConsole(console); + + let result; + const serverChartConfig = shared as QlConfig; + const visualizationId = serverChartConfig?.visualization?.id; + switch (visualizationId) { + case WizardVisualizationId.FlatTable: { + result = {}; + break; + } + default: { + const shouldUseYagr = isYAGRVisualization( + serverChartConfig.chartType, + visualizationId, + ); + if ( + !shouldUseYagr && + isGravityChartsVisualization({id: visualizationId, features}) + ) { + result = {}; + } else { + result = qlModule.buildLibraryConfig({ + shared: serverChartConfig, + ChartEditor: context.ChartEditor, + features, + }); + } + } + } + + return { + exports: result, + runtimeMetadata: context.__runtimeMetadata, + logs: console.getLogs(), + }; + }, + + buildChartConfig: async (args: BuildChartConfigArgs) => { + const {shared, params, actionParams, widgetConfig, userLang, features} = args; + const context = getChartApiContext({ + name: 'Config', + shared, + params, + actionParams, + widgetConfig, + userLang, + }); + + const console = new Console({}); + qlModule.setConsole(console); + + return { + exports: qlModule.buildChartConfig({ + shared: shared as QlConfig, + ChartEditor: context.ChartEditor, + features, + widgetConfig, + }), + runtimeMetadata: context.__runtimeMetadata, + logs: console.getLogs(), + }; + }, + + buildChart: async (args: BuildChartArgs) => { + const { + shared, + params, + actionParams, + widgetConfig, + userLang, + data, + palettes, + features, + defaultColorPaletteId, + } = args; + const context = getChartApiContext({ + name: 'Prepare', + shared, + params, + actionParams, + widgetConfig, + userLang, + data: data as GetChartApiContextArgs['data'], + }); + + const console = new Console({}); + qlModule.setConsole(console); + + const {qlConnectionTypeMap} = getQLAdditionalData(); + const serverChartConfig = shared as QlConfig; + const shouldUseGravityCharts = isGravityChartsVisualization({ + features, + id: serverChartConfig?.visualization?.id, + }); + const plugin = shouldUseGravityCharts ? 'gravity-charts' : undefined; + const result = qlModule.buildGraph({ + shared: serverChartConfig, + ChartEditor: context.ChartEditor, + palettes, + features, + qlConnectionTypeMap, + plugin, + defaultColorPaletteId, + }); + + return { + exports: result, + runtimeMetadata: context.__runtimeMetadata, + logs: console.getLogs(), + }; + }, +}; + +workerPool.worker(worker); diff --git a/src/server/modes/charts/plugins/ql/yagr.ts b/src/server/modes/charts/plugins/ql/yagr.ts new file mode 100644 index 0000000..0f5511d --- /dev/null +++ b/src/server/modes/charts/plugins/ql/yagr.ts @@ -0,0 +1,184 @@ +import type {ChartType, YagrWidgetData} from '@gravity-ui/chartkit/yagr'; +import set from 'lodash/set'; + +import { + AxisAutoScaleModes, + LegendDisplayMode, + PlaceholderId, + QlVisualizationId, +} from '../../../../../shared'; +import type {IChartEditor, QlConfig, ServerVisualization} from '../../../../../shared'; +import {mapQlConfigToLatestVersion} from '../../../../../shared/modules/config/ql'; +import {getAxisTitle} from '../datalens/utils/axis-helpers'; + +const applyPlaceholderSettingsToYAxis = ({ + visualization, + placeholderIndex, + shared, +}: { + visualization: ServerVisualization; + placeholderIndex: number; + shared: QlConfig; +}) => { + const stacking = + (visualization.id === 'area' && shared.extraSettings?.stacking !== 'off') || + visualization.id === 'area100p' || + visualization.id === 'column' || + visualization.id === 'column100p'; + + const scale: any = { + normalize: false, + stacking, + type: 'linear', + }; + + if (visualization.placeholders && visualization.placeholders[placeholderIndex]) { + const yPlaceholder = visualization.placeholders[placeholderIndex]; + + if (yPlaceholder.settings) { + if ( + yPlaceholder.settings.autoscale === false || + (yPlaceholder.settings.scale === 'auto' && + yPlaceholder.settings.scaleValue === AxisAutoScaleModes.ZeroMax) + ) { + scale.min = 0; + } else if (yPlaceholder.settings.scale === 'manual') { + scale.min = Number((yPlaceholder.settings.scaleValue as [string, string])[0]); + scale.max = Number((yPlaceholder.settings.scaleValue as [string, string])[1]); + } + } + } + + return {scale}; +}; + +function getTitleForAxis(placeholders: ServerVisualization['placeholders'], axis: PlaceholderId) { + const placeholder = placeholders?.find((p) => p.id === axis); + + if (!placeholder?.settings) { + return undefined; + } + + return getAxisTitle(placeholder.settings, placeholder.items[0]) ?? undefined; +} + +export default ({shared, ChartEditor}: {shared: QlConfig; ChartEditor: IChartEditor}) => { + const config = mapQlConfigToLatestVersion(shared, {i18n: ChartEditor.getTranslation}); + + const type = (config.visualization.highchartsId || config.visualization.id) as ChartType; + + const percent = + config.visualization.id === 'area100p' || config.visualization.id === 'column100p'; + + const visualizationId = config.visualization.highchartsId || config.visualization.id; + + const tracking = visualizationId === QlVisualizationId.Area ? 'area' : 'sticky'; + + const title = + config.extraSettings?.titleMode === 'show' && config.extraSettings.title + ? {text: config.extraSettings.title} + : undefined; + + const visualization = config.visualization as ServerVisualization; + + const {scale: yScale} = applyPlaceholderSettingsToYAxis({ + visualization, + placeholderIndex: 1, + shared, + }); + const {scale: yRightScale} = applyPlaceholderSettingsToYAxis({ + visualization, + placeholderIndex: 2, + shared, + }); + + const isLegendEnabled = Boolean( + config.colors?.length && config.extraSettings?.legendMode !== LegendDisplayMode.Hide, + ); + + const tooltipSum = config.extraSettings?.tooltipSum; + const isTooltipSumEnabled = typeof tooltipSum === 'undefined' || tooltipSum === 'on'; + + const widgetData: YagrWidgetData['libraryConfig'] = { + title, + axes: { + x: { + label: getTitleForAxis(visualization.placeholders, PlaceholderId.X), + labelSize: 25, + }, + y: { + label: getTitleForAxis(visualization.placeholders, PlaceholderId.Y), + precision: 'auto', + scale: 'y', + side: 'left', + }, + }, + chart: { + appearance: { + drawOrder: ['plotLines', 'series', 'axes'], + }, + series: { + type, + interpolation: 'linear', + ...(type === 'dots' && {pointsSize: 2}), + }, + select: { + zoom: false, + }, + timeMultiplier: 0.001, + }, + cursor: { + snapToValues: false, + x: { + style: '1px solid #ffa0a0', + }, + y: { + visible: false, + }, + }, + legend: { + show: isLegendEnabled, + }, + processing: { + nullValues: { + '-Infinity': '-Infinity', + Infinity: 'Infinity', + }, + }, + scales: { + x: {}, + y: yScale, + yRight: yRightScale, + }, + tooltip: { + boundClassName: '.app', + show: true, + hideNoData: false, + maxLines: 15, + percent, + precision: 2, + sum: isTooltipSumEnabled, + tracking, + }, + }; + + if (config.extraSettings?.tooltip === 'hide') { + set(widgetData, 'tooltip.show', false); + } + + const xAxisSettings = visualization?.placeholders?.find( + (p) => p.id === PlaceholderId.X, + )?.settings; + if (xAxisSettings?.axisVisibility === 'hide') { + set(widgetData, 'axes.x.show', false); + } + + const yAxisSettings = visualization?.placeholders?.find( + (p) => p.id === PlaceholderId.Y, + )?.settings; + if (yAxisSettings?.axisVisibility === 'hide') { + set(widgetData, 'axes.y.show', false); + } + + return widgetData; +}; diff --git a/src/server/modes/charts/plugins/request-with-dataset/index.ts b/src/server/modes/charts/plugins/request-with-dataset/index.ts new file mode 100644 index 0000000..b3f9f69 --- /dev/null +++ b/src/server/modes/charts/plugins/request-with-dataset/index.ts @@ -0,0 +1,52 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {ApiWithRoot} from '@gravity-ui/gateway'; + +import type {DatalensGatewaySchemas} from '../../../../types/gateway'; +import { + CHARTS_MIDDLEWARE_URL_TYPE, + CONTROL_MIDDLEWARE_URL_TYPE, +} from '../constants/middleware-urls'; +import type {MiddlewareSourceAdapterArgs} from '../types'; + +import chartsWithDataset from './middlewareAdapters/charts-with-dataset'; +import controlsWithDataset from './middlewareAdapters/controls-with-dataset'; + +export type ConfigurableRequestWithDatasetPluginOptions = { + cache?: number; + getDataSetFieldsById?: ApiWithRoot< + DatalensGatewaySchemas, + Request['ctx'], + Request, + Response + >['bi']['getDataSetFieldsById']; +}; + +const getPlugin = (options?: ConfigurableRequestWithDatasetPluginOptions) => { + return { + request_with_dataset: { + description: { + title: { + ru: 'DataLens запрос с Датасетом', + en: 'DataLens request with Dataset', + }, + }, + middlewareAdapter: async (args: MiddlewareSourceAdapterArgs) => { + const middlewarePluginUrl = args.source.middlewareUrl.middlewareType; + switch (middlewarePluginUrl) { + case CHARTS_MIDDLEWARE_URL_TYPE: + return chartsWithDataset({...args, pluginOptions: options}); + case CONTROL_MIDDLEWARE_URL_TYPE: + return controlsWithDataset({...args, pluginOptions: options}); + default: + return args.source; + } + }, + }, + }; +}; + +export const configurableRequestWithDatasetPlugin = ( + options?: ConfigurableRequestWithDatasetPluginOptions, +) => { + return {sources: getPlugin(options)}; +}; diff --git a/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/charts-with-dataset.ts b/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/charts-with-dataset.ts new file mode 100644 index 0000000..2841cff --- /dev/null +++ b/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/charts-with-dataset.ts @@ -0,0 +1,79 @@ +import {getResultSchemaFromDataset} from '../../../../../../shared'; +import type {PartialDatasetField} from '../../../../../../shared/schema'; +import {getUrlsRequestBody} from '../../datalens/url/build-request-body'; +import {getDatasetIdAndLayerIdFromKey} from '../../helpers/misc'; +import type {MiddlewareSourceAdapterArgs} from '../../types'; +import type {ConfigurableRequestWithDatasetPluginOptions} from '../index'; +import {getDatasetFields} from '../request-dataset'; + +export default async ( + args: MiddlewareSourceAdapterArgs & { + pluginOptions?: ConfigurableRequestWithDatasetPluginOptions; + }, +) => { + const { + ctx, + source, + sourceName, + cacheClient, + userId, + iamToken, + workbookId, + rejectFetchingSource, + pluginOptions, + authParams, + requestHeaders, + } = args; + + const [datasetId, layerId] = getDatasetIdAndLayerIdFromKey(sourceName); + + const urlsSourceArgs = source.sourceArgs; + + const shared = urlsSourceArgs.shared; + const wizardDataset = shared.wizardDataset; + + let revisionId: string | undefined; + let datasetFields: PartialDatasetField[]; + + // When Urls are executed on the Wizard side, we don't need a dataset from the CHARTS side to avoid an unnecessary request + // Since we get it when loading the chart into Wizard and put it in the api request arguments/run + // the dataset is stored in shared.dataset + if (wizardDataset && wizardDataset.id === datasetId) { + revisionId = wizardDataset.dataset.revisionId; + datasetFields = getResultSchemaFromDataset(wizardDataset); + } else { + const datasetFieldsResponse = await getDatasetFields({ + datasetId, + workbookId: workbookId ?? null, + ctx, + cacheClient, + userId, + iamToken, + rejectFetchingSource, + pluginOptions, + authParams, + requestHeaders, + }); + + revisionId = datasetFieldsResponse.revisionId; + datasetFields = datasetFieldsResponse.datasetFields; + } + + ctx.log('CHARTS_DATASET_FIELDS_RECEIVED', { + count: datasetFields.length, + }); + + const data = getUrlsRequestBody({ + params: source.sourceArgs.params, + shared: source.sourceArgs.shared, + apiVersion: source.sourceArgs.apiVersion, + datasetId, + datasetFields, + layerId, + revisionId, + }); + + ctx.log('CHARTS_DATASET_FIELDS_PROCESSED'); + + return {...source, data, datasetFields}; +}; diff --git a/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/controls-with-dataset.ts b/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/controls-with-dataset.ts new file mode 100644 index 0000000..8084112 --- /dev/null +++ b/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/controls-with-dataset.ts @@ -0,0 +1,59 @@ +import type {ControlShared} from '../../control/types'; +import {getDistinctsRequestBody} from '../../control/url/distincts/build-distincts-body'; +import type {MiddlewareSourceAdapterArgs} from '../../types'; +import type {ConfigurableRequestWithDatasetPluginOptions} from '../index'; +import {getDatasetFields} from '../request-dataset'; + +export default async ( + args: MiddlewareSourceAdapterArgs & { + pluginOptions?: ConfigurableRequestWithDatasetPluginOptions; + }, +) => { + const { + ctx, + source, + cacheClient, + userId, + iamToken, + workbookId, + rejectFetchingSource, + pluginOptions, + authParams, + requestHeaders, + } = args; + + const datasetId = source.datasetId || ''; + + const datasetFieldsResponse = await getDatasetFields({ + datasetId, + workbookId: workbookId ?? null, + ctx, + cacheClient, + userId, + iamToken, + rejectFetchingSource, + pluginOptions, + authParams, + requestHeaders, + }); + + const datasetFields = datasetFieldsResponse.datasetFields; + + ctx.log('CONTROLS_DATASET_FIELDS_RECEIVED', { + count: datasetFields.length, + }); + + const data = getDistinctsRequestBody({ + params: source.sourceArgs.params, + shared: source.sourceArgs.shared as unknown as ControlShared, + datasetFields, + ctx, + }); + + ctx.log('CONTROLS_DATASET_FIELDS_PROCESSED'); + + return { + ...source, + data, + }; +}; diff --git a/src/server/modes/charts/plugins/request-with-dataset/request-dataset.ts b/src/server/modes/charts/plugins/request-with-dataset/request-dataset.ts new file mode 100644 index 0000000..e79b51f --- /dev/null +++ b/src/server/modes/charts/plugins/request-with-dataset/request-dataset.ts @@ -0,0 +1,189 @@ +import type {OutgoingHttpHeaders} from 'http'; + +import type {AppContext} from '@gravity-ui/nodekit'; +import {REQUEST_ID_PARAM_NAME} from '@gravity-ui/nodekit'; +import {isObject} from 'lodash'; +import isNumber from 'lodash/isNumber'; + +import type {WorkbookId} from '../../../../../shared'; +import {DL_EMBED_TOKEN_HEADER} from '../../../../../shared'; +import type {GetDataSetFieldsByIdResponse, PartialDatasetField} from '../../../../../shared/schema'; +import Cache from '../../../../components/cache-client'; +import { + type AuthParams, + addAuthHeaders, +} from '../../../../components/charts-engine/components/processor/data-fetcher'; +import {registry} from '../../../../registry'; +import type {DatalensGatewaySchemas} from '../../../../types/gateway'; + +import type {ConfigurableRequestWithDatasetPluginOptions} from './index'; + +export const DEFAULT_CACHE_TTL = 30; + +const getStatusFromError = (error: unknown) => + typeof error === 'object' && error !== null && 'status' in error && error.status; + +const getDatasetFieldsById = async ({ + datasetId, + workbookId, + ctx, + rejectFetchingSource, + iamToken, + pluginOptions, + authParams, + headers, +}: { + datasetId: string; + workbookId: string | null; + ctx: AppContext; + rejectFetchingSource: (reason?: any) => void; + iamToken?: string; + pluginOptions?: ConfigurableRequestWithDatasetPluginOptions; + authParams: AuthParams | undefined; + headers: OutgoingHttpHeaders; +}): Promise => { + const {gatewayApi} = registry.getGatewayApi(); + + const requestDatasetFields = + pluginOptions?.getDataSetFieldsById || gatewayApi.bi.getDataSetFieldsById; + + const requestDatasetFieldsByToken = gatewayApi.bi.embedsGetDataSetFieldsById; + try { + if (authParams) { + addAuthHeaders({headers, authParams}); + } + + const response = headers[DL_EMBED_TOKEN_HEADER] + ? await requestDatasetFieldsByToken({ + ctx, + headers, + requestId: ctx.get(REQUEST_ID_PARAM_NAME) || '', + args: { + dataSetId: datasetId, + }, + }) + : await requestDatasetFields({ + ctx: ctx, + headers, + requestId: ctx.get(REQUEST_ID_PARAM_NAME) || '', + authArgs: {iamToken}, + args: { + dataSetId: datasetId, + workbookId: workbookId, + }, + }); + + return response.responseData; + } catch (err: unknown) { + if (typeof err === 'object' && err !== null && 'error' in err) { + const {error} = err; + let preparedError = error; + if (isObject(error) && 'message' in error) { + const message = error.message as string; + preparedError = new Error(message); + } + ctx.logError('FAILED_TO_RECEIVE_FIELDS', preparedError); + const status = getStatusFromError(error); + if (isNumber(status) && status < 500) { + rejectFetchingSource({ + [`${datasetId}_result`]: error, + }); + } + } + throw new Error('FAILED_TO_RECEIVE_DATASET_FIELDS'); + } +}; + +export const getDatasetFields = async (args: { + datasetId: string; + workbookId: WorkbookId; + ctx: AppContext; + iamToken?: string; + cacheClient: Cache; + userId: string | null; + rejectFetchingSource: (reason: any) => void; + pluginOptions?: ConfigurableRequestWithDatasetPluginOptions; + authParams: AuthParams | undefined; + requestHeaders: OutgoingHttpHeaders; +}): Promise<{datasetFields: PartialDatasetField[]; revisionId: string}> => { + const { + datasetId, + workbookId, + cacheClient, + ctx, + userId, + iamToken, + rejectFetchingSource, + pluginOptions, + authParams, + requestHeaders, + } = args; + + const cacheKey = `${datasetId}__${userId}`; + + ctx.log('DATASET_FOR_CHARTS_MIDDLEWARE', {cacheKey}); + + let datasetFields: PartialDatasetField[]; + let revisionId: string; + + if (cacheClient.client) { + const cacheResponse = await cacheClient.get({key: cacheKey}); + + if (cacheResponse.status === Cache.OK) { + datasetFields = cacheResponse.data.datasetFields; + revisionId = cacheResponse.data.revisionId; + ctx.log('DATASET_FIELDS_WAS_RECEIVED_FROM_CACHE'); + } else { + ctx.log('DATASET_FIELDS_IN_CACHE_WAS_NOT_FOUND'); + + const response = await getDatasetFieldsById({ + datasetId, + workbookId, + ctx, + rejectFetchingSource, + iamToken, + pluginOptions, + authParams, + headers: requestHeaders, + }); + datasetFields = response.fields; + revisionId = response.revision_id; + cacheClient + .set({ + key: cacheKey, + ttl: pluginOptions?.cache || DEFAULT_CACHE_TTL, + value: {datasetFields, revisionId}, + }) + .then((setCacheResponse) => { + if (setCacheResponse.status === Cache.OK) { + ctx.log('SET_DATASET_IN_CACHE_SUCCESS'); + } else { + ctx.logError( + 'SET_DATASET_FIELDS_IN_CACHE_FAILED', + new Error(setCacheResponse.message), + ); + } + }) + .catch((error) => { + ctx.logError('SET_DATASET_FIELDS_UNHANDLED_ERROR', error); + }); + } + } else { + const response = await getDatasetFieldsById({ + datasetId, + workbookId, + ctx, + rejectFetchingSource, + iamToken, + pluginOptions, + authParams, + headers: requestHeaders, + }); + datasetFields = response.fields; + revisionId = response.revision_id; + } + + ctx.log('DATASET_FIELDS_WAS_SUCCESSFULLY_PROCESSED'); + + return {datasetFields, revisionId}; +}; diff --git a/src/server/modes/charts/plugins/runner-key-adapter.ts b/src/server/modes/charts/plugins/runner-key-adapter.ts new file mode 100644 index 0000000..b7a4e8d --- /dev/null +++ b/src/server/modes/charts/plugins/runner-key-adapter.ts @@ -0,0 +1,71 @@ +import url from 'url'; + +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; + +import {MiddlewareStage} from '../../../components/charts-engine/types'; +import {registry} from '../../../registry'; + +export const plugin = { + middlewares: [ + { + stage: MiddlewareStage.AfterAuth, + fn: (req: Request, _res: Response, next: NextFunction) => { + if (req.url === '/api/run') { + const {body} = req; + + if (body) { + const key = body.key || body.path; + if (key) { + /* + /editor/? + /wizard/? + /preview/editor/? + /preview/wizard/? + /ChartPreview/editor/? + /ChartPreview/wizard/? + /preview/ChartEditor?name=& + /preview/ChartWizard?name=& + /ChartPreview/ChartEditor?name=& + /ChartPreview/ChartWizard?name=& + */ + const parsedKey = url.parse(key, true); + + const keyWithoutQueryParams = key.split('?')[0]; + + const pathname = keyWithoutQueryParams.replace( + /\/?(?:ChartPreview|preview)/, + '', + ); + const params = Object.assign({}, parsedKey.query, req.body.params); + + if ( + pathname.startsWith('/editor') || + pathname.startsWith('editor') || + pathname.startsWith('/wizard') || + pathname.startsWith('wizard') + ) { + params.name = keyWithoutQueryParams + .replace(/^\/?editor\//, '') + .replace(/^\/?wizard\//, ''); + } + + const isEntryId = registry.common.functions.get('isEntryId'); + if (isEntryId(params.name)) { + // If params.name looks like id - use it as id. + body.id = params.name; + + body.unreleased = false; // request publised version by default + } else if (!body.key) { + body.key = params.name; + } + + body.params = params; + } + } + } + + next(); + }, + }, + ], +}; diff --git a/src/server/modes/charts/plugins/types.ts b/src/server/modes/charts/plugins/types.ts new file mode 100644 index 0000000..c1c47c7 --- /dev/null +++ b/src/server/modes/charts/plugins/types.ts @@ -0,0 +1,83 @@ +import type {OutgoingHttpHeaders} from 'http'; + +import type {Request} from '@gravity-ui/expresskit'; +import type {AppContext} from '@gravity-ui/nodekit'; + +import type {WorkbookId} from '../../../../shared'; +import type {PartialDatasetField} from '../../../../shared/schema/bi/types'; +import type {CacheClient} from '../../../components/cache-client'; +import type {AuthParams} from '../../../components/charts-engine/components/processor/data-fetcher'; + +import type { + CHARTS_MIDDLEWARE_URL_TYPE, + CONTROL_MIDDLEWARE_URL_TYPE, + REQUEST_WITH_DATASET_SOURCE_NAME, +} from './constants/middleware-urls'; +import type {SourcesArgs} from './datalens/url/types'; + +export interface SourceAdapterRequestSettings { + useCaching: boolean; + requestOptions: { + body: Record; + headers: Record; + json: boolean; + spStatFormat: any; + timeout: number; + uri: string; + method: string; + }; + requestControl: { + allBuffersLength: number; + }; + processingRequests: any[]; +} + +export interface SourceAdapterArgs { + targetUri: string; + sourceName: string; + req: Request; + source: Record; + fetchingStartTime: number; +} + +export interface MiddlewareUrl { + sourceName: typeof REQUEST_WITH_DATASET_SOURCE_NAME; + middlewareType: MiddlewareUrlType; +} + +export interface MiddlewareSourceAdapterArgs { + ctx: AppContext; + sourceName: string; + source: { + sourceArgs: SourcesArgs; + middlewareUrl: MiddlewareUrl; + datasetId?: string; + }; + iamToken?: string; + workbookId?: WorkbookId; + cacheClient: CacheClient; + userId: string | null; + rejectFetchingSource: (reason: any) => void; + authParams: AuthParams | undefined; + requestHeaders: OutgoingHttpHeaders; +} + +export interface ProcessorHookInitArgs { + req: Request; + config: any; + isEditMode: boolean; +} + +export interface PluginProcessorHook { + name: string; + init: (args: ProcessorHookInitArgs) => any; +} + +export type MiddlewareUrlType = + | typeof CHARTS_MIDDLEWARE_URL_TYPE + | typeof CONTROL_MIDDLEWARE_URL_TYPE; + +export type ResolvedSource = { + datasetId: string; + datasetFields?: PartialDatasetField[]; +}; diff --git a/src/server/modes/charts/telemetry.ts b/src/server/modes/charts/telemetry.ts new file mode 100644 index 0000000..30afe17 --- /dev/null +++ b/src/server/modes/charts/telemetry.ts @@ -0,0 +1,124 @@ +import type {AppContext} from '@gravity-ui/nodekit'; +import get from 'lodash/get'; +import sizeof from 'object-sizeof'; + +import type {TelemetryCallbacks} from '../../components/charts-engine/types'; + +const getTime = () => new Date().toISOString().replace('T', ' ').split('.')[0]; + +export const getTelemetryCallbacks = (ctx: AppContext): TelemetryCallbacks => ({ + onConfigFetched: ({id, statusCode, requestId, latency = 0, traceId, tenantId, userId}) => { + ctx.stats('apiRequests', { + requestId: requestId!, + service: 'us', + action: 'fetchConfig', + responseStatus: statusCode || 200, + requestTime: latency, + requestMethod: 'POST', + requestUrl: id || '', + traceId: traceId || '', + tenantId: tenantId || '', + userId: userId || '', + }); + }, + onConfigFetchingFailed: ( + _error, + {id, statusCode, requestId, latency = 0, traceId, tenantId, userId}, + ) => { + ctx.stats('apiRequests', { + requestId: requestId!, + service: 'us', + action: 'fetchConfig', + responseStatus: statusCode || 500, + requestTime: latency, + requestMethod: 'POST', + requestUrl: id || '', + traceId: traceId || '', + tenantId: tenantId || '', + userId: userId || '', + }); + }, + + onDataFetched: ({ + sourceName, + url, + requestId, + statusCode, + latency, + traceId, + tenantId, + userId, + }) => { + ctx.stats('apiRequests', { + requestId, + service: sourceName || 'unknown-charts-source', + action: 'fetchData', + responseStatus: statusCode || 200, + requestTime: latency, + requestMethod: 'POST', + requestUrl: url || '', + traceId: traceId || '', + tenantId: tenantId || '', + userId: userId || '', + }); + }, + onDataFetchingFailed: ( + _error, + {sourceName, url, requestId, statusCode, latency, traceId, tenantId, userId}, + ) => { + ctx.stats('apiRequests', { + requestId, + service: sourceName || 'unknown-charts-source', + action: 'fetchData', + responseStatus: statusCode || 500, + requestTime: latency, + requestMethod: 'POST', + requestUrl: url || '', + traceId: traceId || '', + tenantId: tenantId || '', + userId: userId || '', + }); + }, + + onCodeExecuted: ({id, requestId, latency}) => { + ctx.stats('executions', { + datetime: getTime(), + requestId, + entryId: id, + jsTabExecDuration: Math.ceil(latency), + }); + }, + + onTabsExecuted: ({result, entryId}) => { + const {sources, sourceData, processedData} = result; + const chartEntryId = entryId || ''; + const datetime = Date.now(); + + let rowsCount = 0; + let columnsCount = 0; + if (sourceData && typeof sourceData === 'object') { + Object.values(sourceData as object).forEach((item) => { + rowsCount += get(item, 'result_data[0].rows.length', 0); + columnsCount = Math.max( + columnsCount, + get(item, 'result_data[0].rows[0].data.length', 0), + ); + }, 0); + } + + ctx.stats('chartSizeStats', { + datetime, + entryId: chartEntryId, + requestedDataSize: + sources && typeof sources === 'object' + ? Object.values(sources as object).reduce( + (sum, item) => sum + get(item, 'size', 0), + 0, + ) + : 0, + requestedDataRowsCount: rowsCount, + requestedDataColumnsCount: columnsCount, + processedDataSize: sizeof(processedData), + }); + }, +}); diff --git a/src/server/modes/opensource/app.ts b/src/server/modes/opensource/app.ts new file mode 100644 index 0000000..ecb2491 --- /dev/null +++ b/src/server/modes/opensource/app.ts @@ -0,0 +1,150 @@ +import {createUikitPlugin} from '@gravity-ui/app-layout'; +import type {AppMiddleware} from '@gravity-ui/expresskit'; +import {AuthPolicy} from '@gravity-ui/expresskit'; +import type {NodeKit} from '@gravity-ui/nodekit'; + +import { + DASH_API_BASE_URL, + Feature, + PUBLIC_API_DASH_API_BASE_URL, + isEnabledServerFeature, +} from '../../../shared'; +import {isApiMode, isChartsMode, isDatalensMode, isFullMode} from '../../app-env'; +import {getAppLayoutSettings} from '../../components/app-layout/app-layout-settings'; +import {createLayoutPlugin} from '../../components/app-layout/plugins/layout'; +import type {ChartsEngine} from '../../components/charts-engine'; +import {createAuthArgsMiddleware} from '../../components/gateway-auth-helpers'; +import {xlsxConverter} from '../../controllers/xlsx-converter'; +import {getExpressKit} from '../../expresskit'; +import { + beforeAuthDefaults, + createAppLayoutMiddleware, + getCtxMiddleware, + patchLogger, + xDlContext, +} from '../../middlewares'; +import {registry} from '../../registry'; +import {initChartsEngine} from '../charts/init-charts-engine'; +import {configuredDashApiPlugin} from '../charts/plugins/dash-api'; +import {plugin as ql} from '../charts/plugins/ql'; +import {configurableRequestWithDatasetPlugin} from '../charts/plugins/request-with-dataset'; + +import {setSubrequestHeaders} from './middlewares'; +import {getRoutes} from './routes'; + +export default function initApp(nodekit: NodeKit) { + const beforeAuth: AppMiddleware[] = []; + const afterAuth: AppMiddleware[] = []; + + registry.setupXlsxConverter(xlsxConverter); + + if (isEnabledServerFeature(nodekit.ctx, Feature.UsDynamicMasterToken)) { + beforeAuth.push(createAuthArgsMiddleware(nodekit.config)); + } + + if (isFullMode || isDatalensMode) { + initDataLensApp({beforeAuth, afterAuth, nodekit}); + } + + let chartsEngine: ChartsEngine | undefined; + + if (isFullMode || isChartsMode) { + chartsEngine = initChartsApp({nodekit, beforeAuth, afterAuth}); + } + + if (isFullMode || isApiMode) { + initApiApp({beforeAuth, afterAuth}); + } + + const extendedRoutes = getRoutes({ + ctx: nodekit.ctx, + chartsEngine, + beforeAuth, + afterAuth, + }); + + return getExpressKit({extendedRoutes, nodekit}); +} + +function initDataLensApp({ + beforeAuth, + afterAuth, +}: { + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; + nodekit: NodeKit; +}) { + beforeAuth.push( + createAppLayoutMiddleware({ + plugins: [createLayoutPlugin(), createUikitPlugin()], + getAppLayoutSettings, + }), + beforeAuthDefaults, + ); + + afterAuth.push(xDlContext(), getCtxMiddleware()); +} + +function initChartsApp({ + beforeAuth, + afterAuth, + nodekit, +}: { + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; + nodekit: NodeKit; +}) { + const chartsEngine = initChartsEngine({ + config: nodekit.config, + ctx: nodekit.ctx, + plugins: [ + configuredDashApiPlugin({ + basePath: DASH_API_BASE_URL, + routeParams: { + authPolicy: nodekit.config.isAuthEnabled + ? AuthPolicy.required + : AuthPolicy.disabled, + }, + privatePath: PUBLIC_API_DASH_API_BASE_URL, + privateRouteParams: { + authPolicy: AuthPolicy.disabled, + }, + }), + ql, + configurableRequestWithDatasetPlugin(), + ], + beforeAuth, + afterAuth, + }); + registry.setupChartsEngine(chartsEngine); + + if (isChartsMode) { + afterAuth.push(xDlContext()); + } + + afterAuth.push(setSubrequestHeaders, patchLogger); + + if (isChartsMode) { + beforeAuth.push(beforeAuthDefaults); + afterAuth.push(getCtxMiddleware()); + } + + if (nodekit.config.enablePreloading) { + chartsEngine.initPreloading(nodekit.ctx); + } + return chartsEngine; +} + +function initApiApp({ + beforeAuth, + afterAuth, +}: { + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; +}) { + // As charts app except chartEngine + if (isApiMode) { + afterAuth.push(xDlContext(), setSubrequestHeaders, patchLogger, getCtxMiddleware()); + beforeAuth.push(beforeAuthDefaults); + } +} diff --git a/src/server/modes/opensource/middlewares/index.ts b/src/server/modes/opensource/middlewares/index.ts new file mode 100644 index 0000000..428f698 --- /dev/null +++ b/src/server/modes/opensource/middlewares/index.ts @@ -0,0 +1 @@ +export * from './subrequest-headers'; diff --git a/src/server/modes/opensource/middlewares/subrequest-headers.ts b/src/server/modes/opensource/middlewares/subrequest-headers.ts new file mode 100644 index 0000000..109457b --- /dev/null +++ b/src/server/modes/opensource/middlewares/subrequest-headers.ts @@ -0,0 +1,56 @@ +import type {NextFunction, Request, Response} from '@gravity-ui/expresskit'; + +import {DL_COMPONENT_HEADER, DL_CONTEXT_HEADER, TENANT_ID_HEADER} from '../../../../shared'; + +type SubrequestHeaders = Record; +type AuthFlags = Record; + +// Middleware for generating authorization headers that will then go to subqueries in ChartsEngine +export function setSubrequestHeaders(req: Request, res: Response, next: NextFunction) { + const subrequestHeaders: SubrequestHeaders = {}; + + // most likely these flags are not needed + const authFlags: AuthFlags = {}; + + if (req.headers.authorization) { + subrequestHeaders.authorization = req.headers.authorization; + + if (req.headers.authorization.startsWith('OAuth ')) { + authFlags.oauth = true; + } + } + + const {folderId: folderIdHeader, subjectToken: subjectTokenHeader} = req.ctx.config.headersMap; + + const folderId = req.headers[folderIdHeader]; + const tenantId = req.headers[TENANT_ID_HEADER]; + + if (folderId) { + // TODO: in the future, abandon this header when BI supports TenantId + subrequestHeaders[folderIdHeader] = folderId; + } else if (tenantId) { + subrequestHeaders[TENANT_ID_HEADER] = tenantId; + } + + req.headers[subjectTokenHeader] = res.locals.iam?.token; + + if (req.headers[subjectTokenHeader]) { + subrequestHeaders[subjectTokenHeader] = req.headers[subjectTokenHeader]; + } + + if (req.headers.cookie) { + subrequestHeaders.cookie = req.headers.cookie; + } + + if (res.locals.tvm) { + authFlags.tvm = true; + } + + subrequestHeaders[DL_CONTEXT_HEADER] = req.headers[DL_CONTEXT_HEADER]; + subrequestHeaders[DL_COMPONENT_HEADER] = req.headers[DL_COMPONENT_HEADER]; + + res.locals.subrequestHeaders = subrequestHeaders; + res.locals.authFlags = authFlags; + + next(); +} diff --git a/src/server/modes/opensource/routes.ts b/src/server/modes/opensource/routes.ts new file mode 100644 index 0000000..79767f8 --- /dev/null +++ b/src/server/modes/opensource/routes.ts @@ -0,0 +1,260 @@ +import type {AppMiddleware, Request, Response} from '@gravity-ui/expresskit'; +import {AuthPolicy} from '@gravity-ui/expresskit'; +import type {AppContext} from '@gravity-ui/nodekit'; + +import {AppEnvironment} from '../../../shared'; +import {getAuthArgs} from '../../../shared/schema/gateway-utils'; +import {appEnv, isApiMode, isChartsMode, isDatalensMode, isFullMode} from '../../app-env'; +import {getAuthRoutes} from '../../components/auth/routes'; +import type {ChartsEngine} from '../../components/charts-engine'; +import {ping} from '../../controllers/ping'; +import {workbooksTransferController} from '../../controllers/workbook-transfer'; +import {getConnectorIconsMiddleware} from '../../middlewares'; +import {getTenantSettingsMiddleware} from '../../middlewares/tenant-settings'; +import type {ExtendedAppRouteDescription} from '../../types/controllers'; +import {getConfiguredRoute, getDashboardsRedirectPath} from '../../utils/routes'; +import {applyPluginRoutes} from '../charts/init-charts-engine'; + +export function getRoutes({ + ctx, + chartsEngine, + beforeAuth, + afterAuth, +}: { + ctx: AppContext; + chartsEngine?: ChartsEngine; + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; +}) { + let routes: Record = { + ping: { + beforeAuth: [], + afterAuth: [], + route: 'GET /ping', + handler: ping, + authPolicy: AuthPolicy.disabled, + }, + }; + + if (appEnv === AppEnvironment.Development || isApiMode) { + routes = { + ...routes, + ...getApiRoutes({beforeAuth, afterAuth}), + }; + } + + if (ctx.config.isAuthEnabled) { + routes = {...routes, ...getAuthRoutes({routeParams: {beforeAuth, afterAuth}})}; + } + + if (isFullMode || isDatalensMode) { + routes = {...routes, ...getDataLensRoutes({ctx, beforeAuth, afterAuth})}; + } + + if (isFullMode || isChartsMode) { + routes = {...routes, ...getChartsRoutes({chartsEngine, beforeAuth, afterAuth})}; + } + + return routes; +} + +function getApiRoutes({ + beforeAuth, + afterAuth, +}: { + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; +}) { + const routes: Record = { + workbooksMetaManagerCapabilities: { + handler: workbooksTransferController.capabilities, + beforeAuth, + afterAuth, + route: 'GET /api/internal/v1/workbooks/meta-manager/capabilities/', + authPolicy: AuthPolicy.disabled, + disableCsrf: true, + }, + workbooksExport: { + handler: workbooksTransferController.export, + beforeAuth, + afterAuth, + route: 'POST /api/internal/v1/workbooks/export/', + authPolicy: AuthPolicy.disabled, + disableCsrf: true, + }, + workbooksImport: { + handler: workbooksTransferController.import, + beforeAuth, + afterAuth, + route: 'POST /api/internal/v1/workbooks/import/', + authPolicy: AuthPolicy.disabled, + disableCsrf: true, + }, + }; + + return routes; +} + +function getDataLensRoutes({ + beforeAuth, + afterAuth, +}: { + ctx: AppContext; + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; +}) { + const ui: Omit = { + beforeAuth, + afterAuth: [ + ...afterAuth, + getConnectorIconsMiddleware({ + getAdditionalArgs: (req, res) => ({ + authArgs: getAuthArgs(req, res), + }), + }), + getTenantSettingsMiddleware(), + ], + ui: true, + }; + + const server: Omit = { + beforeAuth, + afterAuth, + }; + + const routes: Record = { + getConnections: getConfiguredRoute('navigation', {...ui, route: 'GET /connections'}), + getDatasets: getConfiguredRoute('navigation', {...ui, route: 'GET /datasets'}), + getWidgets: getConfiguredRoute('navigation', {...ui, route: 'GET /widgets'}), + getDashboards: getConfiguredRoute('navigation', {...ui, route: 'GET /dashboards'}), + getDatasetsAll: getConfiguredRoute('dl-main', {...ui, route: 'GET /datasets/*'}), + getConnectionsAll: getConfiguredRoute('dl-main', {...ui, route: 'GET /connections/*'}), + getSettingsAll: getConfiguredRoute('dl-main', {...ui, route: 'GET /settings/*'}), + getDashboardsAll: { + route: 'GET /dashboards/*', + beforeAuth, + afterAuth, + handler: (req: Request, res: Response) => res.redirect(getDashboardsRedirectPath(req)), + }, + + getWizardAll: getConfiguredRoute('dl-main', {...ui, route: 'GET /wizard/*'}), + getPreview: getConfiguredRoute('dl-main', {...ui, route: 'GET /preview*'}), + getWorkbooks: getConfiguredRoute('dl-main', {...ui, route: 'GET /workbooks*'}), + + postDeleteLock: getConfiguredRoute('api.deleteLock', { + ...server, + route: 'POST /api/private/deleteLock', + }), + + postGateway: getConfiguredRoute('schematic-gateway', { + ...server, + route: 'POST /gateway/:scope/:service/:action?', + }), + + getNavigate: getConfiguredRoute('navigate', {...ui, route: 'GET /navigate/:entryId'}), + + getEntry: getConfiguredRoute('dl-main', {...ui, route: 'GET /:entryId'}), + getNewWizard: getConfiguredRoute('dl-main', {...ui, route: 'GET /:entryId/new/wizard'}), + getWidget: getConfiguredRoute('dl-main', {...ui, route: 'GET /:entryId/:widgetId'}), + + getRoot: getConfiguredRoute('dl-main', {...ui, route: 'GET /'}), + + getEditorAll: getConfiguredRoute('dl-main', {...ui, route: 'GET /editor*'}), + + getSql: { + handler: (_req: Request, res: Response) => { + res.redirect(`/ql`); + }, + beforeAuth, + afterAuth, + route: 'GET /sql', + }, + + // Path to UI ql Charts + getQlEntry: getConfiguredRoute('dl-main', {...ui, route: 'GET /ql/:entryId'}), + getQlNew: getConfiguredRoute('dl-main', {...ui, route: 'GET /ql/new'}), + getQlNnewMonitoringql: getConfiguredRoute('dl-main', { + ...ui, + route: 'GET /ql/new/monitoringql', + }), + getQlNewSql: getConfiguredRoute('dl-main', {...ui, route: 'GET /ql/new/sql'}), + getQlNewPromql: getConfiguredRoute('dl-main', {...ui, route: 'GET /ql/new/promql'}), + getEntrNewQl: getConfiguredRoute('dl-main', {...ui, route: 'GET /:entryId/new/ql'}), + }; + + return routes; +} + +function getChartsRoutes({ + chartsEngine, + beforeAuth, + afterAuth, +}: { + chartsEngine?: ChartsEngine; + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; +}) { + if (!chartsEngine) { + return {}; + } + + const routes: Record = { + // Routes from Charts Engine + postApiRun: { + beforeAuth, + afterAuth, + route: 'POST /api/run', + handler: chartsEngine.controllers.run, + }, + postApiExport: { + beforeAuth, + afterAuth, + route: 'POST /api/export', + handler: chartsEngine.controllers.export, + }, + + getApiPrivateConfig: { + beforeAuth, + afterAuth, + route: 'GET /api/private/config', + handler: chartsEngine.controllers.config, + }, + + // Routes for charts + postApiChartsV1Charts: { + beforeAuth, + afterAuth, + route: 'POST /api/charts/v1/charts', + handler: chartsEngine.controllers.charts.create, + }, + getApiChartsV1ChartsEntryByKey: { + beforeAuth, + afterAuth, + route: 'GET /api/charts/v1/charts/entryByKey', + handler: chartsEngine.controllers.charts.entryByKey, + }, + getApiChartsV1ChartsEntry: { + beforeAuth, + afterAuth, + route: 'GET /api/charts/v1/charts/:entryId', + handler: chartsEngine.controllers.charts.get, + }, + postApiChartsV1ChartsEntry: { + beforeAuth, + afterAuth, + route: 'POST /api/charts/v1/charts/:entryId', + handler: chartsEngine.controllers.charts.update, + }, + deleteApiChartsV1ChartsEntry: { + beforeAuth, + afterAuth, + route: 'DELETE /api/charts/v1/charts/:entryId', + handler: chartsEngine.controllers.charts.delete, + }, + }; + + // Apply routes from plugins + applyPluginRoutes({chartsEngine, routes, beforeAuth, afterAuth}); + + return routes; +} diff --git a/src/server/nodekit.ts b/src/server/nodekit.ts new file mode 100644 index 0000000..ff91fed --- /dev/null +++ b/src/server/nodekit.ts @@ -0,0 +1,36 @@ +import * as path from 'path'; + +import {extendZodWithOpenApi} from '@asteasolutions/zod-to-openapi'; +import {NodeKit} from '@gravity-ui/nodekit'; +import dotenv from 'dotenv'; +import {z} from 'zod'; + +extendZodWithOpenApi(z); +dotenv.config(); + +import {authSchema, schema} from '../shared/schema'; + +import {getFeaturesConfig} from './components/features'; +import {registry} from './registry'; +import {getGatewayConfig} from './utils/gateway'; + +const nodekit = new NodeKit({ + configsPath: path.resolve(__dirname, 'configs'), +}); + +const {appName, appEnv, appInstallation, appDevMode} = nodekit.config; +nodekit.ctx.log('AppConfig details', { + appName, + appEnv, + appInstallation, + appDevMode, +}); + +nodekit.config.features = getFeaturesConfig(appEnv); + +registry.setupGateway(getGatewayConfig(nodekit), { + root: schema, + auth: authSchema, +}); + +export {nodekit}; diff --git a/src/server/registry/index.ts b/src/server/registry/index.ts new file mode 100644 index 0000000..47bddc4 --- /dev/null +++ b/src/server/registry/index.ts @@ -0,0 +1,144 @@ +import type {MarkdownItPluginCb} from '@diplodoc/transform/lib/plugins/typings'; +import type {ExpressKit, Request, Response} from '@gravity-ui/expresskit'; +import type {ApiWithRoot, GatewayConfig, SchemasByScope} from '@gravity-ui/gateway'; +import {getGatewayControllers} from '@gravity-ui/gateway'; +import type {AppContext} from '@gravity-ui/nodekit'; + +import type {ChartsEngine} from '../components/charts-engine'; +import type {PublicApiConfig} from '../components/public-api/types'; +import type {QLConnectionTypeMap} from '../modes/charts/plugins/ql/utils/connection'; +import {getConnectorToQlConnectionTypeMap} from '../modes/charts/plugins/ql/utils/connection'; +import type {GetLayoutConfig} from '../types/app-layout'; +import type {XlsxConverterFn} from '../types/xlsxConverter'; + +import commonRegistry from './units/common'; + +let app: ExpressKit; +let chartsEngine: ChartsEngine; + +export const wrapperGetGatewayControllers = ( + schemasByScope: SchemasByScope, + config: GatewayConfig, +) => getGatewayControllers(schemasByScope, config); + +let gateway: ReturnType; +let gatewaySchemasByScope: SchemasByScope; +let getLayoutConfig: GetLayoutConfig | undefined; +let yfmPlugins: MarkdownItPluginCb[]; +let getXlsxConverter: XlsxConverterFn | undefined; +let qLConnectionTypeMap: QLConnectionTypeMap | undefined; +let publicApiConfig: PublicApiConfig | undefined; + +export const registry = { + common: commonRegistry, + setupApp(appInstance: ExpressKit) { + if (app) { + throw new Error('The method must not be called more than once'); + } + app = appInstance; + }, + getApp() { + if (!app) { + throw new Error('First of all setup the app'); + } + return app; + }, + setupChartsEngine(chartsEngineInstance: ChartsEngine) { + if (chartsEngine) { + throw new Error('The method must not be called more than once'); + } + chartsEngine = chartsEngineInstance; + }, + getChartsEngine() { + if (!chartsEngine) { + throw new Error('First of all setup the chartsEngine'); + } + return chartsEngine; + }, + setupGateway( + config: GatewayConfig, + schemasByScope: SchemasByScope, + ) { + if (gateway) { + throw new Error('The method must not be called more than once'); + } + gateway = wrapperGetGatewayControllers(schemasByScope, config); + gatewaySchemasByScope = schemasByScope; + }, + getGatewayController() { + if (!gateway) { + throw new Error('First of all setup the gateway'); + } + return {gatewayController: gateway.controller} as { + gatewayController: (typeof gateway)['controller']; + }; + }, + getGatewayApi() { + if (!gateway) { + throw new Error('First of all setup the gateway'); + } + return {gatewayApi: gateway.api} as { + gatewayApi: ApiWithRoot; + }; + }, + getGatewaySchemasByScope() { + if (!gatewaySchemasByScope) { + throw new Error('First of all setup the gateway'); + } + + return gatewaySchemasByScope; + }, + registerGetLayoutConfig(fn: GetLayoutConfig) { + if (getLayoutConfig) { + throw new Error( + 'The method must not be called more than once [registerGetLayoutConfig]', + ); + } + getLayoutConfig = fn; + }, + useGetLayoutConfig(...params: Parameters) { + if (!getLayoutConfig) { + throw new Error('First of all register getLayoutConfig function'); + } + return getLayoutConfig(...params); + }, + setupYfmPlugins(plugins: MarkdownItPluginCb[]) { + if (yfmPlugins) { + throw new Error('The method must not be called more than once'); + } + yfmPlugins = plugins; + }, + getYfmPlugins() { + return yfmPlugins; + }, + setupXlsxConverter(fn: XlsxConverterFn) { + if (getXlsxConverter) { + throw new Error('The method setupXlsxConverter must not be called more than once'); + } + getXlsxConverter = fn; + }, + getXlsxConverter() { + return getXlsxConverter; + }, + setupQLConnectionTypeMap(map: QLConnectionTypeMap) { + if (!qLConnectionTypeMap) { + qLConnectionTypeMap = map; + } + }, + getQLConnectionTypeMap() { + return qLConnectionTypeMap ?? getConnectorToQlConnectionTypeMap(); + }, + setupPublicApiConfig(config: PublicApiConfig) { + if (publicApiConfig) { + throw new Error('The method must not be called more than once [setupPublicApiConfig]'); + } + publicApiConfig = config as PublicApiConfig; + }, + getPublicApiConfig() { + if (!publicApiConfig) { + throw new Error('First of all setup the publicApiConfig'); + } + + return publicApiConfig; + }, +}; diff --git a/src/server/registry/units/common/auth-functions-map.ts b/src/server/registry/units/common/auth-functions-map.ts new file mode 100644 index 0000000..8b8efc9 --- /dev/null +++ b/src/server/registry/units/common/auth-functions-map.ts @@ -0,0 +1,26 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {GetAuthHeadersParams} from '@gravity-ui/gateway'; + +import {makeFunctionTemplate} from '../../../../shared/utils/makeFunctionTemplate'; + +export const authFunctionsMap = { + getAuthArgsUSPrivate: + makeFunctionTemplate<(req: Request, res: Response) => Record>(), + getAuthHeadersUSPrivate: + makeFunctionTemplate< + ( + params: GetAuthHeadersParams>, + ) => Record | undefined + >(), + getAuthArgsProxyUSPrivate: + makeFunctionTemplate<(req: Request, res: Response) => Record>(), + getAuthHeadersBIPrivate: + makeFunctionTemplate< + ( + params: GetAuthHeadersParams>, + ) => Record | undefined + >(), + hasValidWorkbookTransferAuthHeaders: makeFunctionTemplate<(req: Request) => Promise>(), + getAuthArgsProxyBIPrivate: + makeFunctionTemplate<(req: Request, res: Response) => Record>(), +} as const; diff --git a/src/server/registry/units/common/functions-map/index.ts b/src/server/registry/units/common/functions-map/index.ts new file mode 100644 index 0000000..6e9386e --- /dev/null +++ b/src/server/registry/units/common/functions-map/index.ts @@ -0,0 +1,30 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {AppContext} from '@gravity-ui/nodekit'; + +import type {Palette} from '../../../../../shared/constants/colors'; +import type {GetEntryByKeyResponse} from '../../../../../shared/schema'; +import {makeFunctionTemplate} from '../../../../../shared/utils/makeFunctionTemplate'; +import type {SourceConfig} from '../../../../components/charts-engine/types'; + +import type {OnEmbedsControllerBeforeResponse} from './on-embeds-controller-before-response'; +import type {OnEmbedsControllerStart} from './on-embeds-controller-start'; + +export const commonFunctionsMap = { + getAvailablePalettesMap: makeFunctionTemplate<() => Record>(), + getSourceAuthorizationHeaders: + makeFunctionTemplate< + (args: { + ctx: AppContext; + sourceConfig: SourceConfig; + subrequestHeaders: Record; + }) => Record + >(), + isEntryId: makeFunctionTemplate<(value: string) => boolean>(), + extractEntryId: makeFunctionTemplate<(value?: string) => string | null>(), + handleEntryRedirect: + makeFunctionTemplate<(entry: GetEntryByKeyResponse, res: Response) => void>(), + pickAdditionalPublicApiHeaders: + makeFunctionTemplate<(req: Request) => Record>(), + onEmbedsControllerStart: makeFunctionTemplate(), + onEmbedsControllerBeforeResponse: makeFunctionTemplate(), +} as const; diff --git a/src/server/registry/units/common/functions-map/on-embeds-controller-before-response.ts b/src/server/registry/units/common/functions-map/on-embeds-controller-before-response.ts new file mode 100644 index 0000000..5dac28c --- /dev/null +++ b/src/server/registry/units/common/functions-map/on-embeds-controller-before-response.ts @@ -0,0 +1,12 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import type {RunnerHandlerResult} from '../../../../components/charts-engine/runners'; + +export type OnEmbedsControllerBeforeResponse = (args: { + req: Request; + res: Response; + runnerName: string; + runnerHandlerResult: RunnerHandlerResult; +}) => Promise; + +export const onEmbedsControllerBeforeResponse: OnEmbedsControllerBeforeResponse = async () => {}; diff --git a/src/server/registry/units/common/functions-map/on-embeds-controller-start.ts b/src/server/registry/units/common/functions-map/on-embeds-controller-start.ts new file mode 100644 index 0000000..f120227 --- /dev/null +++ b/src/server/registry/units/common/functions-map/on-embeds-controller-start.ts @@ -0,0 +1,5 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +export type OnEmbedsControllerStart = (args: {req: Request; res: Response}) => Promise; + +export const onEmbedsControllerStart: OnEmbedsControllerStart = async () => {}; diff --git a/src/server/registry/units/common/index.ts b/src/server/registry/units/common/index.ts new file mode 100644 index 0000000..e44ea32 --- /dev/null +++ b/src/server/registry/units/common/index.ts @@ -0,0 +1,13 @@ +import {createFunctionsRegistry} from '../../../../shared/utils/functions-registry'; + +import {authFunctionsMap} from './auth-functions-map'; +import {commonFunctionsMap} from './functions-map'; + +const functionsRegistry = createFunctionsRegistry(commonFunctionsMap); +const authRegistry = createFunctionsRegistry(authFunctionsMap); + +const commonRegistry = { + functions: functionsRegistry, + auth: authRegistry, +}; +export default commonRegistry; diff --git a/src/server/registry/units/common/register.ts b/src/server/registry/units/common/register.ts new file mode 100644 index 0000000..5e2d6c8 --- /dev/null +++ b/src/server/registry/units/common/register.ts @@ -0,0 +1,38 @@ +import {extractEntryId, getAvailablePalettesMap, isEntryId} from '../../../../shared'; +import {getSourceAuthorizationHeaders} from '../../../components/charts-engine/components/utils'; +import { + getAuthArgsProxyBIPrivate, + getAuthArgsProxyUSPrivate, + getAuthArgsUSPrivate, + getAuthHeadersBIPrivate, + getAuthHeadersUSPrivate, + hasValidWorkbookTransferAuthHeaders, +} from '../../../components/gateway-auth-helpers'; +import {pickAdditionalPublicApiHeaders} from '../../../components/public-api/utils'; +import {handleEntryRedirect} from '../../../controllers/utils/handle-entry-redirect'; +import {registry} from '../../index'; + +import {onEmbedsControllerBeforeResponse} from './functions-map/on-embeds-controller-before-response'; +import {onEmbedsControllerStart} from './functions-map/on-embeds-controller-start'; + +export const registerCommonPlugins = () => { + registry.common.functions.register({ + getAvailablePalettesMap, + getSourceAuthorizationHeaders, + isEntryId, + extractEntryId, + handleEntryRedirect, + pickAdditionalPublicApiHeaders, + onEmbedsControllerBeforeResponse, + onEmbedsControllerStart, + }); + + registry.common.auth.register({ + getAuthArgsUSPrivate, + getAuthHeadersUSPrivate, + getAuthArgsProxyUSPrivate, + getAuthHeadersBIPrivate, + hasValidWorkbookTransferAuthHeaders, + getAuthArgsProxyBIPrivate, + }); +}; diff --git a/src/server/registry/utils/register-app-plugins.ts b/src/server/registry/utils/register-app-plugins.ts new file mode 100644 index 0000000..4f192cc --- /dev/null +++ b/src/server/registry/utils/register-app-plugins.ts @@ -0,0 +1,5 @@ +import {registerCommonPlugins} from '../units/common/register'; + +export const registerAppPlugins = () => { + registerCommonPlugins(); +}; diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json new file mode 100644 index 0000000..574cd9c --- /dev/null +++ b/src/server/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "outDir": "../../dist", + "resolveJsonModule": true, + "esModuleInterop": true + }, + "include": ["**/*", "../@types", "../i18n", "../shared/**/*"] +} diff --git a/src/server/types/app-layout.ts b/src/server/types/app-layout.ts new file mode 100644 index 0000000..968a888 --- /dev/null +++ b/src/server/types/app-layout.ts @@ -0,0 +1,18 @@ +import type {RenderParams} from '@gravity-ui/app-layout'; +import type {Request, Response} from '@gravity-ui/expresskit'; + +import type {DLGlobalData} from '../../shared'; + +export type AppLayoutSettingsName = 'dl-main' | 'navigation' | 'landing-layout' | 'auth-layout'; + +export type AppLayoutSettings = { + renderConfig: RenderParams<{DL: DLGlobalData}>; + DL: Partial; + bundleName: string; +}; + +export type GetLayoutConfig = (args: { + req: Request; + res: Response; + settingsId: string; +}) => Promise>; diff --git a/src/server/types/connections.ts b/src/server/types/connections.ts new file mode 100644 index 0000000..1fe9d2d --- /dev/null +++ b/src/server/types/connections.ts @@ -0,0 +1,3 @@ +import type {ConnectorType} from '../../shared'; + +export type ConvertConnectorTypeToQLConnectionType = (connectorType: string) => ConnectorType; diff --git a/src/server/types/controllers.ts b/src/server/types/controllers.ts new file mode 100644 index 0000000..4bd6a22 --- /dev/null +++ b/src/server/types/controllers.ts @@ -0,0 +1,24 @@ +import type {AppMiddleware, AppRouteDescription} from '@gravity-ui/expresskit'; +import type {HttpMethod} from '@gravity-ui/expresskit/dist/types'; + +export interface CSPPolicies { + [key: string]: string[]; +} + +export type ExtendedAppRouteDescription = AppRouteDescription & { + route: `${Uppercase} ${string}`; + ui?: boolean; + beforeAuth: AppMiddleware[]; + afterAuth: AppMiddleware[]; + disableUserSettings?: boolean; + cspPresets?: CSPPolicies[]; + disableCsrf?: boolean; + guard?: AppMiddleware; +}; + +export type BasicControllers = + | 'dl-main' + | 'navigate' + | 'navigation' + | 'api.deleteLock' + | 'schematic-gateway'; diff --git a/src/server/types/gateway.ts b/src/server/types/gateway.ts new file mode 100644 index 0000000..f378dda --- /dev/null +++ b/src/server/types/gateway.ts @@ -0,0 +1,10 @@ +import type {ApiServiceActionConfig} from '@gravity-ui/gateway'; + +import type {authSchema, schema} from '../../shared/schema'; + +export type DatalensGatewaySchemas = { + root: typeof schema; + auth: typeof authSchema; +}; + +export type AnyApiServiceActionConfig = ApiServiceActionConfig; diff --git a/src/server/types/xlsxConverter.ts b/src/server/types/xlsxConverter.ts new file mode 100644 index 0000000..a330340 --- /dev/null +++ b/src/server/types/xlsxConverter.ts @@ -0,0 +1,17 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; + +import type {Graph} from '../components/charts-engine/components/processor/comments-fetcher'; + +export type XlsxConverterFn = ( + req: Request, + res: Response, + chartData: { + categories_ms?: number[]; + categories?: string[] | number[]; + graphs: Graph[]; + }, + dataArray: number[], + downloadConfig: { + filename: string; + }, +) => void; diff --git a/src/server/utils/axios.ts b/src/server/utils/axios.ts new file mode 100644 index 0000000..f46d2b7 --- /dev/null +++ b/src/server/utils/axios.ts @@ -0,0 +1,21 @@ +import querystring from 'querystring'; + +import type {AppConfig} from '@gravity-ui/nodekit'; +import type {AxiosInstance, AxiosRequestConfig} from 'axios'; +import axios from 'axios'; + +import {IPV6_AXIOS_OPTIONS} from '../constants/axios'; + +let axiosInstance: AxiosInstance | undefined; + +export const getUtilsAxios = (appConfig: AppConfig): AxiosInstance => { + if (!axiosInstance) { + const config: AxiosRequestConfig = { + paramsSerializer: (params: any) => querystring.stringify(params), + ...(appConfig.useIPV6 ? IPV6_AXIOS_OPTIONS : {}), + }; + axiosInstance = axios.create(config); + } + + return axiosInstance; +}; diff --git a/src/server/utils/env-utils.ts b/src/server/utils/env-utils.ts new file mode 100644 index 0000000..eff1241 --- /dev/null +++ b/src/server/utils/env-utils.ts @@ -0,0 +1 @@ +export const getEnvCert = (envCert?: string) => envCert?.replace(/\\n/g, '\n'); diff --git a/src/server/utils/gateway.ts b/src/server/utils/gateway.ts new file mode 100644 index 0000000..f73b1f3 --- /dev/null +++ b/src/server/utils/gateway.ts @@ -0,0 +1,80 @@ +import type {IncomingHttpHeaders} from 'node:http'; + +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {Headers as DebugHeaders, GatewayConfig, GatewayError} from '@gravity-ui/gateway'; +import type {AppContext, NodeKit} from '@gravity-ui/nodekit'; +import {AppError} from '@gravity-ui/nodekit'; + +import {getAuthArgs, getAuthHeaders} from '../../shared/schema/gateway-utils'; +import {IPV6_AXIOS_OPTIONS} from '../constants/axios'; + +export type GatewayApiErrorResponse = { + error: T; + debugHeaders?: DebugHeaders; +}; + +export const isGatewayError = (error: any): error is GatewayApiErrorResponse => { + if (!error || typeof error !== 'object') { + return false; + } + const err = error.error; + return ( + Boolean(err) && + typeof err === 'object' && + 'code' in err && + 'message' in err && + 'status' in err + ); +}; + +const UNKNOWN_TYPE = 'unknownType'; +const UNKNOWN_ENV = 'unknownEnv'; + +export const getGatewayConfig = ( + nodekit: NodeKit, + config?: Partial>, +): GatewayConfig => { + const axiosConfig = nodekit.config.useIPV6 ? IPV6_AXIOS_OPTIONS : {}; + + return { + installation: nodekit.config.appInstallation || UNKNOWN_TYPE, + env: nodekit.config.appEnv || UNKNOWN_ENV, + proxyHeaders: (headers) => { + const HEADERS_WITH_SENSITIVE_URLS = ['referer']; + const preparedHeaders: IncomingHttpHeaders = {}; + + const proxyHeaders = nodekit.config.gatewayProxyHeaders; + + const proxyHeadersLowerCase = proxyHeaders.map((header) => header.toLowerCase()); + const headersWithSensitiveUrlsLowerCase = HEADERS_WITH_SENSITIVE_URLS.map((header) => + header.toLowerCase(), + ); + + Object.keys(headers).forEach((key) => { + const keyLowerCase = key.toLowerCase(); + + if (proxyHeadersLowerCase.includes(keyLowerCase)) { + const value = headers[key]; + + if ( + headersWithSensitiveUrlsLowerCase.includes(keyLowerCase) && + typeof value === 'string' + ) { + preparedHeaders[key] = nodekit.utils.redactSensitiveQueryParams(value); + } else { + preparedHeaders[key] = value; + } + } + }); + + return preparedHeaders; + }, + caCertificatePath: null, + axiosConfig, + withDebugHeaders: false, + getAuthArgs, + getAuthHeaders, + ErrorConstructor: AppError, + ...(config || {}), + }; +}; diff --git a/src/server/utils/index.ts b/src/server/utils/index.ts new file mode 100644 index 0000000..87f7ca3 --- /dev/null +++ b/src/server/utils/index.ts @@ -0,0 +1,176 @@ +import fs from 'fs'; +import type {IncomingHttpHeaders} from 'http'; + +import type {Request} from '@gravity-ui/expresskit'; +import type {AppContext} from '@gravity-ui/nodekit'; +import type {AxiosResponse} from 'axios'; +import axios from 'axios'; +import pick from 'lodash/pick'; + +import { + AUDIT_MODE_HEADER, + AuthHeader, + DL_CONTEXT_HEADER, + FORWARDED_FOR_HEADER, + PROJECT_ID_HEADER, + REQUEST_ID_HEADER, + REQUEST_SOURCE_HEADER, + RequestSourceHeaderValue, + SuperuserHeader, + TENANT_ID_HEADER, + US_MASTER_TOKEN_HEADER, + makeTenantIdFromOrgId, +} from '../../shared'; +import {isOpensourceInstallation} from '../app-env'; +import {PUBLIC_API_VERSION_HEADER} from '../components/public-api/constants'; +import {PUBLIC_API_ORG_ID_HEADER} from '../constants/public-api'; +import {registry} from '../registry'; + +import {isGatewayError} from './gateway'; + +class Utils { + static getName(key = '') { + return key.split('/').filter(Boolean).pop(); + } + + static pickServiceHeaders(headers: IncomingHttpHeaders, req: Request) { + const {folderId: folderIdHeader, subjectToken: subjectTokenHeader} = + req.ctx.config.headersMap; + + let headersList = [ + AuthHeader.Cookie, + AuthHeader.Authorization, + folderIdHeader, + TENANT_ID_HEADER, + PROJECT_ID_HEADER, + subjectTokenHeader, + ]; + + if (isOpensourceInstallation) { + headersList = []; + } + + return pick(headers, headersList); + } + + static pickAuthHeaders(req: Request) { + return { + [AuthHeader.Authorization]: 'Bearer ' + req.ctx.get('user')?.accessToken, + }; + } + + static pickSuperuserHeaders(headers: IncomingHttpHeaders) { + return pick(headers, [SuperuserHeader.XDlSudo, SuperuserHeader.XDlAllowSuperuser]); + } + + static pickDlContextHeaders(headers: IncomingHttpHeaders) { + return pick(headers, DL_CONTEXT_HEADER); + } + + static pickForwardHeaders(headers: IncomingHttpHeaders) { + return pick(headers, FORWARDED_FOR_HEADER); + } + + static pickHeaders(req: Request) { + return { + ...Utils.pickServiceHeaders(req.headers, req), + ...Utils.pickSuperuserHeaders(req.headers), + ...Utils.pickDlContextHeaders(req.headers), + ...Utils.pickForwardHeaders(req.headers), + ...(req.ctx.config.isAuthEnabled ? {...Utils.pickAuthHeaders(req)} : {}), + [REQUEST_ID_HEADER]: req.id, + }; + } + + static pickPublicApiHeaders(req: Request) { + const orgId = req.headers[PUBLIC_API_ORG_ID_HEADER]; + const tenantId = orgId && !Array.isArray(orgId) ? makeTenantIdFromOrgId(orgId) : undefined; + + const pickAdditionalPublicApiHeaders = registry.common.functions.get( + 'pickAdditionalPublicApiHeaders', + ); + + return { + ...pick(req.headers, [PUBLIC_API_VERSION_HEADER, AUDIT_MODE_HEADER]), + ...Utils.pickForwardHeaders(req.headers), + [TENANT_ID_HEADER]: tenantId, + [REQUEST_SOURCE_HEADER]: RequestSourceHeaderValue.PublicApi, + ...pickAdditionalPublicApiHeaders(req), + }; + } + + static pickUsMasterToken(req: Request) { + const token = req.headers[US_MASTER_TOKEN_HEADER]; + if (typeof token !== 'string') { + return null; + } + + return token; + } + + static getErrorMessage(error: unknown) { + if (axios.isAxiosError(error)) { + return error.response?.data.message || error.message; + } else if (error instanceof Error) { + return error.message; + } else if (isGatewayError(error)) { + return error.error.message; + } else { + return 'Unknown error'; + } + } + + static getErrorDetails(error: unknown) { + if (axios.isAxiosError(error)) { + return error.response?.data.details; + } + return undefined; + } + + static getErrorCode(error: unknown) { + if (axios.isAxiosError(error)) { + return error.response?.data.code; + } else if (isGatewayError(error)) { + return error.error.code; + } + return undefined; + } + + static getErrorStatus(error: unknown) { + if (axios.isAxiosError(error)) { + return error.response?.status; + } + return undefined; + } + + static getFormattedLogin(login: string | undefined) { + let formattedLogin = login; + if (typeof formattedLogin === 'string' && !formattedLogin.includes('@')) { + formattedLogin = formattedLogin.replace(/\./g, '-'); + } + return formattedLogin; + } + + static isDevelopment(ctx: AppContext) { + return ctx.config.appEnv === 'development'; + } + + static getResponseData(response: AxiosResponse) { + return response.data; + } + + static getEnvVariable(envVariableName: string) { + const valueFromEnv = process.env[envVariableName]; + if (valueFromEnv) { + return valueFromEnv; + } + const FILE_PATH_POSTFIX = '_FILE_PATH'; + const filePath = process.env[`${envVariableName}${FILE_PATH_POSTFIX}`]; + if (filePath) { + return fs.readFileSync(filePath, 'utf8') as string; + } + return undefined; + } +} + +export default Utils; diff --git a/src/server/utils/language.ts b/src/server/utils/language.ts new file mode 100644 index 0000000..37e448b --- /dev/null +++ b/src/server/utils/language.ts @@ -0,0 +1,76 @@ +import path from 'path'; + +import type {Script} from '@gravity-ui/app-layout'; +import _ from 'lodash'; + +import {I18N_DEST_PATH} from '../../i18n/constants'; +import {readKeysets} from '../../i18n/read-keysets'; +import type {Keysets, ServerI18n, TypedI18n} from '../../i18n/types'; +import {initI18n} from '../../i18n/utils'; + +export type KeysetData = {content: Keysets; filename: string}; +export type KeysetFile = Record; + +export const keysetsByLang = _.memoize(() => + Object.entries(readKeysets(I18N_DEST_PATH)).reduce((acc, [key, keyset]) => { + // eslint-disable-next-line security/detect-non-literal-require, global-require + const content = require(path.resolve(I18N_DEST_PATH, keyset.filename)); + acc[key] = { + filename: keyset.filename, + content, + }; + + return acc; + }, {} as KeysetFile), +); + +export function addTranslationsScript({ + publicPath = '/build/', + allowLanguages = [], + lang, + region, +}: { + publicPath?: string; + allowLanguages?: string[]; + lang: string; + region?: string; +}): Script { + const allowedLang = allowLanguages.includes(lang) ? lang : allowLanguages[0]; + const fileKey = region ? `${allowedLang}-${region}` : `${allowedLang}`; + + const keysetFile = keysetsByLang()[fileKey] as KeysetData | undefined; + + return { + src: keysetFile ? `${publicPath}i18n/${keysetFile.filename}` : '', + defer: true, + crossOrigin: 'anonymous', + }; +} + +const i18nInstanceByLang: Record = {}; + +export const createI18nInstance = ( + // in some cases we need mutate lang value for i18n. + // for example, we first set i18n in before-auth-default middleware + // but later the lang can be changed in other middlewares + langSettings: {lang: string}, +): ServerI18n => { + return { + get lang(): string { + return langSettings.lang; + }, + getI18nServer() { + if (this.lang && !i18nInstanceByLang[this.lang]) { + const data = keysetsByLang()[this.lang] || {content: {}}; + + const {I18n: i18nServer} = initI18n({lang: this.lang, content: data.content}); + i18nInstanceByLang[this.lang] = i18nServer; + } + return i18nInstanceByLang[this.lang]; + }, + + keyset(keysetName: keyof Keysets) { + return this.getI18nServer().keyset(keysetName); + }, + }; +}; diff --git a/src/server/utils/redis.ts b/src/server/utils/redis.ts new file mode 100644 index 0000000..f7601b8 --- /dev/null +++ b/src/server/utils/redis.ts @@ -0,0 +1,89 @@ +export type RedisSentinel = { + host: string; + port: number; +}; + +export type RedisSentinelsList = Array; + +export type RedisTlsConfig = { + ca?: string; + cert?: string; + key?: string; +}; + +export type RedisConfig = { + sentinels?: RedisSentinelsList; + name?: string; + password?: string; + host?: string; + port?: number; + tls?: RedisTlsConfig; + family?: 4 | 6; + role?: 'master' | 'slave'; +}; + +export type RedisConfigParams = RedisConfig & {envName?: string}; + +export const getRedisConfig = (params: RedisConfigParams = {}): RedisConfig => { + const envName = params.envName || 'REDIS_DSN_LIST'; + const redisDsnList = process.env[envName]; + const redisIpFamilyEnv = process.env.REDIS_IP_FAMILY; + + if (!redisDsnList?.match(/^rediss?:\/\//)) { + return { + name: params.name || '', + sentinels: params.sentinels, + host: params.host, + port: params.port, + family: params.family, + }; + } + + const isTLS = redisDsnList.startsWith('rediss://'); + + const dsnListString = redisDsnList.replace(/^rediss?:\/\//, ''); + + const [namePasswordString, redisSentinelsString] = dsnListString.split('@'); + + const [clusterName, password] = namePasswordString.includes(':') + ? namePasswordString.split(':') + : [undefined, namePasswordString]; + + let redisIpFamily: 4 | 6 | undefined = 6; + if (redisIpFamilyEnv === 'auto') { + redisIpFamily = undefined; + } else if (redisIpFamilyEnv === '4') { + redisIpFamily = 4; + } else if (redisIpFamilyEnv === '6') { + redisIpFamily = 6; + } + + const redisSentinels: Array = redisSentinelsString + .trim() + .split(',') + .map((hostPort) => { + const [host, port] = hostPort.split(':'); + + return {host: host.trim(), port: port ? parseInt(port, 10) : 26379}; + }); + + if (redisSentinels.length < 2) { + return { + host: redisSentinels[0]?.host, + port: redisSentinels[0]?.port, + name: clusterName, + password, + family: redisIpFamily, + tls: isTLS ? {} : undefined, + }; + } + + return { + sentinels: redisSentinels, + name: clusterName, + password, + family: redisIpFamily, + tls: isTLS ? {} : undefined, + role: 'master', + }; +}; diff --git a/src/server/utils/routes.ts b/src/server/utils/routes.ts new file mode 100644 index 0000000..654ad8b --- /dev/null +++ b/src/server/utils/routes.ts @@ -0,0 +1,53 @@ +import type {Request} from '@gravity-ui/expresskit'; + +import { + apiControllers, + dlMainController, + navigateController, + navigationController, +} from '../controllers'; +import {registry} from '../registry'; +import type {BasicControllers, ExtendedAppRouteDescription} from '../types/controllers'; + +export const getConfiguredRoute = ( + controllerName: BasicControllers, + params: Omit, +): ExtendedAppRouteDescription => { + switch (controllerName) { + case 'navigate': + return { + handler: navigateController, + ...params, + }; + case 'api.deleteLock': + return { + handler: apiControllers.deleteLock, + ...params, + }; + case 'dl-main': + return { + handler: dlMainController, + ...params, + }; + case 'navigation': + return { + handler: navigationController, + ...params, + }; + case 'schematic-gateway': { + const {gatewayController} = registry.getGatewayController(); + + return { + handler: gatewayController, + ...params, + }; + } + + default: + return null as never; + } +}; + +export function getDashboardsRedirectPath(req: Request): string { + return req.originalUrl.replace('/dashboards', '').replace(/^\/\/+/, '/'); +} diff --git a/src/server/utils/schema-validate.ts b/src/server/utils/schema-validate.ts new file mode 100644 index 0000000..de59c08 --- /dev/null +++ b/src/server/utils/schema-validate.ts @@ -0,0 +1,9 @@ +import Ajv from 'ajv'; + +const ajv = new Ajv(); + +export function validate(data: unknown, schema: object) { + const validateFn = ajv.compile(schema); + + return validateFn(data) ? false : ajv.errorsText(validateFn.errors); +} diff --git a/src/shared/__tests__/.eslintrc b/src/shared/__tests__/.eslintrc new file mode 100644 index 0000000..eba2077 --- /dev/null +++ b/src/shared/__tests__/.eslintrc @@ -0,0 +1,5 @@ +{ + "env": { + "jest": true + } +} diff --git a/src/shared/__tests__/types.test.ts b/src/shared/__tests__/types.test.ts new file mode 100644 index 0000000..84acb93 --- /dev/null +++ b/src/shared/__tests__/types.test.ts @@ -0,0 +1,13 @@ +import {ChartsConfigVersion} from '../types'; + +describe('wizard', () => { + describe('versions', () => { + it('enum should contains only string values', () => { + const versions = Object.values(ChartsConfigVersion); + + for (const version of versions) { + expect(typeof version).toBe('string'); + } + }); + }); +}); diff --git a/src/shared/__tests__/utils.test.ts b/src/shared/__tests__/utils.test.ts new file mode 100644 index 0000000..015620f --- /dev/null +++ b/src/shared/__tests__/utils.test.ts @@ -0,0 +1,43 @@ +import {extractEntryId} from '../'; + +test('extractEntryId returns null', () => { + const entryId = 'phz6hrfoms3oo'; + const values = [ + '', + '!hz6hr-oms3o?', + '/', + + `/marketplace/${entryId}`, + `/${entryId}?r=2`, + `/${entryId}&d=2`, + `/nav/${entryId}#hash`, + `/market/place/${entryId}`, + + `/datasets/${entryId}text`, + ]; + + expect(values.every((value) => extractEntryId(value) === null)).toEqual(true); +}); + +test("extractEntryId matches entrie's id", () => { + const entryId = 'phz6hrfoms3oo'; + const values = [ + `${entryId}`, + `${entryId}/`, + `/${entryId}`, + `/${entryId}/`, + `/${entryId}-lorem-ipsum-kpi`, + `/datasets/${entryId}-`, + `/wizard/${entryId}-/`, + `/${entryId}-bla-bla-`, + + `/navigation/${entryId}`, + `/datasets/${entryId}/`, + `/wizard/${entryId}-grow-scale/`, + `/preview/${entryId}/`, + `editor/${entryId}`, + `/preview/${entryId}-deprecated`, + ]; + + expect(values.every((value) => extractEntryId(value) === entryId)).toEqual(true); +}); diff --git a/src/shared/components/auth/constants/cookie.ts b/src/shared/components/auth/constants/cookie.ts new file mode 100644 index 0000000..621f0d5 --- /dev/null +++ b/src/shared/components/auth/constants/cookie.ts @@ -0,0 +1 @@ +export const AUTH_DEFAULT_COOKIE_NAME = 'auth'; diff --git a/src/shared/components/auth/constants/role.ts b/src/shared/components/auth/constants/role.ts new file mode 100644 index 0000000..94a271f --- /dev/null +++ b/src/shared/components/auth/constants/role.ts @@ -0,0 +1,7 @@ +export enum UserRole { + Editor = 'datalens.editor', + Admin = 'datalens.admin', + Viewer = 'datalens.viewer', + Visitor = 'datalens.visitor', + Creator = 'datalens.creator', +} diff --git a/src/shared/components/auth/constants/token.ts b/src/shared/components/auth/constants/token.ts new file mode 100644 index 0000000..5f68e49 --- /dev/null +++ b/src/shared/components/auth/constants/token.ts @@ -0,0 +1 @@ +export const ACCESS_TOKEN_TIME_RESERVE = 120; // sec diff --git a/src/shared/components/auth/constants/url.ts b/src/shared/components/auth/constants/url.ts new file mode 100644 index 0000000..7e596ef --- /dev/null +++ b/src/shared/components/auth/constants/url.ts @@ -0,0 +1 @@ +export const RELOADED_URL_QUERY = '_auth_reloaded'; diff --git a/src/shared/components/auth/types/layout.ts b/src/shared/components/auth/types/layout.ts new file mode 100644 index 0000000..afd51d4 --- /dev/null +++ b/src/shared/components/auth/types/layout.ts @@ -0,0 +1,7 @@ +export interface AuthLayoutSettings { + page: 'reload' | 'signin' | 'logout'; +} + +export interface AuthPageSettings extends AuthLayoutSettings { + isAuthPage: true; +} diff --git a/src/shared/components/auth/utils.ts b/src/shared/components/auth/utils.ts new file mode 100644 index 0000000..cf578f9 --- /dev/null +++ b/src/shared/components/auth/utils.ts @@ -0,0 +1,16 @@ +import {AUTH_DEFAULT_COOKIE_NAME} from './constants/cookie'; + +const generateCookieName = ({ + authCookieName, + postfix, +}: { + authCookieName?: string; + postfix?: string; +}) => { + const baseCookieName = authCookieName || AUTH_DEFAULT_COOKIE_NAME; + return baseCookieName + (postfix ? `_${postfix}` : ''); +}; + +export const getAuthCookieName = (authCookieName?: string) => generateCookieName({authCookieName}); +export const getAuthExpCookieName = (authCookieName?: string) => + generateCookieName({authCookieName, postfix: 'exp'}); diff --git a/src/shared/components/public-api/utils/index.ts b/src/shared/components/public-api/utils/index.ts new file mode 100644 index 0000000..b4b944f --- /dev/null +++ b/src/shared/components/public-api/utils/index.ts @@ -0,0 +1,11 @@ +const registeredComponents = new Set(); + +export const registerComponentId = (id: string): string => { + if (registeredComponents.has(id)) { + throw new Error(`OpenAPI component ${id} is already registered`); + } + + registeredComponents.add(id); + + return id; +}; diff --git a/src/shared/constants/api-v2.ts b/src/shared/constants/api-v2.ts new file mode 100644 index 0000000..a4d6edb --- /dev/null +++ b/src/shared/constants/api-v2.ts @@ -0,0 +1,4 @@ +export const enum ApiV2Annotations { + Color = 'color', + BackgroundColor = 'background-color', +} diff --git a/src/shared/constants/chart-modeling.ts b/src/shared/constants/chart-modeling.ts new file mode 100644 index 0000000..990c7a5 --- /dev/null +++ b/src/shared/constants/chart-modeling.ts @@ -0,0 +1,16 @@ +import type {SmoothingLineSettings, TrendLineSettings} from '../types'; + +import {LineShapeType} from './shapes/lines'; + +export const DEFAULT_SMOOTHING: SmoothingLineSettings = { + method: 'sma', + windowSize: 7, + lineWidth: 2, + linked: true, +}; +export const DEFAULT_TREND_SETTINGS: TrendLineSettings = { + method: 'linear', + dashStyle: LineShapeType.Dash, + lineWidth: 2, + linked: true, +}; diff --git a/src/shared/constants/chartkit-handlers.ts b/src/shared/constants/chartkit-handlers.ts new file mode 100644 index 0000000..b6111ee --- /dev/null +++ b/src/shared/constants/chartkit-handlers.ts @@ -0,0 +1,14 @@ +export enum ChartkitHandlers { + DCMonitoringLabelFormatter = 'dc-monitoring-label-formatter', + WizardLabelFormatter = 'wizard-label-formatter', + WizardTooltipHeaderFormatter = 'wizard-tooltip-header-formatter', + WizardManageTooltipConfig = 'wizard-manage-tooltip-config', + WizardXAxisFormatter = 'wizard-x-axis-formatter', + WizardExportColumnNamesFormatter = 'wizard-export-column-names-formatter', + WizardScatterTooltipFormatter = 'wizard-scatter-tooltip-formatter', + WizardTreemapTooltipFormatter = 'wizard-treemap-tooltip-formatter', + WizardScatterYAxisLabelFormatter = 'wizard-scatter-y-axis-label-formatter', + WizardDatetimeAxisFormatter = 'wizard-datetime-axis-formatter', +} + +export const WRAPPED_HTML_KEY = '__wrappedHTML__'; diff --git a/src/shared/constants/charts.ts b/src/shared/constants/charts.ts new file mode 100644 index 0000000..b75e8cc --- /dev/null +++ b/src/shared/constants/charts.ts @@ -0,0 +1,7 @@ +export const WidgetSize = { + L: 'l', + M: 'm', + S: 's', +} as const; + +export const DEFAULT_WIDGET_SIZE = WidgetSize.L; diff --git a/src/shared/constants/collections.ts b/src/shared/constants/collections.ts new file mode 100644 index 0000000..46a3d1f --- /dev/null +++ b/src/shared/constants/collections.ts @@ -0,0 +1,5 @@ +export const CollectionItemEntities = { + COLLECTION: 'collection', + WORKBOOK: 'workbook', + ENTRY: 'entry', +} as const; diff --git a/src/shared/constants/colors/common/classic-20.ts b/src/shared/constants/colors/common/classic-20.ts new file mode 100644 index 0000000..c5ecf19 --- /dev/null +++ b/src/shared/constants/colors/common/classic-20.ts @@ -0,0 +1,29 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.CLASSIC_20, + scheme: [ + '#4DA2F1', + '#FF3D64', + '#8AD554', + '#FFC636', + '#FFB9DD', + '#84D1EE', + '#FF91A1', + '#54A520', + '#DB9100', + '#BA74B3', + '#1F68A9', + '#ED65A9', + '#0FA08D', + '#FF7E00', + '#E8B0A4', + '#52A6C5', + '#BE2443', + '#70C1AF', + '#FFB46C', + '#DCA3D7', + ], + datalens: true, +} as Palette; diff --git a/src/shared/constants/colors/common/datalens.ts b/src/shared/constants/colors/common/datalens.ts new file mode 100644 index 0000000..62f32e7 --- /dev/null +++ b/src/shared/constants/colors/common/datalens.ts @@ -0,0 +1,29 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.DATALENS_NEO_20, + scheme: [ + '#DB9101', + '#4DA2F1', + '#8AD554', + '#BA74B3', + '#FF3D64', + '#FFC636', + '#0CA18C', + '#DBA2D7', + '#BF2543', + '#84D1EE', + '#6B6B6B', + '#EEB184', + '#1E69A9', + '#FF7E00', + '#54A520', + '#B9B0AC', + '#58A9C8', + '#FFB46C', + '#70C1AF', + '#FF90A1', + ], + datalens: true, +} as Palette; diff --git a/src/shared/constants/colors/common/default-20.ts b/src/shared/constants/colors/common/default-20.ts new file mode 100644 index 0000000..f603c43 --- /dev/null +++ b/src/shared/constants/colors/common/default-20.ts @@ -0,0 +1,29 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.DEFAULT_20, + scheme: [ + '#3D97F2', + '#FF6A59', + '#7FD169', + '#FFB433', + '#AB94D9', + '#8CD5FF', + '#FF8CB2', + '#60A196', + '#4C73BF', + '#B8C9CC', + '#52CCC2', + '#C3E55C', + '#807D73', + '#FF8C00', + '#FFDC73', + '#CC9966', + '#CC5252', + '#FFB28C', + '#9661FF', + '#D4BFFF', + ], + datalens: true, +} as Palette; diff --git a/src/shared/constants/colors/common/emerald-20.ts b/src/shared/constants/colors/common/emerald-20.ts new file mode 100644 index 0000000..5c20801 --- /dev/null +++ b/src/shared/constants/colors/common/emerald-20.ts @@ -0,0 +1,29 @@ +import type {GradientPalette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.EMERALD_20, + scheme: [ + '#C2EEBA', + '#AEEAAA', + '#99E69A', + '#89E192', + '#79DD8B', + '#68D986', + '#58D583', + '#48D183', + '#37CD84', + '#30C086', + '#2EB887', + '#2CAF88', + '#2AA788', + '#289F87', + '#269786', + '#248F84', + '#228782', + '#207E7E', + '#1E7276', + '#1C666E', + ], + gradient: true, +} as GradientPalette; diff --git a/src/shared/constants/colors/common/golden-20.ts b/src/shared/constants/colors/common/golden-20.ts new file mode 100644 index 0000000..de3c8eb --- /dev/null +++ b/src/shared/constants/colors/common/golden-20.ts @@ -0,0 +1,29 @@ +import type {GradientPalette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.GOLDEN_20, + scheme: [ + '#FDEC4D', + '#FDDE43', + '#FDD039', + '#FDBF2F', + '#FDAE25', + '#FD9B1B', + '#FD8711', + '#FD7207', + '#F75C02', + '#ED4902', + '#E33702', + '#D92602', + '#CF1702', + '#C40802', + '#BA0208', + '#B00213', + '#A6021D', + '#9C0125', + '#92012D', + '#880132', + ], + gradient: true, +} as GradientPalette; diff --git a/src/shared/constants/colors/common/neutral-20.ts b/src/shared/constants/colors/common/neutral-20.ts new file mode 100644 index 0000000..c571165 --- /dev/null +++ b/src/shared/constants/colors/common/neutral-20.ts @@ -0,0 +1,29 @@ +import type {GradientPalette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.NEUTRAL_20, + scheme: [ + '#EAEAEA', + '#E3E3E3', + '#DBDBDB', + '#D3D3D3', + '#CCCCCC', + '#C4C4C4', + '#BCBCBC', + '#B5B5B5', + '#ADADAD', + '#A6A6A6', + '#9E9E9E', + '#969696', + '#8F8F8F', + '#878787', + '#7F7F7F', + '#787878', + '#707070', + '#686868', + '#616161', + '#595959', + ], + gradient: true, +} as GradientPalette; diff --git a/src/shared/constants/colors/common/oceanic-20.ts b/src/shared/constants/colors/common/oceanic-20.ts new file mode 100644 index 0000000..0b5fb47 --- /dev/null +++ b/src/shared/constants/colors/common/oceanic-20.ts @@ -0,0 +1,29 @@ +import type {GradientPalette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.OCEANIC_20, + scheme: [ + '#AEF8FE', + '#9FF2FE', + '#90EAFE', + '#81E1FE', + '#71D6FE', + '#62CAFE', + '#53BCFE', + '#44ADFE', + '#359DFD', + '#258AFD', + '#1677FD', + '#0762FD', + '#024FF3', + '#023EE4', + '#0230D5', + '#0222C5', + '#0117B6', + '#010DA7', + '#010498', + '#050189', + ], + gradient: true, +} as GradientPalette; diff --git a/src/shared/constants/colors/common/traffic-light-9.ts b/src/shared/constants/colors/common/traffic-light-9.ts new file mode 100644 index 0000000..0979823 --- /dev/null +++ b/src/shared/constants/colors/common/traffic-light-9.ts @@ -0,0 +1,18 @@ +import type {GradientPalette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.TRAFFIC_LIGHT_9, + scheme: [ + '#9C0125', + '#F17A03', + '#228782', + '#C40802', + '#FDA816', + '#2EB887', + '#ED4902', + '#FDCE34', + '#79DD8B', + ], + gradient: true, +} as GradientPalette; diff --git a/src/shared/constants/colors/index.ts b/src/shared/constants/colors/index.ts new file mode 100644 index 0000000..f6536fc --- /dev/null +++ b/src/shared/constants/colors/index.ts @@ -0,0 +1,95 @@ +import classic from './common/classic-20'; +import datalens from './common/datalens'; +import default20 from './common/default-20'; +import emerald from './common/emerald-20'; +import golden from './common/golden-20'; +import neutral from './common/neutral-20'; +import oceanic from './common/oceanic-20'; +import trafficLight from './common/traffic-light-9'; +import {type Palette} from './types'; + +export * from './types'; + +export interface Palettes { + color: string[]; + gradient: string[]; +} + +export const PALETTES = { + default20, + classic, + neutral, + golden, + emerald, + oceanic, + trafficLight, + datalens, +}; + +export const BASE_PALETTES_MAP: Record = { + [default20.id]: default20, + [classic.id]: classic, + [emerald.id]: emerald, + [neutral.id]: neutral, + [golden.id]: golden, + [oceanic.id]: oceanic, + [trafficLight.id]: trafficLight, + [datalens.id]: datalens, +}; + +const sortPalettes = (palettes: string[], defaultPaletteId?: string) => + palettes.sort((a, b) => { + if (a === defaultPaletteId) { + return -1; + } + + if (b === defaultPaletteId) { + return 1; + } + + return a.localeCompare(b); + }); + +export const selectAvailablePalettes = ({ + palettes, + defaultPaletteId, +}: { + palettes: Record; + defaultPaletteId?: string; +}): Palettes => { + const gradientPalettes: string[] = []; + const colorPalettes: string[] = []; + const datalensPalettes: string[] = []; + + const palettesIds = Object.keys(palettes); + + palettesIds.forEach((id: string) => { + if (palettes[id].datalens) { + datalensPalettes.push(id); + } else if (palettes[id].gradient) { + gradientPalettes.push(id); + } else { + colorPalettes.push(id); + } + }); + + return { + color: [ + ...sortPalettes(datalensPalettes, defaultPaletteId), + ...sortPalettes(colorPalettes, defaultPaletteId), + ], + gradient: sortPalettes(gradientPalettes, defaultPaletteId), + }; +}; + +export const getAvailablePalettesMap = () => { + return BASE_PALETTES_MAP; +}; + +export const getPalettesOrder = (): (keyof Palettes)[] => { + return ['color', 'gradient']; +}; + +export const isSystemPaletteId = (paletteId: string, palettes: Record) => { + return Boolean(palettes[paletteId]); +}; diff --git a/src/shared/constants/colors/tableau/blue-red-12.ts b/src/shared/constants/colors/tableau/blue-red-12.ts new file mode 100644 index 0000000..92e41d7 --- /dev/null +++ b/src/shared/constants/colors/tableau/blue-red-12.ts @@ -0,0 +1,20 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.BLUE_RED_12, + scheme: [ + '#ffb6b0', + '#f02720', + '#b5c8e2', + '#ac613c', + '#2c69b0', + '#e9c39b', + '#ddc9b4', + '#b5dffd', + '#ac8763', + '#6ba3d6', + '#f4737a', + '#bd0a36', + ], +} as Palette; diff --git a/src/shared/constants/colors/tableau/blue-red-6.ts b/src/shared/constants/colors/tableau/blue-red-6.ts new file mode 100644 index 0000000..c7f659a --- /dev/null +++ b/src/shared/constants/colors/tableau/blue-red-6.ts @@ -0,0 +1,7 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.BLUE_RED_6, + scheme: ['#f02720', '#ea6b73', '#ac613c', '#6ba3d6', '#2c69b0', '#e9c39b'], +} as Palette; diff --git a/src/shared/constants/colors/tableau/color-blind-10.ts b/src/shared/constants/colors/tableau/color-blind-10.ts new file mode 100644 index 0000000..111ee9d --- /dev/null +++ b/src/shared/constants/colors/tableau/color-blind-10.ts @@ -0,0 +1,18 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.COLOR_BLIND_10, + scheme: [ + '#ff800e', + '#ababab', + '#5f9ed1', + '#595959', + '#006ba4', + '#ffbc79', + '#cfcfcf', + '#c85200', + '#a2c8ec', + '#898989', + ], +} as Palette; diff --git a/src/shared/constants/colors/tableau/gray-5.ts b/src/shared/constants/colors/tableau/gray-5.ts new file mode 100644 index 0000000..a7e7743 --- /dev/null +++ b/src/shared/constants/colors/tableau/gray-5.ts @@ -0,0 +1,7 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.GRAY_5, + scheme: ['#cfcfcf', '#a5acaf', '#8f8782', '#60636a', '#414451'], +} as Palette; diff --git a/src/shared/constants/colors/tableau/green-orange-12.ts b/src/shared/constants/colors/tableau/green-orange-12.ts new file mode 100644 index 0000000..c394cb1 --- /dev/null +++ b/src/shared/constants/colors/tableau/green-orange-12.ts @@ -0,0 +1,20 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.GREEN_ORANGE_12, + scheme: [ + '#ffb977', + '#ff7f0f', + '#acd98d', + '#3cb7cc', + '#32a251', + '#ffd94a', + '#b85a0d', + '#98d9e4', + '#86b4a9', + '#39737c', + '#ccc94d', + '#82853b', + ], +} as Palette; diff --git a/src/shared/constants/colors/tableau/green-orange-6.ts b/src/shared/constants/colors/tableau/green-orange-6.ts new file mode 100644 index 0000000..e1d55be --- /dev/null +++ b/src/shared/constants/colors/tableau/green-orange-6.ts @@ -0,0 +1,7 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.GREEN_ORANGE_6, + scheme: ['#ffd94a', '#ff7f0f', '#3cb7cc', '#39737c', '#32a251', '#b85a0d'], +} as Palette; diff --git a/src/shared/constants/colors/tableau/purpure-gray-12.ts b/src/shared/constants/colors/tableau/purpure-gray-12.ts new file mode 100644 index 0000000..fe9df1f --- /dev/null +++ b/src/shared/constants/colors/tableau/purpure-gray-12.ts @@ -0,0 +1,20 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.PURPURE_GRAY_12, + scheme: [ + '#ffc0da', + '#dc5fbd', + '#a699e8', + '#7b66d2', + '#5f5a41', + '#d898ba', + '#d098ee', + '#b4b19b', + '#ab6ad5', + '#995688', + '#dbd4c5', + '#8b7c6e', + ], +} as Palette; diff --git a/src/shared/constants/colors/tableau/purpure-gray-6.ts b/src/shared/constants/colors/tableau/purpure-gray-6.ts new file mode 100644 index 0000000..78d0c92 --- /dev/null +++ b/src/shared/constants/colors/tableau/purpure-gray-6.ts @@ -0,0 +1,7 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.PURPURE_GRAY_6, + scheme: ['#dc5fbd', '#d098ee', '#995688', '#94917b', '#7b66d2', '#d7d5c5'], +} as Palette; diff --git a/src/shared/constants/colors/tableau/tableau-10-light.ts b/src/shared/constants/colors/tableau/tableau-10-light.ts new file mode 100644 index 0000000..65d4bc8 --- /dev/null +++ b/src/shared/constants/colors/tableau/tableau-10-light.ts @@ -0,0 +1,18 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.TABLEAU_10_LIGHT, + scheme: [ + '#ffbb78', + '#ff9896', + '#c5b0d5', + '#aec7e8', + '#98df8a', + '#f7b6d2', + '#dbdb8d', + '#c7c7c7', + '#c49c94', + '#9edae5', + ], +} as Palette; diff --git a/src/shared/constants/colors/tableau/tableau-10-medium.ts b/src/shared/constants/colors/tableau/tableau-10-medium.ts new file mode 100644 index 0000000..ad3229f --- /dev/null +++ b/src/shared/constants/colors/tableau/tableau-10-medium.ts @@ -0,0 +1,18 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.TABLEAU_10_MEDIUM, + scheme: [ + '#ff9e4a', + '#ed665d', + '#ad8bc9', + '#729ece', + '#67bf5c', + '#ed97ca', + '#cdcc5d', + '#a8786e', + '#a2a2a2', + '#6dccda', + ], +} as Palette; diff --git a/src/shared/constants/colors/tableau/tableau-10.ts b/src/shared/constants/colors/tableau/tableau-10.ts new file mode 100644 index 0000000..f0fe431 --- /dev/null +++ b/src/shared/constants/colors/tableau/tableau-10.ts @@ -0,0 +1,18 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.TABLEAU_10, + scheme: [ + '#ff7f0e', + '#d62728', + '#9467bd', + '#2ca02c', + '#1f77b4', + '#e377c2', + '#bcbd22', + '#8c564b', + '#7f7f7f', + '#17becf', + ], +} as Palette; diff --git a/src/shared/constants/colors/tableau/tableau-20.ts b/src/shared/constants/colors/tableau/tableau-20.ts new file mode 100644 index 0000000..25ba258 --- /dev/null +++ b/src/shared/constants/colors/tableau/tableau-20.ts @@ -0,0 +1,28 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.TABLEAU_20, + scheme: [ + '#ffbb78', + '#ff7f0e', + '#aec7e8', + '#2ca02c', + '#1f77b4', + '#ff9896', + '#d62728', + '#c5b0d5', + '#98df8a', + '#9467bd', + '#f7b6d2', + '#e377c2', + '#c49c94', + '#8c564b', + '#7f7f7f', + '#dbdb8d', + '#c7c7c7', + '#bcbd22', + '#9edae5', + '#17becf', + ], +} as Palette; diff --git a/src/shared/constants/colors/tableau/traffic-light.ts b/src/shared/constants/colors/tableau/traffic-light.ts new file mode 100644 index 0000000..94a6311 --- /dev/null +++ b/src/shared/constants/colors/tableau/traffic-light.ts @@ -0,0 +1,17 @@ +import type {Palette} from '../types'; +import {PALETTE_ID} from '../types'; + +export default { + id: PALETTE_ID.TRAFFIC_LIGHT, + scheme: [ + '#ffc156', + '#dba13a', + '#d82526', + '#b10318', + '#309343', + '#ffdd71', + '#f26c64', + '#9fcd99', + '#69b764', + ], +} as Palette; diff --git a/src/shared/constants/colors/types.ts b/src/shared/constants/colors/types.ts new file mode 100644 index 0000000..80a2e0d --- /dev/null +++ b/src/shared/constants/colors/types.ts @@ -0,0 +1,53 @@ +export const AUTO_PALETTE_ID = 'auto'; + +export const GRADIENT_PALETTE_ID = { + EMERALD_20: 'emerald20', + GOLDEN_20: 'golden20', + NEUTRAL_20: 'neutral20', + OCEANIC_20: 'oceanic20', + TRAFFIC_LIGHT_9: 'traffic-light9', +} as const; + +export const COMMON_PALETTE_ID = { + CLASSIC_20: 'classic20', + DEFAULT_20: 'default20', + DATALENS_NEO_20: 'datalens-neo-20', + ...GRADIENT_PALETTE_ID, +} as const; + +export const TABLEAU_PALETTE_ID = { + BLUE_RED_6: 'Blue-Red 6', + BLUE_RED_12: 'Blue-Red 12', + COLOR_BLIND_10: 'Color-Blind 10', + GRAY_5: 'Gray 5', + GREEN_ORANGE_6: 'Green-Orange 6', + GREEN_ORANGE_12: 'Green-Orange 12', + PURPURE_GRAY_6: 'Purpure-Gray 6', + PURPURE_GRAY_12: 'Purpure-Gray 12', + TABLEAU_10_LIGHT: 'Tableau 10 Light', + TABLEAU_10_MEDIUM: 'Tableau 10 Medium', + TABLEAU_10: 'Tableau 10', + TABLEAU_20: 'Tableau 20', + TRAFFIC_LIGHT: 'Traffic Light', +} as const; + +export const PALETTE_ID = { + AUTO_PALETTE_ID, + ...COMMON_PALETTE_ID, + ...TABLEAU_PALETTE_ID, +} as const; + +export type CommonPaletteId = (typeof COMMON_PALETTE_ID)[keyof typeof COMMON_PALETTE_ID]; +export type TableauPaletteId = (typeof TABLEAU_PALETTE_ID)[keyof typeof TABLEAU_PALETTE_ID]; +export type InternalPaletteId = CommonPaletteId; +export type GradientPaletteId = (typeof COMMON_PALETTE_ID)[keyof typeof GRADIENT_PALETTE_ID]; +export type BasePaletteId = CommonPaletteId | TableauPaletteId; + +export type Palette = { + id: string; + scheme: string[]; + gradient?: boolean; + datalens?: boolean; +}; + +export type GradientPalette = Palette & {gradient: true}; diff --git a/src/shared/constants/common.ts b/src/shared/constants/common.ts new file mode 100644 index 0000000..87cf6a1 --- /dev/null +++ b/src/shared/constants/common.ts @@ -0,0 +1,108 @@ +import {ACTION_PARAM_PREFIX} from '@gravity-ui/dashkit/helpers'; + +import {ControlType} from '../types/dash'; + +export enum AppInstallation { + Opensource = 'opensource', +} + +export enum AppEnvironment { + Production = 'production', + Preprod = 'preprod', + Development = 'development', + Staging = 'staging', + Prod = 'prod', + CI = 'ci', +} + +export enum AppMode { + Full = 'full', + Datalens = 'datalens', + Charts = 'charts', + Api = 'api', + PublicApi = 'public-api', +} + +export enum Language { + Ru = 'ru', + En = 'en', +} + +export enum DeviceType { + Phone = 'phone', + Tablet = 'tablet', + Desktop = 'desktop', +} + +export const DEFAULT_PAGE_SIZE = 1000; + +export const ENABLE = 'enable'; +export const DISABLE = 'disable'; + +export const FALLBACK_LANGUAGES = [Language.En]; + +export const USER_SETTINGS_KEY = 'userSettings'; + +export const URL_ACTION_PARAMS_PREFIX = ACTION_PARAM_PREFIX; + +export const DEFAULT_CHART_LINES_LIMIT = 100; + +export const DEFAULT_DATE_FORMAT = 'DD.MM.YYYY'; + +export const DEFAULT_TIME_FORMAT = '24'; + +export const TIME_FORMAT_12 = 'h:mm a'; + +export const TIME_FORMAT_24 = 'HH:mm'; + +export const UPDATE_STATE_DEBOUNCE_TIME = 1000; + +export const SCROLL_TITLE_DEBOUNCE_TIME = 400; + +export const SHARED_URL_OPTIONS = { + SAFE_CHART: '_safe_chart', + WITHOUT_UI_SANDBOX_LIMIT: '_without_sandbox_time_limit', +}; + +export const WIZARD_CHART_NODE = { + graph_wizard_node: 'statface_graph', + table_wizard_node: 'table', + metric_wizard_node: 'statface_metric', + markup_wizard_node: 'config', + d3_wizard_node: 'config', +}; + +export const QL_CHART_NODE = { + graph_sql_node: 'statface_graph', + graph_ql_node: 'statface_graph', + timeseries_ql_node: 'statface_graph', + table_sql_node: 'table', + table_ql_node: 'table', + markup_ql_node: 'config', + metric2_ql_node: 'statface_graph', + d3_ql_node: 'd3_ql_node', +}; + +export const EDITOR_CHART_NODE = { + graph_node: 'statface_graph', + graph_billing_node: 'statface_graph', + table_node: 'table', + text_node: 'statface_text', + metric_node: 'statface_metric', + metric_sql_node: 'statface_metric', + metric_ql_node: 'statface_metric', + map_node: 'statface_map', + markup_node: 'config', + markdown_node: '', + 'blank-chart_node': 'config', + 'advanced-chart_node': 'config', + d3_node: 'd3', +}; + +export const EDITOR_TYPE_CONFIG_TABS = { + ...WIZARD_CHART_NODE, + ...QL_CHART_NODE, + ...EDITOR_CHART_NODE, + module: '', + control_dash: ControlType.Dash, +}; diff --git a/src/shared/constants/connections.ts b/src/shared/constants/connections.ts new file mode 100644 index 0000000..6591aa7 --- /dev/null +++ b/src/shared/constants/connections.ts @@ -0,0 +1,110 @@ +// All the types contains here (in constants file) because of possible circular dependencies + +// Constants section +export enum ConnectorType { + /** Special value for connectors union. Used to render nested pages with their own connectors list */ + __Meta__ = '__meta__', + AppMetrica = 'appmetrica_api', + Bigquery = 'bigquery', + Bitrix = 'bitrix', + Bitrix24 = 'bitrix24', + ChBillingAnalytics = 'ch_billing_analytics', + ChFrozenBumpyRoads = 'ch_frozen_bumpy_roads', + ChFrozenCovid = 'ch_frozen_covid', + ChFrozenDemo = 'ch_frozen_demo', + ChFrozenDtp = 'ch_frozen_dtp', + ChFrozenGkh = 'ch_frozen_gkh', + ChFrozenHoreca = 'ch_frozen_horeca', + ChFrozenSamples = 'ch_frozen_samples', + ChFrozenTransparency = 'ch_frozen_transparency', + ChFrozenWeather = 'ch_frozen_weather', + ChGeoFiltered = 'ch_geo_filtered', + ChOverYt = 'ch_over_yt', + ChOverYtUserAuth = 'ch_over_yt_user_auth', + ChYaMusicPodcastStats = 'ch_ya_music_podcast_stats', + Chydb = 'chydb', + Clickhouse = 'clickhouse', + Csv = 'csv', + Equeo = 'equeo', + File = 'file', + Greenplum = 'greenplum', + Gsheets = 'gsheets', + GsheetsV2 = 'gsheets_v2', + KonturMarket = 'kontur_market', + MetrikaApi = 'metrika_api', + Moysklad = 'moysklad', + Mssql = 'mssql', + Mysql = 'mysql', + Oracle = 'oracle', + Postgres = 'postgres', + Snowflake = 'snowflake', + Trino = 'trino', + UsageAnalyticsDetailed = 'usage_analytics_detailed', + UsageAnalyticsLight = 'usage_analytics_light', + UsageTrackingYT = 'usage_tracking_ya_team', + Ydb = 'ydb', + Yt = 'yt', + Yq = 'yq', + Promql = 'promql', + Monitoring = 'solomon', + MonitoringExt = 'monitoring', + KpInterestIndex = 'kp_interest_index', + SchoolbookJournal = 'schoolbook_journal', + SmbHeatmaps = 'smb_heatmaps', + Chyt = 'chyt', + ChytNb = 'chyt_nb', + ChytNb_v2 = 'chyt_nb_userauth', + Extractor1c = 'extractor1c', + Yadocs = 'yadocs', + MonitoringV2 = 'monitoring_v2', + ApiConnector = 'json_api', +} + +export type ActualConnectorType = + | ConnectorType.ChOverYt + | ConnectorType.ChOverYtUserAuth + | ConnectorType.Gsheets + | ConnectorType.GsheetsV2 + | ConnectorType.File; + +export const enum ConnectionQueryTypeValues { + GenericQuery = 'generic_query', + GenericLabelValues = 'generic_label_values', +} + +export enum DbConnectMethod { + ServiceName = 'service_name', + Sid = 'sid', +} + +export enum EnforceCollate { + Auto = 'auto', + On = 'on', + Off = 'off', +} + +export enum CSVEncoding { + Utf8 = 'utf8', + Windows1251 = 'windows1251', + Utf8sig = 'utf8sig', +} + +export enum CSVDelimiter { + Comma = 'comma', + Semicolon = 'semicolon', + Tab = 'tab', +} + +export const RAW_SQL_LEVEL = { + OFF: 'off', + SUBSELECT: 'subselect', + TEMPLATE: 'template', + DASHSQL: 'dashsql', +} as const; + +export const CONNECTOR_VISIBILITY_MODE = { + FREE: 'free', + HIDDEN: 'hidden', + UNCREATABLE: 'uncreatable', + BUSINESS: 'business', +} as const; diff --git a/src/shared/constants/cookie.ts b/src/shared/constants/cookie.ts new file mode 100644 index 0000000..37af577 --- /dev/null +++ b/src/shared/constants/cookie.ts @@ -0,0 +1,2 @@ +export const SUPERUSER_SWITCH_MODE_COOKIE_NAME = 'dl_superuser_switch_mode'; +export const DISABLE_JSONFN_SWITCH_MODE_COOKIE_NAME = 'dl_disable_json_fn'; diff --git a/src/shared/constants/dash.ts b/src/shared/constants/dash.ts new file mode 100644 index 0000000..9c3664f --- /dev/null +++ b/src/shared/constants/dash.ts @@ -0,0 +1,59 @@ +import type {DashData} from '../types'; + +export enum DashMailingChartHeight { + Original = 'original', + '280px' = '280', + '320px' = '320', + '480px' = '480', + '640px' = '640', + '720px' = '720', +} + +export enum DashMailingChartWidth { + Full = 'full', + '600px' = '600', +} + +export enum Weekday { + Mon = 'mon', + Tue = 'tue', + Wed = 'wed', + Thu = 'thu', + Fri = 'fri', + Sat = 'sat', + Sun = 'sun', +} + +export const DASH_GRID_LAYOUT = { + ROW_HEIGHT: 18, + MARGIN: 8, + COLS: 36, +}; + +export const FOCUSED_WIDGET_PARAM_NAME = 'focus'; + +export const RESTRICTED_PARAM_NAMES = [ + 'tab', + 'state', + 'mode', + FOCUSED_WIDGET_PARAM_NAME, + 'grid', + 'scale', + 'tz', + 'timezone', + 'date', + 'datetime', + 'genericdatetime', +]; + +export const DASH_CURRENT_SCHEME_VERSION = 8; + +export const LOADED_DASH_CLASS = 'dash-body-loaded'; + +export const DASH_DATA_REQUIRED_FIELDS: Array = [ + 'salt', + 'counter', + 'schemeVersion', + 'tabs', + 'settings', +]; diff --git a/src/shared/constants/datasets.ts b/src/shared/constants/datasets.ts new file mode 100644 index 0000000..8df4b70 --- /dev/null +++ b/src/shared/constants/datasets.ts @@ -0,0 +1,4 @@ +export enum CompatibleAccessMode { + Direct = 'direct', + Materialized = 'materialized', +} diff --git a/src/shared/constants/datepicker/relative-datepicker.ts b/src/shared/constants/datepicker/relative-datepicker.ts new file mode 100644 index 0000000..1ca258e --- /dev/null +++ b/src/shared/constants/datepicker/relative-datepicker.ts @@ -0,0 +1,11 @@ +export const SCALES = { + y: 'years', + Q: 'quarters', + M: 'months', + w: 'weeks', + d: 'days', + h: 'hours', + m: 'minutes', + s: 'seconds', + ms: 'milliseconds', +}; diff --git a/src/shared/constants/entry.ts b/src/shared/constants/entry.ts new file mode 100644 index 0000000..347dc85 --- /dev/null +++ b/src/shared/constants/entry.ts @@ -0,0 +1,88 @@ +import {WizardType} from '../types'; + +import {ConnectorType} from './connections'; + +export const NAVIGATION_ROUTE = 'navigation'; +export const DASHBOARDS_ROUTE = 'dashboards'; +export const DC_DASHBOARDS_ROUTE = 'dash'; +export const WIZARD_ROUTE = 'wizard'; +export const SQL_ROUTE = 'sql'; +export const QL_ROUTE = 'ql'; +export const PREVIEW_ROUTE = 'preview'; +export const EDITOR_ROUTE = 'editor'; +export const DATASETS_ROUTE = 'datasets'; +export const CONNECTIONS_ROUTE = 'connections'; + +export const ENTRY_ROUTES = [ + NAVIGATION_ROUTE, + DASHBOARDS_ROUTE, + DC_DASHBOARDS_ROUTE, + WIZARD_ROUTE, + SQL_ROUTE, + QL_ROUTE, + PREVIEW_ROUTE, + EDITOR_ROUTE, + DATASETS_ROUTE, + CONNECTIONS_ROUTE, +]; + +export const ENTRY_ID_LENGTH = 13; +export const ENTRY_SLUG_SEPARATOR = '-'; +export const MAX_SLUG_LENGTH = 54; + +export const LEGACY_WIZARD_TYPE = { + GRAPH_WIZARD: 'graph_wizard', + METRIC_WIZARD: 'metric_wizard', +}; +export const WIZARD_TYPES: string[] = Object.values(WizardType); + +export const QL_TYPE = { + GRAPH_QL_NODE: 'graph_ql_node', + D3_QL_NODE: 'd3_ql_node', + TIMESERIES_QL_NODE: 'timeseries_ql_node', + TABLE_QL_NODE: 'table_ql_node', + YMAP_QL_NODE: 'ymap_ql_node', + METRIC_QL_NODE: 'metric2_ql_node', + MARKUP_QL_NODE: 'markup_ql_node', + LEGACY_GRAPH_QL_NODE: 'graph_sql_node', + LEGACY_TABLE_QL_NODE: 'table_sql_node', + LEGACY_YMAP_QL_NODE: 'ymap_sql_node', + LEGACY_METRIC_QL_NODE: 'metric2_sql_node', +}; +export const LEGACY_EDITOR_TYPE = { + GRAPH: 'graph', + TABLE: 'table', + MAP: 'map', + MANAGER: 'manager', + TEXT: 'text', + METRIC: 'metric', +} as const; + +export const EDITOR_TYPE = { + MODULE: 'module', + GRAPH_NODE: 'graph_node', + TABLE_NODE: 'table_node', + TEXT_NODE: 'text_node', + METRIC_NODE: 'metric_node', + MAP_NODE: 'map_node', + YMAP_NODE: 'ymap_node', + CONTROL_NODE: 'control_node', + MARKDOWN_NODE: 'markdown_node', + MARKUP_NODE: 'markup_node', + TIMESERIES_NODE: 'timeseries_node', + GRAVITY_CHARTS_NODE: 'd3_node', + ADVANCED_CHART_NODE: 'advanced-chart_node', + + // deprecated + BLANK_CHART_NODE: 'blank-chart_node', +} as const; + +export type EditorTypes = (typeof EDITOR_TYPE)[keyof typeof EDITOR_TYPE]; + +export const ENTRY_TYPES = { + legacyEditor: Object.values(LEGACY_EDITOR_TYPE), + editor: Object.values(EDITOR_TYPE), + wizard: WIZARD_TYPES, + ql: Object.values(QL_TYPE), + connection: Object.values(ConnectorType), +}; diff --git a/src/shared/constants/error-codes.ts b/src/shared/constants/error-codes.ts new file mode 100644 index 0000000..b9d097f --- /dev/null +++ b/src/shared/constants/error-codes.ts @@ -0,0 +1,64 @@ +// List of all error codes, if we need new - add it here. + +export enum ErrorCode { + SourceConfigTableNotConfigured = 'ERR.DS_API.SOURCE_CONFIG.TABLE_NOT_CONFIGURED', + DbCannotParseDatetime = 'ERR.DS_API.DB.CANNOT_PARSE.DATETIME', + DbCannotParseNumber = 'ERR.DS_API.DB.CANNOT_PARSE.NUMBER', + DbJoinColumnTypeMismatch = 'ERR.DS_API.DB.JOIN_COLUMN_TYPE_MISMATCH', + DbDataPreparationNotFinished = 'ERR.DS_API.DB.DATA_PREPARATION_NOT_FINISHED', + DbMaterizalizationNotFinished = 'ERR.DS_API.DB.MATERIALIZATION_NOT_FINISHED', + DbMemoryLimitExceeded = 'ERR.DS_API.DB.MEMORY_LIMIT_EXCEEDED', + ChytTableAccessDenied = 'ERR.DS_API.DB.CHYT.TABLE_ACCESS_DENIED', + ChytTableHasNoSchema = 'ERR.DS_API.DB.CHYT.TABLE_HAS_NO_SCHEMA', + ChytInvalidJoinMoreThanOneTable = 'ERR.DS_API.DB.CHYT.INVALID_SORTED_JOIN.MORE_THAN_ONE_TABLE', + ChytInvalidJoinNotKeyColumn = 'ERR.DS_API.DB.CHYT.INVALID_SORTED_JOIN.NOT_A_KEY_COLUMN', + ChytInvalidJoinNotKeyPrefixColumn = 'ERR.DS_API.DB.CHYT.INVALID_SORTED_JOIN.NOT_KEY_PREFIX_COLUMN', + ValidationAggDouble = 'ERR.DS_API.VALIDATION.AGG.DOUBLE', + ValidationAggInconsistent = 'ERR.DS_API.VALIDATION.AGG.INCONSISTENT', + ValidationWinFuncNoAgg = 'ERR.DS_API.VALIDATION.WIN_FUNC.NO_AGG', + DBError = 'ERR.DS_API.DB', + DBQueryError = 'ERR.DS_API.DB.INVALID_QUERY', + DBSourceDoesntExist = 'ERR.DS_API.DB.SOURCE_DOES_NOT_EXIST', + SourceTimeout = 'ERR.DS_API.DB.SOURCE_ERROR.TIMEOUT', + SourceInvalidResponse = 'ERR.DS_API.DB.SOURCE_ERROR.INVALID_RESPONSE', + SourceClosedPrematurely = 'ERR.DS_API.DB.SOURCE_ERROR.CLOSED_PREMATURELY', + UsAccessDenied = 'ERR.DS_API.US.ACCESS_DENIED', + WorkbookIsolationInterruptionDenied = 'ERR.DS_API.US.WORKBOOK_ISOLATION_INTERRUPTION', + ReferencedEntryAccessDenied = 'ERR.DS_API.REFERENCED_ENTRY_ACCESS_DENIED', + PlatformPermissionRequired = 'ERR.DS_API.PLATFORM_PERMISSION_REQUIRED', + NeedReset = 'NEED_RESET', + AuthNeedReset = 'AUTH.NEED_RESET', + AuthUserNotExists = 'AUTH.USER_NOT_EXISTS', + EntryIsLocked = 'ERR.US.ENTRY_IS_LOCKED', + EntryAlreadyExists = 'ERR.US.ENTRY_ALREADY_EXISTS', + UsUniqViolation = 'ERR.US.DB.UNIQUE_VIOLATION', + ReadOnlyMode = 'READ_ONLY_MODE_ENABLED', + InvalidTokenFormat = 'ERR.CHARTS.INVALID_TOKEN_FORMAT', + TokenNotFound = 'ERR.CHARTS.TOKEN_NOT_FOUND', + InvalidToken = 'ERR.CHARTS.INVALID_TOKEN', + OutdatedDependencies = 'ERR.CHARTS.OUTDATED_DEPENDENCIES', + EntryForbidden = 'ERR.US.ENTRY_FORBIDDEN', + + IncorrectEntry = 'INCORRECT_ENTRY_ID_FOR_EMBED', + IncorrectDepsIds = 'INCORRECT_DEPS_IDS_FOR_EMBED', + IncorrectEntryIdForEmbed = 'ERR.DS_API.US.BAD_REQUEST.INCORRECT_ENTRY_ID_FOR_EMBED', + + WorkbookAlreadyExists = 'WORKBOOK_ALREADY_EXISTS', + MetaManagerWorkbookAlreadyExists = 'META_MANAGER.WORKBOOK_ALREADY_EXISTS', + CollectionAlreadyExists = 'COLLECTION_ALREADY_EXISTS', +} + +export const ErrorContentTypes = { + NOT_FOUND: 'not-found', + NOT_FOUND_BY_RESOLVE_TENANT: 'not-found-by-resolve-tenant', + NO_ACCESS: 'no-access', + NO_ENTRY_ACCESS: 'no-entry-access', + ERROR: 'error', + CREDENTIALS: 'credentials', + AUTH_FAILED: 'auth-failed', + AUTH_DENIED: 'auth-denied', + INACCESSIBLE_ON_MOBILE: 'inaccessible-on-mobile', + NOT_AUTHENTICATED: 'not-authenticated', + FORBIDDEN_AUTH: 'forbidden-auth', + EMBEDDED_DASH_LIMIT_REACHED: 'embedded-dash-limit-reached', +}; diff --git a/src/shared/constants/error/index.ts b/src/shared/constants/error/index.ts new file mode 100644 index 0000000..f9c3641 --- /dev/null +++ b/src/shared/constants/error/index.ts @@ -0,0 +1,16 @@ +export class ServerError extends Error { + readonly status: number; + readonly code?: string; + readonly details?: unknown; + + constructor( + message: string, + {status, code, details}: {status: number; code?: string; details?: unknown}, + ) { + super(message); + this.name = 'ServerError'; + this.status = status; + this.code = code; + this.details = details; + } +} diff --git a/src/shared/constants/exports.ts b/src/shared/constants/exports.ts new file mode 100644 index 0000000..ae8bc06 --- /dev/null +++ b/src/shared/constants/exports.ts @@ -0,0 +1,18 @@ +import type {ValueOf} from '../types'; + +export const EXPORT_FORMATS = { + XLSX: 'xlsx', + CSV: 'csv', + MARKDOWN: 'markdown', + WIKI: 'wiki', + SCREENSHOT: 'screenshort', +} as const; + +export type ExportFormatsType = ValueOf; + +export type ExportParams = { + format: ExportFormatsType; + delValues?: string | null; + delNumbers?: string | null; + encoding?: string | null; +}; diff --git a/src/shared/constants/gradients/default.ts b/src/shared/constants/gradients/default.ts new file mode 100644 index 0000000..1cfbbe6 --- /dev/null +++ b/src/shared/constants/gradients/default.ts @@ -0,0 +1,8 @@ +import redOrangeGreenGradient from './three-point/red-orange-green'; +import blueGradient from './two-point/blue'; + +export const TWO_POINT_DEFAULT_ID = blueGradient.id; +export const TWO_POINT_DEFAULT_GRADIENT = {[TWO_POINT_DEFAULT_ID]: blueGradient}; + +export const THREE_POINT_DEFAULT_ID = redOrangeGreenGradient.id; +export const THREE_POINT_DEFAULT_GRADIENT = {[THREE_POINT_DEFAULT_ID]: redOrangeGreenGradient}; diff --git a/src/shared/constants/gradients/index.ts b/src/shared/constants/gradients/index.ts new file mode 100644 index 0000000..9ec8d6e --- /dev/null +++ b/src/shared/constants/gradients/index.ts @@ -0,0 +1,111 @@ +import type {ColorPalette} from '../../types/color-palettes'; +import type {ValueOf} from '../../types/utility-types'; + +import { + THREE_POINT_DEFAULT_GRADIENT, + THREE_POINT_DEFAULT_ID, + TWO_POINT_DEFAULT_GRADIENT, + TWO_POINT_DEFAULT_ID, +} from './default'; +import {THREE_POINT_GRADIENTS, THREE_POINT_GRADIENT_PALETTES} from './three-point-gradients'; +import {TWO_POINT_GRADIENTS, TWO_POINT_GRADIENT_PALETTES} from './two-point-gradients'; + +export {TWO_POINT_DEFAULT_ID, THREE_POINT_DEFAULT_ID}; + +export enum GradientType { + TWO_POINT = '2-point', + THREE_POINT = '3-point', +} + +export enum ColorMode { + PALETTE = 'palette', + GRADIENT = 'gradient', +} + +export interface Gradient { + id: string; + title?: string; + colors: string[]; +} + +export const GradientNullModes = { + Ignore: 'ignore', + AsZero: 'as-0', +}; + +export type GradientNullMode = ValueOf; + +export interface RGBColor { + red: number; + green: number; + blue: number; +} + +export interface RGBGradient extends Omit { + colors: RGBColor[]; +} + +type Gradients = { + [key in GradientType]: T; +}; + +export type GradientPalettes = Record; +export type RGBGradientPalettes = Record; + +const GRADIENTS: Gradients = { + [GradientType.TWO_POINT]: TWO_POINT_GRADIENTS, + [GradientType.THREE_POINT]: THREE_POINT_GRADIENTS, +}; + +export const selectAvailableGradients = (gradientType: GradientType) => GRADIENTS[gradientType]; + +export const selectAvailableGradientsColors = ( + gradientType: GradientType, + gradientId: string, +): string[] => { + const colors = GRADIENTS[gradientType]?.[gradientId]?.colors; + + if (colors) { + return colors; + } + + if (gradientType === GradientType.TWO_POINT) { + return TWO_POINT_DEFAULT_GRADIENT[TWO_POINT_DEFAULT_ID].colors; + } + + return THREE_POINT_DEFAULT_GRADIENT[THREE_POINT_DEFAULT_ID].colors; +}; + +export const selectCurrentRGBGradient = ( + gradientType: GradientType, + gradientId: string, + loadedColorPalettes: Record, +) => { + if (loadedColorPalettes[gradientId]) { + return loadedColorPalettes[gradientId]; + } else { + return GRADIENTS[gradientType][gradientId]; + } +}; + +export const selectGradient = (gradientType: GradientType, gradientId: string) => + gradientType === GradientType.TWO_POINT + ? TWO_POINT_DEFAULT_GRADIENT[gradientId].id + : THREE_POINT_DEFAULT_GRADIENT[gradientId].id; + +export function transformHexToRgb(color: string): RGBColor { + const red = parseInt(color.slice(1, 3), 16); + const green = parseInt(color.slice(3, 5), 16); + const blue = parseInt(color.slice(5, 7), 16); + + return {red, green, blue}; +} + +export const GRADIENT_PALETTES = { + ...TWO_POINT_GRADIENT_PALETTES, + ...THREE_POINT_GRADIENT_PALETTES, +}; + +export const isSystemGradientPaletteId = (value: string) => { + return Boolean(TWO_POINT_GRADIENTS[value] || THREE_POINT_GRADIENTS[value]); +}; diff --git a/src/shared/constants/gradients/three-point-gradients.ts b/src/shared/constants/gradients/three-point-gradients.ts new file mode 100644 index 0000000..c29a0a7 --- /dev/null +++ b/src/shared/constants/gradients/three-point-gradients.ts @@ -0,0 +1,36 @@ +import blueGrayRedGradient from './three-point/blue-gray-red'; +import blueYellowRedGradient from './three-point/blue-yellow-red'; +import orangeBlueGreenGradient from './three-point/orange-blue-green'; +import orangeGrayBlueGradient from './three-point/orange-gray-blue'; +import orangeVioletBlueGradient from './three-point/orange-violet-blue'; +import pinkGrayGreenGradient from './three-point/pink-gray-green'; +import redOrangeGreenGradient from './three-point/red-orange-green'; +import trafficLightGradient from './three-point/traffic-light'; +import violetBlueGreenGradient from './three-point/violet-blue-green'; +import yellowGreenBlueGradient from './three-point/yellow-green-blue'; + +export const THREE_POINT_GRADIENTS = { + [blueGrayRedGradient.id]: blueGrayRedGradient, + [blueYellowRedGradient.id]: blueYellowRedGradient, + [orangeBlueGreenGradient.id]: orangeBlueGreenGradient, + [orangeGrayBlueGradient.id]: orangeGrayBlueGradient, + [orangeVioletBlueGradient.id]: orangeVioletBlueGradient, + [pinkGrayGreenGradient.id]: pinkGrayGreenGradient, + [redOrangeGreenGradient.id]: redOrangeGreenGradient, + [violetBlueGreenGradient.id]: violetBlueGreenGradient, + [yellowGreenBlueGradient.id]: yellowGreenBlueGradient, + [trafficLightGradient.id]: trafficLightGradient, +}; + +export const THREE_POINT_GRADIENT_PALETTES = { + blueGrayRedGradient, + blueYellowRedGradient, + orangeGrayBlueGradient, + orangeBlueGreenGradient, + orangeVioletBlueGradient, + pinkGrayGreenGradient, + redOrangeGreenGradient, + violetBlueGreenGradient, + yellowGreenBlueGradient, + trafficLightGradient, +}; diff --git a/src/shared/constants/gradients/three-point/blue-gray-red.ts b/src/shared/constants/gradients/three-point/blue-gray-red.ts new file mode 100644 index 0000000..a17f36b --- /dev/null +++ b/src/shared/constants/gradients/three-point/blue-gray-red.ts @@ -0,0 +1,4 @@ +export default { + id: 'blue-gray-red', + colors: ['#0044A3', '#D8D8D8', '#BA2534'], +}; diff --git a/src/shared/constants/gradients/three-point/blue-yellow-red.ts b/src/shared/constants/gradients/three-point/blue-yellow-red.ts new file mode 100644 index 0000000..423b7d1 --- /dev/null +++ b/src/shared/constants/gradients/three-point/blue-yellow-red.ts @@ -0,0 +1,4 @@ +export default { + id: 'blue-yellow-red', + colors: ['#1B6DD1', '#FAAF1A', '#F23630'], +}; diff --git a/src/shared/constants/gradients/three-point/orange-blue-green.ts b/src/shared/constants/gradients/three-point/orange-blue-green.ts new file mode 100644 index 0000000..003fbec --- /dev/null +++ b/src/shared/constants/gradients/three-point/orange-blue-green.ts @@ -0,0 +1,4 @@ +export default { + id: 'orange-blue-green', + colors: ['#FC7E6A', '#8CCBFF', '#A5ED8E'], +}; diff --git a/src/shared/constants/gradients/three-point/orange-gray-blue.ts b/src/shared/constants/gradients/three-point/orange-gray-blue.ts new file mode 100644 index 0000000..c84d9b8 --- /dev/null +++ b/src/shared/constants/gradients/three-point/orange-gray-blue.ts @@ -0,0 +1,4 @@ +export default { + id: 'orange-gray-blue', + colors: ['#FF9580', '#D8D8D8', '#8CCBFF'], +}; diff --git a/src/shared/constants/gradients/three-point/orange-violet-blue.ts b/src/shared/constants/gradients/three-point/orange-violet-blue.ts new file mode 100644 index 0000000..cf45ac6 --- /dev/null +++ b/src/shared/constants/gradients/three-point/orange-violet-blue.ts @@ -0,0 +1,4 @@ +export default { + id: 'orange-violet-blue', + colors: ['#FC7E6A', '#6B32C9', '#8CCBFF'], +}; diff --git a/src/shared/constants/gradients/three-point/pink-gray-green.ts b/src/shared/constants/gradients/three-point/pink-gray-green.ts new file mode 100644 index 0000000..0056699 --- /dev/null +++ b/src/shared/constants/gradients/three-point/pink-gray-green.ts @@ -0,0 +1,4 @@ +export default { + id: 'pink-gray-green', + colors: ['#D0A3FF', '#D8D8D8', '#8EEDCB'], +}; diff --git a/src/shared/constants/gradients/three-point/red-orange-green.ts b/src/shared/constants/gradients/three-point/red-orange-green.ts new file mode 100644 index 0000000..068a3b6 --- /dev/null +++ b/src/shared/constants/gradients/three-point/red-orange-green.ts @@ -0,0 +1,4 @@ +export default { + id: 'red-orange-green', + colors: ['#FF3D64', '#FFC636', '#54A520'], +}; diff --git a/src/shared/constants/gradients/three-point/traffic-light.ts b/src/shared/constants/gradients/three-point/traffic-light.ts new file mode 100644 index 0000000..d457a60 --- /dev/null +++ b/src/shared/constants/gradients/three-point/traffic-light.ts @@ -0,0 +1,4 @@ +export default { + id: 'traffic-light', + colors: ['#9C0125', '#FDCE34', '#228782'], +}; diff --git a/src/shared/constants/gradients/three-point/violet-blue-green.ts b/src/shared/constants/gradients/three-point/violet-blue-green.ts new file mode 100644 index 0000000..1dc88e9 --- /dev/null +++ b/src/shared/constants/gradients/three-point/violet-blue-green.ts @@ -0,0 +1,4 @@ +export default { + id: 'violet-blue-green', + colors: ['#6B32C9', '#57ABFF', '#79E0BF'], +}; diff --git a/src/shared/constants/gradients/three-point/yellow-green-blue.ts b/src/shared/constants/gradients/three-point/yellow-green-blue.ts new file mode 100644 index 0000000..ce45b44 --- /dev/null +++ b/src/shared/constants/gradients/three-point/yellow-green-blue.ts @@ -0,0 +1,4 @@ +export default { + id: 'yellow-green-blue', + colors: ['#FFC533', '#64D1B4', '#69B6FF'], +}; diff --git a/src/shared/constants/gradients/two-point-gradients.ts b/src/shared/constants/gradients/two-point-gradients.ts new file mode 100644 index 0000000..e64a88e --- /dev/null +++ b/src/shared/constants/gradients/two-point-gradients.ts @@ -0,0 +1,45 @@ +import blueGradient from './two-point/blue'; +import cyanGradient from './two-point/cyan'; +import goldenGradient from './two-point/golden'; +import grayGradient from './two-point/gray'; +import greenGradient from './two-point/green'; +import greenBlueGradient from './two-point/green-blue'; +import oceanicGradient from './two-point/oceanic'; +import orangeYellowGradient from './two-point/orange-yellow'; +import redGradient from './two-point/red'; +import redBlueGradient from './two-point/red-blue'; +import violetGradient from './two-point/violet'; +import violetOrangeGradient from './two-point/violet-orange'; +import yellowGradient from './two-point/yellow'; + +export const TWO_POINT_GRADIENTS = { + [redGradient.id]: redGradient, + [greenGradient.id]: greenGradient, + [blueGradient.id]: blueGradient, + [violetGradient.id]: violetGradient, + [yellowGradient.id]: yellowGradient, + [cyanGradient.id]: cyanGradient, + [grayGradient.id]: grayGradient, + [goldenGradient.id]: goldenGradient, + [oceanicGradient.id]: oceanicGradient, + [violetOrangeGradient.id]: violetOrangeGradient, + [orangeYellowGradient.id]: orangeYellowGradient, + [redBlueGradient.id]: redBlueGradient, + [greenBlueGradient.id]: greenBlueGradient, +}; + +export const TWO_POINT_GRADIENT_PALETTES = { + redGradient, + grayGradient, + blueGradient, + cyanGradient, + violetGradient, + yellowGradient, + violetOrangeGradient, + orangeYellowGradient, + redBlueGradient, + greenGradient, + greenBlueGradient, + goldenGradient, + oceanicGradient, +}; diff --git a/src/shared/constants/gradients/two-point/blue.ts b/src/shared/constants/gradients/two-point/blue.ts new file mode 100644 index 0000000..a1da9d8 --- /dev/null +++ b/src/shared/constants/gradients/two-point/blue.ts @@ -0,0 +1,4 @@ +export default { + id: 'blue', + colors: ['#0044A3', '#8CCBFF'], +}; diff --git a/src/shared/constants/gradients/two-point/cyan.ts b/src/shared/constants/gradients/two-point/cyan.ts new file mode 100644 index 0000000..cf141b7 --- /dev/null +++ b/src/shared/constants/gradients/two-point/cyan.ts @@ -0,0 +1,4 @@ +export default { + id: 'cyan', + colors: ['#1C666E', '#C2EEBA'], +}; diff --git a/src/shared/constants/gradients/two-point/golden.ts b/src/shared/constants/gradients/two-point/golden.ts new file mode 100644 index 0000000..9ebda21 --- /dev/null +++ b/src/shared/constants/gradients/two-point/golden.ts @@ -0,0 +1,4 @@ +export default { + id: 'golden', + colors: ['#880132', '#FDEC4D'], +}; diff --git a/src/shared/constants/gradients/two-point/gray.ts b/src/shared/constants/gradients/two-point/gray.ts new file mode 100644 index 0000000..e578c5d --- /dev/null +++ b/src/shared/constants/gradients/two-point/gray.ts @@ -0,0 +1,4 @@ +export default { + id: 'gray', + colors: ['#EAEAEA', '#595959'], +}; diff --git a/src/shared/constants/gradients/two-point/green-blue.ts b/src/shared/constants/gradients/two-point/green-blue.ts new file mode 100644 index 0000000..9578569 --- /dev/null +++ b/src/shared/constants/gradients/two-point/green-blue.ts @@ -0,0 +1,4 @@ +export default { + id: 'green-blue', + colors: ['#A5ED8E', '#0044A3'], +}; diff --git a/src/shared/constants/gradients/two-point/green.ts b/src/shared/constants/gradients/two-point/green.ts new file mode 100644 index 0000000..8641208 --- /dev/null +++ b/src/shared/constants/gradients/two-point/green.ts @@ -0,0 +1,4 @@ +export default { + id: 'green', + colors: ['#0E752E', '#A5ED8E'], +}; diff --git a/src/shared/constants/gradients/two-point/oceanic.ts b/src/shared/constants/gradients/two-point/oceanic.ts new file mode 100644 index 0000000..0242668 --- /dev/null +++ b/src/shared/constants/gradients/two-point/oceanic.ts @@ -0,0 +1,4 @@ +export default { + id: 'oceanic', + colors: ['#050189', '#AEF8FE'], +}; diff --git a/src/shared/constants/gradients/two-point/orange-yellow.ts b/src/shared/constants/gradients/two-point/orange-yellow.ts new file mode 100644 index 0000000..ca17388 --- /dev/null +++ b/src/shared/constants/gradients/two-point/orange-yellow.ts @@ -0,0 +1,4 @@ +export default { + id: 'orange-yellow', + colors: ['#FA5146', '#FFE366'], +}; diff --git a/src/shared/constants/gradients/two-point/red-blue.ts b/src/shared/constants/gradients/two-point/red-blue.ts new file mode 100644 index 0000000..5fad8e3 --- /dev/null +++ b/src/shared/constants/gradients/two-point/red-blue.ts @@ -0,0 +1,4 @@ +export default { + id: 'red-blue', + colors: ['#BA2534', '#1B6DD1'], +}; diff --git a/src/shared/constants/gradients/two-point/red.ts b/src/shared/constants/gradients/two-point/red.ts new file mode 100644 index 0000000..8750804 --- /dev/null +++ b/src/shared/constants/gradients/two-point/red.ts @@ -0,0 +1,4 @@ +export default { + id: 'red', + colors: ['#BA2534', '#FF9580'], +}; diff --git a/src/shared/constants/gradients/two-point/violet-orange.ts b/src/shared/constants/gradients/two-point/violet-orange.ts new file mode 100644 index 0000000..2fb2705 --- /dev/null +++ b/src/shared/constants/gradients/two-point/violet-orange.ts @@ -0,0 +1,4 @@ +export default { + id: 'violet-orange', + colors: ['#6B32C9', '#FF9580'], +}; diff --git a/src/shared/constants/gradients/two-point/violet.ts b/src/shared/constants/gradients/two-point/violet.ts new file mode 100644 index 0000000..4695af5 --- /dev/null +++ b/src/shared/constants/gradients/two-point/violet.ts @@ -0,0 +1,4 @@ +export default { + id: 'violet', + colors: ['#6B32C9', '#D0A3FF'], +}; diff --git a/src/shared/constants/gradients/two-point/yellow.ts b/src/shared/constants/gradients/two-point/yellow.ts new file mode 100644 index 0000000..44c8fbc --- /dev/null +++ b/src/shared/constants/gradients/two-point/yellow.ts @@ -0,0 +1,4 @@ +export default { + id: 'yellow', + colors: ['#944710', '#FFE366'], +}; diff --git a/src/shared/constants/header.ts b/src/shared/constants/header.ts new file mode 100644 index 0000000..fe50716 --- /dev/null +++ b/src/shared/constants/header.ts @@ -0,0 +1,54 @@ +export enum AuthHeader { + Cookie = 'cookie', + Authorization = 'authorization', +} + +export enum SuperuserHeader { + XDlAllowSuperuser = 'x-dl-allow-superuser', + XDlSudo = 'x-dl-sudo', +} + +// header for extending response with specific data for ui or backend +// example: add authorship for ui +export enum DlComponentHeader { + UI = 'ui', + Backend = 'backend', +} + +export const REQUEST_SOURCE_HEADER = 'x-dl-request-source'; +export enum RequestSourceHeaderValue { + // Indicates that the request was made via the public API + PublicApi = 'public-api', +} + +export const AUDIT_MODE_HEADER = 'x-dl-audit-mode'; +export enum AuditModeHeaderValue { + True = 'true', +} + +export const REQUEST_ID_HEADER = 'x-request-id'; +export const TRACE_ID_HEADER = 'x-trace-id'; +export const SERVER_TRACE_ID_HEADER = 'x-server-trace-id'; +export const US_MASTER_TOKEN_HEADER = 'x-us-master-token'; +export const US_DYNAMIC_MASTER_TOKEN_HEADER = 'x-us-dynamic-master-token'; +export const DL_CONTEXT_HEADER = 'x-dl-context'; +export const FORWARDED_FOR_HEADER = 'x-forwarded-for'; +export const ACCEPT_LANGUAGE_HEADER = 'accept-language'; +export const TIMEZONE_OFFSET_HEADER = 'x-timezone-offset'; +export const US_PUBLIC_API_TOKEN_HEADER = 'x-us-public-api-token'; +export const CSRF_TOKEN_HEADER = 'x-csrf-token'; +export const DASH_INFO_HEADER = 'x-dash-info'; +export const DISPLAY_MODE_HEADER = 'x-dl-display-mode'; +export const DL_COMPONENT_HEADER = 'x-dl-component'; +export const DL_EMBED_TOKEN_HEADER = 'x-dl-embed-token'; +export const TENANT_ID_HEADER = 'x-dl-tenantid'; +export const WORKBOOK_ID_HEADER = 'x-dl-workbookid'; +export const DATASET_ID_HEADER = 'x-dl-datasetid'; +export const PROJECT_ID_HEADER = 'x-dc-projectid'; +export const CSP_HEADER = 'content-security-policy'; +export const CSP_REPORT_TO_HEADER = 'report-to'; +export const SET_COOKIE_HEADER = 'set-cookie'; + +export const BACKOFFICE_TENANT_ID_HEADER = 'tenant-id'; + +export const SCR_USER_AGENT_HEADER_VALUE = 'StatScreenshooter'; diff --git a/src/shared/constants/iam/index.ts b/src/shared/constants/iam/index.ts new file mode 100644 index 0000000..8810dd3 --- /dev/null +++ b/src/shared/constants/iam/index.ts @@ -0,0 +1,5 @@ +export enum AccessServiceResourceType { + Collection = 'datalens.collection', + Workbook = 'datalens.workbook', + SharedEntry = 'datalens.sharedEntry', +} diff --git a/src/shared/constants/index.ts b/src/shared/constants/index.ts new file mode 100644 index 0000000..fb888bc --- /dev/null +++ b/src/shared/constants/index.ts @@ -0,0 +1,35 @@ +export * from './common'; +export * from './collections'; +export * from './connections'; +export * from './datasets'; +export * from './dash'; +export * from './ql'; +export * from './entry'; +export * from './exports'; +export * from './old-schema'; +export * from './header'; +export * from './navigation'; +export * from './timeout'; +export * from './error-codes'; +export * from './colors'; +export * from './gradients'; +export * from './shapes'; +export * from './qa'; +export * from './wizard'; +export * from './cookie'; +export * from './yfm'; +export * from './url'; +export * from './visualization'; +export * from './placeholder'; +export * from './chartkit-handlers'; +export * from './user'; +export * from './api-v2'; +export * from './ui-sandbox'; +export * from './themes'; +export * from './markdown'; +export * from './markup'; +export * from './print'; +export * from './charts'; +export * from './chart-modeling'; +export * from './widgets'; +export * from './operations'; diff --git a/src/shared/constants/markdown.ts b/src/shared/constants/markdown.ts new file mode 100644 index 0000000..502272e --- /dev/null +++ b/src/shared/constants/markdown.ts @@ -0,0 +1 @@ +export const WRAPPED_MARKDOWN_KEY = '__wrappedMd__'; diff --git a/src/shared/constants/markup.ts b/src/shared/constants/markup.ts new file mode 100644 index 0000000..be117aa --- /dev/null +++ b/src/shared/constants/markup.ts @@ -0,0 +1 @@ +export const WRAPPED_MARKUP_KEY = '__wrappedMarkup__'; diff --git a/src/shared/constants/meta-manager/index.ts b/src/shared/constants/meta-manager/index.ts new file mode 100644 index 0000000..c2348e1 --- /dev/null +++ b/src/shared/constants/meta-manager/index.ts @@ -0,0 +1,11 @@ +export const PROCESS_STATUS = { + PENDING: 'pending', + SUCCESS: 'success', + ERROR: 'error', +} as const; + +export const NOTIFICATION_LEVEL = { + INFO: 'info', + WARNING: 'warning', + CRITICAL: 'critical', +} as const; diff --git a/src/shared/constants/navigation.ts b/src/shared/constants/navigation.ts new file mode 100644 index 0000000..d7cf52b --- /dev/null +++ b/src/shared/constants/navigation.ts @@ -0,0 +1,21 @@ +export const PLACE = { + ROOT: 'navigation', + FAVORITES: 'favorites', + DASHBOARDS: 'dashboards', + DATASETS: 'datasets', + WIDGETS: 'widgets', + CONNECTIONS: 'connections', + CLUSTERS: 'clusters', + REPORTS: 'reports', +} as const; + +export const MAP_PLACE_TO_SCOPE = { + [PLACE.ROOT]: 'folder', + [PLACE.FAVORITES]: 'folder', + [PLACE.DASHBOARDS]: 'dash', + [PLACE.DATASETS]: 'dataset', + [PLACE.WIDGETS]: 'widget', + [PLACE.CONNECTIONS]: 'connection', + [PLACE.CLUSTERS]: 'clusters', + [PLACE.REPORTS]: 'report', +} as const; diff --git a/src/shared/constants/old-schema.ts b/src/shared/constants/old-schema.ts new file mode 100644 index 0000000..0e8aaab --- /dev/null +++ b/src/shared/constants/old-schema.ts @@ -0,0 +1,5 @@ +// a copy of server/constants specifically for schema + +export const DASH_API_BASE_URL = '/api/dash/v1/dashboards'; +export const PUBLIC_API_DASH_API_BASE_URL = '/private/api/dash/v1/dashboards'; +export const CHARTS_API_BASE_URL = '/api/charts/v1/charts'; diff --git a/src/shared/constants/operations.ts b/src/shared/constants/operations.ts new file mode 100644 index 0000000..c739e6e --- /dev/null +++ b/src/shared/constants/operations.ts @@ -0,0 +1,9 @@ +export const DATALENS_OPERATION = { + CREATE_COLLECTION: 'createCollection', + CREATE_WORKBOOK: 'createWorkbook', + CREATE_SHARED_ENTRY: 'createSharedEntry', + COPY_WORKBOOK: 'copyWorkbook', + UPDATE_COLLECTION_ACCESS_BINDINGS: 'updateCollectionAccessBindings', + UPDATE_WORKBOOK_ACCESS_BINDINGS: 'updateWorkbookAccessBindings', + UPDATE_SHARED_ENTRY_ACCESS_BINDINGS: 'updateSharedEntryAccessBindings', +} as const; diff --git a/src/shared/constants/placeholder.ts b/src/shared/constants/placeholder.ts new file mode 100644 index 0000000..89c774b --- /dev/null +++ b/src/shared/constants/placeholder.ts @@ -0,0 +1,28 @@ +export enum PlaceholderId { + X = 'x', + Y = 'y', + Y2 = 'y2', + FlatTableColumns = 'flat-table-columns', + Geopoint = 'geopoint', + Geopoligon = 'geopolygon', + Measures = 'measures', + Size = 'size', + Points = 'points', + Dimensions = 'dimensions', + PivotTableColumns = 'pivot-table-columns', + PivotTableRows = 'rows', + Polyline = 'polyline', + Grouping = 'grouping', + Heatmap = 'heatmap', + Colors = 'colors', + DashboardFilters = 'dashboard-filters', + DashboardParameters = 'dashboard-parameters', + Filters = 'filters', + Labels = 'labels', + LayerFilters = 'layer-filters', + Segments = 'segments', + Shapes = 'shapes', + Sort = 'sort', + Tooltips = 'tooltips', + Available = 'available', +} diff --git a/src/shared/constants/print.ts b/src/shared/constants/print.ts new file mode 100644 index 0000000..dd7eab5 --- /dev/null +++ b/src/shared/constants/print.ts @@ -0,0 +1,2 @@ +export const PRINT_HIDDEN_ATTR = 'data-print-hidden'; +export const EXPORT_PRINT_HIDDEN_ATTR = 'data-export-print-hidden'; diff --git a/src/shared/constants/qa/action-panel.ts b/src/shared/constants/qa/action-panel.ts new file mode 100644 index 0000000..8d296ba --- /dev/null +++ b/src/shared/constants/qa/action-panel.ts @@ -0,0 +1,16 @@ +export const enum ActionPanelEntryContextMenuQa { + Menu = 'action-panel-entry-context-menu', + Revisions = 'action-panel-entry-context-menu-item-revisions', + Copy = 'action-panel-entry-context-menu-item-copy', + Remove = 'action-panel-entry-context-menu-item-remove', + TableOfContent = 'action-panel-entry-context-menu-item-table-of-content', + RelatedEntities = 'action-panel-entry-context-menu-related-entities', +} + +export const enum ActionPanelDashSaveControlsQa { + SaveAsDraftDropdownItem = 'action-panel-save-as-draft-dropdown-item', + SaveAndPublishDropdownItem = 'action-panel-save-and-publish-dropdown-item', + SaveAsNewDropdownItem = 'action-panel-save-as-new-dropdown-item', + Save = 'action-panel-button-save', + SaveAsDraft = 'action-panel-button-save-as-draft', +} diff --git a/src/shared/constants/qa/chart.ts b/src/shared/constants/qa/chart.ts new file mode 100644 index 0000000..af17b66 --- /dev/null +++ b/src/shared/constants/qa/chart.ts @@ -0,0 +1,62 @@ +export const ChartkitMenuDialogsQA = { + chartMenuDropDown: 'chart-dropdown-menu', + chartMenuDropDownSwitcher: 'chart-dropdown-switcher', + chartMenuShareModalBody: 'chart-share-modal-body', + chartMenuExportCsvDialog: 'chart-export-csv-dialog', + chartMenuExportCsvSelectDelimiter: 'chart-export-select-delimiter', + chartMenuExportCsvSelectFloat: 'chart-export-select-float', + chartMenuExportCsvSelectCharset: 'chart-export-select-charset', + chartMenuExportCsvDecimalDelimiterDot: 'decimal-delimiter-option-dot', + chartMenuExportCsvDecimalDelimiterComma: 'decimal-delimiter-option-comma', + chartMenuExportCsvValueDelimiterSpace: 'value-delimiter-option-space', + chartMenuExportCsvValueDelimiterSemicolon: 'value-delimiter-option-semicolon', + chartMenuExportCsvValueDelimiterTab: 'value-delimiter-option-tab', + chartMenuExportCsvValueDelimiterComma: 'value-delimiter-option-comma', + chartMenuExportModalApplyBtn: 'chart-export-btn-apply', + chartMenuInspectorRenderTime: 'chart-inspector-render-time', + chartMenuInspectorNotEmpty: 'chart-inspector-not-empty', + unsupportedAlertMsg: 'unsupported-alert-msg', + chartMetricsYAxis: 'chart-metrics-yaxis', + chartMetricsSeriesSelect: 'chart-metrics-series-select', + chartAlertValInput: 'chart-alert-value-input', + chartError: 'chartkit-error', + chartPreview: 'chart-preview', + errorButtonRetry: 'chartkit-error-actions', + errorButtonDetails: 'chartkit-error-details', + widgetTab: 'widget-chart-tab', + chartWidget: 'chart-widget', +}; + +export const enum ChartKitQa { + Loader = 'chartkit-loader', + DrillBreadcrumbs = 'chartkit-drillbreadcrumbs', + DrillBreadcrumbsItem = 'chartkit-drillbreadcrumbs-item', + Widget = 'chart-widget', + Tooltip = 'chartkit-tooltip', + Error = 'chartkit-error', +} + +export const enum ChartQa { + Chart = 'chartkit-chart', + CommentLabel = 'chartkit-comment', +} + +export const enum ChartKitTreeNodeStateQa { + Opened = 'chartkit-tree-node-state-opened', + Closed = 'chartkit-tree-node-state-closed', +} + +export const enum ChartKitTableQa { + Widget = 'chartkit-table-widget', + HeadCellContent = 'chartkit-table-header-cell', + CellContent = 'chartkit-cell-content', + Paginator = 'chartkit-table-paginator', + PaginatorPageInput = 'chartkit-table-paginator-page-input', + PaginatorRange = 'chartkit-table-paginator-range', + PaginatorPrevPageButton = 'chartkit-table-paginator-prev-btn', + PaginatorNextPageButton = 'chartkit-table-paginator-next-btn', +} + +export const enum ChartKitQlPreviewTableQa { + Widget = 'chartkit-preview-table-widget', +} diff --git a/src/shared/constants/qa/collections.ts b/src/shared/constants/qa/collections.ts new file mode 100644 index 0000000..487adcd --- /dev/null +++ b/src/shared/constants/qa/collections.ts @@ -0,0 +1,29 @@ +export const CollectionActionsQa = { + CreateActionBtn: 'coll-actions-create-btn', + CreateWorkbookMenuItem: 'coll-actions-create-workbook-menu-item', + SharedObjectsMenuItem: 'coll-actions-shared-objects-menu-item', + SharedConnectionCreateBtn: 'coll-actions-shared-conn-create-btn', + SharedDatasetCreateBtn: 'coll-actions-shared-dataset-create-btn', +} as const; + +export const CollectionTableRowQa = { + CollectionRowDropdownMenuBtn: 'coll-content-table-dropdown-menu-btn', + CollectionDropdownMenuDeleteBtn: 'coll-content-table-dropdown-menu-delete-btn', +} as const; + +export const enum CollectionContentTableQa { + Table = 'coll-content-table', + CollectionLinkRow = 'coll-content-table-coll-link-row', + WorkbookLinkRow = 'coll-content-table-workbook-link-row', + EntryLinkRow = 'coll-content-table-entry-link-row', + CollectionTitleCell = 'coll-content-table-coll-title-cell', +} + +export const CollectionFiltersQa = { + SearchInput: 'coll-filters-search-input', +} as const; + +export const enum DialogCollectionStructureQa { + ApplyButton = 'coll-structure-dialog-apply-button', + ListItem = 'coll-structure-dialog-list-item', +} diff --git a/src/shared/constants/qa/components.ts b/src/shared/constants/qa/components.ts new file mode 100644 index 0000000..ff846bf --- /dev/null +++ b/src/shared/constants/qa/components.ts @@ -0,0 +1,234 @@ +export enum ActionPanelQA { + MoreBtn = 'entry-panel-more-btn', + ActionPanel = 'entry-action-panel', + EntryBreadcrumbs = 'entry-breadcrumbs', +} + +export enum RevisionsPanelQa { + RevisionsPanel = 'revisions-top-panel', + DraftVersion = 'revisions-top-panel-draft-version', + NotActualVersion = 'revisions-top-panel-not-actual-version', + HasDraft = 'revisions-top-panel-has-draft', + ButtonOpenActual = 'revisions-button-open-actual', + ButtonMakeActual = 'revisions-button-make-actual', +} + +export enum RevisionsListQa { + ExpandablePanel = 'expandable-panel', + ExpandablePanelButtonClose = 'expandable-panel-close-btn', + List = 'revisions-list', + RevisionsListRow = 'revisions-list-row', + RevisionsListRowDraft = 'revisions-list-row-draft', + RevisionsListRowActual = 'revisions-list-row-actual', + RevisionsListRowNotActual = 'revisions-list-row-not-actual', +} + +export enum SaveChartControlsQa { + SaveButton = 'action-panel-save-btn', +} + +export const DialogRelatedEntitiesQA = { + Dialog: 'dialog-related-entities', +} as const; + +export enum DialogParameterQA { + Apply = 'dialog-parameter-apply-btn', + Cancel = 'dialog-parameter-cancel-btn', + Reset = 'dialog-parameter-reset-btn', + DefaultValueInput = 'dialog-parameter-default-value-input', + DefaultValueRadioGroup = 'dialog-parameter-default-value-radio-group', + NameInput = 'dialog-parameter-name-input', + TypeSelector = 'dialog-parameter-type_selector', + Dialog = 'dialog-parameter', +} + +export enum AddFieldQA { + Option = 'add-field-option', + AddFieldButton = 'add-field-button', + MeasureFieldIcon = 'add-measure-field-icon', + DimensionsFieldIcon = 'add-dimension-field-icon', +} + +export enum DialogTabsQA { + Dialog = 'dialog-tabs', + PopupWidgetOrder = 'popup-widget-order', + PopupWidgetOrderList = 'popup-widget-order-list', + TabItemMenu = 'tab-item-menu', + TabItemMenuOrder = 'tab-item-menu-order', + RowAdd = 'dialog-tabs-row-add', + Save = 'dialog-tabs-save-button', + Cancel = 'dialog-tabs-cancel-button', + EditTabItem = 'edit-tab-input', + ReadOnlyTabItem = 'read-only-tab-item', +} + +export enum EntryDialogQA { + Apply = 'entry-dialog-apply-button', + Cancel = 'entry-dialog-cancel-button', + Reset = 'entry-dialog-reset-button', + FolderSelect = 'entry-dialog-select', + Content = 'entry-dialog-content', + PathSelect = 'path-select', +} + +export enum MenuItemsQA { + EXPORT = 'export', + EXPORT_CSV = 'exportCsv', + EXPORT_MARKDOWN = 'exportMarkdown', + EXPORT_SCREENSHOT = 'exportScreenshot', + INSPECTOR = 'inspector', + NEW_WINDOW = 'openInNewWindow', + GET_LINK = 'getLink', + ALERTS = 'alerts', +} + +export enum ParamsSettingsQA { + Settings = 'params-settings', + Open = 'params-settings-open-btn', + Add = 'params-settings-add-btn', + Remove = 'params-settings-remove-btn', + RemoveAll = 'params-settings-remove-all-btn', + ParamRow = 'params-settings-param-row', + ParamTitle = 'params-settings-param-title', + ParamValue = 'params-settings-param-value', + ParamAddValue = 'params-settings-param-add-value-btn', +} + +export enum NavigationInputQA { + Apply = 'navigation-input-ok-button', + Input = 'navigation-input', + Link = 'navigation-input-use-link-button', + Open = 'navigation-input-open-button', +} + +export enum TabMenuQA { + List = 'tab-menu-list', + Item = 'tab-menu-list-item', + ItemRemove = 'tab-menu-list-item-remove', + Add = 'tab-menu-add', + Paste = 'tab-menu-paste', +} + +export enum DialogConfirmQA { + Dialog = 'dialog-confirm', + ApplyButton = 'dialog-confirm-apply-button', + CancelButton = 'dialog-confirm-cancel-button', +} + +export enum DialogInfoQA { + Dialog = 'dialog-info', + CloseButton = 'dialog-close-button', +} + +export enum DialogWarningQA { + Dialog = 'dialog-warning', + ApplyButton = 'dialog-warning-apply-button', +} + +export enum DialogDraftWarningQA { + Dialog = 'dialog-draft-warning', + EditButton = 'dialog-draft-warning-edit-btn', +} + +export enum DialogDashWidgetItemQA { + Text = 'dialog-widget-text', + Title = 'dialog-widget-title', + Image = 'dialog-widget-image', +} + +export enum DialogDashTitleQA { + Input = 'dialog-dash-title-input', +} + +export enum DialogDashWidgetQA { + Apply = 'dialog-widget-apply-btn', + Cancel = 'dialog-widget-cancel-btn', + DialogChartWidget = 'dialog-widget-chart-widget', +} + +export const enum DialogFieldEditorQA { + ApplyButton = 'dialog-field-editor-apply-button', + CancelButton = 'dialog-field-editor-cancel-button', + EditNameButton = 'dialog-field-editor-edit-name-button', +} + +export const DialogFilterQA = { + Dialog: 'dialog-filter', + ApplyButton: 'dialog-filter-apply-button', + CancelButton: 'dialog-filter-cancel-button', + ListItem: 'dialog-filter-list-item', + OperationSelect: 'operation-select', +} as const; + +export const enum NavigationMinimalPlaceSelectQa { + Connections = 'navigation-minimal-place-connections', + Datasets = 'navigation-minimal-place-datasets', +} + +export const enum RelativeDatepickerQa { + ScaleSelectStart = 'scale-select-start', + ScaleSelectEnd = 'scale-select-end', +} + +export const enum WorkbookNavigationMinimalQa { + Popup = 'workbook-navigation-minimal-popup', + Input = 'workbook-navigation-minimal-input', + List = 'workbook-navigation-minimal-list', +} + +export const enum DialogCreateWorkbookEntryQa { + Root = 'dialog-create-workbook-entry-root', + Input = 'dialog-create-workbook-entry-input', + ApplyButton = 'dialog-create-workbook-entry-apply-button', +} + +export const enum DialogEntryDescriptionQa { + Root = 'dialog-entry-description-root', + SaveButton = 'dialog-entry-description-save-button', + EditButton = 'dialog-entry-description-edit-button', +} + +export const enum CreateEntityButton { + Button = 'create-entry-button', + Popup = 'create-entry-button-popup', +} + +export enum DocSectionQa { + Group = 'doc-section-group', + Item = 'doc-section-item', + Title = 'doc-item-title', +} + +export enum PreviewQa { + ChartWrapper = 'preview-chart-wrapper', +} + +export enum DatalensTabsQa { + MobileItem = 'dash-mobile-tab-item', + Item = 'dash-tab-item', + SwitcherItem = 'dash-tab-switcher-item', +} + +export enum DatalensHeaderQa { + DesktopContainer = 'datalens-header-desktop-container', + MobileContainer = 'datalens-header-mobile-container', +} + +export enum TextWidgetQa { + Wrapper = 'text-widget-wrapper', +} + +// copied from @gravity-ui/uikit +export enum UikitListQa { + ACTIVE_ITEM = 'list-active-item', +} + +export enum WysiwygEditorQa { + Editor = 'wysiwyg-editor', + SettingsButton = 'g-md-settings-button', + ModeMarkupItemMenu = 'g-md-settings-mode-markup', +} + +export enum ErrorTextQa { + ErrorMessage = 'error-message', +} diff --git a/src/shared/constants/qa/connections.ts b/src/shared/constants/qa/connections.ts new file mode 100644 index 0000000..a3d475e --- /dev/null +++ b/src/shared/constants/qa/connections.ts @@ -0,0 +1,42 @@ +export const ConnectionsBaseQA = { + S3_ACTION_BUTTON: 'conn-s3-action-btn', + S3_UPLOAD_BUTTON: 'conn-s3-upload-btn', + SUBMIT_ACTION_BUTTON: 'conn-submit-action-btn', +}; + +export const ConnectionsWorkspaceQA = { + WORKSPACE: 'conn-workspace', + TABLE_HEADER_FILE_FORM_TYPE_SELECT: 'conn-workspace-table-header-file-form-type-select', +}; + +export const ConnectionsFormQA = { + AUTO_CREATE_DASH_CHECKBOX: 'conn-auto-create-dash-checkbox', + CREATE_CHYT_TOKEN_AUTH_NAV: 'conn-create-token-auth-nav', + CREATE_CHYT_USER_AUTH_NAV: 'conn-create-user-auth-nav', +}; + +export const ConnectionsActionPanelControls = { + CREATE_QL_CHART_BUTTON: 'conn-action-panel-controls-create-ql-chart-button', + CREATE_DATASET_BUTTON: 'conn-action-panel-controls-create-dataset-button', +}; + +export const ConnectionsS3BaseQA = { + LIST: 'conn-s3-list', + LIST_ITEM: 'conn-s3-list-item', + LIST_ITEM_ACTION_MORE: 'conn-s3-list-item-action-more', + LIST_ITEM_ACTION_MORE_DELETE: 'conn-s3-list-item-action-more-delete', + LIST_ITEM_ACTION_MORE_RENAME: 'conn-s3-list-item-action-more-rename', + LIST_ITEM_ACTION_MORE_REPLACE: 'conn-s3-list-item-action-more-replace', + S3_SOURCE_DIALOG_BATCH_CHECKBOX: 'conn-s3-source-dialog-batch-checkbox', + S3_SOURCE_DIALOG_LIST_ITEM: 'conn-s3-source-dialog-list-item', + S3_SOURCE_DIALOG_SUBMIT_BUTTON: 'conn-s3-source-dialog-submit-button', + S3_RENAME_DIALOG_NAME_INPUT: 'conn-s3-rename-dialog-name-input', + S3_RENAME_DIALOG_SUBMIT_BUTTON: 'conn-s3-rename-dialog-submit-button', +}; + +export const ConnectionsYadocsQA = { + ADD_DOCUMENT_BUTTON: 'conn-yadocs-add-document-button', + ADD_DOCUMENT_DIALOG_INPUT: 'conn-yadocs-add-document-dialog-input', + ADD_DOCUMENT_DIALOG_SUBMIT_BUTTON: 'conn-yadocs-add-document-dialog-submit-button', + LIST_SECTION: 'conn-yadocs-list-section', +}; diff --git a/src/shared/constants/qa/control.ts b/src/shared/constants/qa/control.ts new file mode 100644 index 0000000..9f02750 --- /dev/null +++ b/src/shared/constants/qa/control.ts @@ -0,0 +1,87 @@ +export const ControlQA = { + showInnerTitleCheckbox: 'show-inner-title-checkbox', + controlLabel: 'chartkit-control-title', + + controlSelect: 'chartkit-control-select', + controlInput: 'chartkit-control-input', + controlButtonApply: 'control-button-apply', + controlButtonReset: 'control-button-reset', + + controlSelectItems: 'chartkit-control-select-items', + inputInnerLabelControl: 'control-inner-label-input', + inputNameControl: 'control-name-input', + controlLinks: 'dashkit-overlay-control-links', + controlMenu: 'dashkit-overlay-control-menu', + controlSettings: 'dashkit-overlay-control-settings', + controlSelectAcceptable: 'control-select-acceptable', + controlSelectAcceptableInput: 'control-select-acceptable-input', + controlSelectAcceptableButton: 'control-select-acceptable-button', + controlSelectAcceptableItem: 'control-select-acceptable-item', + controlSelectAcceptableRemoveButton: 'control-select-acceptable-remove', + chartkitControl: 'chartkit-control', + groupChartkitControl: 'group-chartkit-control', + selectDefaultAcceptable: 'select-default-acceptable', + acceptableDialogButton: 'acceptable-dialog-button', + filtersClear: 'filters-clear', + + dialogControl: 'dialog-control', + dialogControlApplyBtn: 'control-dialog-apply-button', + dialogControlCancelBtn: 'control-dialog-cancel-button', + + groupCommonLockedBlock: 'group-control-common-locked-block', + + errorBlock: 'type-error', +}; + +export const DialogControlQa = { + selectDatasetButton: 'dialog-control-select-dataset-button', + elementTypeSelect: 'dialog-control-element-type-select', + fieldSelect: 'dialog-control-field-select', + valueSelect: 'dialog-control-value-select', + valueInput: 'dialog-control-value-input', + radioSourceType: 'radio-source-type', + seletSourceType: 'select-source-type', + appearanceTitle: 'dialog-control-appearance-title', + appearanceTitlePlacement: 'dialog-control-appearance-title-placement', + appearanceInnerTitle: 'dialog-control-appearance-inner-title', + fieldNameInput: 'field-name-input', + dateRangeCheckbox: 'date-range-checkbox', + dateTimeCheckbox: 'date-time-checkbox', + requiredValueCheckbox: 'dialog-control-required-checkbox', + + typeControlCalendar: 'type-control-calendar', + typeControlSelect: 'type-control-select', + typeControlInput: 'type-control-input', + typeControlCheckbox: 'type-control-checkbox', + + impactTypeSelect: 'dialog-control-impact-type-select', + impactTabsIdsSelect: 'dialog-control-impact-tabs-ids-select', + impactTypeValueAllTabs: 'dialog-control-impact-type-value-all-tabs', + impactTypeValueSelectedTabs: 'dialog-control-impact-type-value-selected-tabs', + impactTypeValueCurrentTab: 'dialog-control-impact-type-value-current-tab', + impactTypeValueAsGroup: 'dialog-control-impact-type-value-as-group', + impactTabsIdsSelectOption: 'dialog-control-impact-tabs-ids-select-option', +}; + +export const DialogControlParamsQa = { + buttonApply: 'dialog-control-params-apply-button', + buttonCancel: 'dialog-control-params-cancel-button', +}; + +export const DialogGroupControlQa = { + autoHeightSwitch: 'autoheight-switch', + applyButtonSwitch: 'apply-button-switch', + resetButtonSwitch: 'reset-button-switch', + updateControlOnChangeCheckbox: 'update-controls-button-checkbox', + groupSettingsTab: 'group-settings-tab', + placementControlList: 'selectors-placement-control-list', + controlMenu: 'control-menu', + removeControlButton: 'remove-control-button', + copyControlButton: 'copy-control-button', + duplicateControlButton: 'duplicate-control-button', +}; + +export const DialogControlDateQa = { + defaultNotDefined: 'default-date-not-defined', + defaultSelectValue: 'default-date-select-value', +}; diff --git a/src/shared/constants/qa/dash.ts b/src/shared/constants/qa/dash.ts new file mode 100644 index 0000000..adafb43 --- /dev/null +++ b/src/shared/constants/qa/dash.ts @@ -0,0 +1,111 @@ +export const enum DashboardAddWidgetQa { + AddWidget = 'add-widget-button', + AddControl = 'add-control-button', + AddGroupControl = 'add-group-control', + AddText = 'add-text-button', + AddTitle = 'add-title-button', + AddImage = 'add-image-button', +} + +export const enum DashKitOverlayMenuQa { + RemoveButton = 'dashkit-overlay-control-dl-remove-button', + CopyButton = 'dashkit-overlay-control-dl-copy-button', + PinButton = 'dashkit-overlay-control-dl-pin-button', + UnpinButton = 'dashkit-overlay-control-dl-unpin-button', + SettingsButton = 'dashkit-overlay-control-settings', +} + +export const enum DashboardActionPanelControlsQa { + SettingsButton = 'dashboard-action-panel-settings-button', + UndoButton = 'dashboard-action-panel-undo-button', + RedoButton = 'dashboard-action-panel-redo-button', +} + +export const enum DashboardDialogSettingsQa { + DialogRoot = 'dashboard-dialog-settings-root', + ApplyButton = 'dashboard-dialog-settings-apply-button', + CancelButton = 'dashboard-dialog-settings-cancel-button', + TOCSwitch = 'settings-dialog-switch-toc', +} + +export const enum DashboardDialogControl { + AcceptableValues = 'acceptable-dialog-values-', +} + +export enum DashLoadPrioritySettings { + charts = 'charts', + selectors = 'selectors', +} + +export enum DashRevisions { + EXPANDABLE_PANEL = 'expandable-panel', + EXPANDABLE_PANEL_COLLAPSED_BTN = 'expandable-panel-toggle-btn-collapsed', + EXPANDABLE_PANEL_EXPANDED_BTN = 'expandable-panel-toggle-btn-expanded', +} + +export enum DashEntryQa { + EntryName = 'dash-entry-name', + TableOfContent = 'table-of-content', +} + +export enum TableOfContentQa { + TableOfContent = 'table-of-content', + CloseBtn = 'table-of-content-close', + MobileTableOfContent = 'mobile-table-of-content', +} + +export enum DashCommonQa { + RelationTypeButton = 'relation-type-btn', + AliasSelectLeft = 'alias-first-select', + AliasSelectRight = 'alias-second-select', + AliasAddBtn = 'alias-add-new-btn', + AliasAddApplyBtn = 'alias-add-new-apply-btn', + AliasesCancelBtn = 'aliases-dialog-cancel-btn', + AliasShowBtn = 'alias-show-btn', + AliasesListCollapse = 'aliases-list-collapse-btn', + AliasRemoveBtn = 'alias-remove-btn', + AliasItem = 'alias-row-item', + RelationsApplyBtn = 'relations-apply-btn', + RelationsCancelBtn = 'relations-cancel-btn', + WidgetShowTitleCheckbox = 'dialog-widget-settings-show-title', + WidgetEnableAutoHeightCheckbox = 'dialog-widget-settings-enable-autoheight', + WidgetEnableBackgroundCheckbox = 'dialog-widget-settings-enable-background', + WidgetSelectBackgroundButton = 'dialog-widget-settings-select-background-button', + WidgetSelectBackgroundPalleteContainer = 'dialog-widget-settings-select-background-pallete-container', + RelationsDialogEmptyText = 'dialog-relations-empty-text', + RelationsWidgetSelect = 'dialog-relations-widget-select', + RelationsDisconnectAllSwitcher = 'dialog-relations-disconnect-all-switcher', + RelationsDisconnectAllSelectors = 'dialog-relations-disconnect-all-selectors', + RelationsDisconnectAllWidgets = 'dialog-relations-disconnect-all-widgets', + RelationsDisconnectAllCharts = 'dialog-relations-disconnect-all-charts', + RelationsListRow = 'dialog-relations-list-row', + RelationsRowPopover = 'dialog-relations-row-popover', +} + +export enum FixedHeaderQa { + Wrapper = 'dash-fixed-header-wrapper', + Container = 'dash-fixed-header-containter', + Controls = 'dash-fixed-header-controls', + ExpandCollapseButton = 'dash-fixed-header-expand-collapse-button', +} + +export enum DashRelationTypes { + output = 'relation-type-option-output', + input = 'relation-type-option-input', + ignore = 'relation-type-option-ignore', + both = 'relation-type-option-both', + unknown = 'relation-type-option-unknown', +} + +export enum DashkitQa { + GRID_ITEM = 'dashkit-grid-item', +} + +export enum DashBodyQa { + App = 'dash-app', + ContentWrapper = 'dash-body-content-wrapper', +} + +export enum DashTabsQA { + Root = 'dash-tabs', +} diff --git a/src/shared/constants/qa/datasets.ts b/src/shared/constants/qa/datasets.ts new file mode 100644 index 0000000..e01f5cd --- /dev/null +++ b/src/shared/constants/qa/datasets.ts @@ -0,0 +1,152 @@ +export enum AvatarQA { + DeleteButton = 'ds-avatar-delete-button', + RelationsMapContainer = 'ds-avatar-relations-map-container', + RelationsMapCanvas = 'ds-avatar-relations-map-canvas', + Avatar = 'ds-avatar', +} + +export enum ParametersQA { + ParametersTabSection = 'parameters-tab-section', +} + +export const FiltersQA = { + FiltersTabSection: 'filters-tab-section', + TableDeleteRowBtn: 'filters-table-delete-row-button', +} as const; + +export const RelationsMapQA = { + RelationsMap: 'ds-relations-map', +} as const; + +export enum DatasetFieldsTabQa { + DatasetEditor = 'ds-editor', + TableRow = 'dataset-fields-table-row', + FieldNameColumnInput = 'dataset-fields-name-column-input', + FieldIndexColumnCheckbox = 'dataset-fields-index-column-checkbox', + FieldIndexHeaderColumnCheckbox = 'dataset-fields-index-header-column-checkbox', + FieldIdColumnInput = 'dataset-fields-id-column-input', + FieldDescriptionColumnInput = 'dataset-fields-description-column-input', + FieldSourceColumnBtn = 'dataset-fields-source-column-btn', + FieldSettingsColumnIcon = 'dataset-fields-settings-column-icon', + FieldVisibleColumnIcon = 'dataset-fields-visible-column-icon', + FieldTypeColumnBtn = 'dataset-fields-type-column-btn', + FieldAggregationColumnBtn = 'dataset-fields-aggregation-column-btn', + FieldTypeColumnItem = 'dataset-fields-type-column-item', + FieldContextMenuBtn = 'dataset-fields-context-menu-btn', + FieldContextMenuPopup = 'dataset-fields-context-menu-popup', + TableSettingsBtn = 'dataset-editor-table-settings-btn', + BatchActionsPanel = 'dataset-editor-actions-panel', +} + +export const DatasetFieldTabBatchPanelQa = { + BatchDelete: 'ds-fields-batch-delete', + BatchHide: 'ds-fields-batch-hide', + BatchShow: 'ds-fields-batch-show', + BatchType: 'ds-fields-batch-type', + BatchAggregation: 'ds-fields-batch-aggregation', +} as const; + +export const DatasetFieldContextMenuItemsQA = { + DUPLICATE: 'ds-field-context-menu-duplicate', + EDIT: 'ds-field-context-menu-edit', + RLS: 'ds-field-context-menu-rls', + COPY_GUID: 'ds-field-context-menu-copy-guid', + REMOVE: 'ds-field-context-menu-remove', + INSPECT: 'ds-field-context-menu-inspect', +} as const; + +export const DatasetEditorTableSettingsItems = { + ShowHidden: 'ds-table-settings-menu-show-hidden', + ShowId: 'ds-table-settings-menu-show-id', +} as const; + +export enum DatasetFieldSettingsDialogQa { + Dialog = 'dataset-field-settings-dialog', + ColorSettingsButton = 'dataset-field-settings-dialog-color-settings-btn', +} + +export enum DatasetFieldColorsDialogQa { + Dialog = 'dataset-field-colors-dialog', + ValueItem = 'dataset-field-colors-dialog_value-item', + ValueItemColorIcon = 'dataset-field-colors-dialog_value-color', + PaleteItem = 'dataset-field-colors-dialog_palette-item', +} + +export enum DatasetTabSectionQA { + AddButton = 'dataset-tab-section-add-button', + FieldRow = 'dataset-tab-section-field-row', + FieldRowMenu = 'dataset-tab-field-row-menu', + FieldRowHeader = 'dataset-tab-section-field-row-header', + RemoveRow = 'dataset-tab-field-row-remove', + EditRow = 'dataset-tab-field-row-edit', + DuplicateRow = 'dataset-tab-field-row-duplicate', + Copy = 'dataset-tab-field-row-copy', +} + +export enum DatasetPanelQA { + TabRadio = 'dataset-panel-tab-radio', + UpdateFieldsButton = 'dataset-panel-update-fields-button', + PreviewButton = 'dataset-panel-preview-button', +} + +export enum DatasetSourcesLeftPanelQA { + ConnSelection = 'dataset-sources-conn-selection', + ConnContextMenuBtn = 'dataset-sources-conn-context-menu-btn', + ConnContextMenuOpen = 'dataset-sources-conn-context-menu-open', + ConnContextMenuReplace = 'dataset-sources-conn-context-menu-replace', + ConnContextMenuDelete = 'dataset-sources-conn-context-menu-delete', + SelectSourcesDbName = 'dataset-sources-db-name-select', + SourcesServerSearchInput = 'dataset-sources-server-search-input', + SourcesList = 'dataset-sources-list', + SourcesListRetryButton = 'dataset-sources-list-retry-button', +} + +export enum DatasetActionQA { + CreateButton = 'dataset-create-button', + SettingsButton = 'dataset-settings-button', + SettingsShowPreviewByDefault = 'dataset-settings-show-preview-item', +} + +export const enum DatasetDialogRelationQA { + AddRelation = 'dataset-dialog-relation-add-relation-button', + Apply = 'dataset-dialog-relation-save-button', + DialogTitle = 'dataset-dialog-relation-dialog-title', +} + +export const DATASET_TAB = { + DATASET: 'dataset', + FILTERS: 'filters', + SOURCES: 'sources', + PARAMETERS: 'parameters', + CACHE: 'cache', +} as const; + +export const DATASET_CACHE_MODE = { + OFF: 'off', + FORMULA: 'formula', + SQL: 'sql', +} as const; + +export const DatasetSourcesTableQa = { + Source: 'ds-source', + SourceMenu: 'ds-source-menu', + SourceContextMenuBtn: 'ds-source-context-menu-btn', + SourceContextMenuDelete: 'ds-source-context-menu-delete', + SourceContextMenuModify: 'ds-source-context-menu-modify', + SourcesAddItemBtn: 'ds-source-add-item-button', +} as const; + +export enum DatasetPreviewQA { + Preview = 'dataset-preview', + RowCountInput = 'ds-preview-header-row-count-input', + ClosePreviewBtn = 'ds-preview-header-close-btn', +} + +export const DatasetSourceEditorDialogQA = { + Dialog: 'source-editor-dialog', + EditTitleInput: 'source-editor-title', + EditPathInput: 'source-editor-path', + SourceEditorSwitch: 'datasets-source-switcher', + ApplyBtn: 'source-editor-apply', + CancelBtn: 'source-editor-cancel', +} as const; diff --git a/src/shared/constants/qa/dl-navigation.ts b/src/shared/constants/qa/dl-navigation.ts new file mode 100644 index 0000000..3f3790a --- /dev/null +++ b/src/shared/constants/qa/dl-navigation.ts @@ -0,0 +1,9 @@ +export enum DlNavigationQA { + SearchInput = 'dl-nav-search-input', + Row = 'dl-nav-row', + NavigationMinimal = 'navigation-minimal', + MinimalDoneBtn = 'navigation-mininal-done-btn', + AsideMenuItem = 'aside-nav-menu-item', + List = 'navigation-list', + NavigationBreadcrumbs = 'dl-navigation-breadcrumbs', +} diff --git a/src/shared/constants/qa/edit-history.ts b/src/shared/constants/qa/edit-history.ts new file mode 100644 index 0000000..3e5c88f --- /dev/null +++ b/src/shared/constants/qa/edit-history.ts @@ -0,0 +1,4 @@ +export const EditHistoryQA = { + UndoBtn: 'edit-history-undo-btn', + RedoBtn: 'edit-history-redo-btn', +}; diff --git a/src/shared/constants/qa/field-editor.ts b/src/shared/constants/qa/field-editor.ts new file mode 100644 index 0000000..b27b638 --- /dev/null +++ b/src/shared/constants/qa/field-editor.ts @@ -0,0 +1,3 @@ +export const enum FieldEditorQa { + Dialog = 'field-editor', +} diff --git a/src/shared/constants/qa/index.ts b/src/shared/constants/qa/index.ts new file mode 100644 index 0000000..0087db6 --- /dev/null +++ b/src/shared/constants/qa/index.ts @@ -0,0 +1,18 @@ +export {VisualizationsQa} from './visualization'; +export * from './chart'; +export * from './collections'; +export * from './components'; +export * from './connections'; +export * from './control'; +export * from './dash'; +export * from './datasets'; +export * from './dl-navigation'; +export * from './field-editor'; +export * from './ql'; +export * from './settings'; +export * from './sign-in'; +export * from './uikit'; +export * from './wizard'; +export * from './workbooks'; +export * from './shared-entries'; +export * from './edit-history'; diff --git a/src/shared/constants/qa/ql.ts b/src/shared/constants/qa/ql.ts new file mode 100644 index 0000000..ecf3020 --- /dev/null +++ b/src/shared/constants/qa/ql.ts @@ -0,0 +1,34 @@ +export const QLPageQA = { + UndoButton: 'undo-btn', + RedoButton: 'redo-btn', +}; + +export const enum ViewSetupQA { + ViewSetupCreate = 'view-setup-create', + SelectConnection = 'select-connection', +} + +export const enum ScreenEditorQA { + QueryTab = 'query-tab', + ParamsTab = 'params-tab', +} + +export const enum TabQueryQA { + AddPromQLQueryBtn = 'add-promql-query-btn', + SelectConnection = 'select-connection', + GoToConnectionContextMenuBtn = 'go-to-connection-context-menu-btn', +} + +export const enum TabParamsQA { + ParamName = 'param-name', + AddParamBtn = 'add-param-btn', + ParamType = 'param-type', + OpenParamDialogBtn = 'open-param-dialog-btn', +} + +export const enum DialogQLParameterQA { + Dialog = 'dialog-ql-param', + DialogBody = 'dialog-ql-param-body', + DatepickerStart = 'datepicker-start', + DatepickerEnd = 'datepicker-end', +} diff --git a/src/shared/constants/qa/settings.ts b/src/shared/constants/qa/settings.ts new file mode 100644 index 0000000..6556835 --- /dev/null +++ b/src/shared/constants/qa/settings.ts @@ -0,0 +1,12 @@ +export const enum ServiceSettingsQA { + ColorPalettes = 'color-palettes', + AddPaletteButton = 'add-palette-btn', +} + +export const enum PaletteEditorQA { + PaletteNameInput = 'palette-name', + RemoveColorButton = 'remove-color-btn', + AddColorButton = 'add-color-btn', + ColorTextInput = 'color-input', + ApplyButton = 'apply-btn', +} diff --git a/src/shared/constants/qa/shared-entries.ts b/src/shared/constants/qa/shared-entries.ts new file mode 100644 index 0000000..3a69972 --- /dev/null +++ b/src/shared/constants/qa/shared-entries.ts @@ -0,0 +1,22 @@ +export const SharedEntriesPermissionsDialogQa = { + ApplyBtn: 'shared-entries-permissions-dialog-apply', + DelegateBtn: 'shared-entries-permissions-dialog-delegate', + NotDelegateBtn: 'shared-entries-permissions-dialog-not-delegate', +} as const; + +export const SharedEntriesBindingsDialogQa = { + ApplyDeleteBtn: 'shared-entries-bindings-dialog-apply-delete', +} as const; + +export const SharedEntriesSelectDialogQa = { + PastLinkToEntryBtn: 'shared-entries-select-dialog-past-link-btn', +} as const; + +export const SharedEntriesAddFromLinkDialogQa = { + LintTextInput: 'shared-entries-add-from-link-dialog-text-input', + ApplyBtn: 'shared-entries-add-from-link-dialog-apply', +} as const; + +export const SharedEntriesBaseQa = { + OpenOriginalBtn: 'shared-entries-open-original-btn', +} as const; diff --git a/src/shared/constants/qa/sign-in.ts b/src/shared/constants/qa/sign-in.ts new file mode 100644 index 0000000..e66949c --- /dev/null +++ b/src/shared/constants/qa/sign-in.ts @@ -0,0 +1,5 @@ +export const SignInQa = { + SIGN_IN_FORM: 'sign-in-form', + INPUT_LOGIN: 'sign-in-login', + INPUT_PASSWORD: 'sign-in-password', +}; diff --git a/src/shared/constants/qa/uikit.ts b/src/shared/constants/qa/uikit.ts new file mode 100644 index 0000000..754e747 --- /dev/null +++ b/src/shared/constants/qa/uikit.ts @@ -0,0 +1,7 @@ +export const SelectQa = { + LIST: 'select-list', + POPUP: 'select-popup', + SHEET: 'select-sheet', + CLEAR: 'select-clear', + FILTER_INPUT: 'select-filter-input', +}; diff --git a/src/shared/constants/qa/visualization.ts b/src/shared/constants/qa/visualization.ts new file mode 100644 index 0000000..558ed29 --- /dev/null +++ b/src/shared/constants/qa/visualization.ts @@ -0,0 +1,18 @@ +export const enum VisualizationsQa { + Line = 'visualization-item-line', + Area = 'visualization-item-area', + Area100p = 'visualization-item-area100p', + Column = 'visualization-item-column', + Column100p = 'visualization-item-column100p', + Bar = 'visualization-item-bar', + Bar100p = 'visualization-item-bar100p', + Scatter = 'visualization-item-scatter', + Pie = 'visualization-item-pie', + Donut = 'visualization-item-donut', + Metric = 'visualization-item-metric', + Treemap = 'visualization-item-treemap', + FlatTable = 'visualization-item-flatTable', + PivotTable = 'visualization-item-pivotTable', + Geolayer = 'visualization-item-geolayer', + CombinedChart = 'visualization-item-combined-chart', +} diff --git a/src/shared/constants/qa/wizard.ts b/src/shared/constants/qa/wizard.ts new file mode 100644 index 0000000..316da85 --- /dev/null +++ b/src/shared/constants/qa/wizard.ts @@ -0,0 +1,211 @@ +export const enum WizardPageQa { + SectionPreview = 'preview-chartkit', + VisualizationSelectPopup = 'visualization-select-popup', + UndoButton = 'undo-btn', + RedoButton = 'redo-btn', + PlaceholderIconTooltipContent = 'placeholder-icon-tooltip-content', +} + +export const enum SectionDatasetQA { + DatasetContainer = 'datasets-container', + DatasetEmptyMessage = 'datasets-blank', + DatasetSelect = 'dataset-select', + DatasetFields = 'dataset-fields', + AddField = 'add-param', + SectionParameters = 'section-parameters', + ItemIcon = 'item-icon', + ItemTitle = 'section-dataset-draggable-item-title', + ItemFunction = 'section-dataset-item-function', + AddDatasetButton = 'add-dataset-button', + CreateFieldButton = 'create-field-button', + CreateHierarchyButton = 'create-hierarchy-button', + CreateParameterButton = 'create-parameter-button', + DatasetSelectItem = 'dataset-select-item', + GoToDatasetButton = 'go-to-dataset-action-button', + ReplaceDatasetButton = 'replace-dataset-action-button', + RemoveDatasetButton = 'remove-dataset-action-button', + SettingsDatasetButton = 'settings-dataset-action-button', + DatasetSelectMoreMenu = 'dataset-select-more-menu', + RequestDatasetAccess = 'request-rights-button', + DatasetSelectMore = 'dataset-select-more', + FieldActions = 'field-actions', +} + +export const enum DialogColumnSettingsQa { + ResetButton = 'dialog-column-settings-reset-button', + Dialog = 'dialog-column-settings', + ApplyButton = 'dialog-column-settings-apply-button', + CancelButton = 'dialog-column-settings-cancel-button', + UnitRadioButtons = 'dialog-column-settings-unit-radio-buttons', + WidthValueInput = 'dialog-column-settings-width-input', + PinnedColumnsInput = 'dialog-column-settings-pinned-columns-input', +} + +export const enum DialogFieldSettingsQa { + FieldTitleInput = 'dialog-title-input', + MarkupTypeRadioButtons = 'dialog-field-markup-type', +} + +export const enum DialogFieldBackgroundSettingsQa { + EnableButton = 'dialog-field-bg-switcher', + FillTypeButtons = 'dialog-field-bg-fill-type', + ButtonColorDialog = 'button-color-dialog', + NullModeRadioButtons = 'null-node-radio-buttons', +} + +export const enum DialogFieldBarsSettingsQa { + EnableButton = 'dialog-field-bars-switcher', + ColorTypeRadioButtons = 'dialog-field-color-type-radio-buttons', + EnableLabelsButton = 'dialog-field-bars-label-switcher', + ColorSelector = 'dialog-field-bars-color-selector', + PositiveColorSelector = 'dialog-field-bars-positive-color-selector', + NegativeColorSelector = 'dialog-field-bars-negative-color-selector', + GradientColorSelector = 'dialog-field-bars-gradient-color-selector', + MinifiedPaletteSelector = 'dialog-field-minifield-palette-selector', + ScaleModeRadioButtons = 'dialog-field-scale-mode-radio-buttons', + ScaleInputsWrapper = 'dialog-field-scale-inputs-wrapper', + EnableBarsInTotalsButton = 'dialog-field-bars-in-totals-switcher', +} + +export const enum DialogFieldMainSectionQa { + TypeSelector = 'type-selector', + GroupingSelector = 'grouping-selector', + LabelModeSelector = 'label-mode-selector', + AggregationSelector = 'aggregation-selector', + PrecisionInput = 'precision-input', + PrefixInput = 'prefix-input', + PostfixInput = 'postfix-input', + NumberFormat = 'number-format-radio', +} + +export const enum DialogFieldTypeSelectorValuesQa { + Integer = 'type-selector-integer', + String = 'type-selector-string', + Float = 'type-selector-float', + Boolean = 'type-selector-boolean', + Date = 'type-selector-date', + Datetime = 'type-selector-datetime', + GenericDatetime = 'type-selector-genericdatetime', +} + +export const enum DialogFieldAggregationSelectorValuesQa { + None = 'aggregation-selector-none', + Countuniqe = 'aggregation-selector-countunique', + Sum = 'aggregation-selector-sum', + Avg = 'aggregation-selector-avg', + Min = 'aggregation-selector-min', + Max = 'aggregation-selector-max', +} + +export const enum DialogFieldGroupingSelectorValuesQa { + None = 'grouping-selector-none', + TruncYear = 'grouping-selector-trunc-year', + TruncQuarter = 'grouping-selector-trunc-quarter', + TruncMonth = 'grouping-selector-trunc-month', + TruncWeek = 'grouping-selector-trunc-week', + TruncDay = 'grouping-selector-trunc-day', + TruncHour = 'grouping-selector-trunc-hour', + TruncMinute = 'grouping-selector-trunc-minute', + TruncSecond = 'grouping-selector-trunc-second', + PartYear = 'grouping-selector-part-year', + PartQuarter = 'grouping-selector-part-quarter', + PartMonth = 'grouping-selector-part-month', + PartWeek = 'grouping-selector-part-week', + PartDay = 'grouping-selector-part-day', + PartDayOfWeek = 'grouping-selector-part-dayofweek', + PartHour = 'grouping-selector-part-hour', + PartMinute = 'grouping-selector-part-minute', + PartSecond = 'grouping-selector-part-second', +} + +export const enum DialogFieldLabelModeValuesQa { + Percent = 'label-mode-selector-percent', + Absolute = 'label-mode-selector-absolute', +} + +export const enum DialogFieldSubTotalsQa { + SubTotalsSwitch = 'sub-totals-switch', +} + +export const enum DialogColorQa { + PaletteSelect = 'palette-select', + GradientType = 'color-dialog-gradient-type-radio', + ApplyButton = 'color-dialog-apply-button', + CustomColorButton = 'color-dialog-custom-color-button', + CustomColorInput = 'color-dialog-custom-color-input', +} + +export const enum DialogLineShapesQa { + ValueList = 'dialog-line-shapes-value-list', +} + +export const enum SectionVisualizationAddItemQa { + NoFieldsErrorTooltip = 'section-visualisation-no-field-error-tooltip', + LabelsOverflowErrorTooltip = 'section-visualisation-labels-overflow-error-tooltip', + ShapesOverflowErrorTooltip = 'section-visualisation-shapes-overflow-error-tooltip', +} + +export const enum DialogPlaceholderQa { + Dialog = 'dialog-placeholder', + TooltipZeroToMaxScale = 'dialog-placeholder-tooltip-zero-to-max-scale', + TooltipLogarithmicAxis = 'dialog-placeholder-tooltip-logarithmic-axis', + AxisFormatMode = 'dialog-placeholder-axis-format-mode', + LabelsViewRadioButtons = 'dialog-placeholder-axis-labels-view', + AxisScaleModeRadioButtons = 'dialog-placeholder-axis-scale-mode', + AxisMinInput = 'dialog-placeholder-axis-min-input', + AxisMaxInput = 'dialog-placeholder-axis-max-input', + HolidaysRadioButtons = 'holidays-radio-buttons', +} + +export const enum DatasetItemActionsQa { + RemoveField = 'dropdown-field-menu-remove', + RemoveHierarchyField = 'dropdown-field-menu-hierarchy-remove', + EditHierarchyField = 'dropdown-field-menu-hierarchy-edit', + DuplicateField = 'dropdown-field-menu-duplicate', + EditField = 'dropdown-field-menu-edit', + InspectField = 'dropdown-field-menu-inspect', +} + +export const enum DialogLinkQa { + ApplyButton = 'dialog-link-apply-button', +} + +export const enum DialogMultiDatasetQa { + DialogMultiDatasetRoot = 'dialog-multidataset-root', + RemoveDatasetButton = 'dialog-multidataset-remove-button', + ApplyButton = 'dialog-multidataset-apply-button', +} + +export const enum PlaceholderActionQa { + OpenColorDialogIcon = 'placeholder-action-open-color-dialog', + OpenMetricColorDialogIcon = 'placeholder-action-open-metric-color-dialog', +} + +export const enum HierarchyEditorQa { + Dialog = 'hierarchy-editor', + ApplyButton = 'dialog-apply-button', +} + +export const enum VisualizationItemQa { + FormulaIcon = 'formula-icon', +} + +export const enum ChartSaveControlsQA { + SaveMoreDropdown = 'save-more-dropdown', + SaveAsEditorScript = 'save-as-editor-script', +} + +export const enum ChartSettingsDialogQA { + IndicatorTitleMode = 'indicator-title-mode', + PreserveWhiteSpace = 'preserve-white-space', + IndicatorFontSize = 'indicator-font-size', + Stacking = 'stacking', +} + +export const enum DialogMetricColorsQa { + Dialog = 'dialog-metric-colors', + ApplyButton = 'dialog-metric-colors-apply-button', + CancelButton = 'dialog-metric-colors-cancel-button', + Palette = 'dialog-metric-settings-palette', + CustomColorButton = 'dialog-metric-colors-custom-color-btn', +} diff --git a/src/shared/constants/qa/workbooks.ts b/src/shared/constants/qa/workbooks.ts new file mode 100644 index 0000000..83b8773 --- /dev/null +++ b/src/shared/constants/qa/workbooks.ts @@ -0,0 +1,21 @@ +export enum WorkbookPageQa { + Layout = 'workbook-page-layout', + ListItem = 'workbook-page-list-item', + ListItemName = 'workbook-page-list-item-name', + MenuDropDownBtn = 'workbook-page-list-item-menu-btn', + MenuItemDuplicate = 'workbook-page-list-item-menu-item-duplicate', + Filters = 'workbook-page-filters', + ChunkScope = 'workbook-page-chunk-scope-', + ChunkSharedEntryScope = 'workbook-page-chunk-shared-entry-scope-', +} + +export const WorkbookPageActionsMoreQA = { + SWITCHER: 'workbook-page-actions-more-switcher', + DELETE_ITEM: 'workbook-page-actions-more-delete-item', +}; + +export const WorkbookDialogQA = { + APPLY_BUTTON: 'workbook-dialog-apply-button', + ROOT_IMPORT_SUCCESS: 'workbook-dialog-import-success', + TITLE_INPUT: 'workbook-dialog-title-input', +}; diff --git a/src/shared/constants/ql.ts b/src/shared/constants/ql.ts new file mode 100644 index 0000000..eb1c8d9 --- /dev/null +++ b/src/shared/constants/ql.ts @@ -0,0 +1,29 @@ +export const VISUALIZATION_IDS = { + LINE: 'line', + AREA: 'area', + AREA_100P: 'area100p', + COLUMN: 'column', + COLUMN_100P: 'column100p', + BAR: 'bar', + BAR_100P: 'bar100p', + PIE: 'pie', + DONUT: 'donut', + METRIC: 'metric', + TABLE: 'table', +}; + +export enum QLChartType { + Sql = 'sql', + Promql = 'promql', + Monitoringql = 'monitoringql', +} + +export enum QLParamType { + String = 'string', + Number = 'number', + Boolean = 'boolean', + Date = 'date', + Datetime = 'datetime', + DateInterval = 'date-interval', + DatetimeInterval = 'datetime-interval', +} diff --git a/src/shared/constants/shapes/index.ts b/src/shared/constants/shapes/index.ts new file mode 100644 index 0000000..932c352 --- /dev/null +++ b/src/shared/constants/shapes/index.ts @@ -0,0 +1,2 @@ +export * from './lines'; +export * from './points'; diff --git a/src/shared/constants/shapes/lines/index.ts b/src/shared/constants/shapes/lines/index.ts new file mode 100644 index 0000000..d661ee5 --- /dev/null +++ b/src/shared/constants/shapes/lines/index.ts @@ -0,0 +1,53 @@ +export enum LineShapeType { + Solid = 'Solid', + ShortDash = 'ShortDash', + ShortDot = 'ShortDot', + ShortDashDot = 'ShortDashDot', + ShortDashDotDot = 'ShortDashDotDot', + Dot = 'Dot', + Dash = 'Dash', + LongDash = 'LongDash', + DashDot = 'DashDot', + LongDashDot = 'LongDashDot', + LongDashDotDot = 'LongDashDotDot', +} +export const SHAPES_ORDER = { + [LineShapeType.Solid]: 1, + [LineShapeType.Dash]: 2, + [LineShapeType.Dot]: 3, + [LineShapeType.ShortDashDot]: 4, + [LineShapeType.LongDash]: 5, + [LineShapeType.LongDashDot]: 6, + [LineShapeType.ShortDot]: 7, + [LineShapeType.LongDashDotDot]: 8, + [LineShapeType.ShortDash]: 9, + [LineShapeType.DashDot]: 10, + [LineShapeType.ShortDashDotDot]: 11, +}; + +export const SHAPES_PALETTE_ORDER = { + ...SHAPES_ORDER, + auto: Infinity, +}; + +export const selectShapes = (): LineShapeType[] => Object.values(LineShapeType); +export const getServerShapesOrder = () => + selectShapes().sort((a, b) => SHAPES_ORDER[a] - SHAPES_ORDER[b]); + +export const LINE_CAP = { + Butt: 'butt', + Round: 'round', + Square: 'square', + None: 'none', +} as const; + +export type LineCap = (typeof LINE_CAP)[keyof typeof LINE_CAP]; + +export const LINE_JOIN = { + Bevel: 'bevel', + Round: 'round', + Miter: 'miter', + None: 'unset', +} as const; + +export type LineJoin = (typeof LINE_JOIN)[keyof typeof LINE_JOIN]; diff --git a/src/shared/constants/shapes/points/index.ts b/src/shared/constants/shapes/points/index.ts new file mode 100644 index 0000000..07e0254 --- /dev/null +++ b/src/shared/constants/shapes/points/index.ts @@ -0,0 +1,9 @@ +export enum PointsShapeType { + Circle = 'circle', + Diamond = 'diamond', + Square = 'square', + Triangle = 'triangle', + TriangleDown = 'triangle-down', +} + +export const POINT_SHAPES_IN_ORDER = ['circle', 'diamond', 'square', 'triangle', 'triangle-down']; diff --git a/src/shared/constants/themes.ts b/src/shared/constants/themes.ts new file mode 100644 index 0000000..4cc0b05 --- /dev/null +++ b/src/shared/constants/themes.ts @@ -0,0 +1,32 @@ +import type {RealTheme, Theme, ThemeSettings} from '@gravity-ui/uikit'; + +type ThemesArray = ReadonlyArray>; + +export const LIGHT_THEME = 'light'; +export const LIGHT_THEME_HC = 'light-hc'; +export const DARK_THEME = 'dark'; +export const DARK_THEME_HC = 'dark-hc'; +export const SYSTEM_THEME = 'system'; + +export const THEMES: ThemesArray = [LIGHT_THEME, DARK_THEME]; +export const CONTRAST_THEMES: ThemesArray = [LIGHT_THEME_HC, DARK_THEME_HC]; + +export const ALLOW_THEMES = [...THEMES, ...CONTRAST_THEMES, SYSTEM_THEME] as const; + +export const CONTRAST_THEME_SETTINGS: ThemeSettings = { + systemDarkTheme: DARK_THEME_HC, + systemLightTheme: LIGHT_THEME_HC, +}; + +export const DEFAULT_THEME_SETTINGS: ThemeSettings = { + systemDarkTheme: DARK_THEME, + systemLightTheme: LIGHT_THEME, +}; + +export const DEFAULT_THEME_CONFIG: { + theme: Theme; + themeSettings: ThemeSettings; +} = { + theme: SYSTEM_THEME, + themeSettings: DEFAULT_THEME_SETTINGS, +}; diff --git a/src/shared/constants/timeout.ts b/src/shared/constants/timeout.ts new file mode 100644 index 0000000..c4bc533 --- /dev/null +++ b/src/shared/constants/timeout.ts @@ -0,0 +1,11 @@ +export const DEFAULT_TIMEOUT = 20000; + +export const TIMEOUT_10_SEC = 10000; +export const TIMEOUT_59_SEC = 59000; +export const TIMEOUT_60_SEC = 60000; +export const TIMEOUT_65_SEC = 65000; +export const TIMEOUT_90_SEC = 90000; +export const TIMEOUT_95_SEC = 95000; +export const TIMEOUT_100_SEC = 100000; +export const TIMEOUT_120_SEC = 120000; +export const TIMEOUT_125_SEC = 125000; diff --git a/src/shared/constants/ui-sandbox.ts b/src/shared/constants/ui-sandbox.ts new file mode 100644 index 0000000..fafc212 --- /dev/null +++ b/src/shared/constants/ui-sandbox.ts @@ -0,0 +1 @@ +export const WRAPPED_FN_KEY = '__wrappedFn__'; diff --git a/src/shared/constants/url.ts b/src/shared/constants/url.ts new file mode 100644 index 0000000..e536297 --- /dev/null +++ b/src/shared/constants/url.ts @@ -0,0 +1,7 @@ +export const SKIP_PROMO_URL_QUERY = 'skipPromo'; + +export const OPEN_ACCESS_DIALOG_QUERY = '__open-access-dialog'; +export const OPEN_ACCESS_APPROVE_QUERY = '__open-access-approve'; + +export const BACKOFFICE_QUERY = 'backoffice'; +export const PROMO_VERSION_QUERY = 'versionId'; diff --git a/src/shared/constants/user.ts b/src/shared/constants/user.ts new file mode 100644 index 0000000..64f5d40 --- /dev/null +++ b/src/shared/constants/user.ts @@ -0,0 +1,4 @@ +export const SYSTEM_USER = { + LOGIN: 'system', + UID: 'systemId', +}; diff --git a/src/shared/constants/visualization.ts b/src/shared/constants/visualization.ts new file mode 100644 index 0000000..0906763 --- /dev/null +++ b/src/shared/constants/visualization.ts @@ -0,0 +1,62 @@ +export enum WizardVisualizationId { + Line = 'line', + Area = 'area', + Area100p = 'area100p', + Column = 'column', + Column100p = 'column100p', + Bar = 'bar', + Bar100p = 'bar100p', + Scatter = 'scatter', + Pie = 'pie', + Donut = 'donut', + Metric = 'metric', + Treemap = 'treemap', + FlatTable = 'flatTable', + PivotTable = 'pivotTable', + Geolayer = 'geolayer', + Geopoint = 'geopoint', + Geopolygon = 'geopolygon', + GeopointWithCluster = 'geopoint-with-cluster', + CombinedChart = 'combined-chart', + Funnel = 'funnel', +} + +export enum QlVisualizationId { + Line = 'line', + Area = 'area', + Area100p = 'area100p', + Column = 'column', + Column100p = 'column100p', + Bar = 'bar', + Pie = 'pie', + Metric = 'metric', + FlatTable = 'table', +} + +export const PERCENT_VISUALIZATIONS = new Set([ + WizardVisualizationId.Bar100p, + WizardVisualizationId.Column100p, + WizardVisualizationId.Area100p, +]); + +export const VISUALIZATIONS_WITH_SEVERAL_FIELDS_X_PLACEHOLDER = new Set([ + WizardVisualizationId.Column, + WizardVisualizationId.Column100p, + WizardVisualizationId.Bar, + WizardVisualizationId.Bar100p, +]); + +export const VISUALIZATIONS_WITH_LABELS = new Set([ + WizardVisualizationId.Line, + WizardVisualizationId.Area, + WizardVisualizationId.Area100p, + WizardVisualizationId.Column, + WizardVisualizationId.Column100p, + WizardVisualizationId.Bar, + WizardVisualizationId.Bar100p, +]); + +export const VISUALIZATIONS_WITH_LABELS_POSITION = new Set([ + WizardVisualizationId.Column, + WizardVisualizationId.Bar, +]); diff --git a/src/shared/constants/widgets.ts b/src/shared/constants/widgets.ts new file mode 100644 index 0000000..071b0a0 --- /dev/null +++ b/src/shared/constants/widgets.ts @@ -0,0 +1,111 @@ +import {DashTabItemType} from '../types'; +import type {ValueOf} from '../types'; + +export const CustomPaletteBgColors = { + LIKE_CHART: 'like-chart-bg', + NONE: 'transparent', +} as const; + +export const TRANSPARENT_COLOR_HEX = '#00000000'; + +export const LIKE_CHART_COLOR_TOKEN = 'var(--g-color-base-float)'; +export const BASE_GREY_BACKGROUND_COLOR = 'var(--g-color-base-generic)'; + +export const WIDGET_BG_HEAVY_COLORS_PRESET = [ + 'var(--g-color-base-info-heavy)', + 'var(--g-color-base-positive-heavy)', + 'var(--g-color-base-warning-heavy)', + 'var(--g-color-base-danger-heavy)', + 'var(--g-color-base-utility-heavy)', + 'var(--g-color-base-misc-heavy)', + 'var(--g-color-base-neutral-heavy)', + + 'var(--g-color-base-info-heavy-hover)', + 'var(--g-color-base-positive-heavy-hover)', + 'var(--g-color-base-warning-heavy-hover)', + 'var(--g-color-base-danger-heavy-hover)', + 'var(--g-color-base-utility-heavy-hover)', + 'var(--g-color-base-misc-heavy-hover)', + 'var(--g-color-base-neutral-heavy-hover)', +]; + +export const DUPLICATED_WIDGET_BG_COLORS_PRESET = [ + 'var(--g-color-base-info-medium)', + 'var(--g-color-base-positive-medium)', + 'var(--g-color-base-warning-medium)', + 'var(--g-color-base-danger-medium)', + 'var(--g-color-base-utility-medium)', + 'var(--g-color-base-misc-medium)', + 'var(--g-color-base-neutral-medium)', +]; + +export const WIDGET_BG_COLORS_PRESET = [ + 'var(--g-color-base-info-light)', + 'var(--g-color-base-positive-light)', + 'var(--g-color-base-warning-light)', + 'var(--g-color-base-danger-light)', + 'var(--g-color-base-utility-light)', + 'var(--g-color-base-misc-light)', + 'var(--g-color-base-neutral-light)', + + 'var(--g-color-base-info-light-hover)', + 'var(--g-color-base-positive-light-hover)', + 'var(--g-color-base-warning-light-hover)', + 'var(--g-color-base-danger-light-hover)', + 'var(--g-color-base-utility-light-hover)', + 'var(--g-color-base-misc-light-hover)', + 'var(--g-color-base-neutral-light-hover)', + + 'var(--g-color-base-info-medium-hover)', + 'var(--g-color-base-positive-medium-hover)', + 'var(--g-color-base-warning-medium-hover)', + 'var(--g-color-base-danger-medium-hover)', + 'var(--g-color-base-utility-medium-hover)', + 'var(--g-color-base-misc-medium-hover)', + 'var(--g-color-base-neutral-medium-hover)', +]; + +export const TITLE_WIDGET_TEXT_COLORS_PRESET = [ + 'var(--g-color-text-info)', + 'var(--g-color-text-positive)', + 'var(--g-color-text-warning)', + 'var(--g-color-text-danger)', + 'var(--g-color-text-utility)', + 'var(--g-color-text-misc)', + + 'var(--g-color-text-info-heavy)', + 'var(--g-color-text-positive-heavy)', + 'var(--g-color-text-warning-heavy)', + 'var(--g-color-text-danger-heavy)', + 'var(--g-color-text-utility-heavy)', + 'var(--g-color-text-misc-heavy)', +]; + +export const CustomPaletteTextColors = { + PRIMARY: 'var(--g-color-text-primary)', + COMPLEMENTARY: 'var(--g-color-text-complementary)', + SECONDARY: 'var(--g-color-text-secondary)', + HINT: 'var(--g-color-text-hint)', + INVERTED_PRIMARY: 'var(--g-color-text-inverted-primary)', +} as const; + +export type CustomPaletteTextColor = ValueOf; + +export const CONTROLS_PLACEMENT_MODE = { + AUTO: 'auto', + PERCENT: '%', + PIXELS: 'px', +} as const; + +export function getDefaultDashWidgetBgColorByType(type: DashTabItemType) { + return type === DashTabItemType.Widget + ? CustomPaletteBgColors.LIKE_CHART + : CustomPaletteBgColors.NONE; +} + +export function getColorSettingsWithValue( + color: string | undefined, + enableSeparateThemeColorSelector: boolean, +) { + return enableSeparateThemeColorSelector ? {light: color, dark: color} : color; +} diff --git a/src/shared/constants/wizard.ts b/src/shared/constants/wizard.ts new file mode 100644 index 0000000..70d31a3 --- /dev/null +++ b/src/shared/constants/wizard.ts @@ -0,0 +1,86 @@ +import type {DATASET_FIELD_TYPES, ServerFieldFormatting} from '../types'; +import {NumberFormatType} from '../types'; + +import {WizardVisualizationId} from './visualization'; + +export const VISUALIZATIONS_WITH_NAVIGATOR = new Set([ + WizardVisualizationId.Line, + WizardVisualizationId.Area, + WizardVisualizationId.Area100p, + WizardVisualizationId.Column, +]); + +export enum NavigatorModes { + Show = 'show', + Hide = 'hide', +} + +export type NavigatorPeriod = { + type: DATASET_FIELD_TYPES; + value: string; + period: Period; +}; + +export type Period = 'month' | 'year' | 'day' | 'hour' | 'week' | 'quarter'; + +export enum NavigatorLinesMode { + All = 'all', + Selected = 'selected', +} + +export enum PseudoFieldTitle { + MeasureNames = 'Measure Names', + ColumnNames = 'Column Names', + MeasureValues = 'Measure Values', +} + +export const DEFAULT_FORMATTING: ServerFieldFormatting = { + format: NumberFormatType.Number, + showRankDelimiter: true, + prefix: '', + postfix: '', + unit: undefined, + labelMode: 'absolute', + precision: undefined, +}; + +export const DEFAULT_FLOAT_NUMBERS = 2; +export const DEFAULT_INTEGER_NUMBERS = 0; +export const MAX_SEGMENTS_NUMBER = 25; + +export enum BarsColorType { + Gradient = 'gradient', + OneColor = 'one-color', + TwoColor = 'two-color', +} + +export enum BarsAlignValues { + Left = 'left', + Right = 'right', + Default = 'default', +} + +export const enum PlaceholderIndexes { + xPlaceholder, + yPlaceholder, + y2Placeholder, +} + +export enum SortDirection { + ASC = 'ASC', + DESC = 'DESC', +} + +export const ZoomMode = { + Auto: 'auto', + Manual: 'manual', +} as const; + +export type ZoomModes = (typeof ZoomMode)[keyof typeof ZoomMode]; + +export const MapCenterMode = { + Auto: 'auto', + Manual: 'manual', +} as const; + +export type MapCenterModes = (typeof MapCenterMode)[keyof typeof MapCenterMode]; diff --git a/src/shared/constants/workbook-transfer.ts b/src/shared/constants/workbook-transfer.ts new file mode 100644 index 0000000..84171d4 --- /dev/null +++ b/src/shared/constants/workbook-transfer.ts @@ -0,0 +1,20 @@ +import {EntryScope} from '..'; + +export const TRANSFER_UNKNOWN_ENTRY_ID = 'UNKNOWN_ENTRY'; + +export enum TransferErrorCode { + TransferInvalidVersion = 'ERR.UI_API.TRANSFER_INVALID_VERSION', + TransferMissingLinkedEndtry = 'ERR.UI_API.TRANSFER_MISSING_LINKED_ENTRY', + TransferInvalidEntryData = 'ERR.UI_API.TRANSFER_INVALID_ENTRY_DATA', + TransferInvalidEntryScope = 'ERR.UI_API.TRANSFER_INVALID_ENTRY_SCOPE', + TransferInvalidToken = 'ERR.UI_API.TRANSFER_INVALID_TOKEN', +} + +export const TransferCapabilities = { + dependencies: { + [EntryScope.Connection]: [], + [EntryScope.Dataset]: [EntryScope.Connection], + [EntryScope.Widget]: [EntryScope.Dataset, EntryScope.Connection], + [EntryScope.Dash]: [EntryScope.Widget, EntryScope.Dataset], + }, +}; diff --git a/src/shared/constants/workbooks.ts b/src/shared/constants/workbooks.ts new file mode 100644 index 0000000..70c59cd --- /dev/null +++ b/src/shared/constants/workbooks.ts @@ -0,0 +1,10 @@ +import type {ValueOf} from '..'; + +export const WORKBOOK_STATUS = { + CREATING: 'creating', + DELETING: 'deleting', + ACTIVE: 'active', + DELETED: 'deleted', +} as const; + +export type WorkbookStatus = ValueOf; diff --git a/src/shared/constants/yfm.ts b/src/shared/constants/yfm.ts new file mode 100644 index 0000000..b70c6de --- /dev/null +++ b/src/shared/constants/yfm.ts @@ -0,0 +1,25 @@ +export const MAGICLINK_CLASSNAME = 'magiclinks'; + +export const YFM_COLORIFY_MARKDOWN_CLASSNAME = 'yfm-colorify'; + +export const enum YfmQa { + WrapperHtml = 'yfm-wrapper-html', +} + +export const enum YfmTokenTypes { + TemplateOpen = 'template_open', + DefinitionOpen = 'dfn_open', + TermOpen = 'term_open', + Inline = 'inline', +} + +export const enum YfmAttributes { + Id = 'id', + TermKey = 'term-key', + AriaDescribedby = 'aria-describedby', +} + +export const YfmMetaScripts = { + LATEX: 'extension:latex', + MERMAID: 'extension:mermaid', +}; diff --git a/src/shared/endpoints/constants/index.ts b/src/shared/endpoints/constants/index.ts new file mode 100644 index 0000000..ff6b15c --- /dev/null +++ b/src/shared/endpoints/constants/index.ts @@ -0,0 +1,7 @@ +import {removeLastSlash} from '../utils'; + +import {opensourceEndpoints} from './opensource'; + +export const endpoints = removeLastSlash( + opensourceEndpoints, +) as unknown as typeof opensourceEndpoints; diff --git a/src/shared/endpoints/constants/opensource.ts b/src/shared/endpoints/constants/opensource.ts new file mode 100644 index 0000000..95bec8c --- /dev/null +++ b/src/shared/endpoints/constants/opensource.ts @@ -0,0 +1,58 @@ +export const opensourceEndpoints = { + development: { + api: { + us: process.env.US_ENDPOINT || 'http://localhost:3030', + bi: process.env.BI_API_ENDPOINT || 'http://localhost:8010', + biData: process.env.BI_DATA_ENDPOINT || 'http://localhost:8020', + metaManager: process.env.META_MANAGER_ENDPOINT || 'http://localhost:3050', + + charts: process.env.CHARTS_ENDPOINT || '/', + uploader: process.env.UI_UPLOADER_ENDPOINT || '/uploader', + + auth: process.env.AUTH_ENDPOINT || 'http://localhost:8088', + s3Proxy: process.env.S3_PROXY_ENDPOINT || '', + }, + ui: { + gateway: process.env.UI_GATEWAY_ENDPOINT || '/gateway', + charts: process.env.UI_CHARTS_ENDPOINT || '/', + connections: process.env.UI_CONNECTIONS_ENDPOINT || '/connections', + dataset: process.env.UI_DATASET_ENDPOINT || '/datasets', + wizard: process.env.UI_WIZARD_ENDPOINT || '/wizard', + ql: process.env.UI_QL_ENDPOINT || '/ql', + dash: process.env.UI_DASH_ENDPOINT || '/dashboards', + navigation: process.env.UI_NAVIGATION_ENDPOINT || '/navigation', + widgets: process.env.UI_WIDGETS_ENDPOINT || '/widgets', + preview: process.env.UI_PREVIEW_ENDPOINT || '/preview', + serviceSettings: '/settings', + editor: '/editor', + }, + }, + production: { + api: { + us: process.env.US_ENDPOINT || 'http://us:8080', + bi: process.env.BI_API_ENDPOINT || 'http://control-api:8080', + biData: process.env.BI_DATA_ENDPOINT || 'http://data-api:8080', + metaManager: process.env.META_MANAGER_ENDPOINT || 'http://meta-manager:8080', + + charts: process.env.CHARTS_ENDPOINT || '/', + uploader: process.env.UI_UPLOADER_ENDPOINT || '/uploader', + + auth: process.env.AUTH_ENDPOINT || 'http://auth:8080', + s3Proxy: process.env.S3_PROXY_ENDPOINT || '', + }, + ui: { + gateway: process.env.UI_GATEWAY_ENDPOINT || '/gateway', + charts: process.env.UI_CHARTS_ENDPOINT || '/', + connections: process.env.UI_CONNECTIONS_ENDPOINT || '/connections', + dataset: process.env.UI_DATASET_ENDPOINT || '/datasets', + wizard: process.env.UI_WIZARD_ENDPOINT || '/wizard', + ql: process.env.UI_QL_ENDPOINT || '/ql', + dash: process.env.UI_DASH_ENDPOINT || '/dashboards', + navigation: process.env.UI_NAVIGATION_ENDPOINT || '/navigation', + widgets: process.env.UI_WIDGETS_ENDPOINT || '/widgets', + preview: process.env.UI_PREVIEW_ENDPOINT || '/preview', + serviceSettings: '/settings', + editor: '/editor', + }, + }, +} as const; diff --git a/src/shared/endpoints/index.ts b/src/shared/endpoints/index.ts new file mode 100644 index 0000000..e2fb035 --- /dev/null +++ b/src/shared/endpoints/index.ts @@ -0,0 +1,12 @@ +import type {AppEnvironment} from '../constants'; + +import {endpoints} from './constants'; +import {Endpoints} from './types'; + +export {Endpoints}; + +export function getAppEndpointsConfig( + appEnviroment: AppEnvironment.Development | AppEnvironment.Production, +) { + return endpoints[appEnviroment]; +} diff --git a/src/shared/endpoints/schema/index.ts b/src/shared/endpoints/schema/index.ts new file mode 100644 index 0000000..d26eb60 --- /dev/null +++ b/src/shared/endpoints/schema/index.ts @@ -0,0 +1,32 @@ +import type {EndpointsConfig} from '@gravity-ui/gateway'; +import _ from 'lodash'; + +import {opensourceEndpoints} from './opensource'; + +const endpoints = { + opensource: opensourceEndpoints, +}; + +export function getServiceEndpoints(service: string) { + const serviceEndpoints = {} as Record>; + + for (const region in endpoints) { + if (endpoints[region as keyof typeof endpoints]) { + const regionEndpoints = endpoints[region as keyof typeof endpoints]; + + for (const environment in regionEndpoints) { + if (regionEndpoints[environment as keyof typeof regionEndpoints]) { + if (_.get(regionEndpoints, [environment, service])) { + _.set( + serviceEndpoints, + [region, environment], + _.get(regionEndpoints, [environment, service]), + ); + } + } + } + } + } + + return serviceEndpoints; +} diff --git a/src/shared/endpoints/schema/opensource.ts b/src/shared/endpoints/schema/opensource.ts new file mode 100644 index 0000000..771d0c6 --- /dev/null +++ b/src/shared/endpoints/schema/opensource.ts @@ -0,0 +1,34 @@ +import {endpoints} from '../constants'; + +export const opensourceEndpoints = { + development: { + bi: { + endpoint: endpoints.development.api.bi, + datasetDataApiEndpoint: endpoints.development.api.biData, + }, + us: { + endpoint: endpoints.development.api.us, + }, + auth: { + endpoint: endpoints.development.api.auth, + }, + metaManager: { + endpoint: endpoints.development.api.metaManager, + }, + }, + production: { + bi: { + endpoint: endpoints.production.api.bi, + datasetDataApiEndpoint: endpoints.production.api.biData, + }, + us: { + endpoint: endpoints.production.api.us, + }, + auth: { + endpoint: endpoints.production.api.auth, + }, + metaManager: { + endpoint: endpoints.production.api.metaManager, + }, + }, +}; diff --git a/src/shared/endpoints/sources/index.ts b/src/shared/endpoints/sources/index.ts new file mode 100644 index 0000000..5f07188 --- /dev/null +++ b/src/shared/endpoints/sources/index.ts @@ -0,0 +1,10 @@ +import type {AppEnvironment, AppInstallation} from '../../constants'; + +import {opensourceSources} from './opensource'; + +export const resolveSource = ( + _appInstallation: AppInstallation, + appEnvironment: AppEnvironment, +) => { + return opensourceSources[appEnvironment]!; +}; diff --git a/src/shared/endpoints/sources/opensource.ts b/src/shared/endpoints/sources/opensource.ts new file mode 100644 index 0000000..1aee5b6 --- /dev/null +++ b/src/shared/endpoints/sources/opensource.ts @@ -0,0 +1,21 @@ +import {endpoints} from '../constants'; + +import type {SourceDeclaration} from './types'; + +const CONNECTIONS_PATH = '/api/data/v1/connections'; +const DATASETS_PATH = '/api/data/v2/datasets'; + +export const opensourceSources: SourceDeclaration = { + development: { + bi: endpoints.development.api.biData, + bi_connections: endpoints.development.api.biData + CONNECTIONS_PATH, + bi_datasets: endpoints.development.api.biData + DATASETS_PATH, + us: endpoints.development.api.us, + }, + production: { + bi: endpoints.production.api.biData, + bi_connections: endpoints.production.api.biData + CONNECTIONS_PATH, + bi_datasets: endpoints.production.api.biData + DATASETS_PATH, + us: endpoints.production.api.us, + }, +}; diff --git a/src/shared/endpoints/sources/types.ts b/src/shared/endpoints/sources/types.ts new file mode 100644 index 0000000..9942341 --- /dev/null +++ b/src/shared/endpoints/sources/types.ts @@ -0,0 +1,28 @@ +type SourceEnvDeclaration = { + bi?: string; + bi_connections?: string; + bi_datasets?: string; + bi_datasets_embed?: string; + bi_connections_embed?: string; + us?: string; + billing?: string; + + uiConnections?: string; + uiDatasets?: string; + charts?: string; + qc?: string; + stat?: string; + traf?: string; + metrics_history_processing?: string; + lunapark_api?: string; + abc?: string; +}; + +export type SourceDeclaration = { + development?: SourceEnvDeclaration; + preprod?: SourceEnvDeclaration; + staging?: SourceEnvDeclaration; + production?: SourceEnvDeclaration; + prod?: SourceEnvDeclaration; + ci?: SourceEnvDeclaration; +}; diff --git a/src/shared/endpoints/types.ts b/src/shared/endpoints/types.ts new file mode 100644 index 0000000..08ed6f7 --- /dev/null +++ b/src/shared/endpoints/types.ts @@ -0,0 +1,18 @@ +import type {AppEnvironment, AppInstallation} from '../constants'; + +export type Endpoints = { + api: Record; + ui: Record; +}; + +export type EnvironmentEndpoints = { + [key in AppEnvironment]?: Endpoints; +}; + +export type InstallationEndpoints = { + [key in AppInstallation]: EnvironmentEndpoints; +}; + +export type SourceEndpoints = { + [key in AppEnvironment]?: string; +}; diff --git a/src/shared/endpoints/utils.ts b/src/shared/endpoints/utils.ts new file mode 100644 index 0000000..ee938e9 --- /dev/null +++ b/src/shared/endpoints/utils.ts @@ -0,0 +1,10 @@ +// Endpoints do not end with /, and when composing an url, the relative part begins with /... +// So it is not possible to simply specify /, because in the end the url will look like //. +// If you leave an empty string in the env variable, this value will be ignored, +// therefore we specify / and then we remove all slashes at the end of the line with endpoint +export function removeLastSlash(data: object) { + return Object.entries(data).reduce((result: Record, [key, value]) => { + result[key] = typeof value === 'object' ? removeLastSlash(value) : value.replace(/\/$/, ''); + return result; + }, {}); +} diff --git a/src/shared/index.ts b/src/shared/index.ts new file mode 100644 index 0000000..cffd00a --- /dev/null +++ b/src/shared/index.ts @@ -0,0 +1,7 @@ +export {oldSchema} from './old-schema'; +export type {schema, authSchema} from './schema'; + +export * from './constants'; +export * from './modules'; +export * from './types'; +export * from './utils'; diff --git a/src/shared/modules/__tests__/charts-shared.test.ts b/src/shared/modules/__tests__/charts-shared.test.ts new file mode 100644 index 0000000..621e32e --- /dev/null +++ b/src/shared/modules/__tests__/charts-shared.test.ts @@ -0,0 +1,289 @@ +import MockDate from 'mockdate'; + +import type {Field} from '../../types'; +import { + Operations, + OperationsWithoutValue, + resolveIntervalDate, + resolveOperation, + resolveRelativeDate, + splitParamsToParametersAndFilters, +} from '../charts-shared'; + +describe('charts-shared date helpers', () => { + MockDate.set('2020-02-14T22:34:55.359Z'); + + test.each([ + [null, null], + [undefined, null], + ['', null], + ['2020-01-01T00:00:00.000Z', null], + ['__relative_*0d', null], + ['__relative_+0T', null], + ['__relative_-0T_ed', null], + ['__relative_+5d_eD', null], + ['__interval___relative_-14d___relative_-7d', null], + ['__relative_-0d', '2020-02-14T00:00:00.000Z'], + ['__relative_+1w', '2020-02-21T00:00:00.000Z'], + ['__relative_-2M', '2019-12-14T00:00:00.000Z'], + ['__relative_+3Q', '2020-11-14T00:00:00.000Z'], + ['__relative_-4y', '2016-02-14T00:00:00.000Z'], + ['__relative_-0d_sw', '2020-02-10T00:00:00.000Z'], + ['__relative_+1w_eM', '2020-02-29T23:59:59.999Z'], + ['__relative_-2M_sQ', '2019-10-01T00:00:00.000Z'], + ['__relative_+3Q_ey', '2020-12-31T23:59:59.999Z'], + ['__relative_-4y_sw', '2016-02-08T00:00:00.000Z'], + ['__relative_-24h', '2020-02-13T22:34:55.359Z'], + ['__relative_+60m', '2020-02-14T23:34:55.359Z'], + ['__relative_-300s', '2020-02-14T22:29:55.359Z'], + ['__relative_+1000ms', '2020-02-14T22:34:56.359Z'], + ['__relative_-24h_em', '2020-02-13T22:34:59.999Z'], + ['__relative_+60m_ss', '2020-02-14T23:34:55.000Z'], + ['__relative_-300s_eh', '2020-02-14T22:59:59.999Z'], + ['__relative_+1000ms_sd', '2020-02-14T00:00:00.000Z'], + ['now', '2020-02-14T22:34:55.359Z'], + ['now-0d', '2020-02-14T22:34:55.359Z'], + ['now+1w', '2020-02-21T22:34:55.359Z'], + ['now-2M', '2019-12-14T22:34:55.359Z'], + ['now+3Q', '2020-11-14T22:34:55.359Z'], + ['now-4y', '2016-02-14T22:34:55.359Z'], + ['now-24h', '2020-02-13T22:34:55.359Z'], + ['now+60m', '2020-02-14T23:34:55.359Z'], + ['now-300s', '2020-02-14T22:29:55.359Z'], + ['now+1000ms', null], + ['now/s', '2020-02-14T22:34:55.000Z'], + ['now/m', '2020-02-14T22:34:00.000Z'], + ['now/d', '2020-02-14T00:00:00.000Z'], + ['now/w', '2020-02-10T00:00:00.000Z'], + ['now/M', '2020-02-01T00:00:00.000Z'], + ['now/Q', '2020-01-01T00:00:00.000Z'], + ['now/y', '2020-01-01T00:00:00.000Z'], + ])('resolveRelativeDate (args: %j)', (input, expected) => { + const result = resolveRelativeDate(input); + expect(result).toEqual(expected); + }); + + test.each([ + [null, null], + [undefined, null], + ['', null], + ['__relative_-0d', null], + ['2020-01-01T00:00:00.000Z', null], + ['__interval___relative_-0d_', null], + ['__interval_2020-01-01_', null], + ['__interval_2020-01-01T00:15___relative_-15m_sD', null], + ['__interval___relative_-1d_e___relative_+1h_sd', null], + ['__interval___relative_+1h_ed___relative_-1d_M', null], + ['__interval_01_2021', {from: '01', to: '2021'}], + ['__interval_2020-01-01T00:00_2021-01-01', {from: '2020-01-01T00:00', to: '2021-01-01'}], + ['__interval_2020-01-01T23:59_2010-01-01', {from: '2020-01-01T23:59', to: '2010-01-01'}], + [ + '__interval_2020-01-01T00:00:00_2021-01-01T00:00:00.000Z', + {from: '2020-01-01T00:00:00', to: '2021-01-01T00:00:00.000Z'}, + ], + ['__interval_2020___relative_-0d', {from: '2020', to: '2020-02-14T23:59:59.999Z'}], + [ + '__interval___relative_+0d_2099-12-31', + {from: '2020-02-14T00:00:00.000Z', to: '2099-12-31'}, + ], + [ + '__interval_2020-01-01T00:15___relative_-15m', + {from: '2020-01-01T00:15', to: '2020-02-14T22:19:55.359Z'}, + ], + [ + '__interval___relative_-1d___relative_+1h', + {from: '2020-02-13T00:00:00.000Z', to: '2020-02-14T23:34:55.359Z'}, + ], + [ + '__interval___relative_+1h___relative_-1d', + {from: '2020-02-14T23:34:55.359Z', to: '2020-02-13T23:59:59.999Z'}, + ], + ['__interval_2020___relative_-0d_sw', {from: '2020', to: '2020-02-10T00:00:00.000Z'}], + [ + '__interval___relative_+0d_eM_2099-12-31', + {from: '2020-02-29T23:59:59.999Z', to: '2099-12-31'}, + ], + [ + '__interval_2020-01-01T00:15___relative_-15m_sQ', + {from: '2020-01-01T00:15', to: '2020-01-01T00:00:00.000Z'}, + ], + [ + '__interval___relative_-1d_ey___relative_+1h_sd', + {from: '2020-12-31T23:59:59.999Z', to: '2020-02-14T00:00:00.000Z'}, + ], + [ + '__interval___relative_+1h_ed___relative_-1d_sM', + {from: '2020-02-14T23:59:59.999Z', to: '2020-02-01T00:00:00.000Z'}, + ], + ['__interval_now_2021', {from: '2020-02-14T22:34:55.359Z', to: '2021'}], + ['__interval_01_now', {from: '01', to: '2020-02-14T22:34:55.359Z'}], + [ + '__interval_now_now+1d', + {from: '2020-02-14T22:34:55.359Z', to: '2020-02-15T22:34:55.359Z'}, + ], + [ + '__interval_now/d_now/d', + {from: '2020-02-14T00:00:00.000Z', to: '2020-02-14T23:59:59.999Z'}, + ], + ])('resolveIntervalDate (args: %j)', (input, expected) => { + const result = resolveIntervalDate(input); + expect(result).toEqual(expected); + }); +}); + +describe('charts-shared resolveOperation', () => { + const simpleUrlValue = '__eq_2'; + const complexUrlValue = '__between_date1_date2'; + const symbolsInUrlValue = '__contains_symbols_~_[]_\\_|_()_*_&_^_%_$_#_№_@_"_!_;_:_{}_='; + const missedOperation = '__thisOperationIsMissed_{value}'; + const intervalUrlValue = '__interval___relative_+0d_2099-12-31'; + const withoutValue = '__eq_'; + + it('should parse value from url and return object {operation: Operations, value: string}', () => { + const expectedResult = { + operation: Operations.EQ, + value: '2', + }; + + const result = resolveOperation(simpleUrlValue); + + expect(result).toEqual(expectedResult); + }); + + it('should properly parse values with multiple underscores', () => { + const expectedResult = { + operation: Operations.BETWEEN, + value: 'date1_date2', + }; + + const result = resolveOperation(complexUrlValue); + + expect(result).toEqual(expectedResult); + }); + + it('should properly parse values with any characters', () => { + const expectedResult = { + operation: Operations.CONTAINS, + value: 'symbols_~_[]_\\_|_()_*_&_^_%_$_#_№_@_"_!_;_:_{}_=', + }; + + const result = resolveOperation(symbolsInUrlValue); + + expect(result).toEqual(expectedResult); + }); + + it('should return null if input incoming value is casted to false', () => { + const falsyValues = [null, undefined, '']; + + const expectedResult = [null, null, null]; + + const result = falsyValues.map((val) => resolveOperation(val)); + + expect(result).toEqual(expectedResult); + }); + + it('should return the IN/BETWEEN default operation and the entered value, if the operator is not listed in Operations', () => { + const expectedResult = { + operation: Operations.IN, + value: missedOperation, + }; + + const result = resolveOperation(missedOperation); + + expect(result).toEqual(expectedResult); + }); + + it('should return the IN/BETWEEN default operation and the entered value, if operation do not have value', () => { + const expectedResult = { + operation: Operations.IN, + value: withoutValue, + }; + + const result = resolveOperation(withoutValue); + + expect(result).toEqual(expectedResult); + }); + + it('should return the default BETWEEN operation if the operation has a value __interval', () => { + const expectedResult = { + operation: Operations.BETWEEN, + value: intervalUrlValue, + }; + + const result = resolveOperation(intervalUrlValue); + + expect(result).toEqual(expectedResult); + }); + + it.each([ + ...Object.values(OperationsWithoutValue).map((operation) => [`__${operation}_`, operation]), + ] as [string, string][])( + 'should return operation and value = undefined if operation does not imply value', + (stringOperation, rawOperation) => { + const result = resolveOperation(stringOperation); + + expect(result).toEqual({ + operation: rawOperation, + value: undefined, + }); + }, + ); + + it.each([[{fake: 1}], [1], [[]], [true]])( + 'should return value with IN operation if value is not string', + (value: any) => { + const expectedValue = { + operation: Operations.IN, + value: value, + }; + + const result = resolveOperation(value); + + expect(result).toEqual(expectedValue); + }, + ); +}); + +describe('charts-shared splitParamsToParametersAndFilters', () => { + const parameterFields = [ + {guid: 'test_guid_1', calc_mode: 'parameter', title: 'test_guid_1'}, + {guid: 'test_guid_2', calc_mode: 'parameter', title: 'test_guid_2'}, + {guid: 'test_guid_3', calc_mode: 'parameter', title: 'test_guid_3'}, + {guid: 'test_guid_4', calc_mode: 'parameter', title: 'test_guid_4'}, + ] as Field[]; + it('should delete operations from parameters', () => { + const searchParams: Array<[string, string]> = [ + ['test_guid_1', '__eq_abc'], + ['test_guid_2', '__in_test'], + ['test_guid_3', 'abc'], + ]; + const expectedSearchParams = [ + ['test_guid_1', 'abc'], + ['test_guid_2', 'test'], + ['test_guid_3', 'abc'], + ]; + + const result = splitParamsToParametersAndFilters(searchParams, parameterFields); + + expect(result.parametersParams).toEqual(expectedSearchParams); + }); + + it('Should delete existing operations in parameters', () => { + const searchParams: Array<[string, string]> = [ + ['test_guid_1', '__interval_22.12.2023_23.12.2023'], + ['test_guid_2', '__between___interval_22.12.2023_23.12.2023'], + ['test_guid_3', '__in_abc'], + ['test_guid_4', '__qwe_def'], + ]; + const expectedSearchParams = [ + ['test_guid_1', '__interval_22.12.2023_23.12.2023'], + ['test_guid_2', '__interval_22.12.2023_23.12.2023'], + ['test_guid_3', 'abc'], + ['test_guid_4', '__qwe_def'], + ]; + + const result = splitParamsToParametersAndFilters(searchParams, parameterFields); + + expect(result.parametersParams).toEqual(expectedSearchParams); + }); +}); diff --git a/src/shared/modules/__tests__/helpers.test.ts b/src/shared/modules/__tests__/helpers.test.ts new file mode 100644 index 0000000..b672230 --- /dev/null +++ b/src/shared/modules/__tests__/helpers.test.ts @@ -0,0 +1,71 @@ +import MockDate from 'mockdate'; + +import {prepareFilterValuesWithOperations} from '../helpers'; + +MockDate.set('2020-02-14T22:34:55.359Z'); + +describe('prepareFilterValuesWithOperations', () => { + test('should return an array of filter values and operation (IN or BETWEEN for the date by default)', () => { + const result1 = prepareFilterValuesWithOperations({ + values: ['__relative_-1M'], + }); + + expect(result1).toEqual({ + values: ['2020-01-14T00:00:00.000Z'], + operations: ['IN'], + }); + + const result2 = prepareFilterValuesWithOperations({ + values: ['robot'], + }); + + expect(result2).toEqual({ + values: ['robot'], + operations: ['IN'], + }); + }); + + test('for an interval, should return the BETWEEN operator and an array value specifying the range', () => { + const expectedResult = { + operations: ['BETWEEN'], + values: [['2021-08-18T00:00:00.000Z', '2021-08-20T23:59:59.999Z']], + }; + + expect( + prepareFilterValuesWithOperations({ + values: ['__interval_2021-08-18T00:00:00.000Z_2021-08-20T23:59:59.999Z'], + }), + ).toEqual(expectedResult); + }); + + test('should return an array of filter values and operation (specified in values)', () => { + const result = prepareFilterValuesWithOperations({ + values: ['__eq_2'], + }); + + expect(result).toEqual({ + values: ['2'], + operations: ['EQ'], + }); + }); + + test('if the operation is not defined, the function must return the passed value as is and select the default operation (IN)', () => { + const result1 = prepareFilterValuesWithOperations({ + values: [''], + }); + + expect(result1).toEqual({ + values: [''], + operations: ['IN'], + }); + + const result2 = prepareFilterValuesWithOperations({ + values: ['Value without operator'], + }); + + expect(result2).toEqual({ + values: ['Value without operator'], + operations: ['IN'], + }); + }); +}); diff --git a/src/shared/modules/bi-api/index.ts b/src/shared/modules/bi-api/index.ts new file mode 100644 index 0000000..8024855 --- /dev/null +++ b/src/shared/modules/bi-api/index.ts @@ -0,0 +1 @@ +export * from './mappers'; diff --git a/src/shared/modules/bi-api/mappers/index.ts b/src/shared/modules/bi-api/mappers/index.ts new file mode 100644 index 0000000..cb50b32 --- /dev/null +++ b/src/shared/modules/bi-api/mappers/index.ts @@ -0,0 +1 @@ +export * from './v2'; diff --git a/src/shared/modules/bi-api/mappers/v2.ts b/src/shared/modules/bi-api/mappers/v2.ts new file mode 100644 index 0000000..50bacd7 --- /dev/null +++ b/src/shared/modules/bi-api/mappers/v2.ts @@ -0,0 +1,156 @@ +import groupBy from 'lodash/groupBy'; + +import type { + ApiV2Filter, + ApiV2Parameter, + ApiV2RequestField, + DatasetField, + Field, + FieldRoleSpec, + FilterField, +} from '../../../types'; +import {isDateField} from '../../../types'; +import {resolveIntervalDate, resolveRelativeDate} from '../../charts-shared'; +import {prepareFilterValues} from '../../helpers'; + +export const mapFieldToApiV2FieldsFormat = ( + field: Field | DatasetField | {guid: string}, + role: FieldRoleSpec, +): ApiV2RequestField[] => { + if (role === 'range') { + return [ + { + ref: { + type: 'id', + id: field.guid, + }, + role_spec: { + role, + range_type: 'min', + }, + }, + { + ref: { + type: 'id', + id: field.guid, + }, + role_spec: { + role, + range_type: 'max', + }, + }, + ]; + } + + return [ + { + ref: { + type: 'id', + id: field.guid, + }, + role_spec: { + role, + }, + }, + ]; +}; + +export const mapFilterToApiV2FiltersFormat = (filter: FilterField): ApiV2Filter => { + const column = filter.guid; + const value = filter.filter.value; + const values = Array.isArray(value) ? value : [value]; + const operation = filter.filter.operation.code; + + // Check legacy datetime value because of old configs + if (isDateField(filter)) { + return { + values: ([] as string[]).concat(...prepareFilterValues({values})), + operation, + ref: {type: 'id', id: column}, + }; + } + + return { + values, + operation, + ref: {type: 'id', id: column}, + }; +}; + +export const mapParameterToApiV2ParametersFormat = ( + parameter: Field | DatasetField, +): ApiV2Parameter => { + let defaultValue = parameter.default_value; + if (typeof defaultValue === 'string') { + if (/^__relative/.test(defaultValue)) { + defaultValue = resolveRelativeDate(defaultValue)!; + } else if (/^__interval/.test(defaultValue)) { + const parsedInterval = resolveIntervalDate(defaultValue)!; + + defaultValue = `__interval_${parsedInterval.from}_${parsedInterval.to}`; + } + } + + return { + ref: { + type: 'id', + id: parameter.guid, + }, + value: defaultValue!, + }; +}; + +export const getFieldsApiV2RequestSection = ( + fields: (Field | DatasetField | {guid: string})[], + role: FieldRoleSpec, +): ApiV2RequestField[] => { + return fields.reduce( + (apiV2RequestFields, field) => [ + ...apiV2RequestFields, + ...mapFieldToApiV2FieldsFormat(field, role), + ], + [] as ApiV2RequestField[], + ); +}; + +export const getFiltersApiV2RequestSection = (filters: FilterField[]): ApiV2Filter[] => { + const filtersWithoutDashboardFilters = filters.filter((item) => !item.disabled); + return filtersWithoutDashboardFilters.reduce( + (apiV2Filters, filter) => [...apiV2Filters, mapFilterToApiV2FiltersFormat(filter)], + [] as ApiV2Filter[], + ); +}; + +export const getParametersApiV2RequestSection = ({ + parameters, + dashboardParameters = [], +}: { + parameters: (Field | DatasetField)[]; + dashboardParameters: (Field | DatasetField)[]; +}) => { + const groupedDashParams = groupBy(dashboardParameters, (p) => p.guid); + const dashParams = Object.values(groupedDashParams).map((items) => { + if (items.length > 1) { + return { + ...items[0], + default_value: items.map((item) => item.default_value).join(','), + }; + } + + return items[0]; + }); + + const dashboardParametersGuids = dashParams.reduce( + (acc, parameter) => ({...acc, [parameter.guid]: true}), + {} as Record, + ); + const filteredParameters = parameters.filter( + (parameter) => !dashboardParametersGuids[parameter.guid], + ); + + const requestParameters = [...filteredParameters, ...dashParams]; + + return requestParameters.reduce((apiV2Parametes, parameter) => { + return [...apiV2Parametes, mapParameterToApiV2ParametersFormat(parameter)]; + }, [] as ApiV2Parameter[]); +}; diff --git a/src/shared/modules/charts-shared.ts b/src/shared/modules/charts-shared.ts new file mode 100644 index 0000000..a006ecf --- /dev/null +++ b/src/shared/modules/charts-shared.ts @@ -0,0 +1,350 @@ +import {dateTime, dateTimeParse} from '@gravity-ui/date-utils'; +import type {DurationUnit} from '@gravity-ui/date-utils'; + +import type {ServerDatasetField} from '../types'; + +import {isParameter} from './helpers'; + +export type Scale = 'y' | 'Q' | 'M' | 'w' | 'd' | 'h' | 'm' | 's' | 'ms'; + +export const CLIP_SCALES: Scale[] = ['d', 'w', 'M', 'Q', 'y']; + +export const ScaleToDurationUnit: Record = { + y: 'year', + Q: 'quarter', + M: 'month', + w: 'week', + d: 'day', + h: 'hour', + m: 'minute', + s: 'second', + ms: 'millisecond', +}; + +export type Sign = '+' | '-'; + +export type CastType = 's' | 'e'; + +export type IntervalPart = 'start' | 'end'; + +export type FilterValue = string | null | undefined; + +export enum Operations { + IN = 'IN', + NIN = 'NIN', + EQ = 'EQ', + NE = 'NE', + GT = 'GT', + LT = 'LT', + GTE = 'GTE', + LTE = 'LTE', + ISNULL = 'ISNULL', + ISNOTNULL = 'ISNOTNULL', + ISTARTSWITH = 'ISTARTSWITH', + STARTSWITH = 'STARTSWITH', + IENDSWITH = 'IENDSWITH', + ENDSWITH = 'ENDSWITH', + ICONTAINS = 'ICONTAINS', + CONTAINS = 'CONTAINS', + NOTICONTAINS = 'NOTICONTAINS', + NOTCONTAINS = 'NOTCONTAINS', + BETWEEN = 'BETWEEN', + LENEQ = 'LENEQ', + LENGT = 'LENGT', + LENGTE = 'LENGTE', + LENLT = 'LENLT', + LENLTE = 'LENLTE', + NO_SELECTED_VALUES = 'NO_SELECTED_VALUES', +} + +export enum OperationsWithoutValue { + ISNULL = Operations.ISNULL, + ISNOTNULL = Operations.ISNOTNULL, +} + +type ParsedDlRelativeDate = [Sign, string, Scale, CastType, Scale]; + +export const IS_NULL_FILTER_TEMPLATE = `__${Operations.ISNULL}_`; + +export const DEFAULT_OPERATION_TITLE: Record = { + ...Operations, + [Operations.EQ]: '=', + [Operations.NE]: '≠', + [Operations.GT]: '>', + [Operations.LT]: '<', + [Operations.GTE]: '≥', + [Operations.LTE]: '≤', +}; + +export const getDefaultTitleForOperation = (operation: Operations) => + DEFAULT_OPERATION_TITLE[operation]; + +const IntervalRegExp = { + DL: /^__interval_(__relative_\W\d+\w(?:_\w\w)?|[^_]+)_(.+)$/, + DATE_UTILS: /^__interval_(now(?:_now)?|[^_]+)_(.+)$/, +}; + +export type FiltersOperationFromURL = { + operation: Operations; + value: string; +}; + +export function getParsedRelativeDate(value: FilterValue) { + if (!value) { + return null; + } + + const match = value.match( + /^__relative_([+-])(\d+)(y|Q|M|w|d|h|m|s|ms)(?:_([se])([yQMwdhms]))?$/, + ); + + if (!match) { + return null; + } + + const [, sign, amount, scale, cast, castScale] = match; + + return [sign, amount, scale, cast, castScale] as ParsedDlRelativeDate; +} + +function resolveDlRelative(value: FilterValue, intervalPart: IntervalPart = 'start') { + const parsedDate = getParsedRelativeDate(value); + + if (!parsedDate) { + return null; + } + + const [sign, amount, scale, cast, castScale] = parsedDate; + + const durationUnit = ScaleToDurationUnit[scale]; + let date = dateTime({timeZone: 'UTC'}).add(`${sign}${amount}`, durationUnit); + + if (cast) { + const castUnit = ScaleToDurationUnit[castScale]; + date = cast === 's' ? date.startOf(castUnit) : date.endOf(castUnit); + } else if (CLIP_SCALES.indexOf(scale) !== -1) { + date = intervalPart === 'start' ? date.startOf('day') : date.endOf('day'); + } + + return date.toISOString(); +} + +function isDateUtilsRelative(value: FilterValue) { + return typeof value === 'string' && value.startsWith('now'); +} + +function resolveDateUtilsRelative(value: FilterValue, intervalPart?: IntervalPart) { + if (!isDateUtilsRelative(value)) { + return null; + } + + const date = dateTimeParse(value as string, { + timeZone: 'UTC', + roundUp: intervalPart === 'end', + }); + + return date?.toISOString() || null; +} + +// processes the relative format and generates an ISO date +// for scales from a day or more, depending on intervalPart, the time is set to the beginning/end of the day: +// * IntervalPart.Start - YYYY-MM-DDT00:00:00.000Z +// * IntervalPart.End - YYYY-MM-DDT23:59:59.999Z +export function resolveRelativeDate(value: FilterValue, intervalPart?: IntervalPart) { + const parsedDl = resolveDlRelative(value, intervalPart); + const parsedDateUtils = resolveDateUtilsRelative(value, intervalPart); + + return parsedDl || parsedDateUtils; +} + +export function getParsedIntervalDates(value: FilterValue) { + if (!value) { + return null; + } + + const match = value.match(IntervalRegExp.DL) || value.match(IntervalRegExp.DATE_UTILS); + + if (!match) { + return null; + } + + const [, matchedFrom, matchedTo] = match; + + return [matchedFrom, matchedTo]; +} + +// processes interval format and generates two ISO dates: {from, to} +// if from or to is an incorrect date, return null +export function resolveIntervalDate(value: FilterValue) { + const parsedData = getParsedIntervalDates(value); + + if (!parsedData) { + return null; + } + + const [parsedFrom, parsedTo] = parsedData; + const from = + resolveRelativeDate(parsedFrom, 'start') || + (dateTime({input: parsedFrom}).isValid() && parsedFrom) || + null; + const to = + resolveRelativeDate(parsedTo, 'end') || + (dateTime({input: parsedTo}).isValid() && parsedTo) || + null; + + if (from && to) { + return {from, to}; + } + + return null; +} + +export function resolveOperation( + urlValue: FilterValue, + defaultOperation?: Operations, +): FiltersOperationFromURL | null { + if (!urlValue) { + return null; + } + + // In an ideal world, urlValue should always be string. However, in Editor, the user can put in params + // absolutely anything. Therefore, if it is not a string, then we make a fallback to the old behavior. + if (typeof urlValue !== 'string') { + return getFallbackForUrlFilters(urlValue, defaultOperation); + } + + const match = urlValue.match(/^_{2}([^_]+)_([\s\S]+)?$/); + + if (!match) { + return getFallbackForUrlFilters(urlValue, defaultOperation); + } + + const operation = match[1]?.toUpperCase(); + const value = match[2]; + + if ( + typeof value === 'undefined' && + !Object.values(OperationsWithoutValue).includes( + operation as unknown as OperationsWithoutValue, + ) + ) { + return getFallbackForUrlFilters(urlValue); + } + + if (Object.values(Operations).includes(operation as unknown as Operations)) { + return { + operation: operation as Operations, + value, + }; + } + + return getFallbackForUrlFilters(urlValue); +} + +function getFallbackForUrlFilters( + urlValue: any, + defaultOperation?: Operations, +): FiltersOperationFromURL { + let operation = defaultOperation ?? Operations.IN; + + if (typeof urlValue === 'string' && urlValue.indexOf('__interval') > -1) { + operation = Operations.BETWEEN; + } + + return { + operation, + value: urlValue, + }; +} + +export function splitParamsToParametersAndFilters( + urlSearchParams: [string, string][], + fields: ServerDatasetField[], +) { + const parametersMap = fields.filter(isParameter).reduce( + (acc, field) => { + acc[field.guid] = true; + acc[field.title] = true; + return acc; + }, + {} as Record, + ); + + return urlSearchParams.reduce( + (acc, curr) => { + const paramGuid = curr[0] || ''; + + if (parametersMap[paramGuid]) { + const parameterValue = curr[1]; + const resolvedParameterValue = resolveOperation(parameterValue); + + acc.parametersParams.push([ + paramGuid, + resolvedParameterValue?.value || parameterValue, + ]); + } else { + acc.filtersParams.push(curr); + } + return acc; + }, + { + filtersParams: [], + parametersParams: [], + } as { + filtersParams: [string, string][]; + parametersParams: [string, string][]; + }, + ); +} + +export type ChartsInsight = { + locator: ChartsInsightLocator; + title: string; + message: string; + level: string; +}; + +export enum ChartsInsightLocator { + UsingDeprecatedDatetimeFields = 'using_deprecated_datetime_fields', +} + +export function isObjectWith( + value: unknown, + check: (value: unknown) => boolean, + ignore?: string[], + path = '', +): string | false { + if (!value) { + return false; + } + + if (check(value)) { + return path; + } + + if (Array.isArray(value)) { + for (let index = 0; index < value.length; index++) { + const pathToItem = isObjectWith(value[index], check, ignore, `${path}[${index}]`); + if (pathToItem) { + return pathToItem; + } + } + } + + if (typeof value === 'object') { + const entries = Object.entries(value as object); + for (let index = 0; index < entries.length; index++) { + const [key, val] = entries[index]; + if (ignore?.includes(key)) { + continue; + } + + const pathToItem = isObjectWith(val, check, ignore, path ? `${path}.${key}` : key); + if (pathToItem) { + return pathToItem; + } + } + } + + return false; +} diff --git a/src/shared/modules/colors/common-helpers.ts b/src/shared/modules/colors/common-helpers.ts new file mode 100644 index 0000000..896c654 --- /dev/null +++ b/src/shared/modules/colors/common-helpers.ts @@ -0,0 +1,45 @@ +import {QLChartType, WizardVisualizationId} from '../../constants'; +import type {Field, ServerField} from '../../types'; +import {getFakeTitleOrTitle} from '../fields'; + +type GetColorsConfigKeyOptions = { + isMeasureNames: boolean; +}; + +export const getColorsConfigKey = ( + field: ServerField | Field, + fields: (ServerField | Field)[], + options: GetColorsConfigKeyOptions, +) => { + if (options.isMeasureNames) { + const fieldTitle = getFakeTitleOrTitle(field); + const isFieldWithSameNameExist = fields.some( + (item) => + getFakeTitleOrTitle(item) === fieldTitle && item.datasetId !== field.datasetId, + ); + + if (isFieldWithSameNameExist) { + return `${fieldTitle} (${field.datasetName})`; + } + + return fieldTitle; + } + + return undefined; +}; +const chartTypeWithMultipleColors: Record< + string, + Partial> +> = { + [QLChartType.Monitoringql]: { + [WizardVisualizationId.Line]: true, + [WizardVisualizationId.Area]: true, + [WizardVisualizationId.Area100p]: true, + [WizardVisualizationId.Column]: true, + [WizardVisualizationId.Column100p]: true, + [WizardVisualizationId.Bar]: true, + [WizardVisualizationId.Bar100p]: true, + }, +}; +export const isChartSupportMultipleColors = (chartType: string, visualizationId: string): boolean => + Boolean(chartTypeWithMultipleColors[chartType]?.[visualizationId as WizardVisualizationId]); diff --git a/src/shared/modules/colors/distincts-helpers.ts b/src/shared/modules/colors/distincts-helpers.ts new file mode 100644 index 0000000..4fbfcd1 --- /dev/null +++ b/src/shared/modules/colors/distincts-helpers.ts @@ -0,0 +1,14 @@ +export const getLineTimeDistinctValue = ( + distinct: string | null | number, + prevDistinct: string, +): string => { + if (distinct === 'null') { + return prevDistinct; + } + + return prevDistinct.length > 0 ? `${prevDistinct}; ${distinct}` : `${distinct}`; +}; + +export function getDistinctValue(value: unknown) { + return value === null ? 'Null' : String(value); +} diff --git a/src/shared/modules/config/ql/__tests__/mapQlConfigToLatestVersion.test.ts b/src/shared/modules/config/ql/__tests__/mapQlConfigToLatestVersion.test.ts new file mode 100644 index 0000000..86a8773 --- /dev/null +++ b/src/shared/modules/config/ql/__tests__/mapQlConfigToLatestVersion.test.ts @@ -0,0 +1,25 @@ +import type {QlConfig} from '../../../../types'; +import {QlConfigVersions} from '../../../../types/ql/versions'; +import {mapQlConfigToLatestVersion} from '../mapQlConfigToLatestVersion'; + +describe('mapQlConfigToLatestVersion', () => { + it('should return config of the latest version', () => { + const versions = Object.values(QlConfigVersions); + + const latest = versions[versions.length - 1]; + + const config = {} as QlConfig; + + const latestConfig = mapQlConfigToLatestVersion(config, {i18n: jest.fn()}); + + expect(latestConfig.version).toEqual(latest); + }); + + it('should use only string versions', () => { + const versions = Object.values(QlConfigVersions); + + for (const version of versions) { + expect(typeof version).toBe('string'); + } + }); +}); diff --git a/src/shared/modules/config/ql/__tests__/v1.test.ts b/src/shared/modules/config/ql/__tests__/v1.test.ts new file mode 100644 index 0000000..365a502 --- /dev/null +++ b/src/shared/modules/config/ql/__tests__/v1.test.ts @@ -0,0 +1,63 @@ +import type {QLEntryDataShared} from '../../../../types'; +import {mapUndefinedConfigToV1} from '../v1/mapUndefinedConfigToV1'; + +describe('mapUndefinedConfigToV1', () => { + it('should set "nulls" setting for y placeholder to connect, if it is ignore', () => { + const config = { + visualization: { + placeholders: [ + {id: 'y', settings: {nulls: 'ignore'}}, + {id: 'y', settings: {nulls: 'ignore'}}, + ], + }, + version: undefined, + } as unknown as QLEntryDataShared; + + const result = mapUndefinedConfigToV1(config); + + expect(result).toMatchObject({ + version: '1', + visualization: { + placeholders: [ + {id: 'y', settings: {nulls: 'connect'}}, + {id: 'y', settings: {nulls: 'connect'}}, + ], + }, + }); + }); + + it('should leave "nulls" setting for y placeholder as it is, if it is not ignore', () => { + const config = { + visualization: { + placeholders: [ + {id: 'y', settings: {nulls: 'ignore'}}, + {id: 'y', settings: {nulls: 'as-0'}}, + {id: 'y', settings: {nulls: 'connect'}}, + ], + }, + version: undefined, + } as unknown as QLEntryDataShared; + + const result = mapUndefinedConfigToV1(config); + + expect(result).toMatchObject({ + version: '1', + visualization: { + placeholders: [ + {id: 'y', settings: {nulls: 'connect'}}, + {id: 'y', settings: {nulls: 'as-0'}}, + {id: 'y', settings: {nulls: 'connect'}}, + ], + }, + }); + }); + + it.each([{version: undefined, visualization: {id: 'line'}}, {version: undefined}])( + 'should return same config if placeholders does not exists', + (config) => { + const result = mapUndefinedConfigToV1(config as QLEntryDataShared); + + expect(result).toMatchObject({...config, version: '1'}); + }, + ); +}); diff --git a/src/shared/modules/config/ql/__tests__/v2.test.ts b/src/shared/modules/config/ql/__tests__/v2.test.ts new file mode 100644 index 0000000..586dac2 --- /dev/null +++ b/src/shared/modules/config/ql/__tests__/v2.test.ts @@ -0,0 +1,66 @@ +import type {QlConfigV1} from '../../../../types/config/ql/v1'; +import {QlConfigVersions} from '../../../../types/ql/versions'; +import {mapV1ConfigToV2} from '../v2/mapV1ConfigToV2'; + +const mockedI18n = (_keyset: string, _key: string) => 'Query'; + +describe('mapV1ToV2Config', () => { + it('should add queryName to all queries', () => { + const config = { + queries: [ + { + params: [], + value: 'test', + hidden: false, + }, + { + params: [ + {name: 'test-param', type: 'string', defaultValue: 'test-param-value'}, + ], + value: 'test2', + hidden: true, + }, + { + params: [], + value: 'test3', + hidden: true, + }, + ], + version: QlConfigVersions.V1, + } as unknown as QlConfigV1; + + const result = mapV1ConfigToV2(config, mockedI18n); + + expect(result.queries).toEqual([ + { + params: [], + value: 'test', + hidden: false, + queryName: 'Query 1', + }, + { + params: [{name: 'test-param', type: 'string', defaultValue: 'test-param-value'}], + value: 'test2', + hidden: true, + queryName: 'Query 2', + }, + { + params: [], + value: 'test3', + hidden: true, + queryName: 'Query 3', + }, + ]); + }); + + it('should return same config with new version, if queries doesnt exists', () => { + const config = { + version: QlConfigVersions.V1, + visualization: {id: 'line'}, + } as unknown as QlConfigV1; + + const result = mapV1ConfigToV2(config, mockedI18n); + + expect(result).toMatchObject({...config, version: '2'}); + }); +}); diff --git a/src/shared/modules/config/ql/index.ts b/src/shared/modules/config/ql/index.ts new file mode 100644 index 0000000..5ca0985 --- /dev/null +++ b/src/shared/modules/config/ql/index.ts @@ -0,0 +1 @@ +export * from './mapQlConfigToLatestVersion'; diff --git a/src/shared/modules/config/ql/mapQlConfigToLatestVersion.ts b/src/shared/modules/config/ql/mapQlConfigToLatestVersion.ts new file mode 100644 index 0000000..11c1b49 --- /dev/null +++ b/src/shared/modules/config/ql/mapQlConfigToLatestVersion.ts @@ -0,0 +1,52 @@ +import type {QlConfig, QlExtendedConfig} from '../../../types'; +import type {GetTranslationFn} from '../../../types/language'; +import {QlConfigVersions} from '../../../types/ql/versions'; + +import {mapUndefinedConfigToV1} from './v1/mapUndefinedConfigToV1'; +import {mapV1ConfigToV2} from './v2/mapV1ConfigToV2'; +import {mapV2ConfigToV3} from './v3/mapV2ConfigToV3'; +import {mapV3ConfigToV4} from './v4/mapV3ConfigToV4'; +import {mapV4ConfigToV5} from './v5/mapV4ConfigToV5'; +import {mapV5ConfigToV6} from './v6/mapV5ConfigToV6'; +import {mapV6ConfigToV7} from './v7/mapV6ConfigToV7'; + +type MapQlConfigToLatestVersionOptions = { + i18n: GetTranslationFn; +}; + +export const mapQlConfigToLatestVersion = ( + extendedConfig: QlExtendedConfig, + {i18n}: MapQlConfigToLatestVersionOptions, +): QlConfig => { + let config = extendedConfig; + + if (typeof config.version === 'undefined') { + config = mapUndefinedConfigToV1(config); + } + + if (config.version === QlConfigVersions.V1) { + config = mapV1ConfigToV2(config, i18n); + } + + if (config.version === QlConfigVersions.V2) { + config = mapV2ConfigToV3(config); + } + + if (config.version === QlConfigVersions.V3) { + config = mapV3ConfigToV4(config); + } + + if (config.version === QlConfigVersions.V4) { + config = mapV4ConfigToV5(config); + } + + if (config.version === QlConfigVersions.V5) { + config = mapV5ConfigToV6(config); + } + + if (config.version === QlConfigVersions.V6) { + config = mapV6ConfigToV7(config); + } + + return config; +}; diff --git a/src/shared/modules/config/ql/v1/mapUndefinedConfigToV1.ts b/src/shared/modules/config/ql/v1/mapUndefinedConfigToV1.ts new file mode 100644 index 0000000..9872f77 --- /dev/null +++ b/src/shared/modules/config/ql/v1/mapUndefinedConfigToV1.ts @@ -0,0 +1,41 @@ +import {PlaceholderId} from '../../../../constants'; +import {AxisNullsMode, type QLEntryDataShared} from '../../../../types'; +import type {QlConfigV1} from '../../../../types/config/ql/v1'; +import {QlConfigVersions} from '../../../../types/ql/versions'; + +export const mapUndefinedConfigToV1 = (config: QLEntryDataShared): QlConfigV1 => { + const isPlaceholdersExists = config.visualization && 'placeholders' in config.visualization; + + if (!isPlaceholdersExists) { + return { + ...config, + version: QlConfigVersions.V1, + }; + } + + const updatedVisualization = { + ...config.visualization, + placeholders: config.visualization.placeholders.map((placeholder) => { + if (placeholder.id === PlaceholderId.Y || placeholder.id === PlaceholderId.Y2) { + return { + ...placeholder, + settings: { + ...placeholder.settings, + nulls: + placeholder.settings?.nulls === AxisNullsMode.Ignore + ? AxisNullsMode.Connect + : placeholder.settings?.nulls, + }, + }; + } + + return placeholder; + }), + }; + + return { + ...config, + visualization: updatedVisualization, + version: QlConfigVersions.V1, + }; +}; diff --git a/src/shared/modules/config/ql/v2/mapV1ConfigToV2.ts b/src/shared/modules/config/ql/v2/mapV1ConfigToV2.ts new file mode 100644 index 0000000..416bcca --- /dev/null +++ b/src/shared/modules/config/ql/v2/mapV1ConfigToV2.ts @@ -0,0 +1,26 @@ +import type {QLQueryV1, QlConfigV1} from '../../../../types/config/ql/v1'; +import type {QLQueryV2, QlConfigV2} from '../../../../types/config/ql/v2'; +import type {GetTranslationFn} from '../../../../types/language'; +import {QlConfigVersions} from '../../../../types/ql/versions'; + +export const mapV1ConfigToV2 = (config: QlConfigV1, i18n: GetTranslationFn): QlConfigV2 => { + if (!config.queries) { + return { + ...config, + version: QlConfigVersions.V2, + } as QlConfigV2; + } + + const mapQuery = (query: QLQueryV1, index: number): QLQueryV2 => { + return { + ...query, + queryName: `${i18n('sql', 'label_query')} ${index + 1}`, + }; + }; + + return { + ...config, + version: QlConfigVersions.V2, + queries: config.queries.map(mapQuery), + }; +}; diff --git a/src/shared/modules/config/ql/v3/mapV2ConfigToV3.ts b/src/shared/modules/config/ql/v3/mapV2ConfigToV3.ts new file mode 100644 index 0000000..f806460 --- /dev/null +++ b/src/shared/modules/config/ql/v3/mapV2ConfigToV3.ts @@ -0,0 +1,14 @@ +import type {QlConfigV2} from '../../../../types/config/ql/v2'; +import type {QlConfigV3} from '../../../../types/config/ql/v3'; +import {QlConfigVersions} from '../../../../types/ql/versions'; + +export const mapV2ConfigToV3 = (config: QlConfigV2): QlConfigV3 => { + return { + ...config, + colors: config.colors || [], + labels: config.labels || [], + shapes: config.shapes || [], + tooltips: config.tooltips || [], + version: QlConfigVersions.V3, + }; +}; diff --git a/src/shared/modules/config/ql/v4/mapV3ConfigToV4.ts b/src/shared/modules/config/ql/v4/mapV3ConfigToV4.ts new file mode 100644 index 0000000..8e9e7bf --- /dev/null +++ b/src/shared/modules/config/ql/v4/mapV3ConfigToV4.ts @@ -0,0 +1,58 @@ +import {PlaceholderId, WizardVisualizationId} from '../../../../constants'; +import type {Placeholder} from '../../../../types'; +import type {QlConfigV3} from '../../../../types/config/ql/v3'; +import type {QlConfigV4} from '../../../../types/config/ql/v4'; +import {QlConfigVersions} from '../../../../types/ql/versions'; + +export const mapV3ConfigToV4 = (config: QlConfigV3): QlConfigV4 => { + const affectedVisualizations = [ + WizardVisualizationId.Pie, + WizardVisualizationId.Donut, + 'pie-d3', + 'donut-d3', + ]; + + let visualization = config.visualization; + + if (affectedVisualizations.includes(visualization?.id as WizardVisualizationId)) { + const currentPlaceholders = visualization.placeholders || []; + let placeholders = currentPlaceholders; + + if (!currentPlaceholders.some((p) => p.id === PlaceholderId.Colors)) { + const dimensionsPlaceholder = { + id: PlaceholderId.Dimensions, + type: PlaceholderId.Dimensions, + items: [], + }; + const colorsPlaceholder = { + items: [], + ...currentPlaceholders.find((p) => p.id === PlaceholderId.Dimensions), + id: PlaceholderId.Colors, + type: PlaceholderId.Colors, + required: false, + }; + const measuresPlaceholder = { + items: [], + id: PlaceholderId.Measures, + type: PlaceholderId.Measures, + ...currentPlaceholders.find((p) => p.id === PlaceholderId.Measures), + }; + placeholders = [ + dimensionsPlaceholder, + colorsPlaceholder, + measuresPlaceholder, + ] as Placeholder[]; + } + + visualization = { + ...visualization, + placeholders, + }; + } + + return { + ...config, + visualization, + version: QlConfigVersions.V4, + }; +}; diff --git a/src/shared/modules/config/ql/v5/mapV4ConfigToV5.ts b/src/shared/modules/config/ql/v5/mapV4ConfigToV5.ts new file mode 100644 index 0000000..be33b5b --- /dev/null +++ b/src/shared/modules/config/ql/v5/mapV4ConfigToV5.ts @@ -0,0 +1,67 @@ +import {COMMON_PALETTE_ID, QlVisualizationId} from '../../../../constants'; +import type {QlConfigV4} from '../../../../types/config/ql/v4'; +import type {QlConfigV5} from '../../../../types/config/ql/v5'; +import {QlConfigVersions} from '../../../../types/ql/versions'; + +const OLD_DEFAULT_PALETTE_ID = 'default-palette'; + +// replace 'default-palette' (old default20) with classic20 palette +export const mapV4ConfigToV5 = (config: QlConfigV4): QlConfigV5 => { + const migratedConfig = {...config, version: QlConfigVersions.V5} as QlConfigV5; + + if ( + config.visualization?.id === QlVisualizationId.Metric && + config.extraSettings && + config.extraSettings.metricFontColorPalette === OLD_DEFAULT_PALETTE_ID + ) { + return { + ...migratedConfig, + extraSettings: { + ...config.extraSettings, + metricFontColorPalette: COMMON_PALETTE_ID.CLASSIC_20, + }, + }; + } else if ( + config.visualization?.id === QlVisualizationId.FlatTable && + config.visualization.placeholders + ) { + const migratedPlaceholders = config.visualization.placeholders.map((placeholder) => { + const migratedItems = placeholder.items.map((item) => { + const migratedItem = {...item}; + if (migratedItem.backgroundSettings?.settings.paletteState.palette) { + migratedItem.backgroundSettings.settings.paletteState.palette = + migratedItem.backgroundSettings.settings.paletteState.palette === + OLD_DEFAULT_PALETTE_ID + ? COMMON_PALETTE_ID.CLASSIC_20 + : migratedItem.backgroundSettings.settings.paletteState.palette; + } + + if (migratedItem.barsSettings?.colorSettings?.settings.palette) { + migratedItem.barsSettings.colorSettings.settings.palette = + migratedItem.barsSettings.colorSettings.settings.palette === + OLD_DEFAULT_PALETTE_ID + ? COMMON_PALETTE_ID.CLASSIC_20 + : migratedItem.barsSettings.colorSettings.settings.palette; + } + + return migratedItem; + }); + return {...placeholder, items: migratedItems}; + }); + + return { + ...migratedConfig, + visualization: {...config.visualization, placeholders: migratedPlaceholders}, + }; + + // tables also has colorsConfig but only for gradients and we don't touch them. + // indicators also has colorsConfig but it is not used + } else if (config.colorsConfig && config.colorsConfig.palette === OLD_DEFAULT_PALETTE_ID) { + return { + ...migratedConfig, + colorsConfig: {...config.colorsConfig, palette: COMMON_PALETTE_ID.CLASSIC_20}, + }; + } + + return migratedConfig; +}; diff --git a/src/shared/modules/config/ql/v6/mapV5ConfigToV6.ts b/src/shared/modules/config/ql/v6/mapV5ConfigToV6.ts new file mode 100644 index 0000000..7924175 --- /dev/null +++ b/src/shared/modules/config/ql/v6/mapV5ConfigToV6.ts @@ -0,0 +1,31 @@ +import cloneDeep from 'lodash/cloneDeep'; + +import type {QlConfigV5} from '../../../../types/config/ql/v5'; +import type {QlConfigV6} from '../../../../types/config/ql/v6'; +import {QlConfigVersions} from '../../../../types/ql/versions'; + +function getNewPaletteId(value: string) { + return value?.replace('-palette', ''); +} + +function replacePalettesField(item: unknown) { + if (item && typeof item === 'object') { + if ('metricFontColorPalette' in item) { + item.metricFontColorPalette = getNewPaletteId(item.metricFontColorPalette as string); + } else if ('palette' in item) { + item.palette = getNewPaletteId(item.palette as string); + } else { + Object.values(item).forEach(replacePalettesField); + } + } +} + +export const mapV5ConfigToV6 = (config: QlConfigV5): QlConfigV6 => { + const newConfig = cloneDeep(config); + replacePalettesField(newConfig); + + return { + ...newConfig, + version: QlConfigVersions.V6, + }; +}; diff --git a/src/shared/modules/config/ql/v7/mapV6ConfigToV7.ts b/src/shared/modules/config/ql/v7/mapV6ConfigToV7.ts new file mode 100644 index 0000000..2845638 --- /dev/null +++ b/src/shared/modules/config/ql/v7/mapV6ConfigToV7.ts @@ -0,0 +1,15 @@ +import type {QlConfigV6} from '../../../../types/config/ql/v6'; +import type {QlConfigV7} from '../../../../types/config/ql/v7'; +import {QlConfigVersions} from '../../../../types/ql/versions'; +import {mapD3VisualizationsToStandartOnes} from '../../utils'; + +export const mapV6ConfigToV7 = (config: QlConfigV6): QlConfigV7 => { + const newVisualization = {...config.visualization}; + newVisualization.id = mapD3VisualizationsToStandartOnes(config.visualization?.id); + + return { + ...config, + visualization: newVisualization, + version: QlConfigVersions.V7, + }; +}; diff --git a/src/shared/modules/config/utils.ts b/src/shared/modules/config/utils.ts new file mode 100644 index 0000000..3122b33 --- /dev/null +++ b/src/shared/modules/config/utils.ts @@ -0,0 +1,32 @@ +import {WizardVisualizationId} from '../../constants'; + +export function mapD3VisualizationsToStandartOnes(id: string) { + switch (id) { + case 'line-d3': { + return WizardVisualizationId.Line; + } + case 'bar-y-d3': { + return WizardVisualizationId.Bar; + } + case 'bar-y-100p-d3': { + return WizardVisualizationId.Bar100p; + } + case 'bar-x-d3': { + return WizardVisualizationId.Column; + } + case 'scatter-d3': { + return WizardVisualizationId.Scatter; + } + case 'pie-d3': { + return WizardVisualizationId.Pie; + } + case 'donut-d3': { + return WizardVisualizationId.Donut; + } + case 'treemap-d3': { + return WizardVisualizationId.Treemap; + } + } + + return id; +} diff --git a/src/shared/modules/config/wizard/__test__/mapChartsConfigToLatestVersion.test.ts b/src/shared/modules/config/wizard/__test__/mapChartsConfigToLatestVersion.test.ts new file mode 100644 index 0000000..f6e92d6 --- /dev/null +++ b/src/shared/modules/config/wizard/__test__/mapChartsConfigToLatestVersion.test.ts @@ -0,0 +1,13 @@ +import {ChartsConfigVersion, type ExtendedChartsConfig} from '../../../../types'; +import {mapChartsConfigToLatestVersion} from '../mapChartsConfigToLatestVersion'; + +describe('mapChartsConfigToLatestVersion', () => { + // it is necessary to fix the conversion to string, because version 8 was mistakenly a number + it('should cast number version to string', () => { + const mockedConfigWithNumericVersion = {version: 8} as unknown as ExtendedChartsConfig; + + const result = mapChartsConfigToLatestVersion(mockedConfigWithNumericVersion); + + expect(result).toEqual({version: ChartsConfigVersion.V15}); + }); +}); diff --git a/src/shared/modules/config/wizard/__test__/v4.test.ts b/src/shared/modules/config/wizard/__test__/v4.test.ts new file mode 100644 index 0000000..e958cb3 --- /dev/null +++ b/src/shared/modules/config/wizard/__test__/v4.test.ts @@ -0,0 +1,65 @@ +import type {Field} from '../../../../types'; +import { + MOCKED_SHARED_DATA, + MOCKED_V4_FIELD, + MOCKED_V4_FIELD_WITH_DATE_MODE, + MOCKED_V4_HIERARCHY_FIELD, + getMockedV4Config, +} from '../mocks/v4.mock'; +import {mapV4ConfigToV5} from '../v4/mapV4ConfigToV5'; + +describe('mapV4ConfigToV5', () => { + it('Should extract field from hierarchy when sharedData.metaHierarchy exists', () => { + const config = getMockedV4Config({ + visualizationId: 'flat-table', + placeholders: [ + { + id: 'column', + items: [MOCKED_V4_HIERARCHY_FIELD as Field], + settings: {}, + }, + ], + }); + + const result = mapV4ConfigToV5(config, MOCKED_SHARED_DATA); + expect(result.visualization.placeholders[0].items[0]).toBeDefined(); + expect(result.visualization.placeholders[0].items[0].data_type).not.toEqual('hierarchy'); + expect(result.visualization.placeholders[0].items[0].guid).toEqual('field-1'); + }); + + it("Should return hierarchy when sharedData.metaHierarchy doesn't exists", () => { + const config = getMockedV4Config({ + visualizationId: 'flat-table', + placeholders: [ + { + id: 'column', + items: [MOCKED_V4_HIERARCHY_FIELD as Field], + settings: {}, + }, + ], + }); + + const result = mapV4ConfigToV5(config, undefined); + expect(result.visualization.placeholders[0].items[0]).toBeDefined(); + expect(result.visualization.placeholders[0].items[0].data_type).toEqual('hierarchy'); + expect(result.visualization.placeholders[0].items[0].fields).toEqual([MOCKED_V4_FIELD]); + }); + + it('Should move dateMode setting to placeholder settings', () => { + const config = getMockedV4Config({ + visualizationId: 'line', + placeholders: [ + { + id: 'x', + items: [MOCKED_V4_FIELD_WITH_DATE_MODE as Field], + settings: {}, + }, + ], + }); + + const result = mapV4ConfigToV5(config, undefined); + + expect(result.visualization.placeholders[0].settings).toEqual({axisMode: 'discrete'}); + expect((result.visualization.placeholders[0].items[0] as any).dateMode).not.toBeDefined(); + }); +}); diff --git a/src/shared/modules/config/wizard/__test__/v8.test.ts b/src/shared/modules/config/wizard/__test__/v8.test.ts new file mode 100644 index 0000000..4d6bcd9 --- /dev/null +++ b/src/shared/modules/config/wizard/__test__/v8.test.ts @@ -0,0 +1,41 @@ +import type {V8ChartsConfig, V8Color} from '../../../../types'; +import {ChartsConfigVersion} from '../../../../types'; +import {mapV8ConfigToV9} from '../v8/mapV8ConfigToV9'; + +describe('mapV8ConfigToV9', () => { + it('scatter: shapes should be set when dimension field exists in colors', () => { + const colorField: V8Color = { + calc_mode: 'direct', + datasetId: '', + title: '', + type: 'DIMENSION', + guid: 'field-1', + data_type: 'string', + }; + + const config: V8ChartsConfig = { + colors: [colorField], + datasetsIds: [], + datasetsPartialFields: [], + extraSettings: undefined, + filters: [], + hierarchies: [], + labels: [], + links: [], + segments: [], + shapes: [], + sort: [], + tooltips: [], + type: 'datalens', + updates: [], + version: ChartsConfigVersion.V8, + visualization: { + id: 'scatter', + placeholders: [], + }, + }; + + const result = mapV8ConfigToV9(config); + expect(result.shapes).toEqual(config.colors); + }); +}); diff --git a/src/shared/modules/config/wizard/index.ts b/src/shared/modules/config/wizard/index.ts new file mode 100644 index 0000000..1ae3d49 --- /dev/null +++ b/src/shared/modules/config/wizard/index.ts @@ -0,0 +1 @@ +export * from './mapChartsConfigToLatestVersion'; diff --git a/src/shared/modules/config/wizard/mapChartsConfigToLatestVersion.ts b/src/shared/modules/config/wizard/mapChartsConfigToLatestVersion.ts new file mode 100644 index 0000000..a5f9891 --- /dev/null +++ b/src/shared/modules/config/wizard/mapChartsConfigToLatestVersion.ts @@ -0,0 +1,92 @@ +import type {ChartsConfig, ExtendedChartsConfig, ServerChartsConfig, Shared} from '../../../types'; +import {ChartsConfigVersion} from '../../../types'; + +import {mapV1ConfigToV2} from './v1/mapV1ConfigToV2'; +import {mapV10ConfigToV11} from './v10/mapV10ConfigToV11'; +import {mapV11ConfigToV12} from './v11/mapV11ConfigToV12'; +import {mapV12ConfigToV13} from './v12/mapV12ConfigToV13'; +import {mapV13ConfigToV14} from './v13/mapV13ConfigToV14'; +import {mapV14ConfigToV15} from './v14/mapV14ConfigToV15'; +import {mapV2ConfigToV3} from './v2/mapV2ConfigToV3'; +import {mapV3ConfigToV4, migrateDatetime} from './v3/mapV3ConfigToV4'; +import {mapV4ConfigToV5} from './v4/mapV4ConfigToV5'; +import {mapV5ConfigToV6} from './v5/mapV5ConfigToV6'; +import {mapV6ConfigToV7} from './v6/mapV6ConfigToV7'; +import {mapV7ConfigToV8} from './v7/mapV7ConfigToV8'; +import {mapV8ConfigToV9} from './v8/mapV8ConfigToV9'; +import {mapV9ConfigToV10} from './v9/mapV9ConfigToV10'; + +export const mapChartsConfigToLatestVersion = ( + extendedConfig: ExtendedChartsConfig, + options: { + sharedData?: ServerChartsConfig['sharedData']; + } = {}, +): ChartsConfig => { + let config = extendedConfig; + + // CHARTS-7601 + // version 8 of the config mistakenly became a number instead of a string + // in order to maintain consistency, cast all non string versions into string + if (typeof config.version !== 'undefined') { + config.version = String(config.version) as ChartsConfigVersion; + } + + if (config.version === ChartsConfigVersion.V1 || typeof config.version === 'undefined') { + config = mapV1ConfigToV2(config as Shared); + } + + if (config.version === ChartsConfigVersion.V2) { + config = mapV2ConfigToV3(config); + } + + if (config.version === ChartsConfigVersion.V3) { + config = mapV3ConfigToV4(config); + } + + if (config.version === ChartsConfigVersion.V4) { + migrateDatetime(config); + config = mapV4ConfigToV5(config, options.sharedData); + } + + if (config.version === ChartsConfigVersion.V5) { + config = mapV5ConfigToV6(config); + } + + if (config.version === ChartsConfigVersion.V6) { + config = mapV6ConfigToV7(config, options.sharedData); + } + + if (config.version === ChartsConfigVersion.V7) { + config = mapV7ConfigToV8(config); + } + + if (config.version === ChartsConfigVersion.V8) { + config = mapV8ConfigToV9(config); + } + + if (config.version === ChartsConfigVersion.V9) { + config = mapV9ConfigToV10(config); + } + + if (config.version === ChartsConfigVersion.V10) { + config = mapV10ConfigToV11(config); + } + + if (config.version === ChartsConfigVersion.V11) { + config = mapV11ConfigToV12(config); + } + + if (config.version === ChartsConfigVersion.V12) { + config = mapV12ConfigToV13(config); + } + + if (config.version === ChartsConfigVersion.V13) { + config = mapV13ConfigToV14(config); + } + + if (config.version === ChartsConfigVersion.V14) { + config = mapV14ConfigToV15(config); + } + + return config as ChartsConfig; +}; diff --git a/src/shared/modules/config/wizard/mocks/v4.mock.ts b/src/shared/modules/config/wizard/mocks/v4.mock.ts new file mode 100644 index 0000000..4693c4a --- /dev/null +++ b/src/shared/modules/config/wizard/mocks/v4.mock.ts @@ -0,0 +1,63 @@ +import type { + Placeholder, + V4ChartsConfig, + V4Field, + V4HierarchyField, + V4Sort, + V4Visualization, +} from '../../../../types'; + +export const MOCKED_V4_FIELD = { + guid: 'field-1', + data_type: 'string', +} as V4Field; + +export const MOCKED_V4_FIELD_WITH_DATE_MODE = { + guid: 'field-3', + data_type: 'date', + dateMode: 'discrete', +} as V4Field; + +export const MOCKED_V4_HIERARCHY_FIELD = { + guid: 'hierarchy-1', + type: 'PSEUDO', + data_type: 'hierarchy', + fields: [MOCKED_V4_FIELD], +} as V4HierarchyField; + +export const MOCKED_SHARED_DATA = { + metaHierarchy: { + column: { + hierarchyIndex: 0, + hierarchyFieldIndex: 0, + }, + }, +}; + +export const getMockedV4Visualization = ({ + visualizationId, + placeholders, +}: { + visualizationId: string; + placeholders?: Partial[]; +}) => { + return { + id: visualizationId, + placeholders: placeholders || [], + } as V4Visualization; +}; + +export const getMockedV4Config = ({ + visualizationId, + placeholders, + sort, +}: { + visualizationId: string; + placeholders?: Partial[]; + sort?: Partial; +}) => { + return { + visualization: getMockedV4Visualization({visualizationId, placeholders}), + sort: sort || [], + } as V4ChartsConfig; +}; diff --git a/src/shared/modules/config/wizard/v1/mapV1ConfigToV2.ts b/src/shared/modules/config/wizard/v1/mapV1ConfigToV2.ts new file mode 100644 index 0000000..d8f9c0b --- /dev/null +++ b/src/shared/modules/config/wizard/v1/mapV1ConfigToV2.ts @@ -0,0 +1,47 @@ +import type {Dataset, GraphShared, ServerDatasetField, Shared} from '../../../../index'; +import {ChartsConfigVersion} from '../../../../index'; +import type {V2ChartsConfig} from '../../../../types'; + +export const mapV1ConfigToV2 = (config: Shared): V2ChartsConfig => { + let datasetsPartialFields: ServerDatasetField[][]; + let datasets: Dataset[]; + if (!config.datasets) { + datasets = [config.dataset!]; + datasetsPartialFields = [[...(config.dimensions || []), ...(config.measures || [])]]; + } else { + datasets = config.datasets || []; + datasetsPartialFields = (config.datasets || []).map((dataset) => { + const schema = dataset.result_schema || dataset.dataset.result_schema || []; + return schema.map( + (field): ServerDatasetField => ({ + guid: field.guid, + title: field.title, + }), + ); + }); + } + const datasetsIds: string[] = (config.datasets || datasets).map((dataset) => dataset.id); + const v2Config: V2ChartsConfig = { + title: config.title || '', + colors: config.colors || [], + colorsConfig: config.colorsConfig || {}, + extraSettings: config.extraSettings || {}, + filters: config.filters || [], + geopointsConfig: (config as GraphShared).geopointsConfig || {}, + hierarchies: config.hierarchies || [], + labels: config.labels || [], + links: config.links || [], + sort: config.sort || [], + tooltips: config.tooltips || [], + type: 'datalens', + updates: config.updates || [], + visualization: config.visualization as V2ChartsConfig['visualization'], + shapes: config.shapes || [], + shapesConfig: (config as GraphShared).shapesConfig || {}, + version: ChartsConfigVersion.V2, + datasetsIds, + datasetsPartialFields, + }; + + return v2Config; +}; diff --git a/src/shared/modules/config/wizard/v10/mapV10ConfigToV11.ts b/src/shared/modules/config/wizard/v10/mapV10ConfigToV11.ts new file mode 100644 index 0000000..7f8ef88 --- /dev/null +++ b/src/shared/modules/config/wizard/v10/mapV10ConfigToV11.ts @@ -0,0 +1,26 @@ +import {WizardVisualizationId} from '../../../../constants'; +import type { + V10ChartsConfig, + V11ChartsConfig, + V11CommonSharedExtraSettings, +} from '../../../../types'; +import {ChartsConfigVersion, IndicatorTitleMode} from '../../../../types'; + +export const mapV10ConfigToV11 = (config: V10ChartsConfig): V11ChartsConfig => { + let extraSettings = config.extraSettings as V11CommonSharedExtraSettings; + if (config.visualization?.id === WizardVisualizationId.Metric) { + extraSettings = {...config.extraSettings}; + + if (extraSettings.titleMode === 'show' && extraSettings.title) { + extraSettings.indicatorTitleMode = IndicatorTitleMode.Manual; + } else { + extraSettings.indicatorTitleMode = IndicatorTitleMode.ByField; + } + } + + return { + ...config, + extraSettings, + version: ChartsConfigVersion.V11, + }; +}; diff --git a/src/shared/modules/config/wizard/v11/mapV11ConfigToV12.ts b/src/shared/modules/config/wizard/v11/mapV11ConfigToV12.ts new file mode 100644 index 0000000..6157832 --- /dev/null +++ b/src/shared/modules/config/wizard/v11/mapV11ConfigToV12.ts @@ -0,0 +1,24 @@ +import set from 'lodash/set'; + +import type {V11ChartsConfig, V12ChartsConfig} from '../../../../types'; +import {ChartsConfigVersion, MARKUP_TYPE} from '../../../../types'; + +function replaceObjectField(item: unknown) { + if (item && typeof item === 'object') { + if ('isMarkdown' in item) { + delete item['isMarkdown']; + set(item, 'markupType', MARKUP_TYPE.markdown); + } else { + Object.values(item).forEach(replaceObjectField); + } + } +} + +export const mapV11ConfigToV12 = (config: V11ChartsConfig): V12ChartsConfig => { + replaceObjectField(config); + + return { + ...config, + version: ChartsConfigVersion.V12, + }; +}; diff --git a/src/shared/modules/config/wizard/v12/mapV12ConfigToV13.ts b/src/shared/modules/config/wizard/v12/mapV12ConfigToV13.ts new file mode 100644 index 0000000..8096d70 --- /dev/null +++ b/src/shared/modules/config/wizard/v12/mapV12ConfigToV13.ts @@ -0,0 +1,74 @@ +import {COMMON_PALETTE_ID, WizardVisualizationId} from '../../../../constants'; +import type { + V12ChartsConfig, + V13ChartsConfig, + V13CommonSharedExtraSettings, +} from '../../../../types'; +import {ChartsConfigVersion} from '../../../../types'; + +const OLD_DEFAULT_PALETTE_ID = 'default-palette'; + +// replace 'default-palette' (old default20) with classic20 palette +export const mapV12ConfigToV13 = (config: V12ChartsConfig): V13ChartsConfig => { + // there are differences only in the type of extraSettings + const migratedConfig = {...config, version: ChartsConfigVersion.V13} as V13ChartsConfig; + + if ( + config.visualization?.id === WizardVisualizationId.Metric && + config.extraSettings && + config.extraSettings.metricFontColorPalette === OLD_DEFAULT_PALETTE_ID + ) { + const migratedExtraSettings = { + ...config.extraSettings, + metricFontColorPalette: COMMON_PALETTE_ID.CLASSIC_20, + } as V13CommonSharedExtraSettings; + + return { + ...migratedConfig, + extraSettings: migratedExtraSettings, + }; + } else if ( + config.visualization?.id === WizardVisualizationId.PivotTable || + config.visualization?.id === WizardVisualizationId.FlatTable + ) { + const migratedPlaceholders = config.visualization.placeholders.map((placeholder) => { + const migratedItems = placeholder.items.map((item) => { + const migratedItem = {...item}; + if (migratedItem.backgroundSettings?.settings.paletteState.palette) { + migratedItem.backgroundSettings.settings.paletteState.palette = + migratedItem.backgroundSettings.settings.paletteState.palette === + OLD_DEFAULT_PALETTE_ID + ? COMMON_PALETTE_ID.CLASSIC_20 + : migratedItem.backgroundSettings.settings.paletteState.palette; + } + + if (migratedItem.barsSettings?.colorSettings?.settings.palette) { + migratedItem.barsSettings.colorSettings.settings.palette = + migratedItem.barsSettings.colorSettings.settings.palette === + OLD_DEFAULT_PALETTE_ID + ? COMMON_PALETTE_ID.CLASSIC_20 + : migratedItem.barsSettings.colorSettings.settings.palette; + } + + return migratedItem; + }); + + return {...placeholder, items: migratedItems}; + }); + + return { + ...migratedConfig, + visualization: {...config.visualization, placeholders: migratedPlaceholders}, + }; + + // tables also has colorsConfig but only for gradients and we don't touch them. + // indicators also has colorsConfig but it is not used + } else if (config.colorsConfig && config.colorsConfig.palette === OLD_DEFAULT_PALETTE_ID) { + return { + ...migratedConfig, + colorsConfig: {...config.colorsConfig, palette: COMMON_PALETTE_ID.CLASSIC_20}, + }; + } + + return migratedConfig; +}; diff --git a/src/shared/modules/config/wizard/v13/mapV13ConfigToV14.ts b/src/shared/modules/config/wizard/v13/mapV13ConfigToV14.ts new file mode 100644 index 0000000..d9d0195 --- /dev/null +++ b/src/shared/modules/config/wizard/v13/mapV13ConfigToV14.ts @@ -0,0 +1,31 @@ +import cloneDeep from 'lodash/cloneDeep'; + +import {ChartsConfigVersion} from '../../../../types'; +import type {V13ChartsConfig} from '../../../../types/config/wizard/v13'; +import type {V14ChartsConfig} from '../../../../types/config/wizard/v14'; + +function getNewPaletteId(value: string) { + return value?.replace('-palette', ''); +} + +function replacePalettesField(item: unknown) { + if (item && typeof item === 'object') { + if ('metricFontColorPalette' in item) { + item.metricFontColorPalette = getNewPaletteId(item.metricFontColorPalette as string); + } else if ('palette' in item) { + item.palette = getNewPaletteId(item.palette as string); + } else { + Object.values(item).forEach(replacePalettesField); + } + } +} + +export const mapV13ConfigToV14 = (config: V13ChartsConfig): V14ChartsConfig => { + const newConfig = cloneDeep(config); + replacePalettesField(newConfig); + + return { + ...newConfig, + version: ChartsConfigVersion.V14, + }; +}; diff --git a/src/shared/modules/config/wizard/v14/mapV14ConfigToV15.ts b/src/shared/modules/config/wizard/v14/mapV14ConfigToV15.ts new file mode 100644 index 0000000..8cbc511 --- /dev/null +++ b/src/shared/modules/config/wizard/v14/mapV14ConfigToV15.ts @@ -0,0 +1,19 @@ +import {ChartsConfigVersion} from '../../../../types'; +import type {V14ChartsConfig} from '../../../../types/config/wizard/v14'; +import type {V15ChartsConfig} from '../../../../types/config/wizard/v15'; +import {mapD3VisualizationsToStandartOnes} from '../../utils'; + +export const mapV14ConfigToV15 = (config: V14ChartsConfig): V15ChartsConfig => { + const newVisuzalization = config.visualization + ? { + ...config.visualization, + id: mapD3VisualizationsToStandartOnes(config.visualization?.id), + } + : config.visualization; + + return { + ...config, + visualization: newVisuzalization, + version: ChartsConfigVersion.V15, + }; +}; diff --git a/src/shared/modules/config/wizard/v2/mapV2ConfigToV3.ts b/src/shared/modules/config/wizard/v2/mapV2ConfigToV3.ts new file mode 100644 index 0000000..701cd68 --- /dev/null +++ b/src/shared/modules/config/wizard/v2/mapV2ConfigToV3.ts @@ -0,0 +1,44 @@ +import {ChartsConfigVersion} from '../../../../types'; +import type {V2ChartsConfig} from '../../../../types/config/wizard/v2'; +import type {V3ChartsConfig} from '../../../../types/config/wizard/v3'; + +export const mapV2ConfigToV3 = (config: V2ChartsConfig): V3ChartsConfig => { + const visualization = config.visualization; + if (visualization.id === 'geolayer') { + const layers = visualization.layers || []; + const v3Layers = layers.map((layer) => { + const commonPlaceholders = layer.commonPlaceholders; + // Fallback, historically we did not put sort in commonPlaceholders. Get it from the config. + // To support the old charts, switch to have the same behavior for all fields + if (layer.id === 'polyline') { + return { + ...layer, + commonPlaceholders: { + ...commonPlaceholders, + sort: config.sort || [], + }, + }; + } + return { + ...layer, + commonPlaceholders: { + ...commonPlaceholders, + sort: [], + }, + }; + }); + + return { + ...config, + version: ChartsConfigVersion.V3, + visualization: { + ...visualization, + layers: v3Layers, + }, + }; + } + return { + ...(config as unknown as V3ChartsConfig), + version: ChartsConfigVersion.V3, + }; +}; diff --git a/src/shared/modules/config/wizard/v3/mapV3ConfigToV4.ts b/src/shared/modules/config/wizard/v3/mapV3ConfigToV4.ts new file mode 100644 index 0000000..6c8ae10 --- /dev/null +++ b/src/shared/modules/config/wizard/v3/mapV3ConfigToV4.ts @@ -0,0 +1,126 @@ +import {DEFAULT_FORMATTING} from '../../../../constants'; +import type { + Field, + V3ChartsConfig, + V3Label, + V4ChartsConfig, + V4Formatting, + V4Label, + V4Visualization, +} from '../../../../types'; +import {ChartsConfigVersion, DATASET_FIELD_TYPES, DatasetFieldType} from '../../../../types'; +import {getDefaultFormatting} from '../../../wizard-helpers'; + +const createV4LabelFromV3 = (v3Label: V3Label): V4Label => { + return (Object.keys(v3Label) as Array).reduce((v4Label, key) => { + switch (key) { + case 'labelMode': + return { + ...v4Label, + formatting: { + ...v4Label.formatting, + labelMode: v3Label.labelMode || DEFAULT_FORMATTING.labelMode, + }, + }; + case 'formatting': + return { + ...v4Label, + formatting: { + ...v4Label.formatting, + ...(v3Label?.formatting || {}), + } as V4Formatting, + }; + default: + return {...v4Label, [key]: v3Label[key]}; + } + }, {} as V4Label); +}; + +export const mapV3LabelsToV4Labels = (v3Label: V3Label): V4Label => { + if (v3Label.type === DatasetFieldType.Pseudo) { + return { + ...v3Label, + formatting: undefined, + }; + } + const v4Label = createV4LabelFromV3(v3Label); + + const formatting: V4Formatting = { + ...getDefaultFormatting(v4Label as Field), + ...(v4Label.formatting || {}), + }; + + return { + ...v4Label, + formatting, + }; +}; + +export const mapV3ConfigToV4 = (config: V3ChartsConfig): V4ChartsConfig => { + const v4Labels: V4Label[] = (config.labels || []).map(mapV3LabelsToV4Labels); + + const v4Visualization: V4Visualization = { + ...config.visualization, + layers: config.visualization.layers?.map((layer) => { + const commonPlaceholders = layer.commonPlaceholders; + const v3Labels = commonPlaceholders.labels; + const v4LayerLabels = v3Labels.map(mapV3LabelsToV4Labels); + + return { + ...layer, + commonPlaceholders: { + ...commonPlaceholders, + labels: v4LayerLabels, + }, + }; + }), + } as V4Visualization; + + return { + ...config, + visualization: v4Visualization, + labels: v4Labels, + version: ChartsConfigVersion.V4, + } as V4ChartsConfig; +}; + +const mutateFieldDatetime = (field: any): void => { + if (field.data_type === 'datetime') { + field.data_type = DATASET_FIELD_TYPES.GENERICDATETIME; + } + + if (field.cast === 'datetime') { + field.cast = DATASET_FIELD_TYPES.GENERICDATETIME; + } +}; + +export const migrateDatetime = (config: V4ChartsConfig): void => { + config.visualization.placeholders?.forEach((placeholder) => { + placeholder.items?.forEach(mutateFieldDatetime); + }); + + config.visualization.layers?.forEach((layer) => { + layer.placeholders?.forEach((placeholder) => { + placeholder.items?.forEach(mutateFieldDatetime); + }); + + layer.commonPlaceholders.colors?.forEach(mutateFieldDatetime); + layer.commonPlaceholders.filters?.forEach(mutateFieldDatetime); + layer.commonPlaceholders.labels?.forEach(mutateFieldDatetime); + layer.commonPlaceholders.sort?.forEach(mutateFieldDatetime); + layer.commonPlaceholders.tooltips?.forEach(mutateFieldDatetime); + }); + + config.colors?.forEach(mutateFieldDatetime); + config.labels?.forEach(mutateFieldDatetime); + config.filters?.forEach(mutateFieldDatetime); + config.shapes?.forEach(mutateFieldDatetime); + config.sort?.forEach(mutateFieldDatetime); + config.segments?.forEach(mutateFieldDatetime); + + config.updates?.forEach((update) => { + if (update.field) { + mutateFieldDatetime(update.field); + } + }); +}; diff --git a/src/shared/modules/config/wizard/v4/mapV4ConfigToV5.ts b/src/shared/modules/config/wizard/v4/mapV4ConfigToV5.ts new file mode 100644 index 0000000..3c0dc93 --- /dev/null +++ b/src/shared/modules/config/wizard/v4/mapV4ConfigToV5.ts @@ -0,0 +1,122 @@ +import type {PlaceholderId, WizardVisualizationId} from '../../../../constants'; +import type { + ServerChartsConfig, + V4ChartsConfig, + V4Placeholder, + V4Sort, + V5ChartsConfig, + V5Placeholder, + V5Visualization, +} from '../../../../types'; +import {AxisMode, ChartsConfigVersion, DATASET_FIELD_TYPES} from '../../../../types'; +import {isVisualizationWithLayers} from '../../../../utils'; +import {isMeasureField} from '../../../helpers'; +import {isAllAxisModesAvailable, isPlaceholderSupportsAxisMode} from '../../../wizard-helpers'; + +const mapV4PlaceholderToV5Placeholder = ({ + placeholder, + sharedData, + sort, + visualizationId, +}: { + placeholder: V4Placeholder; + sort: V4Sort[]; + visualizationId: string; + sharedData: ServerChartsConfig['sharedData'] | undefined; +}): V5Placeholder => { + const v5Placeholder: V5Placeholder = {...placeholder}; + const isPlaceholderWithAxisMode = isPlaceholderSupportsAxisMode( + placeholder.id as PlaceholderId, + visualizationId as WizardVisualizationId, + ); + const sortHasMeasure = sort.some(isMeasureField); + v5Placeholder.items = (placeholder.items || []).map((rawField, index) => { + let field = rawField; + + if ( + field.data_type === DATASET_FIELD_TYPES.HIERARCHY && + field.fields && + sharedData?.metaHierarchy && + sharedData.metaHierarchy[v5Placeholder.id] && + sharedData.metaHierarchy[v5Placeholder.id].hierarchyIndex === index + ) { + const currentPlaceholderHierarchyMeta = sharedData.metaHierarchy[v5Placeholder.id]; + field = field.fields[currentPlaceholderHierarchyMeta.hierarchyFieldIndex]; + } + + if (field.dateMode) { + if ( + isPlaceholderWithAxisMode && + // Axis mode always depends on first item in placeholder + index === 0 + ) { + v5Placeholder.settings = { + ...v5Placeholder.settings, + axisMode: sortHasMeasure ? AxisMode.Discrete : (field.dateMode as AxisMode), + }; + } + + delete field.dateMode; + } + + return field; + }); + + if (!v5Placeholder.settings?.axisMode && isPlaceholderWithAxisMode) { + const firstPlaceholderItem = v5Placeholder.items[0]; + + if (isAllAxisModesAvailable(firstPlaceholderItem) && !sortHasMeasure) { + v5Placeholder.settings = { + ...v5Placeholder.settings, + axisMode: AxisMode.Continuous, + }; + } else { + v5Placeholder.settings = { + ...v5Placeholder.settings, + axisMode: AxisMode.Discrete, + }; + } + } + + return v5Placeholder; +}; + +export const mapV4ConfigToV5 = ( + config: V4ChartsConfig, + sharedData: ServerChartsConfig['sharedData'] | undefined, +): V5ChartsConfig => { + const v5Visualization: V5Visualization = { + ...config.visualization, + }; + + if (isVisualizationWithLayers(config.visualization)) { + v5Visualization.layers = (v5Visualization.layers || []).map((layer) => { + return { + ...layer, + placeholders: layer.placeholders.map((placeholder) => { + return mapV4PlaceholderToV5Placeholder({ + sharedData: sharedData, + placeholder, + sort: config.sort, + visualizationId: layer.id, + }); + }), + }; + }); + } else if (config.visualization.placeholders) { + v5Visualization.placeholders = config.visualization.placeholders.map((placeholder) => + mapV4PlaceholderToV5Placeholder({ + placeholder, + sort: config.sort, + visualizationId: config.visualization.id, + sharedData, + }), + ); + } + + return { + ...config, + visualization: v5Visualization, + version: ChartsConfigVersion.V5, + }; +}; diff --git a/src/shared/modules/config/wizard/v5/mapV5ConfigToV6.ts b/src/shared/modules/config/wizard/v5/mapV5ConfigToV6.ts new file mode 100644 index 0000000..0d09e0e --- /dev/null +++ b/src/shared/modules/config/wizard/v5/mapV5ConfigToV6.ts @@ -0,0 +1,20 @@ +import {WizardVisualizationId} from '../../../../constants'; +import type {V5ChartsConfig, V6ChartsConfig} from '../../../../types'; +import {ChartsConfigVersion} from '../../../../types'; +import {isMeasureName} from '../../../wizard-helpers'; + +export const mapV5ConfigToV6 = (config: V5ChartsConfig): V6ChartsConfig => { + const updateSortFields = (config.sort || []).filter((field) => { + if (config.visualization.id === WizardVisualizationId.PivotTable) { + return !isMeasureName(field); + } + + return true; + }); + + return { + ...config, + sort: updateSortFields, + version: ChartsConfigVersion.V6, + }; +}; diff --git a/src/shared/modules/config/wizard/v6/mapV6ConfigToV7.ts b/src/shared/modules/config/wizard/v6/mapV6ConfigToV7.ts new file mode 100644 index 0000000..4bbd08d --- /dev/null +++ b/src/shared/modules/config/wizard/v6/mapV6ConfigToV7.ts @@ -0,0 +1,156 @@ +import {omit} from 'lodash'; + +import type {PlaceholderId, WizardVisualizationId} from '../../../../constants'; +import type { + ServerChartsConfig, + V6ChartsConfig, + V6Placeholder, + V6Sort, + V7ChartsConfig, + V7Layer, + V7Placeholder, +} from '../../../../types'; +import {AxisMode, ChartsConfigVersion, isFieldHierarchy} from '../../../../types'; +import {isVisualizationWithLayers} from '../../../../utils'; +import {isMeasureField} from '../../../helpers'; +import {isAllAxisModesAvailable, isPlaceholderSupportsAxisMode} from '../../../wizard-helpers'; + +const mapV6PlaceholdersToV7Placeholders = ( + visualizationId: WizardVisualizationId, + placeholders: V6Placeholder[], + sort: V6Sort[], + sharedData: ServerChartsConfig['sharedData'] | undefined, +): V7Placeholder[] => { + const sortHasMeasure = sort.some(isMeasureField); + return placeholders.map((v6Placeholder): V7Placeholder => { + const v6Settings = v6Placeholder.settings; + + if ( + isPlaceholderSupportsAxisMode(v6Placeholder.id as PlaceholderId, visualizationId) && + v6Settings?.axisMode + ) { + const firstField = v6Placeholder.items?.[0]; + + if (!firstField) { + return { + ...v6Placeholder, + settings: { + ...v6Settings, + axisModeMap: {}, + }, + }; + } + + const fieldIsHierarchyByType = isFieldHierarchy(firstField); + const fieldIsHierarchyByMeta = + sharedData?.metaHierarchy && + sharedData.metaHierarchy[v6Placeholder.id] && + sharedData.metaHierarchy[v6Placeholder.id].hierarchyIndex === 0; + + const fieldIsHierarchy = fieldIsHierarchyByType || fieldIsHierarchyByMeta; + + if (fieldIsHierarchy) { + if (firstField.fields && fieldIsHierarchyByType) { + const axisModeByHierarchyField = firstField.fields.reduce( + (acc, field) => { + const isContinuousMode = + isAllAxisModesAvailable(field) && !sortHasMeasure; + + if (isContinuousMode) { + acc[field.guid] = AxisMode.Continuous; + } else { + acc[field.guid] = AxisMode.Discrete; + } + + return acc; + }, + {} as Record, + ); + + return { + ...v6Placeholder, + settings: { + ...omit(v6Settings, 'axisMode'), + axisModeMap: axisModeByHierarchyField, + }, + }; + } else { + const isContinuousMode = isAllAxisModesAvailable(firstField) && !sortHasMeasure; + + return { + ...v6Placeholder, + settings: { + ...omit(v6Settings, 'axisMode'), + axisModeMap: { + [firstField.guid]: isContinuousMode + ? AxisMode.Continuous + : AxisMode.Discrete, + }, + }, + }; + } + } + + return { + ...v6Placeholder, + settings: { + ...omit(v6Settings, 'axisMode'), + axisModeMap: { + [firstField.guid]: v6Settings.axisMode, + }, + }, + }; + } + return { + ...v6Placeholder, + settings: { + ...omit(v6Settings, 'axisMode'), + axisModeMap: undefined, + }, + }; + }); +}; + +export const mapV6ConfigToV7 = ( + config: V6ChartsConfig, + sharedData: ServerChartsConfig['sharedData'] | undefined, +): V7ChartsConfig => { + let v7Visualization: V7ChartsConfig['visualization']; + const sort = config.sort; + + if (isVisualizationWithLayers(config.visualization)) { + v7Visualization = { + ...config.visualization, + layers: config.visualization.layers.map((layer): V7Layer => { + return { + ...layer, + placeholders: mapV6PlaceholdersToV7Placeholders( + layer.id as WizardVisualizationId, + layer.placeholders, + sort, + sharedData, + ), + }; + }), + }; + } else { + const placeholders = config.visualization.placeholders || []; + + v7Visualization = { + ...config.visualization, + layers: undefined, + placeholders: mapV6PlaceholdersToV7Placeholders( + config.visualization.id as WizardVisualizationId, + placeholders, + sort, + sharedData, + ), + }; + } + + return { + ...config, + visualization: v7Visualization, + version: ChartsConfigVersion.V7, + }; +}; diff --git a/src/shared/modules/config/wizard/v7/mapV7ConfigToV8.ts b/src/shared/modules/config/wizard/v7/mapV7ConfigToV8.ts new file mode 100644 index 0000000..2af082d --- /dev/null +++ b/src/shared/modules/config/wizard/v7/mapV7ConfigToV8.ts @@ -0,0 +1,74 @@ +import {WizardVisualizationId} from '../../../../constants'; +// import placeholderId this way because if it will be imported from constants +// it will be failed when playwright runs tests. +// the TypeError will occur and says that PlaceholderId is undefined +import {PlaceholderId} from '../../../../constants/placeholder'; +import type { + ServerField, + V7ChartsConfig, + V7Placeholder, + V8ChartsConfig, + V8Placeholder, + V8Visualization, +} from '../../../../types'; +import {ChartsConfigVersion} from '../../../../types'; + +const PLACEHOLDERS_WITH_TOTALS_SETTINGS: Record = { + [PlaceholderId.PivotTableRows]: true, + [PlaceholderId.PivotTableColumns]: true, +}; + +const setGrandTotalsSettingToPlaceholderItems = ( + placeholder: V7Placeholder, + isGrandTotalsEnabled: boolean, +): V8Placeholder => { + const placeholderItems = placeholder.items || []; + let updatedItems: ServerField[] = placeholderItems; + + if (placeholderItems.length) { + updatedItems = [ + {...placeholderItems[0], subTotalsSettings: {enabled: isGrandTotalsEnabled}}, + ...placeholderItems.slice(1), + ]; + } + + return { + ...placeholder, + items: updatedItems, + }; +}; + +export const mapV7ConfigToV8 = (config: V7ChartsConfig): V8ChartsConfig => { + const visualization = config.visualization; + const extraSettings = config.extraSettings; + + let updatedVisualization: V8Visualization; + + if (visualization.id === WizardVisualizationId.PivotTable) { + const updatedPlaceholders: V8Placeholder[] = (visualization.placeholders || []).map( + (placeholder) => { + if (PLACEHOLDERS_WITH_TOTALS_SETTINGS[placeholder.id]) { + return setGrandTotalsSettingToPlaceholderItems( + placeholder, + extraSettings?.totals === 'on', + ); + } + + return placeholder; + }, + ); + + updatedVisualization = { + ...visualization, + placeholders: updatedPlaceholders, + }; + } else { + updatedVisualization = visualization; + } + + return { + ...config, + visualization: updatedVisualization, + version: ChartsConfigVersion.V8, + }; +}; diff --git a/src/shared/modules/config/wizard/v8/mapV8ConfigToV9.ts b/src/shared/modules/config/wizard/v8/mapV8ConfigToV9.ts new file mode 100644 index 0000000..a474f32 --- /dev/null +++ b/src/shared/modules/config/wizard/v8/mapV8ConfigToV9.ts @@ -0,0 +1,20 @@ +import {WizardVisualizationId} from '../../../../constants'; +import type {ServerField, V8ChartsConfig, V9ChartsConfig} from '../../../../types'; +import {ChartsConfigVersion, DatasetFieldType} from '../../../../types'; + +export const mapV8ConfigToV9 = (config: V8ChartsConfig): V9ChartsConfig => { + let shapes: ServerField[] = config.shapes; + + if (config?.visualization?.id === WizardVisualizationId.Scatter) { + const colorField = config.colors?.[0]; + if (colorField?.type === DatasetFieldType.Dimension) { + shapes = [colorField]; + } + } + + return { + ...config, + shapes, + version: ChartsConfigVersion.V9, + }; +}; diff --git a/src/shared/modules/config/wizard/v9/mapV9ConfigToV10.ts b/src/shared/modules/config/wizard/v9/mapV9ConfigToV10.ts new file mode 100644 index 0000000..49c8726 --- /dev/null +++ b/src/shared/modules/config/wizard/v9/mapV9ConfigToV10.ts @@ -0,0 +1,45 @@ +import {PlaceholderId, WizardVisualizationId} from '../../../../constants'; +import type { + V10ChartsConfig, + V10Placeholder, + V9ChartsConfig, + V9Placeholder, +} from '../../../../types'; +import {ChartsConfigVersion} from '../../../../types'; + +export const mapV9ConfigToV10 = (config: V9ChartsConfig): V10ChartsConfig => { + const affectedVisualizations = [ + WizardVisualizationId.Pie, + WizardVisualizationId.Donut, + 'pie-d3', + 'donut-d3', + ]; + + let visualization = config.visualization; + + if (affectedVisualizations.includes(visualization?.id as WizardVisualizationId)) { + const placeholders = visualization.placeholders.map((p: V9Placeholder) => { + if (p.id === PlaceholderId.Dimensions) { + return { + ...p, + id: PlaceholderId.Colors, + type: PlaceholderId.Colors, + required: false, + }; + } + + return p; + }); + + visualization = { + ...visualization, + placeholders: placeholders as V10Placeholder[], + }; + } + + return { + ...config, + visualization, + version: ChartsConfigVersion.V10, + }; +}; diff --git a/src/shared/modules/control/typed-query-helpers.ts b/src/shared/modules/control/typed-query-helpers.ts new file mode 100644 index 0000000..c4d9c35 --- /dev/null +++ b/src/shared/modules/control/typed-query-helpers.ts @@ -0,0 +1,12 @@ +//Currently selector supports only one column for distincts, so we always should take data from 0 index in rows +import type {ConnectionTypedQueryApiDataRow} from '../../types'; + +const CONTROL_DISTINCT_ROW_INDEX = 0; + +export const getControlDisticntsFromRows = (rows: ConnectionTypedQueryApiDataRow[]): string[] => { + return rows.reduce((acc: string[], row) => { + const data = String(row[CONTROL_DISTINCT_ROW_INDEX]); + acc.push(data); + return acc; + }, [] as string[]); +}; diff --git a/src/shared/modules/dash-scheme-converter.ts b/src/shared/modules/dash-scheme-converter.ts new file mode 100644 index 0000000..d338bfb --- /dev/null +++ b/src/shared/modules/dash-scheme-converter.ts @@ -0,0 +1,409 @@ +import {dateTimeUtc} from '@gravity-ui/date-utils'; +import omitBy from 'lodash/omitBy'; + +import {DASH_CURRENT_SCHEME_VERSION} from '../constants/dash'; +import { + CustomPaletteBgColors, + DUPLICATED_WIDGET_BG_COLORS_PRESET, + getDefaultDashWidgetBgColorByType, +} from '../constants/widgets'; +import type {DashData, DashTab, DashTabItem, OldBackgroundSettings} from '../types'; +import {DashTabConnectionKind, DashTabItemControlElementType, DashTabItemType} from '../types'; +import {isOldBackgroundSettings} from '../utils/dash'; + +const DATE_FORMAT_V7 = 'YYYY-MM-DD'; + +export function getResultedOldBgColor( + oldBgColor: OldBackgroundSettings | undefined, + defaultColor: string | undefined, +): string | undefined { + if (!oldBgColor) { + return defaultColor; + } + if (typeof oldBgColor === 'string') { + // where was a bug, when new Textwidgets were created with background color set by string + return oldBgColor; + } + if ('enabled' in oldBgColor && oldBgColor.enabled === false) { + if (oldBgColor.color === CustomPaletteBgColors.NONE) { + // where was a bug, when new widgets were created with background color set to transparent, but enabled set to false + return CustomPaletteBgColors.NONE; + } + return defaultColor; + } + if (!oldBgColor.color) { + return defaultColor; + } + return oldBgColor.color; +} + +export function getActualOldBackground( + background: OldBackgroundSettings | undefined, + defaultColor: string | undefined, +): Omit | undefined { + if ( + background && + isOldBackgroundSettings(background) && + background.color && + DUPLICATED_WIDGET_BG_COLORS_PRESET.includes(background.color) + ) { + background.color.replace('medium', 'light-hover'); + } + + const color = getResultedOldBgColor(background, defaultColor); + + return color ? {color} : undefined; +} + +export function migrateBgColor(item: DashTabItem, defaultOldColor?: string): DashTabItem { + if (DashTabItemType.GroupControl === item.type || DashTabItemType.Control === item.type) { + return item; + } + const newItem: DashTabItem = Object.assign({...item}, {data: Object.assign({}, item.data)}); + + if ('background' in newItem.data) { + if ( + newItem.data.background && + DUPLICATED_WIDGET_BG_COLORS_PRESET.includes(newItem.data.background.color) + ) { + newItem.data.background = getActualOldBackground( + newItem.data.background, + defaultOldColor, + ); + + return newItem; + } + } + if (newItem.type === DashTabItemType.Widget) { + newItem.data.tabs = newItem.data.tabs.map((tab) => ({ + ...tab, + background: getActualOldBackground(tab.background, defaultOldColor), + })); + + return newItem; + } + return item; +} + +export function preparedData(data: DashData, defaultOldColor: string) { + data.tabs.forEach((dashTabItem) => { + dashTabItem.items = dashTabItem.items.map((wi) => { + const widgetItem = migrateBgColor(wi, defaultOldColor); + if (widgetItem.type !== DashTabItemType.Control) { + return widgetItem; + } + for (const [key, val] of Object.entries(widgetItem.defaults)) { + widgetItem.defaults[key] = val === null ? '' : val; + } + return widgetItem; + }); + }); + + return data; +} + +// appeared at the time of the transition from 6 to 7, there is possibility that previous conversions are no longer needed + +class DashSchemeConverter { + static isUpdateNeeded(data: DashData) { + return data.schemeVersion < DASH_CURRENT_SCHEME_VERSION; + } + + static update(data: DashData) { + if (data.schemeVersion < DASH_CURRENT_SCHEME_VERSION) { + return DashSchemeConverter.upTo8(data); + } + return data; + } + + static upTo3(data: any) { + const {salt, pages, counter, schemeVersion} = data; + + if (schemeVersion >= 3) { + return data; + } + + const savedResponses: any = {}; + + const [page] = pages; + + const {id: pageId, tabs} = page; + + const convertedTabs = tabs.map((tab: any) => { + const {id: tabId, items, title, layout, ignores = []} = tab; + return { + id: tabId, + items: items.map( + ({id, data: itemData, tabs, type, defaults, namespace = 'default'}: any) => { + const data = itemData || tabs; + if (type === DashTabItemType.Control && !defaults) { + const defaultValue = data.control.defaultValue || ''; + + if (data.dataset) { + const {id: datasetId, fieldName} = data.dataset; + + try { + const {fields} = savedResponses[datasetId]; + + savedResponses[datasetId] = {fields}; + + const field = fields.find( + ({title}: any) => title === fieldName, + ); + + if (field) { + data.dataset.fieldId = field.guid; + delete data.dataset.fieldName; + delete data.dataset.name; + defaults = {[field.guid]: defaultValue}; + } else { + defaults = {[fieldName]: defaultValue}; + } + } catch (error) { + console.error('DATASET_FIELDS', id, error); + defaults = {[fieldName]: defaultValue}; + } + } else { + const connection = tab.connections.find( + ({fromId}: any) => fromId === id, + ); + + if (connection) { + data.control.fieldName = connection.param; + defaults = {[connection.param]: defaultValue}; + } else { + defaults = {}; + } + } + } else if (!defaults) { + defaults = {}; + } + return {id, data, type, defaults, namespace}; + }, + ), + title, + layout, + ignores, + }; + }); + + return { + salt, + counter, + schemeVersion: 3, + pages: [{id: pageId, tabs: convertedTabs}], + }; + } + + // adding the aliases field for each tab + static upTo4(originalData: any) { + const data = DashSchemeConverter.upTo3(originalData); + + const {salt, pages, schemeVersion, counter} = data; + + if (schemeVersion >= 4) { + return data; + } + + const [page] = pages; + + const {id: pageId, tabs} = page; + + const convertedTabs = tabs.map((tab: any) => ({...tab, aliases: {}})); + + return { + salt, + counter, + schemeVersion: 4, + pages: [{id: pageId, tabs: convertedTabs}], + }; + } + + // ignors for the WIDGET-elements is translated into ignors for tabs WIDGET-elements + static upTo6(originalData: any) { + const data = DashSchemeConverter.upTo4(originalData); + + const {salt, pages, counter, schemeVersion} = data; + + if (schemeVersion >= 6) { + return data; + } + + const [page] = pages; + + const {id: pageId, tabs} = page; + + const convertedTabs = tabs.map(({ignores, ...tab}: any) => ({ + ...tab, + ignores: ignores.reduce((result: any, {who, whom}: any) => { + tab.items + .filter(({id, type}: any) => id === who && type === DashTabItemType.Widget) + .forEach(({data}: any) => + data.forEach(({id}: any) => { + result.push({who: id, whom}); + }), + ); + return result; + }, []), + })); + + return { + salt, + counter, + schemeVersion: 6, + pages: [{id: pageId, tabs: convertedTabs}], + }; + } + + static upTo7(originalData: any): DashData { + const data = DashSchemeConverter.upTo6(originalData); + + const {salt, pages, counter, schemeVersion, settings = {}} = data; + + if (schemeVersion >= 7) { + return data; + } + + const tabs: DashTab[] = pages[0].tabs; + + tabs.forEach((tab: any) => { + // eslint-disable-next-line complexity + tab.items = tab.items.map((item: any) => { + const {type, data} = item; + + if (type === DashTabItemType.Widget) { + item.data = { + hideTitle: data[0].hideTitle, + tabs: data.map(({data: {uuid, ...inner}, ...outer}: any) => ({ + chartId: uuid, + ...inner, + ...outer, + })), + }; + } + + if (type === DashTabItemType.Control) { + const { + title, + control, + dataset: {id: datasetId = null, fieldId: datasetFieldId = null} = {}, + external: {entryId = null} = {}, + showTitle, + sourceType, + } = item.data; + + if ( + control.elementType === DashTabItemControlElementType.Date && + typeof control.defaultValue === 'object' + ) { + const { + defaultValue: {type, value = {}}, + } = control; + + if (control.isRange) { + if (type === 'relative') { + const {from = '0', to = '0'} = value; + const resultFrom = + from[0] === '-' ? `+${from.replace('-', '')}` : `-${from || 0}`; + const resultTo = + to[0] === '-' ? `+${to.replace('-', '')}` : `-${to || 0}`; + control.defaultValue = `__interval___relative_${resultFrom}d___relative_${resultTo}d`; + } else if (type === 'date' && value.from && value.to) { + control.defaultValue = `__interval_${dateTimeUtc({input: value.from}).format(DATE_FORMAT_V7)}_${dateTimeUtc({input: value.to}).format(DATE_FORMAT_V7)}`; + } else { + control.defaultValue = ''; + } + } else if (type === 'relative') { + const {from = '0'} = value; + const resultFrom = + from[0] === '-' ? `+${from.replace('-', '')}` : `-${from || 0}`; + control.defaultValue = `__relative_${resultFrom}d`; + } else if (type === 'date' && value.from) { + control.defaultValue = dateTimeUtc({input: value.from}).format( + DATE_FORMAT_V7, + ); + } else { + control.defaultValue = ''; + } + + item.defaults[datasetFieldId || control.fieldName] = control.defaultValue; + } + + if ( + control.elementType === DashTabItemControlElementType.Select && + control.acceptableValues + ) { + control.acceptableValues = control.acceptableValues.map((value: any) => ({ + title: value, + value, + })); + } + + item.data = { + title, + sourceType, + source: omitBy( + { + showTitle, + ...control, + datasetId, + datasetFieldId, + chartId: entryId, + }, + (value) => !value, + ), + }; + } + + return item; + }); + + tab.connections = tab.ignores.map(({who, whom}: any) => ({ + from: who, + to: whom, + kind: DashTabConnectionKind.Ignore, + })); + delete tab.ignores; + }); + + const result: DashData = { + salt, + counter, + schemeVersion: 7, + tabs, + settings, + }; + + return result; + } + + static upTo8(originalData: any): DashData { + const data = DashSchemeConverter.upTo7(originalData); + const {schemeVersion} = data; + + if (schemeVersion >= 8) { + return data; + } + + data.tabs.forEach((dashTabItem) => { + dashTabItem.items = dashTabItem.items.map((wi) => { + const defaultOldColor = getDefaultDashWidgetBgColorByType(wi.type); + const widgetItem = migrateBgColor(wi, defaultOldColor); + if (widgetItem.type !== DashTabItemType.Control) { + return widgetItem; + } + for (const [key, val] of Object.entries(widgetItem.defaults)) { + widgetItem.defaults[key] = val === null ? '' : val; + } + return widgetItem; + }); + }); + + return { + ...data, + schemeVersion: 8, + }; + } +} + +export default DashSchemeConverter; diff --git a/src/shared/modules/entry.ts b/src/shared/modules/entry.ts new file mode 100644 index 0000000..d55c174 --- /dev/null +++ b/src/shared/modules/entry.ts @@ -0,0 +1,23 @@ +export function getEntryNameByKey({key, index = -1}: {key: string | null; index?: number}) { + let name = ''; + + if (key && typeof key === 'string') { + let pathSplit = key.split('/'); + pathSplit = pathSplit.filter(Boolean); + + if (pathSplit.length !== 0) { + name = pathSplit.splice(index, 1)[0]; + } + } + + return name; +} + +export function normalizeDestination(destination = '') { + // Delete extreme slashes, and add one to the right + return destination.replace(/^\/+|\/+$/g, '') + '/'; +} + +export function isUsersFolder(key = '') { + return key.toLowerCase() === 'users/'; +} diff --git a/src/shared/modules/fields.ts b/src/shared/modules/fields.ts new file mode 100644 index 0000000..5c8cc69 --- /dev/null +++ b/src/shared/modules/fields.ts @@ -0,0 +1,56 @@ +import isEmpty from 'lodash/isEmpty'; + +import type {DatasetField, Field, MarkupItem, ServerField, ServerFieldFormatting} from '../types'; +import {DATASET_FIELD_TYPES, isFloatField, isNumberField} from '../types'; +import {getFieldUISettings} from '../utils'; + +import {getDistinctValue} from './colors/distincts-helpers'; +import {markupToRawString} from './wizard-helpers'; + +export const MINIMUM_FRACTION_DIGITS = 2; + +type GetFormatOptionsArgs = { + formatting?: ServerFieldFormatting; + data_type: string; + ui_settings?: string; +}; + +export const getFormatOptions = (field: GetFormatOptionsArgs) => { + if (isNumberField(field)) { + let formatOptions = field.formatting ?? {}; + + if (isEmpty(formatOptions)) { + const fieldUISettings = getFieldUISettings({field}); + formatOptions = fieldUISettings?.numberFormatting ?? {}; + } + + if (typeof formatOptions?.precision !== 'number' && isFloatField(field)) { + formatOptions.precision = MINIMUM_FRACTION_DIGITS; + } + return formatOptions; + } + return undefined; +}; + +export function getFakeTitleOrTitle(field?: ServerField) { + if (!field) { + return ''; + } + + return field.fakeTitle || field.title; +} + +export function getFieldDistinctValues(field: Field | DatasetField, distinctsData: string[][]) { + return distinctsData.reduce((acc: string[], cur: (string | MarkupItem)[]) => { + const rawDistinctValue = cur[0]; + let distinctValue: string; + + if (field.data_type === DATASET_FIELD_TYPES.MARKUP && rawDistinctValue) { + distinctValue = markupToRawString(rawDistinctValue as MarkupItem); + } else { + distinctValue = getDistinctValue(rawDistinctValue); + } + + return acc.concat(distinctValue); + }, [] as string[]); +} diff --git a/src/shared/modules/format-units/formatUnit.test.ts b/src/shared/modules/format-units/formatUnit.test.ts new file mode 100644 index 0000000..c8823a3 --- /dev/null +++ b/src/shared/modules/format-units/formatUnit.test.ts @@ -0,0 +1,31 @@ +import {formatNumber, getXlsxNumberFormat} from './formatUnit'; +import {i18nInstance} from './i18n/i18n'; +import type {FormatNumberOptions} from './types'; + +i18nInstance.setLang('en'); + +describe('chartkit/utils', () => { + test.each<[unknown, FormatNumberOptions | undefined, string]>([ + ['not-a-number', undefined, 'NaN'], + [NaN, undefined, 'NaN'], + ['0.2211556', undefined, '0.2211556'], + ['0.2211556', {precision: 4}, '0.2212'], + ])('formatNumber (args: {value: %p, options: %p})', (value, options, expected) => { + const result = formatNumber(value as number, options); + expect(result).toEqual(expected); + }); + + test.each<[unknown, FormatNumberOptions | undefined, string]>([ + [1, undefined, '#,##0'], + [1000, undefined, '#,##0'], + [1, {precision: 3}, '#,##0.000'], + [1, {format: 'percent'}, '#,##0%'], + [1, {showRankDelimiter: false}, '###0'], + [1000, {unit: 'k'}, '#,##0,"K"'], + [1000, {unit: 'k', precision: 1}, '#,##0.0,"K"'], + [2319420, {unit: 'm', precision: 3}, '#,##0.000,,"M"'], + ])('getXlsxNumberFormat (args: {value: %p, options: %p})', (value, options, expected) => { + const result = getXlsxNumberFormat(value as number, options); + expect(result).toEqual(expected); + }); +}); diff --git a/src/shared/modules/format-units/formatUnit.ts b/src/shared/modules/format-units/formatUnit.ts new file mode 100644 index 0000000..c108f65 --- /dev/null +++ b/src/shared/modules/format-units/formatUnit.ts @@ -0,0 +1,181 @@ +import {i18nInstance, makeInstance} from './i18n/i18n'; +import type {FormatNumberOptions, FormatOptions} from './types'; + +import en from './i18n/en.json'; +import ru from './i18n/ru.json'; + +const i18n = makeInstance('chartkit-units', {ru, en}); + +function getUnitRate(value: number, exponent: number, unitsI18nKeys: string[]) { + let resultUnitRate = 1; + while ( + Math.abs(value / Math.pow(exponent, resultUnitRate)) >= 1 && + resultUnitRate < 10 && + i18n(unitsI18nKeys[resultUnitRate]) + ) { + resultUnitRate++; + } + resultUnitRate--; + + return resultUnitRate; +} + +function unitFormatter({ + exponent, + unitsI18nKeys, + unitDelimiterI18nKey, +}: { + exponent: number; + unitsI18nKeys: string[]; + unitDelimiterI18nKey: string; +}) { + return function formatUnit(value: number, options: FormatOptions = {}) { + const {precision, unitRate, showRankDelimiter = true, lang} = options; + + const i18nLang = i18nInstance.lang; + if (lang) { + i18nInstance.setLang(lang); + } + + let resultUnitRate; + if (typeof unitRate === 'number') { + resultUnitRate = unitRate; + } else { + resultUnitRate = getUnitRate(value, exponent, unitsI18nKeys); + } + + let result: number | string = value / Math.pow(exponent, resultUnitRate); + if (typeof precision === 'number') { + result = Number(result.toFixed(precision)); + } else if (precision === 'auto' && result % 1 !== 0) { + result = Number(result.toFixed(Math.abs(result) > 1 ? 2 : 4)); + } + + // precision is set in toFixed above, + // here you only need to divide the digits by useGrouping, + // and not to lose the decimal part while setting minimumFractionDigits/maximumFractionDigits + result = new Intl.NumberFormat(lang ?? i18nLang, { + // to complement the decimal part, where it absent or where there are fewer digits than in precision + minimumFractionDigits: typeof precision === 'number' ? precision : 0, + maximumFractionDigits: 20, + useGrouping: showRankDelimiter, + // @ts-ignore + signDisplay: 'negative', + }).format(result); + + const unit = i18n(unitsI18nKeys[resultUnitRate]); + const delimiter = i18n(unitDelimiterI18nKey); + + i18nInstance.setLang(i18nLang!); + + return `${result}${delimiter}${unit}`; + }; +} + +export const formatBytes = unitFormatter({ + exponent: 1024, + unitDelimiterI18nKey: 'value_space-delimiter', + unitsI18nKeys: ['value_short-bytes', 'value_short-kilobytes', 'value_short-megabytes'], +}); + +export const formatDuration = unitFormatter({ + exponent: 1000, + unitDelimiterI18nKey: 'value_space-delimiter', + unitsI18nKeys: ['value_short-milliseconds', 'value_short-seconds', 'value_short-minutes'], +}); + +const NUMBER_INITS_I18N_KEYS = [ + 'value_short-empty', + 'value_short-k', + 'value_short-m', + 'value_short-b', + 'value_short-t', +]; + +const baseFormatNumber = unitFormatter({ + exponent: 1000, + unitDelimiterI18nKey: 'value_number-delimiter', + unitsI18nKeys: NUMBER_INITS_I18N_KEYS, +}); + +const NUMBER_UNIT_RATE_BY_UNIT = { + default: 0, + auto: undefined, + k: 1, + m: 2, + b: 3, + t: 4, +}; + +export function formatNumber(value: number | string, options: FormatNumberOptions = {}) { + if (Number.isNaN(value) || Number.isNaN(Number(value))) { + return new Intl.NumberFormat('en').format(Number(value)); + } + + const { + format = 'number', + multiplier = 1, + prefix = '', + postfix = '', + unit, + unitRate, + labelMode, + } = options; + + let changedMultiplier = multiplier; + let prePostfix = ''; + + if (format === 'percent') { + changedMultiplier = 100; + prePostfix = '%'; + } + + if (labelMode === 'percent') { + prePostfix = '%'; + } + + const formattedValue = baseFormatNumber(Number(value) * changedMultiplier, { + ...options, + unitRate: unitRate ?? NUMBER_UNIT_RATE_BY_UNIT[unit ?? 'default'], + }); + + return `${prefix}${formattedValue}${prePostfix}${postfix}`; +} + +export function getXlsxNumberFormat(value: number, options: FormatNumberOptions = {}) { + let mainPart = '#,##0'; + let decimalPart = ''; + let percent = ''; + let unit = ''; + const prefix = options.prefix ? `"${options.prefix}"` : ''; + const postfix = options.postfix ? `"${options.postfix}"` : ''; + + if (options.showRankDelimiter === false) { + mainPart = '###0'; + } + + if (options.precision) { + decimalPart = `.${new Array(options.precision).fill(0).join('')}`; + } + + if (options.format === 'percent') { + percent = '%'; + } else { + const unitRate = options.unitRate ?? NUMBER_UNIT_RATE_BY_UNIT[options.unit ?? 'default']; + let resultUnitRate; + if (typeof unitRate === 'number') { + resultUnitRate = unitRate; + } else { + const exponent = 1000; + resultUnitRate = getUnitRate(value, exponent, NUMBER_INITS_I18N_KEYS); + } + + if (resultUnitRate) { + unit = `"${i18n(NUMBER_INITS_I18N_KEYS[resultUnitRate])}"`; + mainPart += decimalPart + new Array(resultUnitRate).fill(',').join(''); + decimalPart = ''; + } + } + + return `${prefix}${mainPart}${decimalPart}${percent}${unit || ''}${postfix}`; +} diff --git a/src/shared/modules/format-units/i18n/en.json b/src/shared/modules/format-units/i18n/en.json new file mode 100644 index 0000000..47c58b3 --- /dev/null +++ b/src/shared/modules/format-units/i18n/en.json @@ -0,0 +1,17 @@ +{ + "chartkit-units": { + "value_short-bytes": "B", + "value_short-kilobytes": "KB", + "value_short-megabytes": "MB", + "value_short-milliseconds": "ms", + "value_short-seconds": "s", + "value_short-minutes": "m", + "value_short-empty": "", + "value_short-k": "K", + "value_short-m": "M", + "value_short-b": "B", + "value_short-t": "T", + "value_space-delimiter": " ", + "value_number-delimiter": "" + } +} diff --git a/src/shared/modules/format-units/i18n/i18n.ts b/src/shared/modules/format-units/i18n/i18n.ts new file mode 100644 index 0000000..d3235e6 --- /dev/null +++ b/src/shared/modules/format-units/i18n/i18n.ts @@ -0,0 +1,19 @@ +import {I18N} from '@gravity-ui/i18n'; + +import type {Lang} from '../../../types'; + +type KeysetData = Parameters['registerKeysets']>[1]; + +const i18nInstance = new I18N(); + +// equivalent of the previous behavior (undefined -> 'ru') +const defaultLang: Lang = 'ru'; + +i18nInstance.setLang(defaultLang); + +function makeInstance(keysetName: string, keysetsData: Record) { + Object.entries(keysetsData).forEach(([key, value]) => i18nInstance.registerKeysets(key, value)); + return i18nInstance.i18n.bind(i18nInstance, keysetName); +} + +export {i18nInstance, makeInstance}; diff --git a/src/shared/modules/format-units/i18n/ru.json b/src/shared/modules/format-units/i18n/ru.json new file mode 100644 index 0000000..0bfc004 --- /dev/null +++ b/src/shared/modules/format-units/i18n/ru.json @@ -0,0 +1,17 @@ +{ + "chartkit-units": { + "value_short-bytes": "Б", + "value_short-kilobytes": "КБ", + "value_short-megabytes": "МБ", + "value_short-milliseconds": "мс", + "value_short-seconds": "с", + "value_short-minutes": "м", + "value_short-empty": "", + "value_short-k": "K", + "value_short-m": "M", + "value_short-b": "B", + "value_short-t": "T", + "value_space-delimiter": " ", + "value_number-delimiter": "" + } +} diff --git a/src/shared/modules/format-units/index.ts b/src/shared/modules/format-units/index.ts new file mode 100644 index 0000000..fc973b9 --- /dev/null +++ b/src/shared/modules/format-units/index.ts @@ -0,0 +1 @@ +export * from './formatUnit'; diff --git a/src/shared/modules/format-units/types.ts b/src/shared/modules/format-units/types.ts new file mode 100644 index 0000000..4637dac --- /dev/null +++ b/src/shared/modules/format-units/types.ts @@ -0,0 +1,17 @@ +export type FormatOptions = { + // with 'auto', if there is a decimal part, then there will be 2 decimal places, + // and if the number is less than 1, then there will be 4 characters + precision?: number | 'auto'; + unitRate?: number; + showRankDelimiter?: boolean; + lang?: string; + labelMode?: string; +}; + +export type FormatNumberOptions = FormatOptions & { + format?: 'number' | 'percent'; + multiplier?: number; + prefix?: string; + postfix?: string; + unit?: 'auto' | 'k' | 'm' | 'b' | 't' | null; +}; diff --git a/src/shared/modules/helpers.ts b/src/shared/modules/helpers.ts new file mode 100644 index 0000000..e49939b --- /dev/null +++ b/src/shared/modules/helpers.ts @@ -0,0 +1,271 @@ +import type {WizardVisualizationId} from '../constants'; +import { + ENTRY_ID_LENGTH, + ENTRY_ROUTES, + ENTRY_SLUG_SEPARATOR, + NavigatorModes, + VISUALIZATIONS_WITH_NAVIGATOR, + WIZARD_ROUTE, +} from '../constants'; +import type { + Field, + ServerChartsConfig, + ServerCommonSharedExtraSettings, + ServerField, + ServerPlaceholder, + ServerPlaceholderSettings, + StringParams, +} from '../types'; +import {AxisMode, DATASET_FIELD_TYPES, DatasetFieldType, isDateField} from '../types'; + +import { + Operations, + resolveIntervalDate, + resolveOperation, + resolveRelativeDate, +} from './charts-shared'; + +function getEntryId(str: string): string | null { + const possibleEntryId = str.slice(0, ENTRY_ID_LENGTH); + const isEntryIdResult = isEntryId(possibleEntryId); + if (isEntryIdResult && str.length === ENTRY_ID_LENGTH) { + return possibleEntryId; + } + if (isEntryIdResult && str[ENTRY_ID_LENGTH] === ENTRY_SLUG_SEPARATOR) { + return possibleEntryId; + } + return null; +} + +export function extractEntryId(input?: string): string | null { + if (!input || typeof input !== 'string') { + return null; + } + + const [partOne, partTwo, partThree] = input.split('/').filter(Boolean); + if (partThree === WIZARD_ROUTE && partTwo === 'new') { + return getEntryId(partOne); + } + if (partTwo && ENTRY_ROUTES.some((route) => partOne === route)) { + return getEntryId(partTwo); + } + if (partOne && partTwo !== 'new') { + return getEntryId(partOne); + } + return null; +} + +export function decodeURISafe(uri: string) { + if (!uri) { + return uri; + } + return decodeURI(uri.replace(/%(?![0-9a-fA-F][0-9a-fA-F]+)/g, '%25')); +} + +type PrepareFilterValuesArgs = { + field?: ServerField; + values: string[]; +}; + +type PreparedFilterValues = { + values: (string | string[])[]; + operations: Operations[]; +}; + +export function prepareFilterValuesWithOperations({values, field}: PrepareFilterValuesArgs) { + return prepareArrayFilterValues({values, field}); +} + +export function prepareFilterValues({values}: {values: string[]}): (string[] | string)[] { + return prepareArrayFilterValues({values}).values; +} + +function prepareArrayFilterValues({ + field, + values, +}: { + field?: ServerField; + values: string[]; +}): PreparedFilterValues { + return values.reduce( + ( + acc: { + operations: Operations[]; + values: (string[] | string)[]; + }, + rawValue: string, + ) => { + const defaultOperation = + field && isDateField(field) && values.length === 1 ? Operations.EQ : undefined; + const parsedFiltersOperation = resolveOperation(rawValue, defaultOperation); + + if (!parsedFiltersOperation) { + acc.values.push(rawValue); + acc.operations.push(Operations.IN); + return acc; + } + + let value: string[] | string | null = parsedFiltersOperation.value; + let operation = parsedFiltersOperation.operation; + + if (/^__relative/.test(value)) { + value = resolveRelativeDate(value); + } else if (/^__interval/.test(value)) { + const interval = resolveIntervalDate(value); + + if (!(interval === null || interval.from === null || interval.to === null)) { + value = [interval.from, interval.to]; + operation = Operations.BETWEEN; + } + } + + if (value !== null) { + acc.values.push(value); + acc.operations.push(operation); + } + + return acc; + }, + {operations: [], values: []}, + ); +} + +export function getAxisMode( + placeholderSettings?: ServerPlaceholderSettings, + fieldGuid?: string, +): AxisMode { + const xAxisModeMap = placeholderSettings?.axisModeMap || {}; + + let xAxisMode; + + if (fieldGuid) { + xAxisMode = xAxisModeMap[fieldGuid]; + } + + return xAxisMode || AxisMode.Discrete; +} + +export function getIsNavigatorAvailable( + visualization: + | { + id: string; + placeholders: ServerPlaceholder[]; + } + | undefined, +) { + if (!visualization) { + return false; + } + + const {placeholders, id} = visualization; + const xPlaceholder = Array.isArray(placeholders) && placeholders.find((pl) => pl.id === 'x'); + + if (!xPlaceholder) { + return false; + } + + const xItems = xPlaceholder.items; + + return Boolean( + VISUALIZATIONS_WITH_NAVIGATOR.has(id as WizardVisualizationId) && + xItems.length && + isDateField(xItems[0]) && + getAxisMode(xPlaceholder.settings, xItems[0].guid) !== AxisMode.Discrete, + ); +} + +export function getNavigatorMode(extraSettings?: ServerCommonSharedExtraSettings) { + return extraSettings?.navigatorSettings?.navigatorMode || extraSettings?.navigatorMode; +} + +export function getIsNavigatorEnabled(shared: ServerChartsConfig) { + const extraSettings = shared.extraSettings; + const navigatorMode = getNavigatorMode(extraSettings); + return getIsNavigatorAvailable(shared.visualization) && navigatorMode === NavigatorModes.Show; +} + +export const isParameter = (field: Partial | Partial) => { + return field.calc_mode === 'parameter'; +}; + +export const isDimensionField = (field: Partial | Partial | undefined) => { + return field?.type === DatasetFieldType.Dimension && !isParameter(field); +}; + +export const isMeasureField = (field: Partial | Partial | undefined) => { + return field?.type === DatasetFieldType.Measure; +}; + +export const isPseudoField = (field: Partial | Partial | undefined) => { + return field?.type === DatasetFieldType.Pseudo; +}; + +export const isTreeDataType = (data_type: string) => { + return ( + data_type === DATASET_FIELD_TYPES.TREE_STR || + data_type === DATASET_FIELD_TYPES.TREE_INT || + data_type === DATASET_FIELD_TYPES.TREE_FLOAT + ); +}; + +export const isTreeField = (field: {data_type: string}) => { + return isTreeDataType(field.data_type); +}; + +export const transformParamsToUrlParams = (widgetParams: StringParams) => { + return Object.keys(widgetParams).reduce((acc: [string, string][], paramName: string) => { + const paramValue = widgetParams[paramName]; + if (Array.isArray(paramValue)) { + return acc.concat(paramValue.map((value) => [paramName, value])); + } else { + acc.push([paramName, paramValue]); + return acc; + } + }, []); +}; + +export const prepareUrlParams = (params: [string, string][]): [string, string][] => { + return params.map((param: [string, string]) => { + // Ideally, the parameters should always contain strings. + // But sometimes number|boolean|string can be specified. + return param.map(String) as [string, string]; + }); +}; +export const transformUrlParamsToParams = (urlParams: [string, string][]) => { + return prepareUrlParams(urlParams).reduce((acc, paramsPair) => { + const [key, value = ''] = paramsPair; + if (acc[key]) { + (acc[key] as string[]).push(value); + } else { + acc[key] = [value]; + } + return acc; + }, {} as StringParams); +}; + +export function getSortedData(data: Record): Record { + return Object.keys(data) + .sort() + .reduce( + (acc, key) => { + acc[key as K] = data[key as K]; + return acc; + }, + {} as Record, + ); +} + +export function getObjectValueByPossibleKeys(possibleKeys: string[], obj: Record) { + const firstMatchedKey = possibleKeys.find((key) => typeof obj[key] !== 'undefined'); + + if (firstMatchedKey) { + return obj[firstMatchedKey]; + } + + return undefined; +} + +export const isEntryId = (value: string) => { + const ENTRY_ID_FORMAT = /^[0-9a-z]{13}$/; + return ENTRY_ID_FORMAT.test(value); +}; diff --git a/src/shared/modules/index.ts b/src/shared/modules/index.ts new file mode 100644 index 0000000..f1498bd --- /dev/null +++ b/src/shared/modules/index.ts @@ -0,0 +1,20 @@ +export {default as DashSchemeConverter} from './dash-scheme-converter'; + +export * from './helpers'; +export * from './ql-helpers'; +export * from './slugify'; +export * from './entry'; +export * from './tenant'; +export * from './wizard-helpers'; +export * from './charts-shared'; +export * from './url'; +export * from './config/wizard'; +export * from './user'; +export * from './bi-api'; +export * from './fields'; +export * from './sleep'; +export * from './qa-helpers'; +export * from './format-units'; +export * from './wizard'; +export * from './colors/distincts-helpers'; +export * from './markup'; diff --git a/src/shared/modules/language.ts b/src/shared/modules/language.ts new file mode 100644 index 0000000..5c7e669 --- /dev/null +++ b/src/shared/modules/language.ts @@ -0,0 +1,7 @@ +import type {Keysets, TypedI18n} from '../../i18n/types'; + +export const getTranslationFn = + (i18n: TypedI18n) => + (keyset: string, key: string, params?: Record) => { + return i18n.keyset(keyset as keyof Keysets)(key, params); + }; diff --git a/src/shared/modules/markdown/emoji-defs.ts b/src/shared/modules/markdown/emoji-defs.ts new file mode 100644 index 0000000..c2597e7 --- /dev/null +++ b/src/shared/modules/markdown/emoji-defs.ts @@ -0,0 +1,266 @@ +// some emojis from markdown-it-emoji/lib/data/full.json +// copied from markdwn-editor +export const emojiDefs: Record = { + grinning: '😀', + smiley: '😃', + smile: '😄', + grin: '😁', + laughing: '😆', + satisfied: '😆', + sweat_smile: '😅', + rofl: '🤣', + joy: '😂', + slightly_smiling_face: '🙂', + upside_down_face: '🙃', + wink: '😉', + blush: '😊', + innocent: '😇', + smiling_face_with_three_hearts: '🥰', + heart_eyes: '😍', + star_struck: '🤩', + kissing_heart: '😘', + kissing: '😗', + // relaxed: '☺️', + kissing_closed_eyes: '😚', + kissing_smiling_eyes: '😙', + smiling_face_with_tear: '🥲', + yum: '😋', + stuck_out_tongue: '😛', + stuck_out_tongue_winking_eye: '😜', + zany_face: '🤪', + stuck_out_tongue_closed_eyes: '😝', + money_mouth_face: '🤑', + hugs: '🤗', + hand_over_mouth: '🤭', + shushing_face: '🤫', + thinking: '🤔', + zipper_mouth_face: '🤐', + raised_eyebrow: '🤨', + neutral_face: '😐', + expressionless: '😑', + no_mouth: '😶', + smirk: '😏', + unamused: '😒', + roll_eyes: '🙄', + grimacing: '😬', + lying_face: '🤥', + relieved: '😌', + pensive: '😔', + sleepy: '😪', + drooling_face: '🤤', + sleeping: '😴', + mask: '😷', + face_with_thermometer: '🤒', + face_with_head_bandage: '🤕', + nauseated_face: '🤢', + vomiting_face: '🤮', + sneezing_face: '🤧', + hot_face: '🥵', + cold_face: '🥶', + woozy_face: '🥴', + dizzy_face: '😵', + exploding_head: '🤯', + cowboy_hat_face: '🤠', + partying_face: '🥳', + disguised_face: '🥸', + sunglasses: '😎', + nerd_face: '🤓', + monocle_face: '🧐', + confused: '😕', + worried: '😟', + slightly_frowning_face: '🙁', + // frowning_face: '☹️', + open_mouth: '😮', + hushed: '😯', + astonished: '😲', + flushed: '😳', + pleading_face: '🥺', + frowning: '😦', + anguished: '😧', + fearful: '😨', + cold_sweat: '😰', + disappointed_relieved: '😥', + cry: '😢', + sob: '😭', + scream: '😱', + confounded: '😖', + persevere: '😣', + disappointed: '😞', + sweat: '😓', + weary: '😩', + tired_face: '😫', + yawning_face: '🥱', + triumph: '😤', + rage: '😡', + pout: '😡', + angry: '😠', + cursing_face: '🤬', + smiling_imp: '😈', + imp: '👿', + skull: '💀', + skull_and_crossbones: '☠️', + shit: '💩', + clown_face: '🤡', + japanese_ogre: '👹', + japanese_goblin: '👺', + ghost: '👻', + alien: '👽', + space_invader: '👾', + robot: '🤖', + smiley_cat: '😺', + smile_cat: '😸', + joy_cat: '😹', + heart_eyes_cat: '😻', + smirk_cat: '😼', + kissing_cat: '😽', + scream_cat: '🙀', + crying_cat_face: '😿', + pouting_cat: '😾', + see_no_evil: '🙈', + hear_no_evil: '🙉', + speak_no_evil: '🙊', + kiss: '💋', + broken_heart: '💔', + heart: '❤️', + zzz: '💤', + wave: '👋', + raised_back_of_hand: '🤚', + raised_hand_with_fingers_splayed: '🖐️', + hand: '✋', + raised_hand: '✋', + vulcan_salute: '🖖', + ok_hand: '👌', + pinched_fingers: '🤌', + pinching_hand: '🤏', + v: '✌️', + crossed_fingers: '🤞', + love_you_gesture: '🤟', + metal: '🤘', + call_me_hand: '🤙', + point_left: '👈', + point_right: '👉', + point_up_2: '👆', + middle_finger: '🖕', + fu: '🖕', + point_down: '👇', + point_up: '☝️', + '+1': '👍', + thumbsup: '👍', + '-1': '👎', + thumbsdown: '👎', + fist: '✊', + punch: '👊', + fist_left: '🤛', + fist_right: '🤜', + clap: '👏', + raised_hands: '🙌', + open_hands: '👐', + palms_up_together: '🤲', + handshake: '🤝', + pray: '🙏', + writing_hand: '✍️', + nail_care: '💅', + selfie: '🤳', + muscle: '💪', + mechanical_arm: '🦾', + eyes: '👀', + eye: '👁️', + footprints: '👣', + monkey_face: '🐵', + monkey: '🐒', + dog: '🐶', + wolf: '🐺', + fox_face: '🦊', + cat: '🐱', + horse: '🐴', + unicorn: '🦄', + cow: '🐮', + pig: '🐷', + mouse: '🐭', + chicken: '🐔', + rooster: '🐓', + bird: '🐦', + four_leaf_clover: '🍀', + egg: '🥚', + cake: '🍰', + coffee: '☕', + wine_glass: '🍷', + beer: '🍺', + beers: '🍻', + clinking_glasses: '🥂', + house: '🏠', + bank: '🏦', + sunrise: '🌅', + taxi: '🚕', + car: '🚗', + stop_sign: '🛑', + construction: '🚧', + anchor: '⚓', + rocket: '🚀', + hourglass: '⌛', + alarm_clock: '⏰', + moon: '🌔', + zap: '⚡', + fire: '🔥', + tada: '🎉', + gift: '🎁', + trophy: '🏆', + '8ball': '🎱', + video_game: '🎮', + spades: '♠️', + hearts: '♥️', + diamonds: '♦️', + clubs: '♣️', + chess_pawn: '♟️', + art: '🎨', + crown: '👑', + mega: '📣', + bell: '🔔', + no_bell: '🔕', + mag: '🔍', + lock: '🔒', + unlock: '🔓', + key: '🔑', + gun: '🔫', + link: '🔗', + drop_of_blood: '🩸', + pill: '💊', + bed: '🛏️', + moyai: '🗿', + warning: '⚠️', + children_crossing: '🚸', + no_entry: '⛔', + no_entry_sign: '🚫', + no_bicycles: '🚳', + no_smoking: '🚭', + do_not_litter: '🚯', + 'non-potable_water': '🚱', + no_pedestrians: '🚷', + no_mobile_phones: '📵', + underage: '🔞', + radioactive: '☢️', + biohazard: '☣️', + back: '🔙', + end: '🔚', + on: '🔛', + soon: '🔜', + top: '🔝', + cinema: '🎦', + infinity: '♾️', + bangbang: '‼️', + interrobang: '⁉️', + question: '❓', + exclamation: '❗', + o: '⭕', + x: '❌', + copyright: '©️', + registered: '®️', + tm: '™️', + new: '🆕', + ok: '🆗', + sos: '🆘', + up: '🆙', + vs: '🆚', + radio_button: '🔘', + checkered_flag: '🏁', +}; diff --git a/src/shared/modules/markdown/markdown-plugins/unify-terms.ts b/src/shared/modules/markdown/markdown-plugins/unify-terms.ts new file mode 100644 index 0000000..70e512a --- /dev/null +++ b/src/shared/modules/markdown/markdown-plugins/unify-terms.ts @@ -0,0 +1,81 @@ +import type {MarkdownIt, StateCore} from '@diplodoc/transform/lib/typings'; + +import {YfmAttributes, YfmTokenTypes} from '../../../constants'; + +type Tokens = StateCore['tokens']; +type Token = StateCore['tokens'][number]; + +const unifyAttributes = (attrs: [string, string][], attrName: YfmAttributes, prefix: string) => { + for (const attr of attrs) { + if (attr[0] === attrName && !attr[1].startsWith(`:${prefix}`)) { + const value = attr[1]; + attr[1] = `:${prefix}_${value.slice(1)}`; + } + } + + return attrs; +}; + +const modifyTerm = (termToken: Token, prefix: string) => { + if (termToken.attrs) { + termToken.attrs = unifyAttributes(termToken.attrs, YfmAttributes.AriaDescribedby, prefix); + termToken.attrs = unifyAttributes(termToken.attrs, YfmAttributes.TermKey, prefix); + } + + return termToken; +}; + +const traverseLine = (tokens: Tokens, prefix: string) => { + let i = 0; + + while (tokens[i]) { + const currentToken = tokens[i]; + + switch (currentToken.type) { + case YfmTokenTypes.Inline: { + if (currentToken.children) { + currentToken.children = traverseLine(currentToken.children, prefix); + } + + break; + } + + case YfmTokenTypes.TermOpen: { + modifyTerm(currentToken, prefix); + break; + } + + case YfmTokenTypes.TemplateOpen: { + const token = tokens[i]; + const next = tokens[i + 1]; + + if (next && next.type === YfmTokenTypes.DefinitionOpen) { + if (token.attrs) { + token.attrs = unifyAttributes(token.attrs, YfmAttributes.Id, prefix); + } + + if (next.attrs) { + next.attrs = unifyAttributes(next.attrs, YfmAttributes.Id, prefix); + } + + i++; + } + break; + } + } + + i++; + } + + return tokens; +}; + +export const unifyTermIds = (md: MarkdownIt, options: {prefix: string}) => { + const prefix = options.prefix; + + try { + md.core.ruler.after('termReplace', 'termLinkRandom', (state: StateCore) => { + traverseLine(state.tokens, prefix); + }); + } catch (_) {} +}; diff --git a/src/shared/modules/markdown/markdown.ts b/src/shared/modules/markdown/markdown.ts new file mode 100644 index 0000000..1f37974 --- /dev/null +++ b/src/shared/modules/markdown/markdown.ts @@ -0,0 +1,144 @@ +import {transform as yfmCut} from '@diplodoc/cut-extension'; +import {transform as latex} from '@diplodoc/latex-extension/plugin'; +import {transform as mermaid} from '@diplodoc/mermaid-extension/plugin'; +import {transform as yfmTabs} from '@diplodoc/tabs-extension'; +import yfmTransform from '@diplodoc/transform'; +import code from '@diplodoc/transform/lib/plugins/code'; +import deflist from '@diplodoc/transform/lib/plugins/deflist'; +import imsize from '@diplodoc/transform/lib/plugins/imsize'; +import monospace from '@diplodoc/transform/lib/plugins/monospace'; +import notes from '@diplodoc/transform/lib/plugins/notes'; +import sup from '@diplodoc/transform/lib/plugins/sup'; +import table from '@diplodoc/transform/lib/plugins/table'; +import term from '@diplodoc/transform/lib/plugins/term'; +import type {Lang, MarkdownItPluginCb, OptionsType} from '@diplodoc/transform/lib/plugins/typings'; +import {defaultOptions} from '@diplodoc/transform/lib/sanitize'; +import type MarkdownIt from 'markdown-it'; +import type {PluginWithParams} from 'markdown-it'; +import MarkdownItColor from 'markdown-it-color'; +import MarkdownItEmoji from 'markdown-it-emoji'; +import MarkdonwItIns from 'markdown-it-ins'; +import Mila from 'markdown-it-link-attributes'; +import MarkdonwItMark from 'markdown-it-mark'; +import MarkdonwItSub from 'markdown-it-sub'; + +import {YFM_COLORIFY_MARKDOWN_CLASSNAME, YfmMetaScripts} from '../../constants'; + +import {emojiDefs} from './emoji-defs'; + +type RenderHtmlArgs = { + text?: string; + lang: string; + plugins?: MarkdownItPluginCb[]; +}; + +export type RenderHtmlOutput = { + result: string; + meta?: object; +}; + +export function getPreparedTextWithTermLinks(text: string) { + // temp terms bug fix until the editor supports transform plugin + const preparedTextWithTermDefs = text.replace( + /^\s*?\\\[\\\*([\wа-я]+)\\\]:(.*?\S+?.*?)$/gim, + '[*$1]:$2', + ); + + const preparedTextWithTermLinks = preparedTextWithTermDefs.replace( + /(\[.+?\])\(\*(%.+?)\)/g, + (_, p1, p2) => `${p1}(*${decodeURIComponent(p2)})`, + ); + + return preparedTextWithTermLinks; +} + +function getPlugins(additionalPlugins?: MarkdownItPluginCb[]) { + const plugins = [ + deflist, + notes, + yfmCut({bundle: false}) as PluginWithParams, + term, + (md: MarkdownIt) => + md + .use(Mila, { + matcher(href: string) { + return !href.startsWith('#'); + }, + attrs: { + target: '_blank', + rel: 'noopener noreferrer', + }, + }) + .use(MarkdownItColor, { + defaultClassName: YFM_COLORIFY_MARKDOWN_CLASSNAME, + }) + .use(MarkdownItEmoji, {defs: emojiDefs}), + yfmTabs({ + bundle: false, + features: { + enabledVariants: { + regular: true, + radio: false, + dropdown: false, + accordion: true, + }, + }, + }), + imsize, + (md: MarkdownIt) => + md.use(table, { + table_ignoreSplittersInInlineMath: true, + }), + monospace, + sup, + MarkdonwItSub, + MarkdonwItMark, + MarkdonwItIns, + latex({ + bundle: false, + runtime: YfmMetaScripts.LATEX, + }), + mermaid({ + bundle: false, + runtime: YfmMetaScripts.MERMAID, + }), + code, + ]; + + if (additionalPlugins) { + plugins.push(...additionalPlugins); + } + + return plugins; +} + +export function getYfmTransformOptions({ + lang, + plugins: additionalPlugins, +}: Pick): OptionsType { + const plugins = getPlugins(additionalPlugins); + + return { + plugins, + lang: lang as Lang, + vars: {}, + disableLiquid: true, + needToSanitizeHtml: true, + sanitizeOptions: { + ...defaultOptions, + disableStyleSanitizer: true, + }, + }; +} + +export function renderHTML(args: RenderHtmlArgs): RenderHtmlOutput { + const {text = '', lang, plugins = []} = args; + + const transformOptions = getYfmTransformOptions({lang, plugins}); + const preparedTextWithTermLinks = getPreparedTextWithTermLinks(text); + + const { + result: {html, meta}, + } = yfmTransform(preparedTextWithTermLinks, transformOptions); + return {result: html, meta}; +} diff --git a/src/shared/modules/markup.ts b/src/shared/modules/markup.ts new file mode 100644 index 0000000..7ea4777 --- /dev/null +++ b/src/shared/modules/markup.ts @@ -0,0 +1,17 @@ +import isPlainObject from 'lodash/isPlainObject'; + +import {MarkupItemTypes} from '../types'; +import type {MarkupItem, MarkupItemType} from '../types'; + +export function isMarkupItem(obj: unknown): obj is MarkupItem { + return isPlainObject(obj) && isMarkupObject(obj as object); +} + +function isMarkupObject(obj: {type?: string}): boolean { + if (obj.type && Object.values(MarkupItemTypes).includes(obj.type as MarkupItemType)) { + return true; + } + { + return false; + } +} diff --git a/src/shared/modules/qa-helpers.ts b/src/shared/modules/qa-helpers.ts new file mode 100644 index 0000000..dd88cf2 --- /dev/null +++ b/src/shared/modules/qa-helpers.ts @@ -0,0 +1,5 @@ +import type {ConnectorFormItem} from '../schema/bi/types'; + +export const getConnectorFormItemQa = ({id, name}: {id: ConnectorFormItem['id']; name: string}) => { + return `conn-${id}-${name}`; +}; diff --git a/src/shared/modules/ql-helpers.ts b/src/shared/modules/ql-helpers.ts new file mode 100644 index 0000000..4b4ff19 --- /dev/null +++ b/src/shared/modules/ql-helpers.ts @@ -0,0 +1,232 @@ +import {ConnectorType} from '../constants'; + +export const DATALENS_QL_CONNECTION_TYPES = { + CLICKHOUSE: ConnectorType.Clickhouse, + MSSQL: ConnectorType.Mssql, + MYSQL: ConnectorType.Mysql, + ORACLE: ConnectorType.Oracle, + POSTGRESQL: ConnectorType.Postgres, + TRINO: ConnectorType.Trino, + YQL: ConnectorType.Yq, // YDB, YQ + PROMQL: ConnectorType.Promql, + MONITORING: ConnectorType.Monitoring, +}; + +const YQL_TYPES = { + BOOL: 'Bool', + DATE: 'Date', + DATETIME: 'Datetime', + DATETIMETZ: 'DatetimeTZ', + UUID: 'UUID', + STRING: 'String', + DOUBLE: 'Double', + INT64: 'Int64', +}; + +const CLICKHOUSE_TYPES = { + DATE: 'Date', + DATETIME: 'DateTime', + DATETIME64: 'DateTime64', + ENUM8: 'Enum8', + ENUM16: 'Enum16', + UUID: 'UUID', + STRING: 'String', + FIXEDSTRING: 'FixedString', + FLOAT32: 'Float32', + FLOAT64: 'Float64', + DECIMAL: 'Decimal', + INT8: 'Int8', + INT16: 'Int16', + INT32: 'Int32', + INT64: 'Int64', + UINT8: 'UInt8', + UINT16: 'UInt16', + UINT32: 'UInt32', + UINT64: 'UInt64', +}; + +const CLICKHOUSE_TO_YQL_BASE: Record = { + [CLICKHOUSE_TYPES.DATE]: YQL_TYPES.DATE, + [CLICKHOUSE_TYPES.DATETIME]: YQL_TYPES.DATETIME, + [CLICKHOUSE_TYPES.UUID]: YQL_TYPES.UUID, + [CLICKHOUSE_TYPES.STRING]: YQL_TYPES.STRING, + [CLICKHOUSE_TYPES.FIXEDSTRING]: YQL_TYPES.STRING, + [CLICKHOUSE_TYPES.FLOAT32]: YQL_TYPES.DOUBLE, + [CLICKHOUSE_TYPES.FLOAT64]: YQL_TYPES.DOUBLE, + [CLICKHOUSE_TYPES.DECIMAL]: YQL_TYPES.DOUBLE, + [CLICKHOUSE_TYPES.INT8]: YQL_TYPES.INT64, + [CLICKHOUSE_TYPES.INT16]: YQL_TYPES.INT64, + [CLICKHOUSE_TYPES.INT32]: YQL_TYPES.INT64, + [CLICKHOUSE_TYPES.INT64]: YQL_TYPES.INT64, + [CLICKHOUSE_TYPES.UINT8]: YQL_TYPES.INT64, + [CLICKHOUSE_TYPES.UINT16]: YQL_TYPES.INT64, + [CLICKHOUSE_TYPES.UINT32]: YQL_TYPES.INT64, + [CLICKHOUSE_TYPES.UINT64]: YQL_TYPES.INT64, +}; + +const CLICKHOUSE_TO_YQL: Record = Object.keys(CLICKHOUSE_TO_YQL_BASE).reduce( + (res, key) => { + const val = CLICKHOUSE_TO_YQL_BASE[key]; + return { + ...res, + [key]: val, + [`Nullable(${key})`]: val, + [`LowCardinality(${key})`]: val, + [`LowCardinality(Nullable(${key}))`]: val, + }; + }, + {}, +); + +const CLICKHOUSE_TO_YQL_PREFIX_BASE: Record = { + [CLICKHOUSE_TYPES.DECIMAL]: YQL_TYPES.DOUBLE, + [CLICKHOUSE_TYPES.DATETIME]: YQL_TYPES.DATETIMETZ, + [CLICKHOUSE_TYPES.DATETIME64]: YQL_TYPES.DATETIMETZ, + [CLICKHOUSE_TYPES.ENUM8]: YQL_TYPES.STRING, + [CLICKHOUSE_TYPES.ENUM8]: YQL_TYPES.STRING, +}; + +const CLICKHOUSE_TO_YQL_PREFIX: Record = Object.keys( + CLICKHOUSE_TO_YQL_PREFIX_BASE, +).reduce((res, key) => { + const val = CLICKHOUSE_TO_YQL_PREFIX_BASE[key]; + return { + ...res, + [key]: val, + [`Nullable(${key}`]: val, + [`LowCardinality(${key}`]: val, + [`LowCardinality(Nullable(${key}`]: val, + }; +}, {}); + +function clickhouseToYQL(typeName: string) { + let result = CLICKHOUSE_TO_YQL[typeName]; + if (result) { + return result; + } + + const pieces = typeName.split('('); + for (let i = 1; i <= 3; ++i) { + result = CLICKHOUSE_TO_YQL_PREFIX[pieces.slice(0, i).join('(')]; + if (result) { + return result; + } + } + + return undefined; +} + +const POSTGRESQL_TYPES = { + BOOL: 'bool', + INT2: 'int2', + INT4: 'int4', + INT8: 'int8', + OID: 'oid', + TEXT: 'text', + CHAR: 'char', + VARCHAR: 'varchar', + NAME: 'name', + ENUM: 'enum', + FLOAT4: 'float4', + FLOAT8: 'float8', + NUMERIC: 'numeric', + DATE: 'date', + TIMESTAMP: 'timestamp', + TIMESTAMPTZ: 'timestamptz', + UUID: 'uuid', +}; + +const POSTGRESQL_TO_YQL: Record = { + [POSTGRESQL_TYPES.BOOL]: YQL_TYPES.BOOL, + [POSTGRESQL_TYPES.INT2]: YQL_TYPES.INT64, + [POSTGRESQL_TYPES.INT4]: YQL_TYPES.INT64, + [POSTGRESQL_TYPES.INT8]: YQL_TYPES.INT64, + [POSTGRESQL_TYPES.OID]: YQL_TYPES.INT64, + [POSTGRESQL_TYPES.TEXT]: YQL_TYPES.STRING, + [POSTGRESQL_TYPES.CHAR]: YQL_TYPES.STRING, + [POSTGRESQL_TYPES.VARCHAR]: YQL_TYPES.STRING, + [POSTGRESQL_TYPES.NAME]: YQL_TYPES.STRING, + [POSTGRESQL_TYPES.ENUM]: YQL_TYPES.STRING, + [POSTGRESQL_TYPES.FLOAT4]: YQL_TYPES.DOUBLE, + [POSTGRESQL_TYPES.FLOAT8]: YQL_TYPES.DOUBLE, + [POSTGRESQL_TYPES.NUMERIC]: YQL_TYPES.DOUBLE, + [POSTGRESQL_TYPES.DATE]: YQL_TYPES.DATE, + [POSTGRESQL_TYPES.TIMESTAMP]: YQL_TYPES.DATETIME, + [POSTGRESQL_TYPES.TIMESTAMPTZ]: YQL_TYPES.DATETIMETZ, + [POSTGRESQL_TYPES.UUID]: YQL_TYPES.UUID, +}; + +function postgreSQLToYQL(typeName: string) { + return POSTGRESQL_TO_YQL[typeName]; +} + +export const DATALENS_QL_TYPES = { + BOOLEAN: 'boolean', + NUMBER: 'number', + STRING: 'string', + DATE: 'date', + DATETIME: 'datetime', + UNKNOWN: 'unknown', +}; + +const YQL_TO_DATALENS_SQL: Record = { + [YQL_TYPES.BOOL]: DATALENS_QL_TYPES.BOOLEAN, + [YQL_TYPES.INT64]: DATALENS_QL_TYPES.NUMBER, + [YQL_TYPES.STRING]: DATALENS_QL_TYPES.STRING, + [YQL_TYPES.DOUBLE]: DATALENS_QL_TYPES.NUMBER, + [YQL_TYPES.DATE]: DATALENS_QL_TYPES.DATE, + [YQL_TYPES.DATETIME]: DATALENS_QL_TYPES.DATETIME, + [YQL_TYPES.DATETIMETZ]: DATALENS_QL_TYPES.DATETIME, + [YQL_TYPES.UUID]: DATALENS_QL_TYPES.STRING, +}; + +const BI_TYPES = { + // Without: geopoint, geopolygon, markup, unsupported + STRING: 'string', + INTEGER: 'integer', + FLOAT: 'float', + DATE: 'date', + DATETIME: 'datetime', + DATETIMETZ: 'datetimetz', + GENERICDATETIME: 'genericdatetime', + BOOLEAN: 'boolean', + UUID: 'uuid', +}; + +const BI_TO_DATALENS_SQL: Record = { + [BI_TYPES.STRING]: DATALENS_QL_TYPES.STRING, + [BI_TYPES.INTEGER]: DATALENS_QL_TYPES.NUMBER, + [BI_TYPES.FLOAT]: DATALENS_QL_TYPES.NUMBER, + [BI_TYPES.DATE]: DATALENS_QL_TYPES.DATE, + [BI_TYPES.DATETIME]: DATALENS_QL_TYPES.DATETIME, + [BI_TYPES.DATETIMETZ]: DATALENS_QL_TYPES.DATETIME, + [BI_TYPES.GENERICDATETIME]: DATALENS_QL_TYPES.DATETIME, + [BI_TYPES.BOOLEAN]: DATALENS_QL_TYPES.BOOLEAN, + [BI_TYPES.UUID]: DATALENS_QL_TYPES.STRING, +}; + +function yqlToDatalensQL(typeName: string | undefined) { + return typeName + ? YQL_TO_DATALENS_SQL[typeName] || DATALENS_QL_TYPES.UNKNOWN + : DATALENS_QL_TYPES.UNKNOWN; +} + +export function biToDatalensQL(typeName: string | undefined) { + return typeName + ? BI_TO_DATALENS_SQL[typeName] || DATALENS_QL_TYPES.UNKNOWN + : DATALENS_QL_TYPES.UNKNOWN; +} + +export function getDatalensQLTypeName(typeName: string | number, connectionType: string) { + let yqlType: string | undefined = DATALENS_QL_TYPES.UNKNOWN; + + if (connectionType === DATALENS_QL_CONNECTION_TYPES.CLICKHOUSE) { + yqlType = clickhouseToYQL(typeName as string); + } else if (connectionType === DATALENS_QL_CONNECTION_TYPES.POSTGRESQL) { + yqlType = postgreSQLToYQL(typeName as string); + } else { + return yqlType; + } + + return yqlToDatalensQL(yqlType); +} diff --git a/src/shared/modules/sleep.ts b/src/shared/modules/sleep.ts new file mode 100644 index 0000000..fe57bb6 --- /dev/null +++ b/src/shared/modules/sleep.ts @@ -0,0 +1 @@ +export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/src/shared/modules/slugify.ts b/src/shared/modules/slugify.ts new file mode 100644 index 0000000..c4ffc2d --- /dev/null +++ b/src/shared/modules/slugify.ts @@ -0,0 +1,28 @@ +import {ENTRY_SLUG_SEPARATOR, MAX_SLUG_LENGTH} from '../constants'; + +import {transliterate} from './transliterate'; + +export function slugify(str: string, maxSlugLength: number = MAX_SLUG_LENGTH) { + return transliterate(str) + .replace(/\s+|_+|\.+/g, '-') // replace the whitespace characters, '.' and '_' with '-' + .replace(/[^a-z0-9-]+/g, '') // we remove all non-alphanumeric, numeric and '-' characters + .replace(/-+/g, '-') // replacing multiple '-' with single '-' + .slice(0, maxSlugLength) + .replace(/^-+/, '') // delete the '-' at the beginning of the text + .replace(/-+$/, ''); // delete the '-' at the end of the text +} + +export function isSlugified(str: string, maxSlugLength: number = Number.MAX_SAFE_INTEGER) { + return str === slugify(str, maxSlugLength); +} + +export function makeSlugName(entryId: string, name: string) { + if (!name) { + return entryId; + } + const slugName = slugify(name); + if (!slugName) { + return entryId; + } + return `${entryId}${ENTRY_SLUG_SEPARATOR}${slugify(name)}`; +} diff --git a/src/shared/modules/tenant.ts b/src/shared/modules/tenant.ts new file mode 100644 index 0000000..93cd522 --- /dev/null +++ b/src/shared/modules/tenant.ts @@ -0,0 +1,13 @@ +const ORG_TENANT_PREFIX = 'org_'; + +export function isTenantIdWithOrgId(tenantId = '') { + return tenantId.startsWith(ORG_TENANT_PREFIX); +} + +export function makeTenantIdFromOrgId(orgId: string) { + return ORG_TENANT_PREFIX + orgId; +} + +export function getOrgIdFromTenantId(tenantId: string) { + return tenantId.slice(ORG_TENANT_PREFIX.length); +} diff --git a/src/shared/modules/transliterate.ts b/src/shared/modules/transliterate.ts new file mode 100644 index 0000000..d7047b5 --- /dev/null +++ b/src/shared/modules/transliterate.ts @@ -0,0 +1,45 @@ +const dict: Record = { + а: 'a', + б: 'b', + в: 'v', + г: 'g', + д: 'd', + е: 'e', + ж: 'zh', + з: 'z', + и: 'i', + й: 'y', + к: 'k', + л: 'l', + м: 'm', + н: 'n', + о: 'o', + п: 'p', + р: 'r', + с: 's', + т: 't', + у: 'u', + ф: 'f', + х: 'h', + ц: 'c', + ч: 'ch', + ш: 'sh', + щ: 'sh', + ъ: '', + ы: 'y', + ь: '', + э: 'e', + ю: 'yu', + я: 'ya', + ё: 'yo', +} as const; + +const hasOwnProperty = Object.prototype.hasOwnProperty; + +function mapWords(char: string): string { + return hasOwnProperty.call(dict, char) ? dict[char] : char; +} + +export function transliterate(str: string) { + return str.toLowerCase().split('').map(mapWords).join(''); +} diff --git a/src/shared/modules/typed-query-api/helpers/parameters.ts b/src/shared/modules/typed-query-api/helpers/parameters.ts new file mode 100644 index 0000000..8929397 --- /dev/null +++ b/src/shared/modules/typed-query-api/helpers/parameters.ts @@ -0,0 +1,10 @@ +import {omit} from 'lodash'; + +import type {StringParams} from '../../../types'; + +export const extractTypedQueryParams = ( + params: StringParams | undefined, + fieldName: string | undefined, +): StringParams => { + return omit(params ?? {}, fieldName ?? ''); +}; diff --git a/src/shared/modules/typed-query-api/index.ts b/src/shared/modules/typed-query-api/index.ts new file mode 100644 index 0000000..61e8ea4 --- /dev/null +++ b/src/shared/modules/typed-query-api/index.ts @@ -0,0 +1 @@ +export * from './mappers/parameters'; diff --git a/src/shared/modules/typed-query-api/mappers/parameters.ts b/src/shared/modules/typed-query-api/mappers/parameters.ts new file mode 100644 index 0000000..0c6da28 --- /dev/null +++ b/src/shared/modules/typed-query-api/mappers/parameters.ts @@ -0,0 +1,25 @@ +import type {ConnectionTypedQueryParameter} from '../../../types'; + +export const mapStringParameterToTypedQueryApiParameter = ( + key: string, + value: string | string[], +): ConnectionTypedQueryParameter => { + return { + name: key, + data_type: 'string', + value: Array.isArray(value) ? value[0] : value, + }; +}; + +export const mapParametersRecordToTypedQueryApiParameters = ( + parameters: Record, +): ConnectionTypedQueryParameter[] => { + return Object.entries(parameters) + .filter(([_key, value]) => value.length) + .map((pair) => { + const key = pair[0]; + const value = pair[1]; + + return mapStringParameterToTypedQueryApiParameter(key, value); + }); +}; diff --git a/src/shared/modules/url.ts b/src/shared/modules/url.ts new file mode 100644 index 0000000..5565ee8 --- /dev/null +++ b/src/shared/modules/url.ts @@ -0,0 +1,5 @@ +const TRUE_FLAGS = ['1', 'true', true]; + +export function isTrueArg(arg: any): boolean { + return TRUE_FLAGS.includes(arg); +} diff --git a/src/shared/modules/user.ts b/src/shared/modules/user.ts new file mode 100644 index 0000000..76cc9d9 --- /dev/null +++ b/src/shared/modules/user.ts @@ -0,0 +1,12 @@ +const UID_PREFIX = 'uid:'; +const ANONYMOUS_USER_PREFIX = '__ANONYMOUS'; + +export const makeUserId = (userId: string) => `uid:${userId}`; + +export const getUserId = (userIdWithPrefix: string) => userIdWithPrefix.slice(UID_PREFIX.length); + +export const isUserId = (userIdOrLogin = '') => { + return userIdOrLogin.startsWith(UID_PREFIX) && !userIdOrLogin.includes(ANONYMOUS_USER_PREFIX); +}; + +export const filterUsersIds = (ids: string[]) => ids.filter(isUserId).map(getUserId); diff --git a/src/shared/modules/visualization/placeholder.ts b/src/shared/modules/visualization/placeholder.ts new file mode 100644 index 0000000..6ea224c --- /dev/null +++ b/src/shared/modules/visualization/placeholder.ts @@ -0,0 +1,19 @@ +import type {PlaceholderId} from '../../constants'; +import type {Placeholder} from '../../types'; + +export const selectPlaceholders = ( + placeholders: Placeholder[] | undefined, + ids: T[], +): Record => { + const uniqueIds = new Set(ids); + return (placeholders ?? []).reduce( + (selectedPlaceholders, p) => { + const id = p.id as T; + + return uniqueIds.has(id) + ? Object.defineProperty(selectedPlaceholders, id, {value: p}) + : selectedPlaceholders; + }, + {} as Record, + ); +}; diff --git a/src/shared/modules/wizard-helpers.ts b/src/shared/modules/wizard-helpers.ts new file mode 100644 index 0000000..83b7a2a --- /dev/null +++ b/src/shared/modules/wizard-helpers.ts @@ -0,0 +1,263 @@ +import type {RGBColor} from '../constants'; +import { + DEFAULT_FLOAT_NUMBERS, + DEFAULT_FORMATTING, + DEFAULT_INTEGER_NUMBERS, + GradientType, + PERCENT_VISUALIZATIONS, + PlaceholderId, + PseudoFieldTitle, + VISUALIZATIONS_WITH_SEVERAL_FIELDS_X_PLACEHOLDER, + WizardVisualizationId, +} from '../constants'; +import type { + CommonNumberFormattingOptions, + Dataset, + Field, + MarkupItem, + ServerFieldFormatting, + ServerSort, + Update, + V3Label, +} from '../types'; +import { + DATASET_FIELD_TYPES, + DatasetFieldType, + isDateField, + isFloatField, + isNumberField, +} from '../types'; +import {getFieldUISettings} from '../utils'; + +import {isMeasureField} from './helpers'; + +export const isMeasureName = (field: {type: string; title: string} | undefined) => + field?.type === 'PSEUDO' && + (field.title === PseudoFieldTitle.MeasureNames || field.title === PseudoFieldTitle.ColumnNames); + +export const isMeasureValue = (field?: {type: string; title: string}) => + field?.type === 'PSEUDO' && field?.title === PseudoFieldTitle.MeasureValues; + +export const isMeasureType = (field: {type: string}) => field.type === 'MEASURE'; + +export const isMeasureNameOrValue = (field: {type: string; title: string}) => + isMeasureName(field) || isMeasureValue(field); + +export const createMeasureNames = (isQL?: boolean) => + ({ + title: isQL ? PseudoFieldTitle.ColumnNames : PseudoFieldTitle.MeasureNames, + type: DatasetFieldType.Pseudo, + className: 'item pseudo-item dimension-item', + data_type: DATASET_FIELD_TYPES.STRING, + }) as Field; + +export const createMeasureValues = () => + ({ + title: PseudoFieldTitle.MeasureValues, + type: DatasetFieldType.Pseudo, + className: 'item pseudo-item measure-item', + data_type: DATASET_FIELD_TYPES.FLOAT, + }) as Field; + +export const getDefaultFormatting = ( + field: Field | undefined, +): ServerFieldFormatting | CommonNumberFormattingOptions => { + if (!field) { + return {} as CommonNumberFormattingOptions; + } + + const fieldUISettings = getFieldUISettings({field}); + + return { + ...DEFAULT_FORMATTING, + precision: isFloatField(field) ? DEFAULT_FLOAT_NUMBERS : DEFAULT_INTEGER_NUMBERS, + ...fieldUISettings?.numberFormatting, + labelMode: + (field as V3Label).labelMode || + fieldUISettings?.numberFormatting?.labelMode || + DEFAULT_FORMATTING.labelMode, + }; +}; +export const getResultSchemaFromDataset = (dataset?: Dataset | Dataset['dataset']): Field[] => { + if (!dataset) { + return []; + } + + const schema = ( + 'dataset' in dataset ? dataset.dataset.result_schema : dataset.result_schema + ) as Field[]; + + // Return the default empty array, because some cases + // there may be a dataset without result_schema + return schema || []; +}; + +export const filterUpdatesByDatasetId = (updates: Update[], datasetId: string) => { + return updates.filter((update) => { + return ( + // Checking for typeof - fallback, because in the old charts updates did not contain datasetId + typeof update.field.datasetId === 'undefined' || update.field.datasetId === datasetId + ); + }); +}; + +export const isPercentVisualization = (visualizationId: string) => + PERCENT_VISUALIZATIONS.has(visualizationId); + +export const isVisualizationWithSeveralFieldsXPlaceholder = (visualizationId: string) => + VISUALIZATIONS_WITH_SEVERAL_FIELDS_X_PLACEHOLDER.has(visualizationId); + +function markupToRawString(obj: MarkupItem, str = ''): string { + let text = str; + + if (obj.children) { + text = + text + + obj.children + .map((item) => { + if (typeof item === 'string') { + return item; + } + return markupToRawString(item, text); + }) + .join(''); + } else if (obj.content && typeof obj.content === 'string') { + text = text + obj.content; + } else if (obj.content && typeof obj.content === 'object') { + text = markupToRawString(obj.content, text); + } + + return text; +} + +export {markupToRawString}; + +export function getDeltasByColorValuesMap( + colorValues: (number | null)[], + min: number, + range: number, +) { + return colorValues.reduce( + (acc, colorValue) => { + const delta = getRangeDelta(colorValue, min, range); + if (typeof colorValue !== 'number' || typeof delta !== 'number') { + return acc; + } + + return { + ...acc, + [colorValue]: delta, + }; + }, + {} as Record, + ); +} + +export function getRgbColorValue( + delta: number, + gradientMode: string | GradientType, + rangeMiddleRatio: number, + colors: RGBColor[], +): string { + let resultDelta = delta; + let shadeA: RGBColor; + let shadeB: RGBColor; + if (gradientMode === GradientType.THREE_POINT) { + if (delta >= rangeMiddleRatio) { + // ../technotes.md -> utils/colors-helpers p1 + resultDelta = delta === 1 ? delta : delta - rangeMiddleRatio; + shadeA = colors[1]; + shadeB = colors[2]; + } else { + shadeA = colors[0]; + shadeB = colors[1]; + } + } else { + shadeA = colors[0]; + shadeB = colors[1]; + } + + const red = Math.floor((shadeB.red - shadeA.red) * resultDelta + shadeA.red); + const green = Math.floor((shadeB.green - shadeA.green) * resultDelta + shadeA.green); + const blue = Math.floor((shadeB.blue - shadeA.blue) * resultDelta + shadeA.blue); + + return `rgb(${red}, ${green}, ${blue})`; +} + +export function getRangeDelta( + colorValue: number | null, + min: number, + range: number, +): number | null { + let delta = typeof colorValue === 'number' ? (colorValue - min) / range : null; + + if (delta !== null) { + if (delta > 1) { + delta = 1; + } else if (delta < 0) { + delta = 0; + } + } + + return delta; +} + +export const isPlaceholderSupportsAxisMode = ( + placeholderId: PlaceholderId, + visualizationId: WizardVisualizationId, +) => { + const isReversedXYPlaceholderVisualization = + visualizationId === WizardVisualizationId.Bar || + visualizationId === WizardVisualizationId.Bar100p; + const isXPlaceholder = + placeholderId === PlaceholderId.X && !isReversedXYPlaceholderVisualization; + + const isReversedYPlaceholder = + placeholderId === PlaceholderId.Y && isReversedXYPlaceholderVisualization; + + return isXPlaceholder || isReversedYPlaceholder; +}; + +export const isAllAxisModesAvailable = (field?: {data_type: string}) => { + return isNumberField(field) || isDateField(field); +}; + +export function doesSortingAffectAxisMode(visualizationId: WizardVisualizationId) { + return ![ + WizardVisualizationId.Area, + WizardVisualizationId.Area100p, + WizardVisualizationId.Column100p, + WizardVisualizationId.Bar100p, + ].includes(visualizationId); +} + +export enum AxisModeDisabledReason { + FieldType = 'fieldType', + HasSortingField = 'sorting', + Unknown = 'unknown', +} + +export function isContinuousAxisModeDisabled(args: { + field: {guid: string; data_type: string}; + axisSettings: {disableAxisMode?: boolean} | undefined; + visualizationId: WizardVisualizationId; + sort: ServerSort[]; +}) { + const {field, axisSettings, visualizationId, sort} = args; + + if (!isAllAxisModesAvailable(field)) { + return AxisModeDisabledReason.FieldType; + } + + const disableDueToSorting = + doesSortingAffectAxisMode(visualizationId) && sort.some(isMeasureField); + if (disableDueToSorting) { + return AxisModeDisabledReason.HasSortingField; + } + + if (axisSettings?.disableAxisMode) { + return AxisModeDisabledReason.Unknown; + } + + return null; +} diff --git a/src/shared/modules/wizard/axis-mode.ts b/src/shared/modules/wizard/axis-mode.ts new file mode 100644 index 0000000..b1ab428 --- /dev/null +++ b/src/shared/modules/wizard/axis-mode.ts @@ -0,0 +1,114 @@ +import {PlaceholderId, WizardVisualizationId} from '../../constants'; +import type {ServerChartsConfig, ServerField, ServerPlaceholder, ServerSort} from '../../types'; +import {AxisMode} from '../../types'; +import {isMeasureField} from '../helpers'; +import {isContinuousAxisModeDisabled} from '../wizard-helpers'; + +const Y_AS_MAIN_AXIS: WizardVisualizationId[] = [ + WizardVisualizationId.Bar, + WizardVisualizationId.Bar100p, +]; + +type GetXAxisModeArgs = { + config: Partial; + /* new field in the placeholder (not yet in the config) */ + xField?: ServerField; +}; + +function getXPlaceholder(args: {id: WizardVisualizationId; placeholders: ServerPlaceholder[]}) { + const {id, placeholders} = args; + // Historically, x for a bar chart is y + return placeholders.find((p) => { + return Y_AS_MAIN_AXIS.includes(id) ? p.id === PlaceholderId.Y : p.id === PlaceholderId.X; + }); +} + +export function getXAxisMode(args: GetXAxisModeArgs): AxisMode { + const {config, xField: newXField} = args; + const layers = config.visualization?.layers ?? []; + + const getVisualizationAxisMode = (visualization: { + placeholders: ServerPlaceholder[]; + id: WizardVisualizationId; + xField?: ServerField; + sort?: ServerSort[]; + }) => { + const {placeholders, xField, sort = []} = visualization; + const visualizationId = visualization.id as WizardVisualizationId; + const xPlaceholder = getXPlaceholder({placeholders, id: visualizationId}); + const field = xField ?? xPlaceholder?.items[0]; + const axisSettings = xPlaceholder?.settings; + + if (!field) { + return AxisMode.Discrete; + } + + const isContinuousModeRestricted = isContinuousAxisModeDisabled({ + field, + axisSettings, + visualizationId, + sort, + }); + + if (isContinuousModeRestricted) { + return AxisMode.Discrete; + } + + return axisSettings?.axisModeMap?.[field.guid] ?? AxisMode.Continuous; + }; + + if (layers.length) { + const selectedLayerId = config.visualization?.selectedLayerId; + let xField: ServerField | undefined = newXField; + + if (!xField) { + const selectedLayer = layers.find((l) => l.id === selectedLayerId) ?? layers[0]; + const xPlaceholder = getXPlaceholder({ + placeholders: selectedLayer.placeholders ?? [], + id: selectedLayer.id as WizardVisualizationId, + }); + xField = xPlaceholder?.items[0]; + } + + return ( + layers.reduce((res, layer) => { + if (res !== AxisMode.Discrete) { + const layerAxisMode = getVisualizationAxisMode({ + id: layer.id as WizardVisualizationId, + placeholders: layer.placeholders, + xField, + sort: hasSortThanAffectAxisMode(config) + ? layer.commonPlaceholders.sort + : [], + }); + + return layerAxisMode ?? res; + } + + return res; + }, undefined) ?? AxisMode.Continuous + ); + } + + const visualization = config.visualization; + return getVisualizationAxisMode({ + id: visualization?.id as WizardVisualizationId, + placeholders: visualization?.placeholders ?? [], + xField: newXField, + sort: hasSortThanAffectAxisMode(config) ? config.sort : [], + }); +} + +export function hasSortThanAffectAxisMode(config: Partial) { + if (config.visualization?.layers) { + return config.visualization.layers.some((layer) => { + const {colors = [], shapes = [], sort = []} = layer.commonPlaceholders || {}; + return sort.length && ![...colors, ...shapes].some((field) => !isMeasureField(field)); + }); + } + + const {colors = [], shapes = [], sort = []} = config || {}; + // There is a grouping of data - continuous axis can be used + // (sorting will be applied to all other dimensions except x) + return sort.length && ![...colors, ...shapes].some((field) => !isMeasureField(field)); +} diff --git a/src/shared/modules/wizard/axis-settings.ts b/src/shared/modules/wizard/axis-settings.ts new file mode 100644 index 0000000..e458ae3 --- /dev/null +++ b/src/shared/modules/wizard/axis-settings.ts @@ -0,0 +1,16 @@ +import {WizardVisualizationId} from '../../constants'; +import {AxisNullsMode} from '../../types'; + +export function getAxisNullsSettings(value: AxisNullsMode, visualizationId: string) { + const isArea = + visualizationId === WizardVisualizationId.Area || + visualizationId === WizardVisualizationId.Area100p; + const defaultValue = isArea ? AxisNullsMode.AsZero : AxisNullsMode.Connect; + const isCurrentValueValid = value && !(value === AxisNullsMode.UsePrevious && !isArea); + + if (isCurrentValueValid) { + return value; + } + + return defaultValue; +} diff --git a/src/shared/modules/wizard/config.ts b/src/shared/modules/wizard/config.ts new file mode 100644 index 0000000..5376371 --- /dev/null +++ b/src/shared/modules/wizard/config.ts @@ -0,0 +1,12 @@ +import type {ChartsConfig} from '../../types'; + +export function getDatasetLinks(config: ChartsConfig) { + const links: Record = {}; + + config.datasetsIds.forEach((id, i) => { + const key = `dataset${i > 0 ? i : ''}`; + links[key] = id; + }); + + return links; +} diff --git a/src/shared/modules/wizard/index.ts b/src/shared/modules/wizard/index.ts new file mode 100644 index 0000000..1e86008 --- /dev/null +++ b/src/shared/modules/wizard/index.ts @@ -0,0 +1,4 @@ +export * from './axis-mode'; +export * from './axis-settings'; +export * from './config'; +export * from './tooltip'; diff --git a/src/shared/modules/wizard/tooltip.ts b/src/shared/modules/wizard/tooltip.ts new file mode 100644 index 0000000..3ec521f --- /dev/null +++ b/src/shared/modules/wizard/tooltip.ts @@ -0,0 +1,15 @@ +import {WizardVisualizationId} from '../..'; + +const TOOLTIP_SUM_SUPPORTED_VISUALIZATION = new Set([ + WizardVisualizationId.Line, + WizardVisualizationId.Area, + WizardVisualizationId.Area100p, + WizardVisualizationId.Column, + WizardVisualizationId.Column100p, + WizardVisualizationId.Bar, + WizardVisualizationId.Bar100p, +]); + +export function isTooltipSumEnabled({visualizationId}: {visualizationId: string}) { + return TOOLTIP_SUM_SUPPORTED_VISUALIZATION.has(visualizationId); +} diff --git a/src/shared/old-schema/charts.ts b/src/shared/old-schema/charts.ts new file mode 100644 index 0000000..0aec78d --- /dev/null +++ b/src/shared/old-schema/charts.ts @@ -0,0 +1,178 @@ +import type {IncomingHttpHeaders} from 'http'; + +import {CHARTS_API_BASE_URL, DASH_API_BASE_URL} from '../constants'; +import type {Endpoints} from '../endpoints'; +import {filterUrlFragment} from '../schema/utils'; +import type { + CreateEntryRequest, + DashEntry, + DashEntryCreateParams, + EntryAnnotationArgs, + EntryReadParams, + Params, + UpdateEntryRequest, +} from '../types'; +import {EntryUpdateMode} from '../types'; + +type UiEndpoints = Endpoints['ui']; + +export type CreateWidgetArgs = + | { + key: string; + data: Record; + template?: string; + description?: string; + annotation?: EntryAnnotationArgs; + } + | { + workbookId: string; + name: string; + data: Record; + template?: string; + description?: string; + annotation?: EntryAnnotationArgs; + }; + +const CHARTS_API_SCHEMA = { + getWidget: ( + headers: IncomingHttpHeaders, + endpoints: UiEndpoints, + { + entryId, + unreleased, + revId, + }: { + entryId: string; + unreleased?: boolean; + revId?: string; + }, + ) => { + const params: Params = { + includeLinks: '1', + includePermissionsInfo: '1', + }; + + // previously, by default, if the unreleased parameter was not passed, true value was set + // for the history of chart changes, change the default to return published version + if (unreleased) { + params.unreleased = '1'; + } + + if (revId) { + params.revId = revId; + } + + return { + method: 'get', + url: `${endpoints.charts}${CHARTS_API_BASE_URL}/${filterUrlFragment(entryId)}`, + headers, + params, + }; + }, + createWidget: ( + headers: IncomingHttpHeaders, + endpoints: UiEndpoints, + {template = 'datalens', annotation, description = '', ...restArgs}: CreateWidgetArgs, + ) => ({ + method: 'post', + url: `${endpoints.charts}${CHARTS_API_BASE_URL}`, + headers, + data: { + template, + annotation: { + description: annotation?.description ?? description, + }, + ...restArgs, + }, + }), + updateWidget: ( + headers: IncomingHttpHeaders, + endpoints: UiEndpoints, + { + entryId, + data, + template = 'datalens', + mode = EntryUpdateMode.Publish, + annotation, + description = '', + }: { + entryId: string; + revId: string; + data: any; + template: string; + mode?: EntryUpdateMode; + annotation?: EntryAnnotationArgs; + description?: string; + }, + ) => ({ + method: 'post', + url: `${endpoints.charts}${CHARTS_API_BASE_URL}/${filterUrlFragment(entryId)}`, + headers, + data: { + data, + mode, + template, + annotation: { + description: annotation?.description ?? description, + }, + }, + }), + + // DASH API + createDash: ( + headers: IncomingHttpHeaders, + endpoints: UiEndpoints, + { + data: {annotation, description, ...restArgs}, + }: {data: CreateEntryRequest}, + ) => ({ + method: 'post', + url: `${endpoints.charts}${DASH_API_BASE_URL}`, + headers, + data: { + ...restArgs, + annotation: { + description: annotation?.description ?? description, + }, + }, + }), + readDash: ( + headers: IncomingHttpHeaders, + endpoints: UiEndpoints, + { + id, + params, + }: { + id: string; + params: EntryReadParams; + }, + ) => ({ + method: 'get', + url: `${endpoints.charts}${DASH_API_BASE_URL}/${filterUrlFragment(id)}`, + headers, + params, + }), + updateDash: ( + headers: IncomingHttpHeaders, + endpoints: UiEndpoints, + { + id, + data: {annotation, description, ...restArgs}, + }: { + id: string; + data: UpdateEntryRequest; + }, + ) => ({ + method: 'post', + url: `${endpoints.charts}${DASH_API_BASE_URL}/${filterUrlFragment(id)}`, + headers, + data: { + ...restArgs, + annotation: { + description: annotation?.description ?? description, + }, + }, + }), +}; + +export default CHARTS_API_SCHEMA; diff --git a/src/shared/old-schema/index.js b/src/shared/old-schema/index.js new file mode 100644 index 0000000..1e94754 --- /dev/null +++ b/src/shared/old-schema/index.js @@ -0,0 +1,9 @@ +const CHARTS_API_SCHEMA = require('./charts').default; + +const SCHEMA_API = { + charts: { + ...CHARTS_API_SCHEMA, + }, +}; + +export const oldSchema = SCHEMA_API; diff --git a/src/shared/schema/auth-schema/auth/actions.ts b/src/shared/schema/auth-schema/auth/actions.ts new file mode 100644 index 0000000..8156d98 --- /dev/null +++ b/src/shared/schema/auth-schema/auth/actions.ts @@ -0,0 +1,37 @@ +import {AuthHeader, SET_COOKIE_HEADER} from '../../../constants'; +import {createAction} from '../../gateway-utils'; + +import type {SigninArgs, SignupArgs} from './types'; + +export const actions = { + refreshTokens: createAction({ + method: 'POST', + proxyHeaders: [AuthHeader.Cookie], + proxyResponseHeaders: [SET_COOKIE_HEADER], + path: () => '/refresh', + }), + signin: createAction({ + method: 'POST', + proxyResponseHeaders: [SET_COOKIE_HEADER], + path: () => '/signin', + params: ({login, password}, headers) => ({ + body: {login, password}, + headers, + }), + }), + signup: createAction({ + method: 'POST', + proxyResponseHeaders: [SET_COOKIE_HEADER], + path: () => '/signup', + params: ({login, password, firstName, lastName, email}, headers) => ({ + body: {login, password, firstName, lastName, email}, + headers, + }), + }), + logout: createAction({ + method: 'GET', + proxyHeaders: [AuthHeader.Cookie], + proxyResponseHeaders: [SET_COOKIE_HEADER], + path: () => '/logout', + }), +}; diff --git a/src/shared/schema/auth-schema/auth/index.ts b/src/shared/schema/auth-schema/auth/index.ts new file mode 100644 index 0000000..411dd65 --- /dev/null +++ b/src/shared/schema/auth-schema/auth/index.ts @@ -0,0 +1,9 @@ +import {getServiceEndpoints} from '../../../endpoints/schema'; + +import {actions} from './actions'; + +export default { + actions, + endpoints: getServiceEndpoints('auth'), + serviceName: 'authNotAuthenticated', +}; diff --git a/src/shared/schema/auth-schema/auth/types.ts b/src/shared/schema/auth-schema/auth/types.ts new file mode 100644 index 0000000..b544cd0 --- /dev/null +++ b/src/shared/schema/auth-schema/auth/types.ts @@ -0,0 +1,12 @@ +export type SigninArgs = { + login: string; + password: string; +}; + +export type SignupArgs = { + login: string; + email: string; + firstName: string; + lastName: string; + password: string; +}; diff --git a/src/shared/schema/auth-schema/index.ts b/src/shared/schema/auth-schema/index.ts new file mode 100644 index 0000000..5e64c22 --- /dev/null +++ b/src/shared/schema/auth-schema/index.ts @@ -0,0 +1,5 @@ +import auth from './auth'; + +export const authSchema = { + auth, +}; diff --git a/src/shared/schema/auth/actions.ts b/src/shared/schema/auth/actions.ts new file mode 100644 index 0000000..b5aa91f --- /dev/null +++ b/src/shared/schema/auth/actions.ts @@ -0,0 +1,112 @@ +import {createAction} from '../gateway-utils'; +import {defaultParamsSerializer} from '../utils'; + +import type {SuccessResponse} from './types/common'; +import type { + AddUsersRolesArgs, + CreateUserArgs, + CreateUserResponse, + DeleteUserArgs, + GetUserProfileArgs, + GetUserProfileResponse, + GetUsersByIdsArgs, + GetUsersByIdsResponse, + GetUsersListArgs, + GetUsersListResponse, + RemoveUsersRolesArgs, + UpdateMyUserPasswordArgs, + UpdateMyUserProfileArgs, + UpdateUserPasswordArgs, + UpdateUserProfileArgs, + UpdateUsersRolesArgs, +} from './types/users'; + +const PATH_PREFIX = '/v1'; + +export const actions = { + addUsersRoles: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/management/users/roles/add`, + params: ({deltas}, headers) => ({body: {deltas}, headers}), + }), + updateUsersRoles: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/management/users/roles/update`, + params: ({deltas}, headers) => ({body: {deltas}, headers}), + }), + removeUsersRoles: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/management/users/roles/remove`, + params: ({deltas}, headers) => ({body: {deltas}, headers}), + }), + createUser: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/management/users/create`, + params: ({login, password, email, firstName, lastName, roles}, headers) => ({ + body: {login, password, email, firstName, lastName, roles}, + headers, + }), + }), + deleteUser: createAction({ + method: 'DELETE', + path: ({userId}) => `${PATH_PREFIX}/management/users/${userId}`, + params: (_args, headers) => ({headers}), + }), + getUserProfile: createAction({ + method: 'GET', + path: ({userId}) => `${PATH_PREFIX}/management/users/${userId}/profile`, + params: (_args, headers) => ({headers}), + }), + updateUserProfile: createAction({ + method: 'POST', + path: ({userId}) => `${PATH_PREFIX}/management/users/${userId}/profile`, + params: ({email, firstName, lastName}, headers) => ({ + body: {email, firstName, lastName}, + headers, + }), + }), + updateUserPassword: createAction({ + method: 'POST', + path: ({userId}) => `${PATH_PREFIX}/management/users/${userId}/password`, + params: ({newPassword}, headers) => ({body: {newPassword}, headers}), + }), + + getMyUserProfile: createAction({ + method: 'GET', + path: () => `${PATH_PREFIX}/users/me/profile`, + params: (_args, headers) => ({headers}), + }), + updateMyUserProfile: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/users/me/profile`, + params: ({email, firstName, lastName}, headers) => ({ + body: {email, firstName, lastName}, + headers, + }), + }), + updateMyUserPassword: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/users/me/password`, + params: ({oldPassword, newPassword}, headers) => ({ + body: {oldPassword, newPassword}, + headers, + }), + }), + getUsersList: createAction({ + method: 'GET', + path: () => `${PATH_PREFIX}/users/list`, + params: ({page, pageSize, filterString, roles, idpType}, headers) => ({ + query: {page, pageSize, filterString, roles, idpType}, + headers, + }), + paramsSerializer: defaultParamsSerializer, + }), + getUsersByIds: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/users/get-by-ids`, + params: ({subjectIds}, headers) => ({ + body: {subjectIds}, + headers, + }), + }), +}; diff --git a/src/shared/schema/auth/index.ts b/src/shared/schema/auth/index.ts new file mode 100644 index 0000000..ddb768d --- /dev/null +++ b/src/shared/schema/auth/index.ts @@ -0,0 +1,9 @@ +import {getServiceEndpoints} from '../../endpoints/schema'; + +import {actions} from './actions'; + +export default { + actions, + endpoints: getServiceEndpoints('auth'), + serviceName: 'auth', +}; diff --git a/src/shared/schema/auth/types/common.ts b/src/shared/schema/auth/types/common.ts new file mode 100644 index 0000000..272e29e --- /dev/null +++ b/src/shared/schema/auth/types/common.ts @@ -0,0 +1,3 @@ +export interface SuccessResponse { + done: true; +} diff --git a/src/shared/schema/auth/types/users.ts b/src/shared/schema/auth/types/users.ts new file mode 100644 index 0000000..c65fd7e --- /dev/null +++ b/src/shared/schema/auth/types/users.ts @@ -0,0 +1,102 @@ +import type {UserRole} from '../../../components/auth/constants/role'; + +export interface AddUsersRolesArgs { + deltas: { + role: `${UserRole}`; + subjectId: string; + }[]; +} + +export interface RemoveUsersRolesArgs extends AddUsersRolesArgs {} + +export interface UpdateUsersRolesArgs { + deltas: { + oldRole: `${UserRole}`; + newRole: `${UserRole}`; + subjectId: string; + }[]; +} + +export type CreateUserArgs = { + login: string; + password: string; + email?: string; + firstName?: string; + lastName?: string; + roles?: `${UserRole}`[]; +}; + +export interface CreateUserResponse { + userId: string; +} + +export interface DeleteUserArgs { + userId: string; +} + +export interface GetUserProfileArgs { + userId: string; +} + +export interface UserSubject { + userId: string; + login: string | null; + email: string | null; + firstName: string | null; + lastName: string | null; + idpSlug: string | null; + idpType: string | null; +} + +export interface GetUsersByIdsArgs { + subjectIds: string[]; +} + +export interface GetUsersByIdsResponse { + users: UserProfile[]; +} + +export interface UserProfile extends UserSubject { + roles: `${UserRole}`[]; +} + +export interface GetUserProfileResponse { + profile: UserProfile; +} + +interface UpdateUserProfile { + email?: string | null; + firstName?: string | null; + lastName?: string | null; +} + +export interface UpdateUserProfileArgs extends UpdateUserProfile { + userId: string; +} + +export interface UpdateMyUserProfileArgs extends UpdateUserProfile {} + +export interface UpdateUserPasswordArgs { + userId: string; + newPassword: string; +} + +export interface UpdateMyUserPasswordArgs { + oldPassword: string; + newPassword: string; +} + +export interface GetUsersListArgs { + page?: number; + pageSize?: number; + filterString?: string; + roles?: `${UserRole}`[]; + idpType?: string; +} + +export interface ListUser extends UserProfile {} + +export interface GetUsersListResponse { + nextPageToken?: string; + users: ListUser[]; +} diff --git a/src/shared/schema/bi/actions/connections.ts b/src/shared/schema/bi/actions/connections.ts new file mode 100644 index 0000000..71284ab --- /dev/null +++ b/src/shared/schema/bi/actions/connections.ts @@ -0,0 +1,303 @@ +import {registry} from '../../../../server/registry'; +import {DATASET_ID_HEADER, WORKBOOK_ID_HEADER} from '../../../constants'; +import {createAction, createTypedAction} from '../../gateway-utils'; +import {filterUrlFragment} from '../../utils'; +import {transformConnectionResponseError} from '../helpers'; +import { + createConnectionArgsSchema, + createConnectionResultSchema, + deleteConnectionArgsSchema, + deleteConnectionResultSchema, + getConnectionArgsSchema, + getConnectionResultSchema, + updateConnectionArgsSchema, + updateConnectionResultSchema, +} from '../schemas/connections'; +import type { + AddGoogleSheetArgs, + AddGoogleSheetResponse, + AddYandexDocumentArgs, + AddYandexDocumentResponse, + ApplySourceSettingsArgs, + ApplySourceSettingsResponse, + CreateConnectionArgs, + CreateConnectionResponse, + DownloadPresignedUrlArgs, + DownloadPresignedUrlResponse, + EnsureUploadRobotArgs, + EnsureUploadRobotResponse, + ExportConnectionArgs, + ExportConnectionResponse, + GetAvailableCountersArgs, + GetAvailableCountersResponse, + GetConnectionArgs, + GetConnectionResponse, + GetConnectionSourceSchemaArgs, + GetConnectionSourceSchemaResponse, + GetConnectionSourcesArgs, + GetConnectionSourcesResponse, + GetConnectionTypedQueryDataArgs, + GetConnectionTypedQueryDataResponse, + GetConnectorSchemaArgs, + GetConnectorSchemaResponse, + GetConnectorsResponse, + GetFileSourceStatusArgs, + GetFileSourceStatusResponse, + GetFileSourcesArgs, + GetFileSourcesResponse, + GetFileStatusArgs, + GetFileStatusResponse, + GetPresignedUrlResponse, + ImportConnectionArgs, + ImportConnectionResponse, + ListConnectorIconsResponse, + UpdateConnectionArgs, + UpdateConnectionResponse, + UpdateFileSourceArgs, + UpdateFileSourceResponse, + UpdateS3BasedConnectionDataArgs, + UpdateS3BasedConnectionDataResponse, + VerifyConnectionArgs, + VerifyConnectionParamsArgs, + VerifyConnectionParamsResponse, + VerifyConnectionResponse, +} from '../types'; + +const PATH_PREFIX = '/api/v1'; +const PATH_DATA_API_PREFIX = '/api/data/v1'; +const PATH_FILE_UPLOADER_PREFIX_V2 = '/file-uploader/api/v2'; + +export const actions = { + ensureUploadRobot: createAction({ + method: 'POST', + path: ({connectionId}) => + `${PATH_PREFIX}/connections/${filterUrlFragment(connectionId)}/ensure_upload_robot`, + params: (_, headers) => ({headers}), + }), + getAvailableCounters: createAction({ + method: 'GET', + path: ({connectionId}) => + `${PATH_PREFIX}/connections/${filterUrlFragment( + connectionId, + )}/metrica_available_counters`, + params: (_, headers) => ({headers}), + }), + getConnectors: createAction({ + method: 'GET', + path: () => `${PATH_PREFIX}/info/connectors`, + params: (_, headers) => ({headers}), + }), + getConnection: createTypedAction( + { + paramsSchema: getConnectionArgsSchema, + resultSchema: getConnectionResultSchema, + }, + { + method: 'GET', + path: ({connectionId}) => `${PATH_PREFIX}/connections/${connectionId}`, + params: ({workbookId, bindedDatasetId, rev_id}, headers) => ({ + headers: { + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...(bindedDatasetId ? {[DATASET_ID_HEADER]: bindedDatasetId} : {}), + ...headers, + }, + query: {rev_id}, + }), + }, + ), + createConnection: createTypedAction( + { + paramsSchema: createConnectionArgsSchema, + resultSchema: createConnectionResultSchema, + }, + { + method: 'POST', + path: () => `${PATH_PREFIX}/connections`, + params: (body, headers) => ({body, headers}), + transformResponseError: transformConnectionResponseError, + }, + ), + verifyConnection: createAction({ + method: 'POST', + path: ({connectionId}) => `${PATH_PREFIX}/connections/test_connection/${connectionId}`, + params: ({connectionId: _connectionId, workbookId, ...body}, headers) => ({ + body, + headers: {...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), ...headers}, + }), + transformResponseError: transformConnectionResponseError, + }), + verifyConnectionParams: createAction< + VerifyConnectionParamsResponse, + VerifyConnectionParamsArgs + >({ + method: 'POST', + path: () => `${PATH_PREFIX}/connections/test_connection_params`, + params: (body, headers) => ({body, headers}), + transformResponseError: transformConnectionResponseError, + }), + updateConnection: createTypedAction( + { + paramsSchema: updateConnectionArgsSchema, + resultSchema: updateConnectionResultSchema, + }, + { + method: 'PUT', + path: ({connectionId}) => `${PATH_PREFIX}/connections/${connectionId}`, + params: ({data}, headers) => ({body: data, headers}), + transformResponseError: transformConnectionResponseError, + }, + ), + deleteConnection: createTypedAction( + { + paramsSchema: deleteConnectionArgsSchema, + resultSchema: deleteConnectionResultSchema, + }, + { + method: 'DELETE', + path: ({connectionId}) => + `${PATH_PREFIX}/connections/${filterUrlFragment(connectionId)}`, + params: (_, headers) => ({headers}), + }, + ), + getConnectionSources: createAction({ + method: 'GET', + path: ({connectionId}) => `${PATH_PREFIX}/connections/${connectionId}/info/sources`, + params: ({workbookId}, headers) => ({ + headers: {...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), ...headers}, + }), + }), + getConnectionSourceSchema: createAction< + GetConnectionSourceSchemaResponse, + GetConnectionSourceSchemaArgs + >({ + method: 'POST', + path: ({connectionId}) => `${PATH_PREFIX}/connections/${connectionId}/info/source/schema`, + params: ({connectionId: _connectionId, workbookId, ...body}, headers) => ({ + body: {...body}, + headers: {...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), ...headers}, + }), + }), + getConnectorSchema: createAction({ + method: 'GET', + path: ({type, mode}) => `${PATH_PREFIX}/info/connectors/forms/${type}/${mode}`, + params: ({connectionId}, headers) => ({headers, query: {conn_id: connectionId}}), + }), + getConnectionTypedQueryData: createAction< + GetConnectionTypedQueryDataResponse, + GetConnectionTypedQueryDataArgs + >({ + method: 'POST', + endpoint: 'datasetDataApiEndpoint', + path: ({connectionId}) => `${PATH_DATA_API_PREFIX}/connections/${connectionId}/typed_query`, + params: ({body, workbookId}, headers) => ({ + body: {...body}, + headers: {...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), ...headers}, + }), + }), + listConnectorIcons: createAction({ + method: 'GET', + path: () => `${PATH_PREFIX}/info/connectors/icons`, + params: (_, headers) => ({headers}), + }), + _proxyExportConnection: createAction({ + method: 'GET', + path: ({connectionId}) => `${PATH_PREFIX}/connections/export/${connectionId}`, + params: ({workbookId}, headers) => ({ + headers: { + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...headers, + }, + }), + getAuthHeaders: (params) => { + return registry.common.auth.getAll().getAuthHeadersBIPrivate(params); + }, + }), + _proxyImportConnection: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/connections/import`, + params: ({workbookId, connection}, headers) => ({ + headers: { + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...headers, + }, + body: { + data: { + workbook_id: workbookId, + connection, + }, + id_mapping: {}, + }, + }), + getAuthHeaders: (params) => { + return registry.common.auth.getAll().getAuthHeadersBIPrivate(params); + }, + }), + getFileStatus: createAction({ + method: 'GET', + path: ({fileId}) => `${PATH_FILE_UPLOADER_PREFIX_V2}/files/${fileId}/status`, + params: (_, headers) => ({headers}), + }), + getFileSources: createAction({ + method: 'GET', + path: ({fileId}) => `${PATH_FILE_UPLOADER_PREFIX_V2}/files/${fileId}/sources`, + params: (_, headers) => ({headers}), + }), + getFileSourceStatus: createAction({ + method: 'GET', + path: ({fileId, sourceId}) => + `${PATH_FILE_UPLOADER_PREFIX_V2}/files/${fileId}/sources/${sourceId}/status`, + params: (_, headers) => ({headers}), + }), + updateFileSource: createAction({ + method: 'POST', + path: ({fileId, sourceId}) => + `${PATH_FILE_UPLOADER_PREFIX_V2}/files/${fileId}/sources/${sourceId}`, + params: ({fileId: _1, sourceId: _2, ...body}, headers) => ({body, headers}), + }), + applySourceSettings: createAction({ + method: 'POST', + path: ({fileId, sourceId}) => + `${PATH_FILE_UPLOADER_PREFIX_V2}/files/${fileId}/sources/${sourceId}/apply_settings`, + params: ({fileId: _1, sourceId: _2, ...body}, headers) => ({body, headers}), + }), + addGoogleSheet: createAction({ + method: 'POST', + path: () => `${PATH_FILE_UPLOADER_PREFIX_V2}/links`, + params: (body, headers) => ({body, headers}), + }), + updateS3BasedConnectionData: createAction< + UpdateS3BasedConnectionDataResponse, + UpdateS3BasedConnectionDataArgs + >({ + method: 'POST', + path: () => `${PATH_FILE_UPLOADER_PREFIX_V2}/update_connection_data`, + params: (body, headers) => ({body, headers}), + }), + addYandexDocument: createAction({ + method: 'POST', + path: () => `${PATH_FILE_UPLOADER_PREFIX_V2}/documents`, + params: (body, headers) => ({body, headers}), + }), + getPresignedUrl: createAction({ + method: 'GET', + path: () => `${PATH_FILE_UPLOADER_PREFIX_V2}/make_presigned_url`, + params: (_, headers) => ({headers}), + transformResponseData(data, config) { + const s3ProxyEndpoint: string = config.ctx.config.endpoints.api?.s3Proxy; + // fix s3 endpoint with reverse proxy in k8s or docker internal service + // more details: https://github.com/minio/minio-js/issues/514 + if (s3ProxyEndpoint) { + return { + ...data, + url: data.url.replace(/^https?:\/\/.+?\//, `${s3ProxyEndpoint}/`), + }; + } + return data; + }, + }), + downloadPresignedUrl: createAction({ + method: 'POST', + path: () => `${PATH_FILE_UPLOADER_PREFIX_V2}/download_presigned_url`, + params: (body, headers) => ({body, headers}), + }), +}; diff --git a/src/shared/schema/bi/actions/datasets.ts b/src/shared/schema/bi/actions/datasets.ts new file mode 100644 index 0000000..ab81a78 --- /dev/null +++ b/src/shared/schema/bi/actions/datasets.ts @@ -0,0 +1,374 @@ +import {registry} from '../../../../server/registry'; +import { + DATASET_ID_HEADER, + TIMEOUT_60_SEC, + TIMEOUT_95_SEC, + WORKBOOK_ID_HEADER, +} from '../../../constants'; +import {createAction, createTypedAction} from '../../gateway-utils'; +import {filterUrlFragment} from '../../utils'; +import { + prepareDatasetProperty, + transformApiV2DistinctsResponse, + transformValidateDatasetFormulaResponseError, + transformValidateDatasetResponseError, +} from '../helpers'; +import { + createDatasetArgsSchema, + createDatasetResultSchema, + deleteDatasetArgsSchema, + deleteDatasetResultSchema, + getDatasetByVersionArgsSchema, + getDatasetByVersionResultSchema, + updateDatasetArgsSchema, + updateDatasetResultSchema, + validateDatasetArgsSchema, + validateDatasetResultSchema, +} from '../schemas'; +import type { + CheckConnectionsForPublicationArgs, + CheckConnectionsForPublicationResponse, + CheckDatasetsForPublicationArgs, + CheckDatasetsForPublicationResponse, + CopyDatasetArgs, + CopyDatasetResponse, + CreateDatasetArgs, + CreateDatasetResponse, + ExportDatasetArgs, + ExportDatasetResponse, + GetDataSetFieldsByIdArgs, + GetDataSetFieldsByIdResponse, + GetDatasetByVersionArgs, + GetDatasetByVersionResponse, + GetDbNamesResponse, + GetDistinctsApiV2Args, + GetDistinctsApiV2Response, + GetDistinctsApiV2TransformedResponse, + GetFieldTypesResponse, + GetPreviewArgs, + GetPreviewResponse, + GetSourceArgs, + GetSourceListingOptionsArgs, + GetSourceListingOptionsResponse, + GetSourceResponse, + ImportDatasetArgs, + ImportDatasetResponse, + UpdateDatasetArgs, + UpdateDatasetResponse, + ValidateDatasetArgs, + ValidateDatasetFormulaArgs, + ValidateDatasetFormulaResponse, + ValidateDatasetResponse, +} from '../types'; + +const API_V1 = '/api/v1'; +const API_DATA_V1 = '/api/data/v1'; +const API_DATA_V2 = '/api/data/v2'; + +export const actions = { + getSources: createAction({ + method: 'GET', + path: ({connectionId}) => + `${API_V1}/connections/${filterUrlFragment(connectionId)}/info/sources`, + params: ({limit, offset, search_text, db_name, workbookId, bindedDatasetId}, headers) => ({ + query: {limit, offset, db_name, search_text}, + headers: { + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...(bindedDatasetId ? {[DATASET_ID_HEADER]: bindedDatasetId} : {}), + ...headers, + }, + }), + timeout: TIMEOUT_60_SEC, + }), + getDatasetByVersion: createTypedAction( + { + paramsSchema: getDatasetByVersionArgsSchema, + resultSchema: getDatasetByVersionResultSchema, + }, + { + method: 'GET', + path: ({datasetId, version = 'draft'}) => + `${API_V1}/datasets/${filterUrlFragment(datasetId)}/versions/${filterUrlFragment( + version, + )}`, + params: ({workbookId, rev_id}, headers) => ({ + headers: {...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), ...headers}, + query: {rev_id}, + }), + }, + ), + getDbNames: createAction({ + method: 'GET', + path: ({connectionId}) => + `${API_V1}/connections/${filterUrlFragment(connectionId)}/db_names`, + params: ({workbookId, bindedDatasetId}, headers) => ({ + headers: { + ...headers, + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...(bindedDatasetId ? {[DATASET_ID_HEADER]: bindedDatasetId} : {}), + }, + }), + }), + getSourceListingOptions: createAction< + GetSourceListingOptionsResponse, + GetSourceListingOptionsArgs + >({ + method: 'GET', + path: ({connectionId}) => + `${API_V1}/connections/${filterUrlFragment(connectionId)}/info/source_listing_options`, + params: ({workbookId, bindedDatasetId}, headers) => ({ + headers: { + ...headers, + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...(bindedDatasetId ? {[DATASET_ID_HEADER]: bindedDatasetId} : {}), + }, + }), + }), + getFieldTypes: createAction({ + method: 'GET', + path: () => `${API_V1}/info/field_types`, + params: (_, headers) => ({headers}), + }), + getDataSetFieldsById: createAction({ + method: 'GET', + endpoint: 'datasetDataApiEndpoint', + path: ({dataSetId}) => `${API_DATA_V1}/datasets/${filterUrlFragment(dataSetId)}/fields`, + params: ({workbookId}, headers) => ({ + headers: {...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), ...headers}, + }), + }), + embedsGetDataSetFieldsById: createAction< + GetDataSetFieldsByIdResponse, + Omit + >({ + method: 'GET', + endpoint: 'datasetDataEmbedsApiEndpoint', + path: ({dataSetId}) => `${API_DATA_V1}/datasets/${filterUrlFragment(dataSetId)}/fields`, + params: (_, headers) => ({headers}), + }), + publicGetDataSetFieldsById: createAction< + GetDataSetFieldsByIdResponse, + Omit + >({ + method: 'GET', + endpoint: 'datasetDataApiEndpoint', + path: ({dataSetId}) => + `/public${API_DATA_V1}/datasets/${filterUrlFragment(dataSetId)}/fields`, + params: (_, headers, {ctx}) => ({ + headers: { + ...headers, + [ctx.config.headersMap.publicApiToken]: process.env.PUBLIC_API_KEY, + }, + }), + }), + checkDatasetsForPublication: createAction< + CheckDatasetsForPublicationResponse, + CheckDatasetsForPublicationArgs + >({ + method: 'POST', + path: () => `${API_V1}/info/datasets_publicity_checker`, + params: ({datasetsIds, workbookId}, headers) => ({ + body: {datasets: datasetsIds}, + headers: {...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), ...headers}, + }), + }), + checkConnectionsForPublication: createAction< + CheckConnectionsForPublicationResponse, + CheckConnectionsForPublicationArgs + >({ + method: 'POST', + path: () => `${API_V1}/info/connections_publicity_checker`, + params: ({connectionsIds, workbookId}, headers) => ({ + body: {connections: connectionsIds}, + headers: {...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), ...headers}, + }), + }), + createDataset: createTypedAction( + { + paramsSchema: createDatasetArgsSchema, + resultSchema: createDatasetResultSchema, + }, + { + method: 'POST', + path: () => `${API_V1}/datasets`, + params: ({dataset, ...restBody}, headers, {ctx}) => { + const resultDataset = prepareDatasetProperty(ctx, dataset); + return {body: {...restBody, dataset: resultDataset}, headers}; + }, + }, + ), + validateDataset: createTypedAction( + { + paramsSchema: validateDatasetArgsSchema, + resultSchema: validateDatasetResultSchema, + }, + { + method: 'POST', + path: ({datasetId, version = 'draft'}) => + datasetId + ? `${API_V1}/datasets/${filterUrlFragment(datasetId)}/versions/${filterUrlFragment( + version, + )}/validators/schema` + : `${API_V1}/datasets/validators/dataset`, + params: ( + {data: {dataset, ...restData}, workbookId, bindedDatasetId}, + headers, + {ctx}, + ) => { + const resultDataset = prepareDatasetProperty(ctx, dataset); + return { + body: {...restData, dataset: resultDataset}, + headers: { + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...(bindedDatasetId ? {[DATASET_ID_HEADER]: bindedDatasetId} : {}), + ...headers, + }, + }; + }, + transformResponseError: transformValidateDatasetResponseError, + timeout: TIMEOUT_95_SEC, + }, + ), + updateDataset: createTypedAction( + { + paramsSchema: updateDatasetArgsSchema, + resultSchema: updateDatasetResultSchema, + }, + { + method: 'PUT', + path: ({datasetId, version = 'draft'}) => + `${API_V1}/datasets/${filterUrlFragment(datasetId)}/versions/${filterUrlFragment( + version, + )}`, + params: ({data: {dataset, ...restData}}, headers, {ctx}) => { + const resultDataset = prepareDatasetProperty(ctx, dataset); + return {body: {...restData, dataset: resultDataset}, headers}; + }, + }, + ), + getPreview: createAction({ + method: 'POST', + endpoint: 'datasetDataApiEndpoint', + path: ({datasetId, version}) => + datasetId + ? `${API_DATA_V1}/datasets/${filterUrlFragment( + datasetId, + )}/versions/${filterUrlFragment(version)}/preview` + : `${API_DATA_V1}/datasets/data/preview`, + params: ({dataset, workbookId, limit}, headers, {ctx}) => { + const resultDataset = prepareDatasetProperty(ctx, dataset); + return { + body: {dataset: resultDataset, limit}, + headers: {...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), ...headers}, + }; + }, + timeout: TIMEOUT_95_SEC, + }), + validateDatasetFormula: createAction< + ValidateDatasetFormulaResponse, + ValidateDatasetFormulaArgs + >({ + method: 'POST', + path: ({datasetId}) => + datasetId + ? `${API_V1}/datasets/${filterUrlFragment( + datasetId, + )}/versions/draft/validators/field` + : `${API_V1}/datasets/validators/field`, + params: ({dataset, workbookId, field}, headers, {ctx}) => { + const resultDataset = prepareDatasetProperty(ctx, dataset); + return { + body: {dataset: resultDataset, field}, + headers: {...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), ...headers}, + }; + }, + transformResponseError: transformValidateDatasetFormulaResponseError, + }), + copyDataset: createAction({ + method: 'POST', + path: ({datasetId}) => `${API_V1}/datasets/${filterUrlFragment(datasetId)}/copy`, + params: ({new_key}, headers) => ({body: {new_key}, headers}), + }), + getDistinctsApiV2: createAction< + GetDistinctsApiV2Response, + GetDistinctsApiV2Args, + GetDistinctsApiV2TransformedResponse + >({ + method: 'POST', + endpoint: 'datasetDataApiEndpoint', + path: ({datasetId}) => `${API_DATA_V2}/datasets/${datasetId}/values/distinct`, + params: ({datasetId: _datasetId, workbookId, ...body}, headers) => ({ + body, + headers: { + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...headers, + }, + }), + transformResponseData: transformApiV2DistinctsResponse, + timeout: TIMEOUT_95_SEC, + }), + getPublicDistinctsApiV2: createAction< + GetDistinctsApiV2Response, + GetDistinctsApiV2Args, + GetDistinctsApiV2TransformedResponse + >({ + method: 'POST', + path: ({datasetId}) => `/public${API_DATA_V2}/datasets/${datasetId}/values/distinct`, + params: ({datasetId: _datasetId, ...body}, headers, {ctx}) => ({ + body, + headers: { + ...headers, + [ctx.config.headersMap.publicApiToken]: process.env.PUBLIC_API_KEY, + }, + }), + transformResponseData: transformApiV2DistinctsResponse, + timeout: TIMEOUT_95_SEC, + }), + deleteDataset: createTypedAction( + { + paramsSchema: deleteDatasetArgsSchema, + resultSchema: deleteDatasetResultSchema, + }, + { + method: 'DELETE', + path: ({datasetId}) => `${API_V1}/datasets/${filterUrlFragment(datasetId)}`, + params: (_, headers) => ({headers}), + }, + ), + _proxyExportDataset: createAction({ + method: 'POST', + path: ({datasetId}) => `${API_V1}/datasets/export/${datasetId}`, + params: ({workbookId, idMapping}, headers) => ({ + headers: { + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...headers, + }, + body: { + id_mapping: idMapping, + }, + }), + getAuthHeaders: (params) => { + return registry.common.auth.getAll().getAuthHeadersBIPrivate(params); + }, + }), + _proxyImportDataset: createAction({ + method: 'POST', + path: () => `${API_V1}/datasets/import`, + params: ({workbookId, idMapping, dataset}, headers) => ({ + headers: { + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...headers, + }, + body: { + data: { + workbook_id: workbookId, + dataset, + }, + id_mapping: idMapping, + }, + }), + getAuthHeaders: (params) => { + return registry.common.auth.getAll().getAuthHeadersBIPrivate(params); + }, + }), +}; diff --git a/src/shared/schema/bi/actions/index.ts b/src/shared/schema/bi/actions/index.ts new file mode 100644 index 0000000..3e4aafe --- /dev/null +++ b/src/shared/schema/bi/actions/index.ts @@ -0,0 +1,9 @@ +import {actions as connActions} from './connections'; +import {actions as dsActions} from './datasets'; +import {actions as oauthActions} from './oauth'; + +export const actions = { + ...connActions, + ...dsActions, + ...oauthActions, +}; diff --git a/src/shared/schema/bi/actions/oauth.ts b/src/shared/schema/bi/actions/oauth.ts new file mode 100644 index 0000000..8f55ab5 --- /dev/null +++ b/src/shared/schema/bi/actions/oauth.ts @@ -0,0 +1,25 @@ +import {createAction} from '../../gateway-utils'; +import {transformConnectionResponseError} from '../helpers'; +import type { + GetOAuthTokenArgs, + GetOAuthTokenResponse, + GetOAuthUriArgs, + GetOAuthUriResponse, +} from '../types'; + +const PATH_PREFIX = '/oauth'; + +export const actions = { + getOAuthUri: createAction({ + method: 'GET', + path: ({scope}) => `${PATH_PREFIX}/uri/${scope}`, + params: ({conn_type}, headers) => ({headers, query: {conn_type}}), + transformResponseError: transformConnectionResponseError, + }), + getOAuthToken: createAction({ + method: 'POST', + path: ({scope}) => `${PATH_PREFIX}/token/${scope}`, + params: ({code, conn_type}, headers) => ({headers, body: {code, conn_type}}), + transformResponseError: transformConnectionResponseError, + }), +}; diff --git a/src/shared/schema/bi/helpers/datasets.ts b/src/shared/schema/bi/helpers/datasets.ts new file mode 100644 index 0000000..2b94ed3 --- /dev/null +++ b/src/shared/schema/bi/helpers/datasets.ts @@ -0,0 +1,68 @@ +import type {ResponseError} from '@gravity-ui/gateway'; +import type {AppContext} from '@gravity-ui/nodekit'; + +import type {Dataset} from '../../../types'; +import {Feature} from '../../../types/feature'; +import type { + ConnectionErrorResponse, + GetDistinctsApiV2Response, + GetDistinctsApiV2TransformedResponse, + GetDistinctsResponse, + ValidateDatasetErrorResponse, + ValidateDatasetFormulaErrorResponse, +} from '../types'; + +const transformResponseError = ( + response: ResponseError, +) => { + const data = (response.data || {}) as T; + const {code, message} = data; + + return { + code, + message, + status: response.status, + details: {data}, + }; +}; + +export const transformValidateDatasetResponseError = (response: ResponseError) => + transformResponseError(response); + +export const transformValidateDatasetFormulaResponseError = (response: ResponseError) => + transformResponseError(response); + +export const transformConnectionResponseError = (response: ResponseError) => + transformResponseError(response); + +export const transformApiV2DistinctsResponse = ( + response: GetDistinctsApiV2Response, +): GetDistinctsApiV2TransformedResponse => { + const {result_data} = response; + + const rows = result_data[0].rows; + + const responseData = rows.reduce( + (data, row) => { + return [...data, row.data]; + }, + [] as GetDistinctsResponse['result']['data']['Data'], + ); + + return { + result: { + data: { + Data: responseData, + }, + }, + }; +}; + +export const prepareDatasetProperty = (ctx: AppContext, dataset: Partial) => { + const result = {...dataset}; + const keyToDelete = ctx.get('isEnabledServerFeature')(Feature.EnableRLSV2) ? 'rls' : 'rls2'; + + delete result[keyToDelete]; + + return result; +}; diff --git a/src/shared/schema/bi/helpers/index.ts b/src/shared/schema/bi/helpers/index.ts new file mode 100644 index 0000000..81df914 --- /dev/null +++ b/src/shared/schema/bi/helpers/index.ts @@ -0,0 +1 @@ +export * from './datasets'; diff --git a/src/shared/schema/bi/index.ts b/src/shared/schema/bi/index.ts new file mode 100644 index 0000000..c85c33c --- /dev/null +++ b/src/shared/schema/bi/index.ts @@ -0,0 +1,9 @@ +import {getServiceEndpoints} from '../../endpoints/schema'; + +import {actions} from './actions'; + +export default { + actions, + endpoints: getServiceEndpoints('bi'), + serviceName: 'bi', +}; diff --git a/src/shared/schema/bi/schemas/connections.ts b/src/shared/schema/bi/schemas/connections.ts new file mode 100644 index 0000000..e35aaf8 --- /dev/null +++ b/src/shared/schema/bi/schemas/connections.ts @@ -0,0 +1,45 @@ +import z from 'zod'; + +import {makeSchemaRef} from '../../../utils/openapi'; + +const BI_SCHEMA_NAME = { + ConnectionCreate: 'ConnectionCreate', + ConnectionRead: 'ConnectionRead', + ConnectionUpdate: 'ConnectionUpdate', +}; + +export const deleteConnectionArgsSchema = z.strictObject({ + connectionId: z.string(), +}); + +export const deleteConnectionResultSchema = z.unknown(); + +export const getConnectionArgsSchema = z.strictObject({ + connectionId: z.string(), + workbookId: z.string().nullable().optional(), + bindedDatasetId: z.string().nullable().optional(), + rev_id: z.string().optional(), +}); + +export const getConnectionResultSchema = z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.ConnectionRead), +}); + +export const createConnectionArgsSchema = z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.ConnectionCreate), +}); + +export const createConnectionResultSchema = z.object({ + id: z.string(), +}); + +export const updateConnectionArgsSchema = z.strictObject({ + connectionId: z.string(), + data: z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.ConnectionUpdate), + }), +}); + +export const updateConnectionResultSchema = z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.ConnectionRead), +}); diff --git a/src/shared/schema/bi/schemas/datasets.ts b/src/shared/schema/bi/schemas/datasets.ts new file mode 100644 index 0000000..ca4cbf3 --- /dev/null +++ b/src/shared/schema/bi/schemas/datasets.ts @@ -0,0 +1,57 @@ +import z from 'zod'; + +import {makeSchemaRef} from '../../../utils/openapi'; + +const BI_SCHEMA_NAME = { + DatasetCreate: 'DatasetCreate', + DatasetUpdate: 'DatasetUpdate', + DatasetRead: 'DatasetRead', + DatasetValidate: 'DatasetValidate', +}; + +export const createDatasetArgsSchema = z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.DatasetCreate), +}); + +export const createDatasetResultSchema = z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.DatasetRead), +}); + +export const updateDatasetArgsSchema = z.strictObject({ + datasetId: z.string(), + data: z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.DatasetUpdate), + }), +}); + +export const updateDatasetResultSchema = z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.DatasetRead), +}); + +export const deleteDatasetArgsSchema = z.strictObject({ + datasetId: z.string(), +}); + +export const deleteDatasetResultSchema = z.unknown(); + +export const getDatasetByVersionArgsSchema = z.strictObject({ + datasetId: z.string(), + workbookId: z.string().nullable().optional(), + rev_id: z.string().optional(), +}); + +export const getDatasetByVersionResultSchema = z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.DatasetRead), +}); + +export const validateDatasetArgsSchema = z.strictObject({ + datasetId: z.string(), + workbookId: z.string().nullable().optional(), + data: z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.DatasetValidate), + }), +}); + +export const validateDatasetResultSchema = z.any().meta({ + $ref: makeSchemaRef(BI_SCHEMA_NAME.DatasetRead), +}); diff --git a/src/shared/schema/bi/schemas/index.ts b/src/shared/schema/bi/schemas/index.ts new file mode 100644 index 0000000..81df914 --- /dev/null +++ b/src/shared/schema/bi/schemas/index.ts @@ -0,0 +1 @@ +export * from './datasets'; diff --git a/src/shared/schema/bi/types/common.ts b/src/shared/schema/bi/types/common.ts new file mode 100644 index 0000000..125e793 --- /dev/null +++ b/src/shared/schema/bi/types/common.ts @@ -0,0 +1,6 @@ +import type {WorkbookId} from '../../../types'; + +export type WorkbookIdArg = { + workbookId: WorkbookId; + rev_id?: string; +}; diff --git a/src/shared/schema/bi/types/connections.ts b/src/shared/schema/bi/types/connections.ts new file mode 100644 index 0000000..b1210e1 --- /dev/null +++ b/src/shared/schema/bi/types/connections.ts @@ -0,0 +1,408 @@ +import type z from 'zod'; + +import type { + CONNECTOR_VISIBILITY_MODE, + CSVDelimiter, + CSVEncoding, + ConnectorType, +} from '../../../constants'; +import type { + ConnectionData, + ConnectionTypedQueryApiRequest, + ConnectionTypedQueryApiResponse, + DATASET_FIELD_TYPES, + TransferNotification, + ValueOf, + WorkbookId, +} from '../../../types'; +import type {deleteConnectionResultSchema} from '../schemas/connections'; + +import type {WorkbookIdArg} from './common'; + +type BICounter = { + id: string; + name: string; +}; + +type BaseArgs = { + connectionId: string; +}; + +type BaseConnectorItem = { + conn_type: ConnectorType; + backend_driven_form: boolean; + hidden: boolean; + history: boolean; + title: string; + /** + * Controls the behavior of connector's list item in the list. + * + * 1. `free` - connector **is** shown in the list and **is** available for creation + * 2. `hidden` - connector **is not** shown in the list and **is** available for creation + * 3. `uncreatable` - connector **is not** shown in the list and **is not** available for creation + * 4. `business` - connector **is** shown in the list and **is not** available for creation + */ + visibility_mode: ValueOf; + alias?: string; +}; + +export type ConnectorItem = BaseConnectorItem & { + includes?: BaseConnectorItem[]; +}; + +export type ConnectorItemSection = { + title: string; + connectors: ConnectorItem[]; +}; + +export type EnsureUploadRobotResponse = { + svc_acct_id: string; + svc_acct_key_id: string; + svc_acct_private_key: string; +}; + +export type EnsureUploadRobotArgs = BaseArgs; + +export type GetAvailableCountersResponse = { + counters: BICounter[]; +}; + +export type GetAvailableCountersArgs = BaseArgs; + +export type DeleteConnectionResponse = z.infer; + +export type GetConnectorsResponse = { + /** @deprecated use `sections` & `uncategorized` fields instead */ + result: ConnectorItem[]; + /** Represent groups of connectors */ + sections?: ConnectorItemSection[]; + /** Represent connectors out of groups. These one should be rendered before grouped connectors */ + uncategorized?: ConnectorItem[]; +}; + +export type GetConnectionResponse = ConnectionData; + +export type GetConnectionArgs = BaseArgs & { + workbookId?: WorkbookId; + bindedDatasetId?: string | null; + rev_id?: string; +}; + +export type CreateConnectionResponse = { + id: string; +}; + +export type ConnectionErrorResponse = { + code: string; + message: string; + details: {data: unknown}; +}; + +export type CreateConnectionArgs = ConnectionData; + +export type UpdateConnectionResponse = {}; + +export type UpdateConnectionArgs = { + connectionId: string; + data: ConnectionData; +}; + +export type VerifyConnectionResponse = {}; + +export type VerifyConnectionArgs = BaseArgs & ConnectionData & WorkbookIdArg; + +export type VerifyConnectionParamsResponse = {}; + +export type VerifyConnectionParamsArgs = ConnectionData; + +export type GetConnectionSourcesResponse = { + sources: Record[]; + freeform_sources: Record[]; +}; + +export type GetConnectionSourcesArgs = BaseArgs & WorkbookIdArg; + +export type GetConnectionSourceSchemaResponse = { + raw_schema: Record[]; +}; + +export type GetConnectionSourceSchemaArgs = BaseArgs & + WorkbookIdArg & { + source: { + title: string; + group: string[]; + id: string; + }; + }; + +export type GetConnectionTypedQueryDataArgs = BaseArgs & + WorkbookIdArg & {body: ConnectionTypedQueryApiRequest}; + +export type GetConnectionTypedQueryDataResponse = ConnectionTypedQueryApiResponse; + +export type GetConnectionTypedQueryErrorResponse = { + status: number; + message: string; + code: string; + details?: { + title?: string; + description?: string; + db_message?: string; + }; + requestId: string; +}; + +export type ConnectorIconView = 'standard' | 'nav'; + +type ConnectorIconDataMap = Record; + +export type ConnectorIconData = {conn_type: string} & ( + | { + /** Indicates that icons data store in base64 format. */ + type: 'data'; + /** Map of links to sources on s3 or icons in base64 format. */ + data: ConnectorIconDataMap; + } + | { + /** Indicates that icons data store as links on cdn. */ + type: 'url'; + /** Map of links to sources on s3 or icons in base64 format. */ + url: ConnectorIconDataMap; + } +); + +export type ListConnectorIconsResponse = { + icons: ConnectorIconData[]; +}; + +export type ExportConnectionArgs = { + connectionId: string; + workbookId?: string | null; +}; + +export type ExportConnectionResponse = { + connection: ConnectionData; + notifications: TransferNotification[]; +}; + +export type ImportConnectionResponse = { + id: string; + notifications: TransferNotification[]; +}; + +export type ImportConnectionArgs = { + workbookId: string; + connection: ConnectionData; +}; + +export type StateStatus = 'ready' | 'in_progress' | 'failed'; +export type RefreshToken = string | null | undefined; + +export type GetFileStatusResponse = { + file_id: string; + status: StateStatus; + errors: { + code: string; + details: { + token: string | null; + row: number; + column: number; + }; + level: string; + message: string; + }[]; + error?: { + code: string; + }; +}; + +export type GetFileStatusArgs = { + fileId: string; +}; + +export type FileSourceInfo = { + is_applicable: boolean; + source_id: string; + title: string; + error?: {code: string} | null; +}; + +export type GetFileSourcesResponse = { + file_id: string; + sources: FileSourceInfo[]; +}; + +export type GetFileSourcesArgs = { + fileId: string; +}; + +export type GetFileSourceStatusResponse = { + file_id: string; + source_id: string; + status: StateStatus; +}; + +export type GetFileSourceStatusArgs = { + fileId: string; + sourceId: string; +}; + +export type FileSourceDataSettings = { + delimiter: CSVDelimiter; + encoding: CSVEncoding; + first_line_is_header: boolean; +}; + +export type FileSourceSchema = + | { + user_type: DATASET_FIELD_TYPES; + title: string; + name: string; + }[] + | null; + +export type FileSourcePreview = (string | number)[][]; + +export type UpdateFileSourceResponse = { + data_settings: FileSourceDataSettings; + file_id: string; + options: { + columns: { + name: string; + user_type: DATASET_FIELD_TYPES[]; + }[]; + data_settings: { + delimiter: CSVDelimiter[]; + encoding: CSVEncoding[]; + }; + } | null; + source: { + is_valid: boolean; + preview: FileSourcePreview; + raw_schema: FileSourceSchema; + sheet_id: S3BasedConnectionSheetId; + source_id: string; + title: string; + error?: { + code: string; + details: {'request-id': string}; + level: string; + message: string; + }; + private_path?: string; + public_link?: string; + spreadsheet_id?: string; + }; +}; + +export type FileSourceColumnType = { + name: string; + user_type: DATASET_FIELD_TYPES; +}; + +export type UpdateFileSourceArgs = { + fileId: string; + sourceId: string; + // The field is in the snake case, because it is passed directly to backend request + column_types?: FileSourceColumnType[]; +}; + +export type ApplySourceSettingsResponse = {}; + +export type ApplySourceSettingsArgs = { + fileId: string; + sourceId: string; + // The field is in the snake case, because it is passed directly to backend request + data_settings: FileSourceDataSettings; + title?: string; +}; + +export type AddGoogleSheetResponse = { + file_id: string; + title: string; +}; + +export type AddGoogleSheetArgs = { + type: 'gsheets'; + url: string; + authorized: boolean; + refresh_token?: RefreshToken; + connection_id?: string; +}; + +export type UpdateS3BasedConnectionDataResponse = { + files: { + file_id: string; + sources: { + source_id: string; + }[]; + }[]; +}; + +type BaseUpdatedSource = { + id: string; + title: string; + first_line_is_header: boolean; +}; + +type S3BasedConnectionSheetId = string | number; + +export type GoogleSheetUpdatedSource = BaseUpdatedSource & { + sheet_id: S3BasedConnectionSheetId; + spreadsheet_id?: string; +}; + +export type YandexDocumentUpdatedSource = BaseUpdatedSource & { + public_link?: string; + private_path?: string; + sheet_id?: S3BasedConnectionSheetId; +}; + +type UpdateGoogleSheetDataArgs = { + type: 'gsheets'; + sources: GoogleSheetUpdatedSource[]; + refresh_token?: RefreshToken; +}; + +type UpdateYandexDocumentDataArgs = { + type: 'yadocs'; + sources: YandexDocumentUpdatedSource[]; + oauth_token?: string; +}; + +export type UpdateS3BasedConnectionDataArgs = ( + | UpdateGoogleSheetDataArgs + | UpdateYandexDocumentDataArgs +) & { + authorized: boolean; + connection_id?: string; +}; + +export type AddYandexDocumentResponse = { + file_id: string; + title: string; +}; + +export type AddYandexDocumentArgs = { + authorized: boolean; + type: 'yadocs'; + connection_id?: string; + private_path?: string; + public_link?: string; + oauth_token?: RefreshToken; +}; + +export type GetPresignedUrlResponse = { + url: string; + fields: Record; +}; + +export type DownloadPresignedUrlArgs = { + filename: string; + key: string; +}; + +export type DownloadPresignedUrlResponse = { + file_id: string; + filename: string; +}; diff --git a/src/shared/schema/bi/types/datasets.ts b/src/shared/schema/bi/types/datasets.ts new file mode 100644 index 0000000..4356467 --- /dev/null +++ b/src/shared/schema/bi/types/datasets.ts @@ -0,0 +1,330 @@ +import type z from 'zod'; + +import type { + Dataset, + DatasetField, + DatasetFieldCalcMode, + DatasetFieldError, + SourceListingOptions, + TransferIdMapping, + TransferNotification, + WorkbookId, +} from '../../../types'; +import type {ApiV2RequestBody, ApiV2ResultData} from '../../../types/bi-api/v2'; +import type {EntryFieldData} from '../../types'; +import type {deleteDatasetResultSchema} from '../schemas'; + +import type {WorkbookIdArg} from './common'; + +type Id = {id: string}; + +type DatasetId = {datasetId: string}; + +type DatasetVersion = 'draft'; + +export type ValidateDatasetUpdate = { + action: string; + field: Partial; +}; + +type FieldDocKey = + | 'CHYT_TABLE/table_name' + | 'CHYT_TABLE_LIST/table_names' + | 'CHYT_TABLE_LIST/title' + | 'CHYT_TABLE_RANGE/title' + | 'CHYT_USER_AUTH_TABLE_LIST/title' + | 'CHYT_USER_AUTH_TABLE_RANGE/title' + | 'CHYT_USER_AUTH_TABLE/table_name' + | 'CHYT_USER_AUTH_TABLE/table_names' + | 'CHYT_TABLE_RANGE/directory_path' + | 'CHYDB_TABLE/table_name' + | 'CHYDB_TABLE/ydb_database' + | 'ANY_SUBSELECT/subsql' + | 'CHYT_SUBSELECT/subsql' + | 'MSSQL_SUBSELECT/subsql' + | 'PG_SUBSELECT/subsql' + | 'YTsaurus/CHYT_TABLE/table_name' + | 'CHYT_YTSAURUS_TABLE_LIST/title' + | 'YTsaurus/CHYT_TABLE_LIST/table_names' + | 'CHYT_YTSAURUS_TABLE_RANGE/title' + | 'YTsaurus/CHYT_TABLE_RANGE/directory_path' + | 'YTsaurus/CHYT_SUBSELECT/subsql'; + +type BaseOptions = { + name: string; + default: string; + title: string; + required?: boolean; + field_doc_key?: FieldDocKey; + template_enabled?: boolean; +}; + +export type TextFormOptions = {input_type: 'text'} & BaseOptions; + +export type TextareaFormOptions = {input_type: 'textarea'} & BaseOptions; + +type SqlFormOptions = {input_type: 'sql'} & BaseOptions; + +export type SelectFormOptions = { + input_type: 'select'; + select_options: string[]; + select_allow_user_input: boolean; +} & BaseOptions; + +export type FormOptions = + | TextFormOptions + | TextareaFormOptions + | SqlFormOptions + | SelectFormOptions; + +export type BaseSource = { + connection_id: string; + form: FormOptions[]; + disabled: boolean; + group: string[]; + is_ref: boolean; + parameter_hash: string; + parameters: Record; + ref_source_id: string | null; + source_type: string; + title: string; + managed_by?: 'user'; + tab_title: string; +}; + +type DatasetWithOptions = { + dataset: Dataset['dataset']; + options: Dataset['options']; +}; + +type DistinctResultData = { + Data: string[][]; + Type: [string, [string, [string, [string, string[]]][]]]; +}; + +export type DistinctResult = {result: {data: DistinctResultData}}; +export type DistinctRegularResult = {result: {regular: DistinctResultData}}; + +export type DatasetDistinctWhere = { + column: string; + operation: string; + values: string[]; +}; + +export type GetSourceResponse = { + freeform_sources: BaseSource[]; + sources: BaseSource[]; +}; + +export type GetSourceArgs = { + connectionId: string; + limit?: number; + offset?: number; + db_name?: string; + search_text?: string; + bindedDatasetId?: string; + workbookId?: WorkbookId; +}; + +export type GetSourceListingOptionsArgs = { + connectionId: string; + bindedDatasetId?: string; + workbookId?: WorkbookId; +}; + +export type GetDatasetByVersionResponse = Dataset; + +export type GetDatasetByVersionArgs = DatasetId & { + version?: DatasetVersion; + workbookId?: WorkbookId; + rev_id?: string; +}; + +export type DeleteDatasetResponse = z.infer; + +export type CheckDatasetsForPublicationResponse = { + result: { + allowed: boolean; + dataset_id: string; + reason: string | null; + }[]; +}; + +export type CheckDatasetsForPublicationArgs = { + datasetsIds: string[]; +} & WorkbookIdArg; + +export type CheckConnectionsForPublicationResponse = { + result: { + allowed: boolean; + connection_id: string; + reason: string | null; + }[]; +}; + +export type CheckConnectionsForPublicationArgs = { + connectionsIds: string[]; +} & WorkbookIdArg; + +export type ValidateDatasetErrorResponse = { + code: string; + message: string; + details: {data: {dataset_errors: []} & DatasetWithOptions}; +}; + +export type ValidateDatasetResponse = { + code: string; + message: string; + dataset_errors: string[]; +} & DatasetWithOptions; + +export type ValidateDatasetArgs = { + version?: DatasetVersion; + data: { + dataset: Partial; + updates: ValidateDatasetUpdate[]; + }; + workbookId?: WorkbookId; + bindedDatasetId?: string; + rev_id?: string; +} & DatasetId; + +export type GetFieldTypesResponse = { + types: { + name: string; + aggregations: string[]; + }[]; +}; + +export type PartialDatasetField = { + data_type: string; + guid: string; + hidden: boolean; + title: string; + type: string; + calc_mode: DatasetFieldCalcMode; + ui_settings?: string; +}; + +export type GetDataSetFieldsByIdResponse = { + fields: PartialDatasetField[]; + revision_id: string; +}; + +export type GetDataSetFieldsByIdArgs = WorkbookIdArg & { + dataSetId: string; +}; + +type CreateDatasetBaseArgs = { + dataset: Partial; + name: string; + created_via?: string; +}; + +export type CreateDirDatasetArgs = CreateDatasetBaseArgs & { + dir_path: string; +}; + +export type CreateWorkbookDatasetArgs = CreateDatasetBaseArgs & { + workbook_id: string; +}; + +export type CreateCollectionDatasetArgs = CreateDatasetBaseArgs & { + collection_id: string; +}; + +export type CreateDatasetArgs = + | CreateDirDatasetArgs + | CreateWorkbookDatasetArgs + | CreateCollectionDatasetArgs; + +export type CreateDatasetResponse = Id & DatasetWithOptions; + +export type UpdateDatasetResponse = DatasetWithOptions; + +export type UpdateDatasetArgs = { + version?: DatasetVersion; + data: { + dataset: Partial; + }; +} & DatasetId; + +export type GetPreviewResponse = Partial; + +export type GetPreviewArgs = { + dataset: Dataset['dataset']; + version: DatasetVersion; + limit?: number; + workbookId?: WorkbookId; +} & DatasetId; + +export type ValidateDatasetFormulaErrorResponse = { + code: string; + message: string; + details: {data: {field_errors: DatasetFieldError[]}}; +}; + +export type ValidateDatasetFormulaResponse = { + code: string; + field_errors: DatasetFieldError[]; + message: string; +}; + +export type ValidateDatasetFormulaArgs = { + dataset: Dataset['dataset']; + field: DatasetField; +} & DatasetId & + WorkbookIdArg; + +export type CopyDatasetResponse = Id; + +export type CopyDatasetArgs = {new_key: string} & DatasetId; + +export type GetDistinctsResponse = DistinctResult; + +export type GetDistinctsApiV2Response = ApiV2ResultData; + +export type GetDistinctsApiV2TransformedResponse = { + result: { + data: { + Data: GetDistinctsResponse['result']['data']['Data']; + }; + }; +}; + +export type GetDistinctsApiV2InfoHeadersArg = Record; + +export type GetDistinctsApiV2Args = Omit< + ApiV2RequestBody, + 'pivot' | 'order_by' | 'disable_group_by' | 'with_totals' | 'autofill_legend' +> & + DatasetId & + WorkbookIdArg; + +export type ExportDatasetResponse = { + dataset: EntryFieldData; + notifications: TransferNotification[]; +}; + +export type ExportDatasetArgs = { + datasetId: string; + idMapping: TransferIdMapping; + workbookId?: string | null; +}; + +export type ImportDatasetResponse = { + id: string; + notifications: TransferNotification[]; +}; + +export type ImportDatasetArgs = { + workbookId: string; + dataset: EntryFieldData; + idMapping: TransferIdMapping; +}; + +export type GetDbNamesResponse = { + db_names: string[]; +}; + +export type GetSourceListingOptionsResponse = SourceListingOptions; diff --git a/src/shared/schema/bi/types/forms.ts b/src/shared/schema/bi/types/forms.ts new file mode 100644 index 0000000..671bcda --- /dev/null +++ b/src/shared/schema/bi/types/forms.ts @@ -0,0 +1,247 @@ +import type {DatePickerProps} from '@gravity-ui/date-components'; +import type { + ButtonProps, + CheckboxProps, + SegmentedRadioGroupProps as RadioButtonProps, + RadioGroupProps, + SelectProps, + TextAreaProps, + TextInputProps, +} from '@gravity-ui/uikit'; + +import type {CollapseProps} from '../../../../ui/components/Collapse/types'; +import type {ConnectionData} from '../../../types'; + +type MarkdownString = string; + +export type BaseItem = { + name: string; + inner?: boolean; + displayConditions?: ConnectionData; +}; + +export type BaseControlItem = { + width?: 's' | 'm' | 'l' | 'auto' | number; // default 'auto' + defaultValue?: string; // 'value1,value2, ...' in the case of multiple: true + fakeValue?: string; // fills in the control, but does not participate in the form/initialForm/innerForm + placeholder?: string; + hintText?: MarkdownString; + prepareValue?: (value: string) => string | boolean | null; +}; + +export type LabelItem = { + id: 'label'; + text: string; + align?: 'center' | 'start' | 'end'; + displayConditions?: ConnectionData; + helpText?: MarkdownString; +}; + +type TextAreaControlProps = {multiline: true} & TextAreaProps; +type TextInputControlProps = {multiline?: undefined} & TextInputProps; +export type InputItemControlProps = TextAreaControlProps | TextInputControlProps; + +export type InputItem = BaseItem & + BaseControlItem & { + id: 'input'; + controlProps?: InputItemControlProps; + }; + +export type SelectItem = BaseItem & + BaseControlItem & { + id: 'select'; + // option.data?.description is occupied for meta information in the select option. + availableValues?: SelectProps['options']; + hintText?: MarkdownString; + controlProps?: Partial; + loading?: boolean; + beforeUpdate?: (nextValue: string[]) => void; + }; + +export type RadioButtonItemOption = { + text: string; + value: string; +}; + +export type RadioButtonItem = BaseItem & + Omit & { + id: 'radio_button'; + options: RadioButtonItemOption[]; + defaultValue?: string; + hintText?: MarkdownString; + controlProps?: Partial; + beforeUpdate?: (nextValue: string) => void; + }; + +export type RadioGroupItemOption = { + content: { + text: string; + hintText?: MarkdownString; + textEndIcon?: { + name: 'CircleExclamation'; + view?: 'error' | 'warn'; + }; + }; + value: string; +}; + +export type RadioGroupItem = BaseItem & { + id: 'radio_group'; + options: RadioGroupItemOption[]; + defaultValue?: string; + controlProps?: Partial; +}; + +export type CheckboxItem = BaseItem & { + id: 'checkbox'; + text: string; + defaultValue?: boolean; + controlProps?: Partial; +}; + +export type DatepickerItem = BaseItem & + Omit & { + id: 'datepicker'; + controlProps?: Partial; + }; + +export type PlainTextItem = Pick & { + id: 'plain_text'; + text: string; + hintText?: MarkdownString; +}; + +export type DescriptionItem = Pick & { + id: 'description'; + text: MarkdownString; +}; + +export type FileInputItem = BaseItem & { + id: 'file-input'; + controlProps?: Partial>; +}; + +export type HiddenItem = BaseItem & { + id: 'hidden'; + defaultValue?: ConnectionData[keyof ConnectionData]; +}; + +export type KeyValueItem = BaseItem & { + id: 'key_value'; + keys: SelectProps['options']; + keySelectProps?: Partial; + valueInputProps?: Partial; + secret?: boolean; +}; + +export type ConnectorFormItem = + | LabelItem + | InputItem + | SelectItem + | RadioButtonItem + | RadioGroupItem + | CheckboxItem + | DatepickerItem + | PlainTextItem + | DescriptionItem + | FileInputItem + | HiddenItem + | KeyValueItem; + +export type CustomizableRow = { + items: ConnectorFormItem[]; +}; + +export type CacheTtlRowItem = Omit & { + type: 'cache_ttl_sec'; + labelText?: string; + disabled?: boolean; +}; + +export type EarlyInvalidationCacheRowItem = Omit & { + type: 'cache_invalidation_throttling_interval_sec'; + labelText?: string; + disabled?: boolean; +}; + +export type CollapseRowItem = BaseItem & { + type: 'collapse'; + text: string; + inner: true; + componentProps?: Partial; +}; + +export type RawSQLLevelRowItem = Omit & { + type: 'raw_sql_level'; + label: Omit; + radioGroup: Omit; + switchOffValue: string; + disabled?: boolean; +}; + +export type PreparedRow = + | EarlyInvalidationCacheRowItem + | CacheTtlRowItem + | CollapseRowItem + | RawSQLLevelRowItem; + +export type Row = CustomizableRow | PreparedRow; + +export type ValidatedItemAction = 'include' | 'skip'; + +export type ValidatedItem = { + name: string; + defaultAction: ValidatedItemAction; + type?: 'string' | 'boolean' | 'object'; // default 'string' + required?: boolean; + nullable?: boolean; + length?: number; +}; + +type FormApiSchemaItemCondition = { + when: { + name: string; + }; + equals: ConnectionData[keyof ConnectionData]; + then: { + selector: { + name: string; + }; + action: ValidatedItemAction; + }[]; +}; + +export type FormApiSchemaItem = { + items: ValidatedItem[]; + conditions?: FormApiSchemaItemCondition[]; +}; + +type FormApiSchema = { + create?: FormApiSchemaItem; + edit?: FormApiSchemaItem; + check?: FormApiSchemaItem; +}; + +type FormUiSchema = { + showCreateDatasetButton?: boolean; + showCreateQlChartButton?: boolean; + showCreateEditorChartButton?: boolean; +}; + +export type FormSchema = { + title: string; + rows: Row[]; + apiSchema?: FormApiSchema; + templateName?: string; + uiSchema?: FormUiSchema; +}; + +export type GetConnectorSchemaResponse = { + form: FormSchema; +}; + +export type GetConnectorSchemaArgs = { + type: string; + mode: 'create' | 'edit'; + connectionId?: string; +}; diff --git a/src/shared/schema/bi/types/index.ts b/src/shared/schema/bi/types/index.ts new file mode 100644 index 0000000..05d6acc --- /dev/null +++ b/src/shared/schema/bi/types/index.ts @@ -0,0 +1,4 @@ +export * from './connections'; +export * from './datasets'; +export * from './forms'; +export * from './oauth'; diff --git a/src/shared/schema/bi/types/oauth.ts b/src/shared/schema/bi/types/oauth.ts new file mode 100644 index 0000000..b7f5877 --- /dev/null +++ b/src/shared/schema/bi/types/oauth.ts @@ -0,0 +1,21 @@ +type OAuthScope = 'yandex' | 'google'; + +export type GetOAuthUriArgs = { + conn_type: string; + scope: OAuthScope; +}; + +export type GetOAuthUriResponse = { + uri: string; +}; + +export type GetOAuthTokenArgs = { + code: string; + conn_type: string; + scope: OAuthScope; +}; + +export type GetOAuthTokenResponse = { + access_token: string; + refresh_token: string; +}; diff --git a/src/shared/schema/extensions/actions/iam-access-dialog.ts b/src/shared/schema/extensions/actions/iam-access-dialog.ts new file mode 100644 index 0000000..6e69dab --- /dev/null +++ b/src/shared/schema/extensions/actions/iam-access-dialog.ts @@ -0,0 +1,56 @@ +import {createAction} from '../../gateway-utils'; +import type {GetDatalensOperationResponse} from '../../us/types/operations'; +import type { + BatchListMembersArgs, + BatchListMembersResponse, + GetClaimsArgs, + GetClaimsResponse, + ListCollectionAccessBindingsArgs, + ListCollectionAccessBindingsResponse, + ListSharedEntryAccessBindingsArgs, + ListSharedEntryAccessBindingsResponse, + ListWorkbookAccessBindingsArgs, + ListWorkbookAccessBindingsResponse, + UpdateCollectionAccessBindingsArgs, + UpdateSharedEntryAccessBindingsArgs, + UpdateWorkbookAccessBindingsArgs, +} from '../types'; + +export const iamAccessDialogActions = { + listCollectionAccessBindings: createAction< + ListCollectionAccessBindingsResponse, + ListCollectionAccessBindingsArgs + >(async () => []), + updateCollectionAccessBindings: createAction< + GetDatalensOperationResponse, + UpdateCollectionAccessBindingsArgs + >(async () => { + return {} as GetDatalensOperationResponse; + }), + listWorkbookAccessBindings: createAction< + ListWorkbookAccessBindingsResponse, + ListWorkbookAccessBindingsArgs + >(async () => []), + updateWorkbookAccessBindings: createAction< + GetDatalensOperationResponse, + UpdateWorkbookAccessBindingsArgs + >(async () => { + return {} as GetDatalensOperationResponse; + }), + listSharedEntryAccessBindings: createAction< + ListSharedEntryAccessBindingsResponse, + ListSharedEntryAccessBindingsArgs + >(async () => []), + updateSharedEntryAccessBindings: createAction< + GetDatalensOperationResponse, + UpdateSharedEntryAccessBindingsArgs + >(async () => { + return {} as GetDatalensOperationResponse; + }), + getClaims: createAction(async () => { + return {subjectDetails: []}; + }), + batchListMembers: createAction(async () => { + return {members: [], nextPageToken: ''}; + }), +}; diff --git a/src/shared/schema/extensions/actions/index.ts b/src/shared/schema/extensions/actions/index.ts new file mode 100644 index 0000000..01e3d36 --- /dev/null +++ b/src/shared/schema/extensions/actions/index.ts @@ -0,0 +1,5 @@ +import {iamAccessDialogActions} from './iam-access-dialog'; + +export const actions = { + ...iamAccessDialogActions, +}; diff --git a/src/shared/schema/extensions/index.ts b/src/shared/schema/extensions/index.ts new file mode 100644 index 0000000..b03d96b --- /dev/null +++ b/src/shared/schema/extensions/index.ts @@ -0,0 +1,6 @@ +import {actions} from './actions'; + +export default { + actions, + serviceName: 'extensions', +}; diff --git a/src/shared/schema/extensions/types/iam-access-dialog.ts b/src/shared/schema/extensions/types/iam-access-dialog.ts new file mode 100644 index 0000000..21a2a35 --- /dev/null +++ b/src/shared/schema/extensions/types/iam-access-dialog.ts @@ -0,0 +1,144 @@ +import type {Lang} from '../../..'; +import type {AccessServiceResourceType} from '../../../constants/iam'; + +type ResourcePathItem = { + type: `${AccessServiceResourceType}`; + id: string; +}; + +export enum SubjectType { + System = 'system', + UserAccount = 'userAccount', + FederatedUser = 'federatedUser', + ServiceAccount = 'serviceAccount', + Group = 'group', + Invitee = 'invitee', +} + +export enum ClaimsSubjectType { + UserAccount = 'USER_ACCOUNT', + Group = 'GROUP', + Invitee = 'INVITEE', + ServiceAccount = 'SERVICE_ACCOUNT', + SystemGroup = '_system', +} + +type Subject = { + id: string; + type: string; +}; + +type AccessBinding = { + roleId: string; + subject: Subject; +}; + +type ListAccessBindingsResponse = { + accessBindings: AccessBinding[]; + nextPageToken: string; +}; + +export type ListAccessBindingsResultItem = { + resource: ResourcePathItem; + response: ListAccessBindingsResponse; +}; + +export type ListCollectionAccessBindingsArgs = { + collectionId: string; + withInherits?: boolean; + pageTokenData?: PageTokenData; + pageSize?: number; +}; + +export type ListCollectionAccessBindingsResponse = ListAccessBindingsResultItem[]; + +export type UpdateCollectionAccessBindingsArgs = { + collectionId: string; + deltas: AccessBindingDelta[]; +}; + +export type ListWorkbookAccessBindingsArgs = { + workbookId: string; + withInherits?: boolean; + pageTokenData?: PageTokenData; + pageSize?: number; +}; + +export type ListWorkbookAccessBindingsResponse = ListAccessBindingsResultItem[]; + +export type UpdateWorkbookAccessBindingsArgs = { + workbookId: string; + deltas: AccessBindingDelta[]; +}; + +export type ListSharedEntryAccessBindingsArgs = { + entryId: string; + withInherits?: boolean; + pageTokenData?: PageTokenData; + pageSize?: number; +}; + +export type ListSharedEntryAccessBindingsResponse = ListAccessBindingsResultItem[]; + +export type UpdateSharedEntryAccessBindingsArgs = { + entryId: string; + deltas: AccessBindingDelta[]; +}; + +export type PageTokenData = { + type: `${AccessServiceResourceType}`; + id: string; + pageToken?: string; +}[]; + +export enum AccessBindingAction { + Add = 'ADD', + Remove = 'REMOVE', +} + +export type AccessBindingDelta = { + action: `${AccessBindingAction}`; + accessBinding: AccessBinding; +}; + +export type GetClaimsArgs = { + subjectIds: string[]; + language?: Lang; +}; + +export interface SubjectClaims { + sub: string; + subType: `${ClaimsSubjectType}`; + email: string; + name: string; + givenName: string; + familyName: string; + preferredUsername: string; + federation?: unknown; + pictureData?: string; + picture?: string; + idpType?: string | null; +} + +export type SubjectDetails = { + subjectClaims: SubjectClaims; +}; + +export type GetClaimsResponse = { + subjectDetails: SubjectDetails[]; +}; + +export type BatchListMembersArgs = { + id: string | undefined; + search: string; + tabId?: string; + pageToken?: string; + pageSize: number; + filter?: string; + language?: Lang; +}; + +export type BatchListMembersResponse = { + members: SubjectClaims[]; + nextPageToken: string; +}; diff --git a/src/shared/schema/extensions/types/index.ts b/src/shared/schema/extensions/types/index.ts new file mode 100644 index 0000000..38b9d8b --- /dev/null +++ b/src/shared/schema/extensions/types/index.ts @@ -0,0 +1 @@ +export * from './iam-access-dialog'; diff --git a/src/shared/schema/gateway-utils.ts b/src/shared/schema/gateway-utils.ts new file mode 100644 index 0000000..96fb227 --- /dev/null +++ b/src/shared/schema/gateway-utils.ts @@ -0,0 +1,122 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import type {ApiServiceActionConfig, GetAuthHeaders} from '@gravity-ui/gateway'; +import type {AppContext} from '@gravity-ui/nodekit'; +import type z from 'zod'; + +import {AuthHeader} from '../constants'; + +export const getAuthHeadersNone = () => undefined; + +export function createAction( + config: ApiServiceActionConfig, +) { + return config; +} + +type TypedActionSchema = { + paramsSchema?: z.ZodType; + resultSchema: z.ZodType; +}; + +const VALIDATION_SCHEMA_KEY = Symbol('$schema'); + +const registerValidationSchema = (value: T, schema: TypedActionSchema): T => { + Object.defineProperty(value, VALIDATION_SCHEMA_KEY, { + value: schema, + configurable: true, + }); + + return value; +}; + +export const hasValidationSchema = ( + value: object, +): value is {[VALIDATION_SCHEMA_KEY]: TypedActionSchema} => { + return Object.prototype.hasOwnProperty.call(value, VALIDATION_SCHEMA_KEY); +}; + +export const getValidationSchema = (value: object): TypedActionSchema | null => { + return hasValidationSchema(value) ? value[VALIDATION_SCHEMA_KEY] : null; +}; + +export const createTypedAction = ( + schema: { + resultSchema: z.ZodType; + paramsSchema?: z.ZodType; + transformedSchema?: z.ZodType; + }, + actionConfig: ApiServiceActionConfig< + AppContext, + Request, + Response, + NoInfer, + NoInfer, + NoInfer + >, +) => { + const schemaValidationObject = { + paramsSchema: schema.paramsSchema, + resultSchema: schema.transformedSchema ?? schema.resultSchema, + }; + + return registerValidationSchema(actionConfig, schemaValidationObject); +}; + +export const createExtendedTypedAction = + ( + actionConfig: ApiServiceActionConfig< + AppContext, + Request, + Response, + TConfigOutput, + TConfigParams, + TConfigTransformed + >, + ) => + (schema: { + resultSchema: z.ZodType; + paramsSchema: z.ZodType; + }) => { + const schemaValidationObject = { + paramsSchema: schema.paramsSchema, + resultSchema: schema.resultSchema, + }; + + return registerValidationSchema( + actionConfig as unknown as ApiServiceActionConfig< + AppContext, + Request, + Response, + TConfigOutput, + TParams, + TResult + >, + schemaValidationObject, + ); + }; + +type AuthArgsData = { + accessToken?: string; +}; + +export const getAuthArgs = (req: Request, _res: Response): AuthArgsData => { + return { + accessToken: req.ctx.get('user')?.accessToken, + }; +}; + +const createGetAuthHeaders: () => GetAuthHeaders = () => (params) => { + const {authArgs} = params; + + const resultHeaders = {}; + + if (authArgs?.accessToken) { + Object.assign(resultHeaders, { + [AuthHeader.Authorization]: `Bearer ${authArgs.accessToken}`, + }); + } + + return resultHeaders; +}; + +export const getAuthHeaders: GetAuthHeaders = createGetAuthHeaders(); diff --git a/src/shared/schema/googleapis/actions.ts b/src/shared/schema/googleapis/actions.ts new file mode 100644 index 0000000..f48ad51 --- /dev/null +++ b/src/shared/schema/googleapis/actions.ts @@ -0,0 +1,2 @@ +// TODO: will be removed in CHARTS-10240 +export const actions = {}; diff --git a/src/shared/schema/index.ts b/src/shared/schema/index.ts new file mode 100644 index 0000000..5fbb588 --- /dev/null +++ b/src/shared/schema/index.ts @@ -0,0 +1,31 @@ +import {getTypedApiFactory} from '@gravity-ui/gateway'; + +import auth from './auth'; +import type {authSchema} from './auth-schema'; +import bi from './bi'; +import extensions from './extensions'; +import metaManager from './meta-manager'; +import mix from './mix'; +import us from './us'; +import usPrivate from './us-private'; + +export {authSchema} from './auth-schema'; + +export const schema = { + us, + usPrivate, + bi, + extensions, + auth, + metaManager, + mix, +}; + +export * from './types'; + +export const getTypedApi = getTypedApiFactory<{ + root: typeof schema; + auth: typeof authSchema; +}>(); + +export type TypedApi = ReturnType; diff --git a/src/shared/schema/meta-manager/actions/export/cancel-workbook-export.ts b/src/shared/schema/meta-manager/actions/export/cancel-workbook-export.ts new file mode 100644 index 0000000..4e22cb4 --- /dev/null +++ b/src/shared/schema/meta-manager/actions/export/cancel-workbook-export.ts @@ -0,0 +1,19 @@ +import {createTypedAction} from '../../../gateway-utils'; +import { + cancelWorkbookExportArgsSchema, + cancelWorkbookExportResultSchema, +} from '../../schemas/export'; + +const PATH_PREFIX = '/workbooks/export'; + +export const cancelWorkbookExport = createTypedAction( + { + paramsSchema: cancelWorkbookExportArgsSchema, + resultSchema: cancelWorkbookExportResultSchema, + }, + { + method: 'POST', + path: ({exportId}) => `${PATH_PREFIX}/${exportId}/cancel`, + params: (_, headers) => ({headers}), + }, +); diff --git a/src/shared/schema/meta-manager/actions/export/get-workbook-export-result.ts b/src/shared/schema/meta-manager/actions/export/get-workbook-export-result.ts new file mode 100644 index 0000000..fa2bad0 --- /dev/null +++ b/src/shared/schema/meta-manager/actions/export/get-workbook-export-result.ts @@ -0,0 +1,19 @@ +import {createTypedAction} from '../../../gateway-utils'; +import { + getWorkbookExportResultArgsSchema, + getWorkbookExportResultResultSchema, +} from '../../schemas/export'; + +const PATH_PREFIX = '/workbooks/export'; + +export const getWorkbookExportResult = createTypedAction( + { + paramsSchema: getWorkbookExportResultArgsSchema, + resultSchema: getWorkbookExportResultResultSchema, + }, + { + method: 'GET', + path: ({exportId}) => `${PATH_PREFIX}/${exportId}/result`, + params: (_, headers) => ({headers}), + }, +); diff --git a/src/shared/schema/meta-manager/actions/export/get-workbook-export-status.ts b/src/shared/schema/meta-manager/actions/export/get-workbook-export-status.ts new file mode 100644 index 0000000..0eaf13a --- /dev/null +++ b/src/shared/schema/meta-manager/actions/export/get-workbook-export-status.ts @@ -0,0 +1,19 @@ +import {createTypedAction} from '../../../gateway-utils'; +import { + getWorkbookExportStatusArgsSchema, + getWorkbookExportStatusResultSchema, +} from '../../schemas/export'; + +const PATH_PREFIX = '/workbooks/export'; + +export const getWorkbookExportStatus = createTypedAction( + { + paramsSchema: getWorkbookExportStatusArgsSchema, + resultSchema: getWorkbookExportStatusResultSchema, + }, + { + method: 'GET', + path: ({exportId}) => `${PATH_PREFIX}/${exportId}`, + params: (_, headers) => ({headers}), + }, +); diff --git a/src/shared/schema/meta-manager/actions/export/index.ts b/src/shared/schema/meta-manager/actions/export/index.ts new file mode 100644 index 0000000..fc126c1 --- /dev/null +++ b/src/shared/schema/meta-manager/actions/export/index.ts @@ -0,0 +1,11 @@ +import {cancelWorkbookExport} from './cancel-workbook-export'; +import {getWorkbookExportResult} from './get-workbook-export-result'; +import {getWorkbookExportStatus} from './get-workbook-export-status'; +import {startWorkbookExport} from './start-workbook-export'; + +export const exportActions = { + startWorkbookExport, + getWorkbookExportStatus, + getWorkbookExportResult, + cancelWorkbookExport, +}; diff --git a/src/shared/schema/meta-manager/actions/export/start-workbook-export.ts b/src/shared/schema/meta-manager/actions/export/start-workbook-export.ts new file mode 100644 index 0000000..e7b605f --- /dev/null +++ b/src/shared/schema/meta-manager/actions/export/start-workbook-export.ts @@ -0,0 +1,19 @@ +import {createTypedAction} from '../../../gateway-utils'; +import {startWorkbookExportArgsSchema, startWorkbookExportResultSchema} from '../../schemas/export'; + +const PATH_PREFIX = '/workbooks/export'; + +export const startWorkbookExport = createTypedAction( + { + paramsSchema: startWorkbookExportArgsSchema, + resultSchema: startWorkbookExportResultSchema, + }, + { + method: 'POST', + path: () => PATH_PREFIX, + params: (params, headers) => ({ + body: params, + headers, + }), + }, +); diff --git a/src/shared/schema/meta-manager/actions/import/get-workbook-import-status.ts b/src/shared/schema/meta-manager/actions/import/get-workbook-import-status.ts new file mode 100644 index 0000000..195b5eb --- /dev/null +++ b/src/shared/schema/meta-manager/actions/import/get-workbook-import-status.ts @@ -0,0 +1,19 @@ +import {createTypedAction} from '../../../gateway-utils'; +import { + getWorkbookImportStatusArgsSchema, + getWorkbookImportStatusResultSchema, +} from '../../schemas/import'; + +const PATH_PREFIX = '/workbooks/import'; + +export const getWorkbookImportStatus = createTypedAction( + { + paramsSchema: getWorkbookImportStatusArgsSchema, + resultSchema: getWorkbookImportStatusResultSchema, + }, + { + method: 'GET', + path: ({importId}) => `${PATH_PREFIX}/${importId}`, + params: (_, headers) => ({headers}), + }, +); diff --git a/src/shared/schema/meta-manager/actions/import/index.ts b/src/shared/schema/meta-manager/actions/import/index.ts new file mode 100644 index 0000000..f0c6527 --- /dev/null +++ b/src/shared/schema/meta-manager/actions/import/index.ts @@ -0,0 +1,7 @@ +import {getWorkbookImportStatus} from './get-workbook-import-status'; +import {startWorkbookImport} from './start-workbook-import'; + +export const importActions = { + startWorkbookImport, + getWorkbookImportStatus, +}; diff --git a/src/shared/schema/meta-manager/actions/import/start-workbook-import.ts b/src/shared/schema/meta-manager/actions/import/start-workbook-import.ts new file mode 100644 index 0000000..42fb14d --- /dev/null +++ b/src/shared/schema/meta-manager/actions/import/start-workbook-import.ts @@ -0,0 +1,19 @@ +import {createTypedAction} from '../../../gateway-utils'; +import {startWorkbookImportArgsSchema, startWorkbookImportResultSchema} from '../../schemas/import'; + +const PATH_PREFIX = '/workbooks/import'; + +export const startWorkbookImport = createTypedAction( + { + paramsSchema: startWorkbookImportArgsSchema, + resultSchema: startWorkbookImportResultSchema, + }, + { + method: 'POST', + path: () => PATH_PREFIX, + params: (params, headers) => ({ + body: params, + headers, + }), + }, +); diff --git a/src/shared/schema/meta-manager/actions/index.ts b/src/shared/schema/meta-manager/actions/index.ts new file mode 100644 index 0000000..6bc09d5 --- /dev/null +++ b/src/shared/schema/meta-manager/actions/index.ts @@ -0,0 +1,7 @@ +import {exportActions} from './export'; +import {importActions} from './import'; + +export const actions = { + ...importActions, + ...exportActions, +}; diff --git a/src/shared/schema/meta-manager/index.ts b/src/shared/schema/meta-manager/index.ts new file mode 100644 index 0000000..25eb12d --- /dev/null +++ b/src/shared/schema/meta-manager/index.ts @@ -0,0 +1,9 @@ +import {getServiceEndpoints} from '../../endpoints/schema'; + +import {actions} from './actions'; + +export default { + actions, + endpoints: getServiceEndpoints('metaManager'), + serviceName: 'metaManager', +}; diff --git a/src/shared/schema/meta-manager/schemas/common/index.ts b/src/shared/schema/meta-manager/schemas/common/index.ts new file mode 100644 index 0000000..034875e --- /dev/null +++ b/src/shared/schema/meta-manager/schemas/common/index.ts @@ -0,0 +1,17 @@ +import z from 'zod'; + +import {EntryScope} from '../../../..'; +import {NOTIFICATION_LEVEL, PROCESS_STATUS} from '../../../../constants/meta-manager'; + +export const notificationLevelSchema = z.enum(NOTIFICATION_LEVEL); + +export const processStatusSchema = z.enum(PROCESS_STATUS); + +export const entryNotificationSchema = z.object({ + entryId: z.string().optional(), + scope: z.enum(EntryScope).optional(), + code: z.string(), + message: z.string().optional(), + level: notificationLevelSchema, + details: z.unknown().optional(), +}); diff --git a/src/shared/schema/meta-manager/schemas/export/cancel-workbook-export.ts b/src/shared/schema/meta-manager/schemas/export/cancel-workbook-export.ts new file mode 100644 index 0000000..2b412bc --- /dev/null +++ b/src/shared/schema/meta-manager/schemas/export/cancel-workbook-export.ts @@ -0,0 +1,9 @@ +import z from 'zod'; + +export const cancelWorkbookExportArgsSchema = z.object({ + exportId: z.string(), +}); + +export const cancelWorkbookExportResultSchema = z.object({ + exportId: z.string(), +}); diff --git a/src/shared/schema/meta-manager/schemas/export/get-workbook-export-result.ts b/src/shared/schema/meta-manager/schemas/export/get-workbook-export-result.ts new file mode 100644 index 0000000..7a34f8e --- /dev/null +++ b/src/shared/schema/meta-manager/schemas/export/get-workbook-export-result.ts @@ -0,0 +1,16 @@ +import z from 'zod'; + +import {processStatusSchema} from '../common'; + +export const getWorkbookExportResultArgsSchema = z.object({ + exportId: z.string(), +}); + +export const getWorkbookExportResultResultSchema = z.object({ + exportId: z.string(), + data: z.object({ + export: z.record(z.string(), z.unknown()), + hash: z.string(), + }), + status: processStatusSchema, +}); diff --git a/src/shared/schema/meta-manager/schemas/export/get-workbook-export-status.ts b/src/shared/schema/meta-manager/schemas/export/get-workbook-export-status.ts new file mode 100644 index 0000000..ee696f6 --- /dev/null +++ b/src/shared/schema/meta-manager/schemas/export/get-workbook-export-status.ts @@ -0,0 +1,14 @@ +import z from 'zod'; + +import {entryNotificationSchema, processStatusSchema} from '../common'; + +export const getWorkbookExportStatusArgsSchema = z.object({ + exportId: z.string(), +}); + +export const getWorkbookExportStatusResultSchema = z.object({ + exportId: z.string(), + status: processStatusSchema, + progress: z.number(), + notifications: z.array(entryNotificationSchema).nullable().optional(), +}); diff --git a/src/shared/schema/meta-manager/schemas/export/index.ts b/src/shared/schema/meta-manager/schemas/export/index.ts new file mode 100644 index 0000000..51894c4 --- /dev/null +++ b/src/shared/schema/meta-manager/schemas/export/index.ts @@ -0,0 +1,4 @@ +export * from './start-workbook-export'; +export * from './get-workbook-export-status'; +export * from './get-workbook-export-result'; +export * from './cancel-workbook-export'; diff --git a/src/shared/schema/meta-manager/schemas/export/start-workbook-export.ts b/src/shared/schema/meta-manager/schemas/export/start-workbook-export.ts new file mode 100644 index 0000000..2b5abda --- /dev/null +++ b/src/shared/schema/meta-manager/schemas/export/start-workbook-export.ts @@ -0,0 +1,9 @@ +import z from 'zod'; + +export const startWorkbookExportArgsSchema = z.object({ + workbookId: z.string(), +}); + +export const startWorkbookExportResultSchema = z.object({ + exportId: z.string(), +}); diff --git a/src/shared/schema/meta-manager/schemas/import/get-workbook-import-status.ts b/src/shared/schema/meta-manager/schemas/import/get-workbook-import-status.ts new file mode 100644 index 0000000..559dd88 --- /dev/null +++ b/src/shared/schema/meta-manager/schemas/import/get-workbook-import-status.ts @@ -0,0 +1,15 @@ +import z from 'zod'; + +import {entryNotificationSchema, processStatusSchema} from '../common'; + +export const getWorkbookImportStatusArgsSchema = z.object({ + importId: z.string(), +}); + +export const getWorkbookImportStatusResultSchema = z.object({ + importId: z.string(), + workbookId: z.string(), + status: processStatusSchema, + progress: z.number(), + notifications: z.array(entryNotificationSchema).nullable(), +}); diff --git a/src/shared/schema/meta-manager/schemas/import/index.ts b/src/shared/schema/meta-manager/schemas/import/index.ts new file mode 100644 index 0000000..9215236 --- /dev/null +++ b/src/shared/schema/meta-manager/schemas/import/index.ts @@ -0,0 +1,2 @@ +export * from './start-workbook-import'; +export * from './get-workbook-import-status'; diff --git a/src/shared/schema/meta-manager/schemas/import/start-workbook-import.ts b/src/shared/schema/meta-manager/schemas/import/start-workbook-import.ts new file mode 100644 index 0000000..b1ff5a9 --- /dev/null +++ b/src/shared/schema/meta-manager/schemas/import/start-workbook-import.ts @@ -0,0 +1,13 @@ +import z from 'zod'; + +export const startWorkbookImportArgsSchema = z.object({ + data: z.record(z.string(), z.unknown()), + title: z.string(), + description: z.string().optional(), + collectionId: z.string().nullable(), +}); + +export const startWorkbookImportResultSchema = z.object({ + importId: z.string(), + workbookId: z.string(), +}); diff --git a/src/shared/schema/meta-manager/schemas/index.ts b/src/shared/schema/meta-manager/schemas/index.ts new file mode 100644 index 0000000..8b3b672 --- /dev/null +++ b/src/shared/schema/meta-manager/schemas/index.ts @@ -0,0 +1,2 @@ +export * from './export'; +export * from './import'; diff --git a/src/shared/schema/meta-manager/types/common.ts b/src/shared/schema/meta-manager/types/common.ts new file mode 100644 index 0000000..591ec1e --- /dev/null +++ b/src/shared/schema/meta-manager/types/common.ts @@ -0,0 +1,5 @@ +import type z from 'zod'; + +import type {entryNotificationSchema} from '../schemas/common'; + +export type EntryNotification = z.infer; diff --git a/src/shared/schema/meta-manager/types/export.ts b/src/shared/schema/meta-manager/types/export.ts new file mode 100644 index 0000000..ada86b4 --- /dev/null +++ b/src/shared/schema/meta-manager/types/export.ts @@ -0,0 +1,13 @@ +import type z from 'zod'; + +import type { + getWorkbookExportResultResultSchema, + getWorkbookExportStatusResultSchema, + startWorkbookExportResultSchema, +} from '../schemas/export'; + +export type StartWorkbookExportResponse = z.infer; + +export type GetWorkbookExportStatusResponse = z.infer; + +export type GetWorkbookExportResultResponse = z.infer; diff --git a/src/shared/schema/meta-manager/types/import.ts b/src/shared/schema/meta-manager/types/import.ts new file mode 100644 index 0000000..0902bc2 --- /dev/null +++ b/src/shared/schema/meta-manager/types/import.ts @@ -0,0 +1,10 @@ +import type z from 'zod'; + +import type { + getWorkbookImportStatusResultSchema, + startWorkbookImportResultSchema, +} from '../schemas/import'; + +export type StartWorkbookImportResponse = z.infer; + +export type GetWorkbookImportStatusResponse = z.infer; diff --git a/src/shared/schema/meta-manager/types/index.ts b/src/shared/schema/meta-manager/types/index.ts new file mode 100644 index 0000000..0fccc49 --- /dev/null +++ b/src/shared/schema/meta-manager/types/index.ts @@ -0,0 +1,3 @@ +export * from './export'; +export * from './import'; +export * from './common'; diff --git a/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts new file mode 100644 index 0000000..c9cac60 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/create-dashboard-v1.ts @@ -0,0 +1,69 @@ +import {getTypedApi} from '../../..'; +import {DASH_CURRENT_SCHEME_VERSION, EntryScope} from '../../../..'; +import {Dash} from '../../../../../server/components/sdk'; +import {createTypedAction} from '../../../gateway-utils'; +import { + createDashV1ArgsSchema, + createDashV1ResultSchema, +} from '../../schemas/dash/create-dashboard-v1'; +import {DASH_VERSION_1} from '../../schemas/dash/dash-v1'; +import type {CreateDashV1Result, DashV1} from '../../types'; + +export const createDashboardV1 = createTypedAction( + { + paramsSchema: createDashV1ArgsSchema, + resultSchema: createDashV1ResultSchema, + }, + async (api, args): Promise => { + const typedApi = getTypedApi(api); + + const argsEntry = args.entry; + const argsData = argsEntry.data; + + const data = { + ...argsData, + schemeVersion: DASH_CURRENT_SCHEME_VERSION, + }; + + const links = Dash.gatherLinks(data); + + Dash.validateData(data); + + const createEntryResult = await typedApi.us._createEntry({ + key: argsEntry.key, + workbookId: argsEntry.workbookId, + name: argsEntry.name, + meta: argsEntry.meta, + annotation: argsEntry.annotation, + data, + type: '', + scope: EntryScope.Dash, + links, + }); + + return { + entry: { + version: DASH_VERSION_1, + data: createEntryResult.data as DashV1['data'], + meta: createEntryResult.meta as DashV1['meta'], + scope: createEntryResult.scope as EntryScope.Dash, + type: createEntryResult.type as DashV1['type'], + entryId: createEntryResult.entryId, + key: createEntryResult.key, + createdAt: createEntryResult.createdAt, + createdBy: createEntryResult.createdBy, + updatedAt: createEntryResult.updatedAt, + updatedBy: createEntryResult.updatedBy, + revId: createEntryResult.revId, + savedId: createEntryResult.savedId, + publishedId: createEntryResult.publishedId, + tenantId: createEntryResult.tenantId, + hidden: createEntryResult.hidden, + public: createEntryResult.public, + workbookId: createEntryResult.workbookId, + annotation: createEntryResult.annotation, + links: createEntryResult.links, + }, + }; + }, +); diff --git a/src/shared/schema/mix/actions/dash/delete-dashboard.ts b/src/shared/schema/mix/actions/dash/delete-dashboard.ts new file mode 100644 index 0000000..c845ac9 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/delete-dashboard.ts @@ -0,0 +1,23 @@ +import type {DeleteDashResult} from '../../..'; +import {getTypedApi} from '../../..'; +import {EntryScope} from '../../../..'; +import {createTypedAction} from '../../../gateway-utils'; +import {deleteDashArgsSchema, deleteDashResultSchema} from '../../schemas/dash/delete-dashboard'; + +export const deleteDashboard = createTypedAction( + { + paramsSchema: deleteDashArgsSchema, + resultSchema: deleteDashResultSchema, + }, + async (api, {lockToken, dashboardId}): Promise => { + const typedApi = getTypedApi(api); + + await typedApi.us._deleteUSEntry({ + entryId: dashboardId, + lockToken, + scope: EntryScope.Dash, + }); + + return {}; + }, +); diff --git a/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts new file mode 100644 index 0000000..bab0086 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/get-dashboard-v1.ts @@ -0,0 +1,51 @@ +import type {GetDashV1Result} from '../../..'; +import {getTypedApi} from '../../..'; +import {EntryScope} from '../../../..'; +import {ServerError} from '../../../../constants/error'; +import {createTypedAction} from '../../../gateway-utils'; +import {migrateDashToV1} from '../../helpers/dash/migrate-dash-to-v1'; +import {getDashV1ArgsSchema, getDashV1ResultSchema} from '../../schemas/dash/get-dashboard-v1'; + +export const getDashboardV1 = createTypedAction( + { + paramsSchema: getDashV1ArgsSchema, + resultSchema: getDashV1ResultSchema, + }, + async (api, args): Promise => { + const { + dashboardId, + includePermissions, + includeLinks, + includeFavorite, + branch, + revId, + workbookId, + } = args; + + const typedApi = getTypedApi(api); + + const getEntryResponse = await typedApi.us._getEntryWithAudit({ + entryId: dashboardId, + includePermissionsInfo: includePermissions, + includeLinks, + includeFavorite, + revId, + workbookId, + branch: branch ?? 'published', + }); + + if (getEntryResponse.scope !== EntryScope.Dash) { + throw new ServerError('No entry found', { + status: 404, + }); + } + + const entry = migrateDashToV1(getEntryResponse); + + return { + entry, + isFavorite: getEntryResponse.isFavorite, + permissions: getEntryResponse.permissions, + }; + }, +); diff --git a/src/shared/schema/mix/actions/dash/index.ts b/src/shared/schema/mix/actions/dash/index.ts new file mode 100644 index 0000000..81488d8 --- /dev/null +++ b/src/shared/schema/mix/actions/dash/index.ts @@ -0,0 +1,208 @@ +import type {DeepNonNullable} from 'utility-types'; + +import {getTypedApi} from '../../..'; +import type {ChartsStats} from '../../../../types/charts'; +import {createAction} from '../../../gateway-utils'; +import {getEntryVisualizationType} from '../../helpers'; +import type {DatasetDictResponse, DatasetFieldsDictResponse} from '../../helpers/dash'; +import { + fetchDataset, + fetchDatasetFieldsById, + prepareDatasetData, + prepareWidgetDatasetData, +} from '../../helpers/dash'; +import type { + CollectChartkitStatsArgs, + CollectChartkitStatsResponse, + CollectDashStatsArgs, + CollectDashStatsResponse, + GetEntriesDatasetsFieldsArgs, + GetEntriesDatasetsFieldsResponse, + GetWidgetsDatasetsFieldsArgs, + GetWidgetsDatasetsFieldsResponse, +} from '../../types'; + +import {createDashboardV1} from './create-dashboard-v1'; +import {deleteDashboard} from './delete-dashboard'; +import {getDashboardV1} from './get-dashboard-v1'; +import {updateDashboardV1} from './update-dashboard-v1'; + +export const dashActions = { + getDashboardV1, + createDashboardV1, + updateDashboardV1, + deleteDashboard, + + collectDashStats: createAction( + async (_, args, {ctx}): Promise => { + ctx.stats('dashStats', { + datetime: Date.now(), + ...(args as DeepNonNullable), + }); + return {status: 'success'}; + }, + ), + collectChartkitStats: createAction( + async (_, args, {ctx}): Promise => { + ctx.log('ChartKit stats collect', {rowsCount: args.length}); + + args.forEach((data) => { + ctx.stats('chartKitStats', { + datetime: new Date().toISOString().replace('T', ' ').split('.')[0], + ...(data as DeepNonNullable), + }); + }); + return {status: 'success', rowsCount: args.length}; + }, + ), + // in the entriesIds, the id of the entities for which you need to find out the dataset, + // and if the dataset id is not in datasetsIds, then you need to get a list of dataset fields + getEntriesDatasetsFields: createAction< + GetEntriesDatasetsFieldsResponse, + GetEntriesDatasetsFieldsArgs + >( + async ( + api, + {entriesIds, datasetsIds, workbookId}, + {ctx}, + ): Promise => { + const typedApi = getTypedApi(api); + const {entries} = await typedApi.us.getEntries({ + scope: 'widget', + ids: entriesIds, + includeLinks: true, + includeData: true, + }); + + const allDatasetsIdsSet = new Set([...datasetsIds]); + entries.forEach((entry) => { + if (!entry.isLocked) { + const {links, meta} = entry; + const {dataset} = links || {}; + // deprecated + const {datasetId: metaDatasetId} = meta || {}; + const datasetId = (dataset || metaDatasetId) as string | undefined; + if (datasetId) { + allDatasetsIdsSet.add(datasetId); + } + } + }); + + const allDatasetsIds = [...allDatasetsIdsSet]; + const allDatasetsPromises = allDatasetsIds.map((datasetId) => + fetchDataset({datasetId, workbookId, typedApi, ctx}), + ); + + const allDatasetsFetchedData = await Promise.all([...allDatasetsPromises]); + + const allDatasetsFetchedDataDict = allDatasetsFetchedData + .filter((item) => Boolean(item.data && item.datasetId)) + .reduce((res: Record, item: DatasetDictResponse) => { + res[item.datasetId] = {...item}; + return res; + }, {}); + + const res: GetEntriesDatasetsFieldsResponse = []; + entries.forEach((entry) => { + if (!entry.isLocked) { + const {links, meta, type, entryId} = entry; + const {dataset} = links || {}; + // deprecated + const {datasetId: metaDatasetId} = meta || {}; + const datasetId = (dataset || metaDatasetId) as string | undefined; + if (datasetId) { + const widgetType = type.match(/^[^_]*/)?.[0] || null; + res.push( + prepareDatasetData({ + items: allDatasetsFetchedDataDict[datasetId], + type: widgetType, + datasetId, + entryId, + visualizationType: getEntryVisualizationType(entry), + }), + ); + } + } + }); + datasetsIds.forEach((datasetId) => { + res.push( + prepareDatasetData({ + items: allDatasetsFetchedDataDict[datasetId], + type: 'dataset', + entryId: datasetId, + datasetId, + }), + ); + }); + return res; + }, + ), + getWidgetsDatasetsFields: createAction< + GetWidgetsDatasetsFieldsResponse, + GetWidgetsDatasetsFieldsArgs + >(async (api, {entriesIds, workbookId}, opt): Promise => { + const {ctx, headers} = opt; + const typedApi = getTypedApi(api); + + const {entries} = await typedApi.us.getEntries({ + scope: 'widget', + ids: entriesIds, + includeLinks: true, + }); + + const allDatasetsIdsSet = new Set(); + entries.forEach((entry) => { + if (!entry.isLocked) { + const {links, meta} = entry; + const {dataset} = links || {}; + // deprecated + const {datasetId: metaDatasetId} = meta || {}; + const datasetId = (dataset || metaDatasetId) as string | undefined; + if (datasetId) { + allDatasetsIdsSet.add(datasetId); + } + } + }); + + const allDatasetsIds = [...allDatasetsIdsSet] as string[]; + const allDatasetsPromises = allDatasetsIds.map((datasetId) => + fetchDatasetFieldsById({datasetId, workbookId, ctx, headers}), + ); + + const allDatasetsFetchedData = await Promise.all([...allDatasetsPromises]); + + const allDatasetsFetchedDataDict = allDatasetsFetchedData + .filter((item) => Boolean(item.data && item.datasetId)) + .reduce( + ( + res: Record, + item: DatasetFieldsDictResponse, + ) => { + res[item.datasetId] = {...item}; + return res; + }, + {}, + ); + + const res: GetWidgetsDatasetsFieldsResponse = []; + entries.forEach((entry) => { + if (!entry.isLocked) { + const {links, meta, entryId} = entry; + const {dataset} = links || {}; + // deprecated + const {datasetId: metaDatasetId} = meta || {}; + const datasetId = (dataset || metaDatasetId) as string | undefined; + if (datasetId) { + res.push( + prepareWidgetDatasetData({ + items: allDatasetsFetchedDataDict[datasetId], + datasetId, + entryId, + }), + ); + } + } + }); + return res; + }), +}; diff --git a/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts new file mode 100644 index 0000000..44e96bf --- /dev/null +++ b/src/shared/schema/mix/actions/dash/update-dashboard-v1.ts @@ -0,0 +1,74 @@ +import {getTypedApi} from '../../..'; +import {EntryScope, EntryUpdateMode} from '../../../..'; +import {Dash} from '../../../../../server/components/sdk'; +import {ServerError} from '../../../../constants/error'; +import {createTypedAction} from '../../../gateway-utils'; +import {DASH_VERSION_1} from '../../schemas/dash/dash-v1'; +import { + updateDashV1ArgsSchema, + updateDashV1ResultSchema, +} from '../../schemas/dash/update-dashboard-v1'; +import type {DashV1, UpdateDashV1Result} from '../../types'; + +export const updateDashboardV1 = createTypedAction( + { + paramsSchema: updateDashV1ArgsSchema, + resultSchema: updateDashV1ResultSchema, + }, + async ( + api, + {mode = EntryUpdateMode.Publish, entry: {entryId, meta, data, revId, annotation}}, + ): Promise => { + const typedApi = getTypedApi(api); + + const {entry: savedDash} = await typedApi.mix.getDashboardV1({dashboardId: entryId}); + + if (savedDash.scope !== EntryScope.Dash) { + throw new ServerError('Entry not found', { + status: 404, + }); + } + + const links = Dash.gatherLinks(data); + + Dash.validateData(data); + + const skipSyncLinks = mode !== EntryUpdateMode.Publish; + + const updateEntryResult = await typedApi.us._updateEntry({ + entryId, + mode, + meta, + data, + revId, + links, + annotation, + skipSyncLinks, + }); + + return { + entry: { + version: DASH_VERSION_1, + data: updateEntryResult.data as DashV1['data'], + meta: updateEntryResult.meta as DashV1['meta'], + scope: updateEntryResult.scope as EntryScope.Dash, + type: updateEntryResult.type as DashV1['type'], + entryId: updateEntryResult.entryId, + key: updateEntryResult.key, + createdAt: updateEntryResult.createdAt, + createdBy: updateEntryResult.createdBy, + updatedAt: updateEntryResult.updatedAt, + updatedBy: updateEntryResult.updatedBy, + revId: updateEntryResult.revId, + savedId: updateEntryResult.savedId, + publishedId: updateEntryResult.publishedId, + tenantId: updateEntryResult.tenantId, + hidden: updateEntryResult.hidden, + public: updateEntryResult.public, + workbookId: updateEntryResult.workbookId, + annotation: updateEntryResult.annotation, + links: updateEntryResult.links, + }, + }; + }, +); diff --git a/src/shared/schema/mix/actions/editor.ts b/src/shared/schema/mix/actions/editor.ts new file mode 100644 index 0000000..be9bd50 --- /dev/null +++ b/src/shared/schema/mix/actions/editor.ts @@ -0,0 +1,48 @@ +import {getTypedApi} from '../..'; +import {DeveloperModeCheckStatus} from '../../../types'; +import {createAction} from '../../gateway-utils'; +import type { + CreateEditorChartArgs, + CreateEditorChartResponse, + UpdateEditorChartArgs, + UpdateEditorChartResponse, +} from '../../us/types'; +import {getEntryLinks} from '../helpers'; +import {validateData} from '../helpers/editor/validation'; + +export const editorActions = { + createEditorChart: createAction( + async (api, args, {ctx}): Promise => { + const {checkRequestForDeveloperModeAccess} = ctx.get('gateway'); + + const checkResult = await checkRequestForDeveloperModeAccess({ctx}); + + if (checkResult === DeveloperModeCheckStatus.Allowed) { + validateData(args.data); + + const typedApi = getTypedApi(api); + + return await typedApi.us._createEditorChart({...args, links: getEntryLinks(args)}); + } else { + throw new Error('Access to Editor developer mode was denied'); + } + }, + ), + updateEditorChart: createAction( + async (api, args, {ctx}): Promise => { + const {checkRequestForDeveloperModeAccess} = ctx.get('gateway'); + + const checkResult = await checkRequestForDeveloperModeAccess({ctx}); + + if (checkResult === DeveloperModeCheckStatus.Allowed) { + validateData(args.data); + + const typedApi = getTypedApi(api); + + return await typedApi.us._updateEditorChart({...args, links: getEntryLinks(args)}); + } else { + throw new Error('Access to Editor developer mode was denied'); + } + }, + ), +}; diff --git a/src/shared/schema/mix/actions/entries.ts b/src/shared/schema/mix/actions/entries.ts new file mode 100644 index 0000000..1bbbf9c --- /dev/null +++ b/src/shared/schema/mix/actions/entries.ts @@ -0,0 +1,280 @@ +import keyBy from 'lodash/keyBy'; +import type {Required} from 'utility-types'; + +import {getTypedApi} from '../..'; +import {EntryScope} from '../../../types'; +import {createAction} from '../../gateway-utils'; +import type { + GetEntriesEntryResponse, + GetRelationsEntry, + SwitchPublicationStatusResponse, +} from '../../us/types'; +import { + buildEnrichedLinksTree, + checkEntriesForPublication, + collectAllRelatedEntryIds, + escapeStringForLike, + fetchEntriesWithLinks, + getEntryMetaStatusByError, +} from '../helpers'; +import {isValidPublishLink} from '../helpers/validation'; +import type { + DeleteEntryArgs, + DeleteEntryResponse, + GetBatchEntriesByIdsArgs, + GetBatchEntriesByIdsResponse, + GetEnrichedLinksTreeArgs, + GetEnrichedLinksTreeResponse, + GetEntriesInFolderArgs, + GetEntriesInFolderResponse, + GetEntryMetaStatusArgs, + GetEntryMetaStatusResponse, + GetEntryRelationsArgs, + GetEntryRelationsResponse, + GetPublicationPreviewArgs, + GetPublicationPreviewResponse, + MixedSwitchPublicationStatusArgs, + ResolveEntryByLinkArgs, + ResolveEntryByLinkResponse, +} from '../types'; + +export const entriesActions = { + deleteEntry: createAction( + async (api, args): Promise => { + const typedApi = getTypedApi(api); + const {entryId, lockToken, scope} = args; + switch (scope) { + case EntryScope.Dataset: { + const data = await typedApi.bi.deleteDataset({datasetId: entryId}); + return data; + } + case EntryScope.Connection: { + const data = await typedApi.bi.deleteConnection({connectionId: entryId}); + return data; + } + default: { + const data = await typedApi.us._deleteUSEntry({entryId, lockToken}); + return data; + } + } + }, + ), + getPublicationPreview: createAction( + async (api, {entryId, workbookId}): Promise => { + const typedApi = getTypedApi(api); + const relations = (await typedApi.us.getRelations({ + entryId, + includePermissionsInfo: true, + })) as Required[]; + + const [datasets] = await checkEntriesForPublication({ + entries: relations, + typedApi, + workbookId, + }); + + const normalizedDatasets = datasets + ? keyBy(datasets.result, (dataset) => dataset.dataset_id) + : {}; + // TODO: wait for back fix + // const normalizedConnections = connections + // ? keyBy(connections.result, (connection) => connection.connection_id) + // : {}; + + return relations.map((entry) => { + let lockPublication = false; + let lockPublicationReason = null; + + if (entry.scope === EntryScope.Dataset) { + const datasetEntry = normalizedDatasets[entry.entryId]; + lockPublication = datasetEntry && !datasetEntry.allowed; + lockPublicationReason = datasetEntry.reason; + } + + // if (entry.scope === EntryScope.Connection) { + // const connectionEntry = normalizedConnections[entry.entryId]; + // lockPublication = connectionEntry && !connectionEntry.allowed; + // lockPublicationReason = connectionEntry.reason; + // } + + return { + ...entry, + lockPublication, + lockPublicationReason, + }; + }); + }, + ), + switchPublicationStatus: createAction< + SwitchPublicationStatusResponse, + MixedSwitchPublicationStatusArgs + >(async (api, {entries, mainEntry, workbookId}): Promise => { + if (!isValidPublishLink(mainEntry?.unversionedData?.publicAuthor?.link)) { + throw new Error('Failed to publish dashboard - invalid publish link.'); + } + + const typedApi = getTypedApi(api); + + const [datasets] = await checkEntriesForPublication({ + entries, + typedApi, + workbookId, + }); + + let errorMessage = ''; + + if (datasets && datasets.result.some((datasetEntry) => !datasetEntry.allowed)) { + errorMessage += JSON.stringify( + datasets.result + .filter(({allowed}) => !allowed) + .map(({dataset_id: entryId, reason}) => ({entryId, reason})), + null, + 4, + ); + } + + // if (connections && connections.result.some((connectionEntry) => !connectionEntry.allowed)) { + // errorMessage += JSON.stringify( + // connections.result + // .filter(({allowed}) => !allowed) + // .map(({connection_id: entryId, reason}) => ({entryId, reason})), + // null, + // 4, + // ); + // } + + if (errorMessage) { + throw new Error(`Failed to publish entries:\n ${errorMessage}`); + } + + const result = await typedApi.us._switchPublicationStatus({entries, mainEntry}); + return result; + }), + resolveEntryByLink: createAction( + async (api, {url}, {ctx}): Promise => { + const typedApi = getTypedApi(api); + const {resolveEntryByLink} = ctx.get('gateway'); + const result = await resolveEntryByLink({ + originalUrl: url, + ctx, + getEntryMeta: typedApi.us.getEntryMeta, + getEntryByKey: typedApi.us.getEntryByKey, + }); + return result; + }, + ), + getEntryMetaStatus: createAction( + async (api, args): Promise => { + const typedApi = getTypedApi(api); + const {entryId, bindedWorkbookId, bindedDatasetId} = args; + try { + await typedApi.us.getEntryMeta({entryId, bindedWorkbookId, bindedDatasetId}); + return {code: 'OK'}; + } catch (errorWrapper) { + return getEntryMetaStatusByError(errorWrapper); + } + }, + ), + getEntriesInFolder: createAction( + async (api, {folderId}): Promise => { + const typedApi = getTypedApi(api); + const folderEntry = await typedApi.us.getEntry({ + entryId: folderId, + includePermissionsInfo: true, + }); + if (folderEntry.permissions?.admin !== true) { + throw new Error('Forbidden'); + } + const entries = await typedApi.us._getEntriesByKeyPattern({ + keyPattern: `${escapeStringForLike(folderEntry.key)}%`, + }); + const yqlFolderKey = 'yql/charts/'; + return entries.filter(({key}) => !key.toLowerCase().startsWith(yqlFolderKey)); + }, + ), + getEntryRelations: createAction( + async (api, {entryId, direction = 'parent'}): Promise => { + return await getTypedApi(api).us.getRelations({ + entryId, + direction, + }); + }, + ), + getBatchEntriesByIds: createAction( + async (api, args): Promise => { + const typedApi = getTypedApi(api); + const {ids, ...restArgs} = args; + + if (ids.length === 0) { + return { + entries: [], + }; + } + + // If we have more than 50 IDs, split them into batches to avoid URL length limitations + if (ids.length > 50) { + const batches = []; + for (let i = 0; i < ids.length; i += 50) { + batches.push(ids.slice(i, i + 50)); + } + + const batchResults = await Promise.all( + batches.map((batchIds) => + typedApi.us.getEntries({ + ...restArgs, + ids: batchIds, + }), + ), + ); + + const combinedEntries = batchResults.reduce( + (acc, result) => [...acc, ...result.entries], + [] as GetEntriesEntryResponse[], + ); + + return { + entries: combinedEntries, + }; + } + + const entriesResponse = await typedApi.us.getEntries(args); + + return {entries: entriesResponse.entries}; + }, + ), + getEnrichedLinksTree: createAction( + async (api, {entryId}, {ctx}) => { + const typedApi = getTypedApi(api); + + const allRelatedEntryIds = await collectAllRelatedEntryIds({ + entryId, + typedApi, + ctx, + }); + + const entriesData = await fetchEntriesWithLinks({ + entryIds: Array.from(allRelatedEntryIds), + typedApi, + ctx, + }); + + let annotations; + if (allRelatedEntryIds.size > 0) { + try { + annotations = await typedApi.us.getEntriesAnnotation({ + entryIds: Array.from(allRelatedEntryIds), + }); + } catch (error) { + ctx.logError('Error getting entries annotation', error as Error); + } + } + + const linksTree = buildEnrichedLinksTree({ + entriesData, + annotations, + }); + + return {linksTree}; + }, + ), +}; diff --git a/src/shared/schema/mix/actions/index.ts b/src/shared/schema/mix/actions/index.ts new file mode 100644 index 0000000..d8b068a --- /dev/null +++ b/src/shared/schema/mix/actions/index.ts @@ -0,0 +1,17 @@ +import {dashActions} from './dash'; +import {editorActions} from './editor'; +import {entriesActions} from './entries'; +import {markdownActions} from './markdown'; +import {navigationActions} from './navigation'; +import {qlActions} from './ql'; +import {wizardActions} from './wizard'; + +export const actions = { + ...navigationActions, + ...entriesActions, + ...markdownActions, + ...dashActions, + ...editorActions, + ...wizardActions, + ...qlActions, +}; diff --git a/src/shared/schema/mix/actions/markdown.ts b/src/shared/schema/mix/actions/markdown.ts new file mode 100644 index 0000000..a857ffc --- /dev/null +++ b/src/shared/schema/mix/actions/markdown.ts @@ -0,0 +1,29 @@ +import {createAction} from '../../gateway-utils'; +import type { + BatchRenderMarkdownArgs, + BatchRenderMarkdownResponse, + RenderMarkdownArgs, + RenderMarkdownResponse, +} from '../types'; + +export const markdownActions = { + renderMarkdown: createAction( + async (_, {text, lang}, {ctx}): Promise => { + return ctx.get('gateway').markdown({text, lang}); + }, + ), + batchRenderMarkdown: createAction( + async (_, {texts, lang}, {ctx}): Promise => { + const {markdown} = ctx.get('gateway'); + const results = {} as BatchRenderMarkdownResponse; + for (const key of Object.keys(texts)) { + const text = texts[key]; + results[key] = markdown({ + text, + lang, + }); + } + return results; + }, + ), +}; diff --git a/src/shared/schema/mix/actions/navigation.ts b/src/shared/schema/mix/actions/navigation.ts new file mode 100644 index 0000000..1fea043 --- /dev/null +++ b/src/shared/schema/mix/actions/navigation.ts @@ -0,0 +1,45 @@ +import {getTypedApi} from '../..'; +import {MAP_PLACE_TO_SCOPE, PLACE} from '../../../constants'; +import {createAction} from '../../gateway-utils'; +import type {GetEntriesResponse, GetFavoritesResponse, ListDirectoryResponse} from '../../us/types'; +import type {GetNavigationListArgs, GetNavigationListResponse, NavigationEntry} from '../types'; + +export const navigationActions = { + getNavigationList: createAction( + async (api, args): Promise => { + const {place, path, ...restArgs} = args; + const typedApi = getTypedApi(api); + let data: ListDirectoryResponse | GetFavoritesResponse | GetEntriesResponse; + switch (place) { + case PLACE.ROOT: { + data = await typedApi.us.listDirectory({ + ...restArgs, + path, + includePermissionsInfo: true, + }); + break; + } + case PLACE.FAVORITES: { + data = await typedApi.us.getFavorites({ + ...restArgs, + includePermissionsInfo: true, + }); + break; + } + default: + data = await typedApi.us.getEntries({ + ...restArgs, + scope: MAP_PLACE_TO_SCOPE[place], + includePermissionsInfo: true, + excludeLocked: true, + }); + } + + return { + breadCrumbs: 'breadCrumbs' in data ? data.breadCrumbs : [], + hasNextPage: data.hasNextPage, + entries: data.entries as NavigationEntry[], + }; + }, + ), +}; diff --git a/src/shared/schema/mix/actions/ql/create-ql-chart.ts b/src/shared/schema/mix/actions/ql/create-ql-chart.ts new file mode 100644 index 0000000..fe0d982 --- /dev/null +++ b/src/shared/schema/mix/actions/ql/create-ql-chart.ts @@ -0,0 +1,39 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import z from 'zod'; + +import type {ChartsEngine} from '../../../../../server/components/charts-engine'; +import {keyOrWorkbookIdNameSchema} from '../../../../zod-schemas/entry'; +import {createTypedAction} from '../../../gateway-utils'; + +const paramsSchema = z + .looseObject({ + template: z.literal('ql'), + annotation: z + .object({ + description: z.string(), + }) + .optional(), + data: z.looseObject({}), + }) + .and(keyOrWorkbookIdNameSchema); + +export const __createQLChart__ = createTypedAction( + { + paramsSchema, + resultSchema: z.looseObject({}), + }, + async (_, args, {headers}): Promise<{}> => { + const req = args.req as Request; + const res = args.res as Response; + const chartsEngine = args.chartsEngine as ChartsEngine; + + req.headers = { + ...req.headers, + ...headers, + }; + + await chartsEngine.controllers.charts.create(req, res); + + return {}; + }, +); diff --git a/src/shared/schema/mix/actions/ql/delete-ql-chart.ts b/src/shared/schema/mix/actions/ql/delete-ql-chart.ts new file mode 100644 index 0000000..5b76c4e --- /dev/null +++ b/src/shared/schema/mix/actions/ql/delete-ql-chart.ts @@ -0,0 +1,23 @@ +import {getTypedApi} from '../../..'; +import {ENTRY_TYPES, EntryScope} from '../../../..'; +import {createTypedAction} from '../../../gateway-utils'; +import {deleteQLChartArgsSchema, deleteQLChartResultSchema} from '../../schemas/ql'; +import type {DeleteQLChartResult} from '../../types'; + +export const deleteQLChart = createTypedAction( + { + paramsSchema: deleteQLChartArgsSchema, + resultSchema: deleteQLChartResultSchema, + }, + async (api, {chartId}): Promise => { + const typedApi = getTypedApi(api); + + await typedApi.us._deleteUSEntry({ + entryId: chartId, + scope: EntryScope.Widget, + types: ENTRY_TYPES.ql, + }); + + return {}; + }, +); diff --git a/src/shared/schema/mix/actions/ql/get-ql-chart.ts b/src/shared/schema/mix/actions/ql/get-ql-chart.ts new file mode 100644 index 0000000..f1b5353 --- /dev/null +++ b/src/shared/schema/mix/actions/ql/get-ql-chart.ts @@ -0,0 +1,64 @@ +import {getTypedApi} from '../../..'; +import {ENTRY_TYPES, EntryScope} from '../../../..'; +import {ServerError} from '../../../../constants/error'; +import {mapQlConfigToLatestVersion} from '../../../../modules/config/ql'; +import {getTranslationFn} from '../../../../modules/language'; +import {createTypedAction} from '../../../gateway-utils'; +import {getQLChartArgsSchema, getQLChartResultSchema} from '../../schemas/ql'; +import type {GetQLChartResult} from '../../types'; + +export const __getQLChart__ = createTypedAction( + { + paramsSchema: getQLChartArgsSchema, + resultSchema: getQLChartResultSchema, + }, + async (api, args, {ctx}): Promise => { + const { + includePermissions, + includeLinks, + includeFavorite = false, + revId, + chartId, + branch, + workbookId, + } = args; + + const typedApi = getTypedApi(api); + + const getEntryResponse = await typedApi.us._getEntryWithAudit({ + entryId: chartId, + includePermissionsInfo: includePermissions, + includeLinks, + includeFavorite, + revId, + workbookId, + branch: branch ?? 'published', + }); + + if ( + getEntryResponse.scope !== EntryScope.Widget || + !ENTRY_TYPES.ql.includes(getEntryResponse.type) + ) { + throw new ServerError('Entry not found', { + status: 404, + }); + } + + if (getEntryResponse.data) { + const shared = getEntryResponse.data.shared; + + const i18nServer = ctx.get('i18n'); + + const mappedData = + shared && typeof shared === 'string' + ? (mapQlConfigToLatestVersion(JSON.parse(shared), { + i18n: getTranslationFn(i18nServer.getI18nServer()), + }) as unknown as Record) + : null; + + getEntryResponse.data = mappedData; + } + + return getEntryResponse; + }, +); diff --git a/src/shared/schema/mix/actions/ql/index.ts b/src/shared/schema/mix/actions/ql/index.ts new file mode 100644 index 0000000..eea7f34 --- /dev/null +++ b/src/shared/schema/mix/actions/ql/index.ts @@ -0,0 +1,17 @@ +import {__createQLChart__} from './create-ql-chart'; +import {deleteQLChart} from './delete-ql-chart'; +import {__getQLChart__} from './get-ql-chart'; +import {__updateQLChart__} from './update-ql-chart'; + +export const qlActions = { + // WIP + __getQLChart__, + + deleteQLChart, + + // WIP + __createQLChart__, + + // WIP + __updateQLChart__, +}; diff --git a/src/shared/schema/mix/actions/ql/update-ql-chart.ts b/src/shared/schema/mix/actions/ql/update-ql-chart.ts new file mode 100644 index 0000000..8fca2de --- /dev/null +++ b/src/shared/schema/mix/actions/ql/update-ql-chart.ts @@ -0,0 +1,54 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import z from 'zod'; + +import {getTypedApi} from '../../..'; +import {ENTRY_TYPES, EntryScope, EntryUpdateMode} from '../../../..'; +import type {ChartsEngine} from '../../../../../server/components/charts-engine'; +import {ServerError} from '../../../../constants/error'; +import {createTypedAction} from '../../../gateway-utils'; + +const paramsSchema = z.looseObject({ + entryId: z.string(), + template: z.literal('ql'), + annotation: z + .object({ + description: z.string(), + }) + .optional(), + mode: z.enum(EntryUpdateMode), + data: z.looseObject({}), +}); + +export const __updateQLChart__ = createTypedAction( + { + paramsSchema, + resultSchema: z.looseObject({}), + }, + async (api, args, {headers}): Promise<{}> => { + const req = args.req as Request; + const res = args.res as Response; + const chartsEngine = args.chartsEngine as ChartsEngine; + + const typedApi = getTypedApi(api); + + const savedEntry = await typedApi.us.getEntry({ + entryId: req.body.entryId, + }); + + if (savedEntry.scope !== EntryScope.Widget || !ENTRY_TYPES.ql.includes(savedEntry.type)) { + throw new ServerError('No entry found', { + status: 404, + }); + } + + req.params.entryId = req.body.entryId; + req.headers = { + ...req.headers, + ...headers, + }; + + await chartsEngine.controllers.charts.update(req, res); + + return {}; + }, +); diff --git a/src/shared/schema/mix/actions/wizard/create-wizard-chart.ts b/src/shared/schema/mix/actions/wizard/create-wizard-chart.ts new file mode 100644 index 0000000..5f6490d --- /dev/null +++ b/src/shared/schema/mix/actions/wizard/create-wizard-chart.ts @@ -0,0 +1,39 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import z from 'zod'; + +import type {ChartsEngine} from '../../../../../server/components/charts-engine'; +import {keyOrWorkbookIdNameSchema} from '../../../../zod-schemas/entry'; +import {createTypedAction} from '../../../gateway-utils'; + +const paramsSchema = z + .looseObject({ + template: z.literal('datalens'), + annotation: z + .object({ + description: z.string(), + }) + .optional(), + data: z.looseObject({}), + }) + .and(keyOrWorkbookIdNameSchema); + +export const __createWizardChart__ = createTypedAction( + { + paramsSchema, + resultSchema: z.looseObject({}), + }, + async (_, args, {headers}): Promise<{}> => { + const req = args.req as Request; + const res = args.res as Response; + const chartsEngine = args.chartsEngine as ChartsEngine; + + req.headers = { + ...req.headers, + ...headers, + }; + + await chartsEngine.controllers.charts.create(req, res); + + return {}; + }, +); diff --git a/src/shared/schema/mix/actions/wizard/delete-wizard-chart.ts b/src/shared/schema/mix/actions/wizard/delete-wizard-chart.ts new file mode 100644 index 0000000..14dee9b --- /dev/null +++ b/src/shared/schema/mix/actions/wizard/delete-wizard-chart.ts @@ -0,0 +1,23 @@ +import {getTypedApi} from '../../..'; +import {ENTRY_TYPES, EntryScope} from '../../../..'; +import {createTypedAction} from '../../../gateway-utils'; +import {deleteWizardChartArgsSchema, deleteWizardChartResultSchema} from '../../schemas/wizard'; +import type {DeleteWizardResult} from '../../types'; + +export const deleteWizardChart = createTypedAction( + { + paramsSchema: deleteWizardChartArgsSchema, + resultSchema: deleteWizardChartResultSchema, + }, + async (api, {chartId}): Promise => { + const typedApi = getTypedApi(api); + + await typedApi.us._deleteUSEntry({ + entryId: chartId, + scope: EntryScope.Widget, + types: ENTRY_TYPES.wizard, + }); + + return {}; + }, +); diff --git a/src/shared/schema/mix/actions/wizard/get-wizard-chart.ts b/src/shared/schema/mix/actions/wizard/get-wizard-chart.ts new file mode 100644 index 0000000..ab215da --- /dev/null +++ b/src/shared/schema/mix/actions/wizard/get-wizard-chart.ts @@ -0,0 +1,59 @@ +import {getTypedApi} from '../../..'; +import {ENTRY_TYPES, EntryScope, mapChartsConfigToLatestVersion} from '../../../..'; +import {ServerError} from '../../../../constants/error'; +import {createTypedAction} from '../../../gateway-utils'; +import {getWizardChartArgsSchema, getWizardChartResultSchema} from '../../schemas/wizard'; +import type {GetWizardResult} from '../../types'; + +// WIP +export const __getWizardChart__ = createTypedAction( + { + paramsSchema: getWizardChartArgsSchema, + resultSchema: getWizardChartResultSchema, + }, + async (api, args): Promise => { + const { + includePermissions, + includeLinks, + includeFavorite = false, + revId, + chartId, + branch, + workbookId, + } = args; + + const typedApi = getTypedApi(api); + + const getEntryResponse = await typedApi.us._getEntryWithAudit({ + entryId: chartId, + includePermissionsInfo: includePermissions, + includeLinks, + includeFavorite, + revId, + workbookId, + branch: branch ?? 'published', + }); + + if ( + getEntryResponse.scope !== EntryScope.Widget || + !ENTRY_TYPES.wizard.includes(getEntryResponse.type) + ) { + throw new ServerError('Entry not found', { + status: 404, + }); + } + + if (getEntryResponse.data) { + const shared = getEntryResponse.data.shared; + + const mappedData = + shared && typeof shared === 'string' + ? mapChartsConfigToLatestVersion(JSON.parse(shared)) + : null; + + getEntryResponse.data = mappedData; + } + + return getEntryResponse; + }, +); diff --git a/src/shared/schema/mix/actions/wizard/index.ts b/src/shared/schema/mix/actions/wizard/index.ts new file mode 100644 index 0000000..df7dfbc --- /dev/null +++ b/src/shared/schema/mix/actions/wizard/index.ts @@ -0,0 +1,17 @@ +import {__createWizardChart__} from './create-wizard-chart'; +import {deleteWizardChart} from './delete-wizard-chart'; +import {__getWizardChart__} from './get-wizard-chart'; +import {__updateWizardChart__} from './update-wizard-chart'; + +export const wizardActions = { + // WIP + __getWizardChart__, + + deleteWizardChart, + + // WIP + __updateWizardChart__, + + // WIP + __createWizardChart__, +}; diff --git a/src/shared/schema/mix/actions/wizard/update-wizard-chart.ts b/src/shared/schema/mix/actions/wizard/update-wizard-chart.ts new file mode 100644 index 0000000..0a66d85 --- /dev/null +++ b/src/shared/schema/mix/actions/wizard/update-wizard-chart.ts @@ -0,0 +1,57 @@ +import type {Request, Response} from '@gravity-ui/expresskit'; +import z from 'zod'; + +import {getTypedApi} from '../../..'; +import {ENTRY_TYPES, EntryScope, EntryUpdateMode} from '../../../..'; +import type {ChartsEngine} from '../../../../../server/components/charts-engine'; +import {ServerError} from '../../../../constants/error'; +import {createTypedAction} from '../../../gateway-utils'; + +const paramsSchema = z.looseObject({ + entryId: z.string(), + template: z.literal('datalens'), + annotation: z + .object({ + description: z.string(), + }) + .optional(), + mode: z.enum(EntryUpdateMode), + data: z.looseObject({}), +}); + +export const __updateWizardChart__ = createTypedAction( + { + paramsSchema, + resultSchema: z.looseObject({}), + }, + async (api, args, {headers}): Promise<{}> => { + const req = args.req as Request; + const res = args.res as Response; + const chartsEngine = args.chartsEngine as ChartsEngine; + + const typedApi = getTypedApi(api); + + const savedEntry = await typedApi.us.getEntry({ + entryId: req.body.entryId, + }); + + if ( + savedEntry.scope !== EntryScope.Widget || + !ENTRY_TYPES.wizard.includes(savedEntry.type) + ) { + throw new ServerError('No entry found', { + status: 404, + }); + } + + req.params.entryId = req.body.entryId; + req.headers = { + ...req.headers, + ...headers, + }; + + await chartsEngine.controllers.charts.update(req, res); + + return {}; + }, +); diff --git a/src/shared/schema/mix/helpers/__tests__/editor.test.ts b/src/shared/schema/mix/helpers/__tests__/editor.test.ts new file mode 100644 index 0000000..3cc2a88 --- /dev/null +++ b/src/shared/schema/mix/helpers/__tests__/editor.test.ts @@ -0,0 +1,103 @@ +import {getEntryLinks, validateData} from '../editor'; + +describe('shared/schema/mix/helpers/validateData', () => { + test.each([ + { + name: 'valid meta with links', + data: { + meta: JSON.stringify({ + links: {a: 'id1', b: 'id2'}, + }), + }, + }, + { + name: 'empty data', + data: null, + }, + { + name: 'empty meta object', + data: { + meta: JSON.stringify({}), + }, + }, + ])('should validate successfully: $name', ({data}) => { + expect(() => validateData(data)).not.toThrow(); + }); + + test.each([ + { + name: 'invalid JSON in meta', + data: {meta: '{invalid json'}, + error: 'Meta must be a valid JSON', + }, + { + name: 'meta is not an object', + data: {meta: JSON.stringify(['array'])}, + error: 'Meta must be an object', + }, + { + name: 'links is not an object', + data: {meta: JSON.stringify({links: 'string'})}, + error: '"links" property must be an object', + }, + { + name: 'unknown meta tab keys', + data: {meta: JSON.stringify({unknownKey: 'value'})}, + error: 'Unknown keys in tab "meta": unknownKey', + }, + { + name: 'links contain non-string values', + data: { + meta: JSON.stringify({ + links: { + validLink: 'id1', + number: 42, + boolean: true, + object: {id: 'test'}, + }, + }), + }, + error: 'Next keys in "links" property has unsupported types: number, boolean, object. They must have a "string" type', + }, + ])('should throw error: $name', ({data, error}) => { + expect(() => validateData(data)).toThrow(error); + }); +}); + +describe('shared/schema/mix/helpers/getEntryLinks', () => { + test.each([ + { + name: 'returns empty object when no meta', + args: { + data: {}, + }, + expected: {}, + }, + { + name: 'returns empty object when meta is empty', + args: { + data: {meta: '{}'}, + }, + expected: {}, + }, + { + name: 'returns flattened links from meta', + args: { + data: { + meta: JSON.stringify({ + links: { + a: 'id1', + b: 'id2', + }, + }), + }, + }, + expected: { + id1: 'id1', + id2: 'id2', + }, + }, + ])('$name', ({args, expected}) => { + expect(getEntryLinks(args)).toEqual(expected); + }); +}); diff --git a/src/shared/schema/mix/helpers/dash/index.ts b/src/shared/schema/mix/helpers/dash/index.ts new file mode 100644 index 0000000..145972a --- /dev/null +++ b/src/shared/schema/mix/helpers/dash/index.ts @@ -0,0 +1,154 @@ +import type {IncomingHttpHeaders} from 'http'; + +import type {ContextApiWithRoot} from '@gravity-ui/gateway/build/models/common'; +import type {AppContext, AppContextParams} from '@gravity-ui/nodekit'; + +import type {schema} from '../../..'; +import {registry} from '../../../../../server/registry'; +import type {DatalensGatewaySchemas} from '../../../../../server/types/gateway'; +import type {WizardVisualizationId} from '../../../../constants'; +import type {DatasetField, WorkbookId} from '../../../../types'; +import type {GetDataSetFieldsByIdResponse} from '../../../bi/types'; +import type {GetEntryResponse} from '../../../us/types'; + +export type DatasetDictResponse = {datasetId: string; data: GetEntryResponse | null}; + +export const fetchDataset = async ({ + datasetId, + workbookId, + typedApi, + ctx, +}: { + datasetId: string; + workbookId: WorkbookId; + typedApi: ContextApiWithRoot<{root: typeof schema}>; + ctx: AppContext; +}): Promise => { + try { + const data: GetEntryResponse = await typedApi.us.getEntry({ + entryId: datasetId, + workbookId, + }); + + return { + datasetId, + data, + }; + } catch (error) { + ctx.logError('DASH_FETCH_DATASET_BY_GET_ENTRY_FAILED', error); + } + return {datasetId, data: null}; +}; + +export const prepareDatasetData = (args: { + items: DatasetDictResponse; + type: string | null; + entryId: string; + datasetId: string; + visualizationType?: WizardVisualizationId; +}) => { + const {entryId, datasetId, type, items, visualizationType} = args; + + const emptyValue = {entryId, type: null}; + + if (!items?.data) { + return emptyValue; + } + + const {data, key} = items.data; + + const result_schema = data?.result_schema as DatasetField[]; + + if (!result_schema) { + return emptyValue; + } + + // we form an array of elements of the following type: + // * wizard and dataset are not in datasetsIds + // {entryId: "0tk6pkyusg", type: "graph_wizard_node", datasetId: "3a3em9nwkk", datasetFields: Array(8)} + // * wizard and dataset are in datasetsIds + // {entryId: "0lwgk7z2kw", type: "graph_wizard_node", datasetId: "fbnaupoasc"} + // * wizard that doesn't have a datasetId in meta + // {entryId: "0epkfeanqv", type: "graph_wizard_node"} + // * node script + // {entryId: "ilslg0le88", type: "graph_node"} + return { + entryId, + type, + visualizationType, + datasetId, + datasetName: key.match(/[^/]*$/)?.[0] || '', + datasetFields: result_schema.map(({title, guid, type: fieldType}) => { + return { + title, + guid, + type: fieldType, + }; + }), + }; +}; + +type DatasetFieldsData = { + responseData: GetDataSetFieldsByIdResponse; +}; + +export type DatasetFieldsDictResponse = { + datasetId: string; + data: DatasetFieldsData | null; +}; + +export const prepareWidgetDatasetData = (args: { + items: DatasetFieldsDictResponse; + entryId: string; + datasetId: string; +}) => { + const {entryId, datasetId, items} = args; + + if (!items.data) { + return {entryId}; + } + + return { + entryId, + datasetId, + datasetFields: items.data.responseData.fields.map(({guid}) => guid), + }; +}; + +export const fetchDatasetFieldsById = async ({ + datasetId, + workbookId, + ctx, + headers, +}: { + datasetId: string; + workbookId: WorkbookId; + ctx: AppContext; + headers: IncomingHttpHeaders; +}): Promise => { + try { + const {gatewayApi} = registry.getGatewayApi(); + const requestDatasetFields = gatewayApi.bi.getDataSetFieldsById; + + const {iamToken} = ctx.get('gateway') as AppContextParams['gateway'] & {iamToken: string}; + + const data = await requestDatasetFields({ + ctx: ctx, + headers: headers, + requestId: ctx.getMetadata()?.[ctx.config.requestIdHeaderName], + authArgs: {iamToken}, + args: { + dataSetId: datasetId, + workbookId, + }, + }); + + return { + datasetId, + data, + }; + } catch (error) { + ctx.logError('DASH_GET_DATASET_FIELDS_BY_IDS_FIELDS_GET_DATASET_BY_VERSION_FAILED', error); + } + return {datasetId, data: null}; +}; diff --git a/src/shared/schema/mix/helpers/dash/migrate-dash-to-v1.ts b/src/shared/schema/mix/helpers/dash/migrate-dash-to-v1.ts new file mode 100644 index 0000000..a92cedb --- /dev/null +++ b/src/shared/schema/mix/helpers/dash/migrate-dash-to-v1.ts @@ -0,0 +1,85 @@ +import type {DashData} from '../../../..'; +import {DashSchemeConverter} from '../../../..'; +import {ServerError} from '../../../../constants/error'; +import type {DashV1, GetEntryResponse} from '../../../types'; +import {DASH_VERSION_1} from '../../schemas/dash/dash-v1'; + +import {normalizeDashData} from './normalize-dash-data'; + +export const migrateDashToV1 = ({ + version, + scope, + entryId, + key, + createdAt, + createdBy, + updatedAt, + updatedBy, + revId, + savedId, + publishedId, + tenantId, + hidden, + public: isPublic, + workbookId, + meta, + type, + data, + annotation, + links, +}: GetEntryResponse): DashV1 => { + if (version && version > DASH_VERSION_1) { + throw new ServerError(`Unsupported dashboard version: ${version}`, { + status: 500, + }); + } + + let migratedAnnotation = annotation; + + if ( + !migratedAnnotation && + data && + 'description' in data && + typeof data.description === 'string' + ) { + migratedAnnotation = { + description: data.description, + }; + } + + let migratedData: DashData; + + if ( + version === DASH_VERSION_1 || + !DashSchemeConverter.isUpdateNeeded(data as unknown as DashData) + ) { + migratedData = data as unknown as DashData; + } else { + migratedData = DashSchemeConverter.update(data as unknown as DashData); + } + + const normalizedData = normalizeDashData(migratedData); + + return { + version: DASH_VERSION_1, + scope, + entryId, + key, + createdAt, + createdBy, + updatedAt, + updatedBy, + revId, + savedId, + publishedId, + tenantId, + hidden, + public: isPublic, + workbookId, + meta, + annotation: migratedAnnotation, + links, + type, + data: normalizedData, + } as DashV1; +}; diff --git a/src/shared/schema/mix/helpers/dash/normalize-dash-data.ts b/src/shared/schema/mix/helpers/dash/normalize-dash-data.ts new file mode 100644 index 0000000..8733001 --- /dev/null +++ b/src/shared/schema/mix/helpers/dash/normalize-dash-data.ts @@ -0,0 +1,85 @@ +import type { + DashData, + DashTabItem, + DashTabItemControl, + DashTabItemControlData, + DashTabItemControlDataset, + DashTabItemControlManual, + DashTabItemGroupControl, +} from '../../../..'; +import { + DashTabItemControlElementType, + DashTabItemControlSourceType, + DashTabItemType, +} from '../../../..'; + +function normalizeControlSource(controlData: DashTabItemControlData): DashTabItemControlData { + if (controlData.sourceType === DashTabItemControlSourceType.External) { + return controlData; + } + + const normalizedSource = { + ...controlData.source, + } as DashTabItemControlDataset['source'] | DashTabItemControlManual['source']; + + if (normalizedSource.elementType === DashTabItemControlElementType.Select) { + normalizedSource.multiselectable = normalizedSource.multiselectable ?? false; + } else if (normalizedSource.elementType === DashTabItemControlElementType.Date) { + normalizedSource.isRange = normalizedSource.isRange ?? false; + } + + return {...controlData, source: normalizedSource} as + | DashTabItemControlDataset + | DashTabItemControlManual; +} + +function normalizeControlItem( + item: DashTabItemControl | DashTabItemGroupControl, +): DashTabItemControl | DashTabItemGroupControl { + if (item.type === DashTabItemType.Control) { + const normalizedData = normalizeControlSource(item.data); + + return { + ...item, + defaults: item.defaults ?? {}, + data: normalizedData, + }; + } + + const normalizedGroup = item.data.group.map((groupItem) => { + const normalized = normalizeControlSource(groupItem); + + return { + ...normalized, + defaults: groupItem.defaults ?? {}, + }; + }); + + return { + ...item, + data: { + ...item.data, + showGroupName: item.data.showGroupName ?? false, + group: normalizedGroup, + }, + } as DashTabItemGroupControl; +} + +function normalizeItem(item: DashTabItem): DashTabItem { + if (item.type === DashTabItemType.Control || item.type === DashTabItemType.GroupControl) { + return normalizeControlItem(item); + } + + return item; +} + +export function normalizeDashData(data: DashData): DashData { + return { + ...data, + tabs: data.tabs.map((tab) => ({ + ...tab, + items: tab.items.map(normalizeItem), + ...(tab.globalItems ? {globalItems: tab.globalItems.map(normalizeControlItem)} : {}), + })), + }; +} diff --git a/src/shared/schema/mix/helpers/editor/index.ts b/src/shared/schema/mix/helpers/editor/index.ts new file mode 100644 index 0000000..ce70d29 --- /dev/null +++ b/src/shared/schema/mix/helpers/editor/index.ts @@ -0,0 +1,50 @@ +import get from 'lodash/get'; +import isPlainObject from 'lodash/isPlainObject'; + +import {getDatasetLinks} from '../../../../modules'; +import type {ChartsConfig} from '../../../../types'; +import type {EntryFieldData, EntryFieldLinks} from '../../../us/types'; + +export * from './validation'; + +type GetEntryLinksArgs = { + data: EntryFieldData; +}; + +export function getEntryLinks(args: GetEntryLinksArgs) { + const {data} = args; + const links: EntryFieldLinks = {}; + + if (typeof data?.meta === 'string') { + try { + const meta = JSON.parse(data.meta); + const metaLinks = get(meta, 'links') as Record; + + if (isPlainObject(metaLinks)) { + Object.values(metaLinks).forEach((value) => { + links[value] = value; + }); + } + + return links; + } catch (e) {} + } + + if (typeof data?.shared === 'string') { + try { + const shared = JSON.parse(data.shared); + + const datasetIds = get(shared, 'datasetsIds'); + if (datasetIds) { + Object.assign(links, getDatasetLinks(shared as ChartsConfig)); + } + + const connectionEntryId = get(shared, 'connection.entryId'); + if (connectionEntryId) { + links.connection = connectionEntryId; + } + } catch (e) {} + } + + return links; +} diff --git a/src/shared/schema/mix/helpers/editor/validation.ts b/src/shared/schema/mix/helpers/editor/validation.ts new file mode 100644 index 0000000..6756da2 --- /dev/null +++ b/src/shared/schema/mix/helpers/editor/validation.ts @@ -0,0 +1,66 @@ +import isPlainObject from 'lodash/isPlainObject'; + +import type {EntryFieldData} from '../../../us/types'; + +type MetaTab = { + links?: Record; +}; + +const AVAILABLE_META_TAB_KEYS: Array = ['links']; + +function validateMetaTab(tabContent?: unknown) { + if (!tabContent || typeof tabContent !== 'string') { + return; + } + + let metaTab: MetaTab; + + try { + metaTab = JSON.parse(tabContent); + } catch { + throw new Error('Meta must be a valid JSON\n'); + } + + if (!isPlainObject(metaTab)) { + throw new Error('Meta must be an object\n'); + } + + if ('links' in metaTab && !isPlainObject(metaTab.links)) { + throw new Error('"links" property must be an object\n'); + } + + if (metaTab.links) { + const unsupportedTypeKeys: string[] = []; + Object.entries(metaTab.links).forEach(([key, value]) => { + if (typeof value !== 'string') { + unsupportedTypeKeys.push(key); + } + }); + + if (unsupportedTypeKeys.length) { + throw new Error( + `Next keys in "links" property has unsupported types: ${unsupportedTypeKeys.join(', ')}. They must have a "string" type\n`, + ); + } + } + + const unknownKeys: string[] = []; + + Object.keys(metaTab).forEach((key) => { + if (!AVAILABLE_META_TAB_KEYS.includes(key as keyof MetaTab)) { + unknownKeys.push(key); + } + }); + + if (unknownKeys.length) { + throw new Error(`Unknown keys in tab "meta": ${unknownKeys.join(', ')}\n`); + } +} + +export function validateData(data: EntryFieldData>) { + if (!data) { + return; + } + + validateMetaTab(data.meta); +} diff --git a/src/shared/schema/mix/helpers/entries.ts b/src/shared/schema/mix/helpers/entries.ts new file mode 100644 index 0000000..65f7692 --- /dev/null +++ b/src/shared/schema/mix/helpers/entries.ts @@ -0,0 +1,272 @@ +import get from 'lodash/get'; + +import type {TypedApi} from '../..'; +import type {WizardVisualizationId} from '../../../constants'; +import {EntryScope, type Shared, type WorkbookId} from '../../../types'; +import type { + CheckConnectionsForPublicationResponse, + CheckDatasetsForPublicationResponse, + GetEntryMetaStatusResponse, +} from '../../types'; +import type {EntryFields, GetEntriesEntryResponse} from '../../us/types'; + +export function filterEntirsForCheck(entries: Pick[]) { + const datasetIds: string[] = []; + const connectionsIds: string[] = []; + + entries.forEach((entry) => { + if (!('scope' in entry)) { + throw new Error("Entry should have required field 'scope'"); + } + + if (entry.scope === EntryScope.Dataset) { + datasetIds.push(entry.entryId); + } else if (entry.scope === EntryScope.Connection) { + connectionsIds.push(entry.entryId); + } + }); + return {datasetIds, connectionsIds}; +} + +export function escapeStringForLike(str: string) { + return str.replace(/[%_]/g, '\\$&'); +} + +export function getEntryVisualizationType(entry: Partial) { + try { + const sharedData = get(entry, 'data.shared'); + const shared: Shared | null = + typeof sharedData === 'string' ? JSON.parse(sharedData) : null; + return shared?.visualization?.id as WizardVisualizationId | undefined; + } catch (e) { + return undefined; + } +} + +export function getEntryHierarchy(entry: Partial) { + try { + const sharedData = get(entry, 'data.shared'); + const shared: Shared | null = + typeof sharedData === 'string' ? JSON.parse(sharedData) : null; + return shared?.hierarchies; + } catch (e) { + return undefined; + } +} + +export const checkEntriesForPublication = async ({ + entries, + typedApi, + workbookId, +}: { + entries: Pick[]; + typedApi: TypedApi; + workbookId: WorkbookId; +}) => { + const {datasetIds} = filterEntirsForCheck(entries); + const promises: [ + Promise | null, + Promise | null, + ] = [null, null]; + + if (datasetIds.length) { + promises[0] = typedApi.bi.checkDatasetsForPublication({ + datasetsIds: datasetIds, + workbookId, + }); + } + // if (connectionsIds.length) { + // promises[1] = typedApi.bi.checkConnectionsForPublication({ + // connectionsIds: connectionsIds, + // workbookId, + // }); + // } + + return Promise.all(promises); +}; + +export function getEntryMetaStatusByError(errorWrapper: unknown): GetEntryMetaStatusResponse { + let error; + if (errorWrapper instanceof Object && 'error' in errorWrapper) { + error = errorWrapper.error; + } + if (typeof error === 'object' && error !== null && 'status' in error) { + switch (error.status) { + case 400: + // us ajv validation + if ('code' in error && error.code === 'DECODE_ID_FAILED') { + return {code: 'NOT_FOUND'}; + } + // us zod validation + if ('code' in error && error.code === 'VALIDATION_ERROR') { + const path = get(error, ['details', 'details', 0, 'path', 0]); + if (path === 'entryId') { + return {code: 'NOT_FOUND'}; + } + } + return {code: 'UNHANDLED'}; + case 403: + return {code: 'FORBIDDEN'}; + case 404: + return {code: 'NOT_FOUND'}; + default: + return {code: 'UNHANDLED'}; + } + } else { + return {code: 'UNHANDLED'}; + } +} + +export async function collectAllRelatedEntryIds({ + entryId, + typedApi, + ctx, +}: { + entryId: string; + typedApi: TypedApi; + ctx?: {logError: (message: string, error: Error) => void}; +}): Promise> { + const allRelatedEntryIds = new Set(); + const visited = new Set(); + + async function collectRelations(currentEntryId: string): Promise { + if (visited.has(currentEntryId)) { + return; + } + visited.add(currentEntryId); + + try { + const relations = await typedApi.us.getRelations({ + entryId: currentEntryId, + direction: 'parent', + }); + + for (const relation of relations) { + allRelatedEntryIds.add(relation.entryId); + await collectRelations(relation.entryId); + } + } catch (error) { + ctx?.logError(`Error getting relations for entry ${currentEntryId}`, error as Error); + } + } + + await collectRelations(entryId); + + return allRelatedEntryIds; +} + +export async function fetchEntriesWithLinks({ + entryIds, + typedApi, + ctx, +}: { + entryIds: string[]; + typedApi: TypedApi; + ctx?: {logError: (message: string, error: Error) => void}; +}): Promise<(GetEntriesEntryResponse | null)[]> { + if (entryIds.length === 0) { + return []; + } + + const results: (GetEntriesEntryResponse | null)[] = []; + + // Process in batches of 50 to avoid URL length limitations + for (let i = 0; i < entryIds.length; i += 50) { + const batchIds = entryIds.slice(i, i + 50); + + try { + const entriesResult = await typedApi.us.getEntries({ + ids: batchIds, + includeLinks: true, + }); + + // Create map for quick lookup + const entriesMap = new Map( + entriesResult.entries.map((entry) => [entry.entryId, entry]), + ); + + // Preserve order from batchIds + batchIds.forEach((id) => { + const entry = entriesMap.get(id); + results.push(entry || null); + }); + } catch (error) { + ctx?.logError(`Error fetching entries batch (${batchIds.length} IDs)`, error as Error); + results.push(...new Array(batchIds.length).fill(null)); + } + } + + return results; +} + +export function buildEnrichedLinksTree({ + entriesData, + annotations, +}: { + entriesData: (GetEntriesEntryResponse | null)[]; + annotations?: Array<{ + entryId: string; + result?: { + scope?: EntryScope; + type?: string; + annotation?: { + description?: string; + }; + }; + error?: { + code?: string; + message?: string; + details?: unknown; + }; + }>; +}) { + const linksTree: Record}> = {}; + + // Build basic tree structure + for (const entry of entriesData) { + if (!entry) continue; + + linksTree[entry.entryId] = { + links: {}, + }; + + if ('links' in entry && entry.links) { + for (const [_linkId, linkEntryId] of Object.entries(entry.links)) { + if (typeof linkEntryId === 'string') { + linksTree[entry.entryId].links[linkEntryId] = { + entryId: linkEntryId, + links: {}, + }; + } + } + } + } + + // Enrich with annotations if provided + if (annotations) { + const annotationsMap = new Map( + annotations + .filter( + (ann): ann is typeof ann & {result: NonNullable} => + 'result' in ann && Boolean(ann.result), + ) + .map((ann) => [ann.entryId, ann.result.annotation?.description]), + ); + + for (const [currentEntryId, node] of Object.entries(linksTree)) { + const description = annotationsMap.get(currentEntryId); + if (description) { + node.description = description; + } + + for (const [linkId, linkNode] of Object.entries(node.links)) { + const linkDescription = annotationsMap.get(linkId); + if (linkDescription) { + linkNode.description = linkDescription; + } + } + } + } + + return linksTree; +} diff --git a/src/shared/schema/mix/helpers/index.ts b/src/shared/schema/mix/helpers/index.ts new file mode 100644 index 0000000..739e350 --- /dev/null +++ b/src/shared/schema/mix/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './entries'; +export * from './editor'; diff --git a/src/shared/schema/mix/helpers/validation.ts b/src/shared/schema/mix/helpers/validation.ts new file mode 100644 index 0000000..24d06f2 --- /dev/null +++ b/src/shared/schema/mix/helpers/validation.ts @@ -0,0 +1,9 @@ +export const isValidPublishLink = (link?: string) => { + const validLinkRe = /^(http(s)?:\/\/|tel:|mailto:)/g; + + if (link) { + return validLinkRe.test(link); + } + + return true; +}; diff --git a/src/shared/schema/mix/index.ts b/src/shared/schema/mix/index.ts new file mode 100644 index 0000000..1a2a174 --- /dev/null +++ b/src/shared/schema/mix/index.ts @@ -0,0 +1,6 @@ +import {actions} from './actions'; + +export default { + actions, + serviceName: 'mix', +}; diff --git a/src/shared/schema/mix/schemas/dash/create-dashboard-v1.ts b/src/shared/schema/mix/schemas/dash/create-dashboard-v1.ts new file mode 100644 index 0000000..709f905 --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/create-dashboard-v1.ts @@ -0,0 +1,26 @@ +import z from 'zod'; + +import {dataSchema} from '../../../../zod-schemas/dash'; +import {keyOrWorkbookIdNameSchema} from '../../../../zod-schemas/entry'; + +import {dashSchemaV1} from './dash-v1'; + +const createDashData = dataSchema.omit({schemeVersion: true}); + +export const createDashV1ArgsSchema = z.object({ + entry: z + .object({ + data: createDashData, + meta: z.record(z.string(), z.string()).nullable(), + annotation: z + .object({ + description: z.string(), + }) + .optional(), + }) + .and(keyOrWorkbookIdNameSchema), +}); + +export const createDashV1ResultSchema = z.object({ + entry: dashSchemaV1, +}); diff --git a/src/shared/schema/mix/schemas/dash/dash-v1.ts b/src/shared/schema/mix/schemas/dash/dash-v1.ts new file mode 100644 index 0000000..3805dd2 --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/dash-v1.ts @@ -0,0 +1,34 @@ +import z from 'zod'; + +import {EntryScope} from '../../../..'; +import {dashMetaSchema, dataSchema} from '../../../../zod-schemas/dash'; + +export const DASH_VERSION_1 = 1; + +export const dashSchemaV1 = z.object({ + annotation: z + .object({ + description: z.string().optional(), + }) + .nullable() + .optional(), + createdAt: z.string(), + createdBy: z.string(), + data: dataSchema, + entryId: z.string(), + hidden: z.boolean(), + key: z.union([z.null(), z.string()]), + links: z.record(z.string(), z.string()).nullable().optional(), + meta: dashMetaSchema, + public: z.boolean(), + publishedId: z.string().nullable(), + revId: z.string(), + savedId: z.string(), + scope: z.literal(EntryScope.Dash), + tenantId: z.string(), + type: z.literal(''), + updatedAt: z.string(), + updatedBy: z.string(), + version: z.literal(DASH_VERSION_1), + workbookId: z.union([z.null(), z.string()]), +}); diff --git a/src/shared/schema/mix/schemas/dash/delete-dashboard.ts b/src/shared/schema/mix/schemas/dash/delete-dashboard.ts new file mode 100644 index 0000000..d71c29f --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/delete-dashboard.ts @@ -0,0 +1,8 @@ +import z from 'zod'; + +export const deleteDashArgsSchema = z.strictObject({ + dashboardId: z.string(), + lockToken: z.string().optional(), +}); + +export const deleteDashResultSchema = z.object({}); diff --git a/src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts b/src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts new file mode 100644 index 0000000..7d0f7ed --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/get-dashboard-v1.ts @@ -0,0 +1,30 @@ +import z from 'zod'; + +import {registerComponentId} from '../../../../components/public-api/utils'; +import {permissionsSchema} from '../../../../zod-schemas/permissions'; + +import {dashSchemaV1} from './dash-v1'; + +export const getDashV1ArgsSchema = z + .object({ + dashboardId: z.string(), + revId: z.string().optional(), + includePermissions: z.boolean().optional(), + includeLinks: z.boolean().optional(), + includeFavorite: z.boolean().optional(), + branch: z.enum(['published', 'saved']).optional(), + workbookId: z.string().optional(), + }) + .openapi(registerComponentId('GetDashboardArgs'), { + title: 'GetDashboardArgs', + }); + +export const getDashV1ResultSchema = z + .object({ + entry: dashSchemaV1, + isFavorite: z.boolean().optional(), + permissions: permissionsSchema.optional(), + }) + .openapi(registerComponentId('GetDashboardResult'), { + title: 'GetDashboardResult', + }); diff --git a/src/shared/schema/mix/schemas/dash/update-dashboard-v1.ts b/src/shared/schema/mix/schemas/dash/update-dashboard-v1.ts new file mode 100644 index 0000000..e63030a --- /dev/null +++ b/src/shared/schema/mix/schemas/dash/update-dashboard-v1.ts @@ -0,0 +1,25 @@ +import z from 'zod'; + +import {EntryUpdateMode} from '../../../..'; +import {dashMetaSchema, dataSchema} from '../../../../zod-schemas/dash'; + +import {dashSchemaV1} from './dash-v1'; + +export const updateDashV1ArgsSchema = z.strictObject({ + entry: z.strictObject({ + entryId: z.string(), + data: dataSchema, + meta: dashMetaSchema, + revId: z.string().optional(), + annotation: z + .object({ + description: z.string(), + }) + .optional(), + }), + mode: z.enum(EntryUpdateMode), +}); + +export const updateDashV1ResultSchema = z.object({ + entry: dashSchemaV1, +}); diff --git a/src/shared/schema/mix/schemas/ql.ts b/src/shared/schema/mix/schemas/ql.ts new file mode 100644 index 0000000..3a05673 --- /dev/null +++ b/src/shared/schema/mix/schemas/ql.ts @@ -0,0 +1,19 @@ +import z from 'zod'; + +export const deleteQLChartArgsSchema = z.strictObject({ + chartId: z.string(), +}); + +export const deleteQLChartResultSchema = z.object({}); + +export const getQLChartArgsSchema = z.strictObject({ + chartId: z.string(), + workbookId: z.string().nullable().optional(), + revId: z.string().optional(), + includePermissions: z.boolean().optional(), + includeLinks: z.boolean().optional(), + includeFavorite: z.boolean().optional(), + branch: z.enum(['saved', 'published']).optional(), +}); + +export const getQLChartResultSchema = z.unknown(); diff --git a/src/shared/schema/mix/schemas/wizard.ts b/src/shared/schema/mix/schemas/wizard.ts new file mode 100644 index 0000000..96d45cb --- /dev/null +++ b/src/shared/schema/mix/schemas/wizard.ts @@ -0,0 +1,19 @@ +import z from 'zod'; + +export const deleteWizardChartArgsSchema = z.strictObject({ + chartId: z.string(), +}); + +export const deleteWizardChartResultSchema = z.object({}); + +export const getWizardChartArgsSchema = z.strictObject({ + chartId: z.string(), + workbookId: z.string().nullable().optional(), + revId: z.string().optional(), + includePermissions: z.boolean().optional(), + includeLinks: z.boolean().optional(), + includeFavorite: z.boolean().optional(), + branch: z.enum(['saved', 'published']).optional(), +}); + +export const getWizardChartResultSchema = z.unknown(); diff --git a/src/shared/schema/mix/types/dash/index.ts b/src/shared/schema/mix/types/dash/index.ts new file mode 100644 index 0000000..187ff98 --- /dev/null +++ b/src/shared/schema/mix/types/dash/index.ts @@ -0,0 +1,70 @@ +import type z from 'zod'; + +import type {WizardVisualizationId} from '../../../../constants'; +import type {ChartsStats, DashStats, WorkbookId} from '../../../../types'; +import type {GetEntriesEntryResponse} from '../../../us/types'; +import type {createDashV1ResultSchema} from '../../schemas/dash/create-dashboard-v1'; +import type {dashSchemaV1} from '../../schemas/dash/dash-v1'; +import type {deleteDashResultSchema} from '../../schemas/dash/delete-dashboard'; +import type {getDashV1ResultSchema} from '../../schemas/dash/get-dashboard-v1'; +import type {updateDashV1ResultSchema} from '../../schemas/dash/update-dashboard-v1'; + +export type CollectDashStatsResponse = { + status: string; +}; + +export type CollectDashStatsArgs = DashStats; + +export type CollectChartkitStatsResponse = { + status: string; + rowsCount?: number; +}; + +export type CollectChartkitStatsArgs = ChartsStats[]; + +export type GetEntriesDatasetsFieldsListItem = { + title: string; + guid: string; + dataType?: string; + type?: string; +}; + +export type GetEntriesDatasetsFieldsItem = { + entryId: string; + type: GetEntriesEntryResponse['type'] | null; + visualizationType?: WizardVisualizationId; + datasetId?: string; + datasetName?: string; + datasetFields?: GetEntriesDatasetsFieldsListItem[]; +}; + +export type GetEntriesDatasetsFieldsResponse = GetEntriesDatasetsFieldsItem[]; + +export type GetEntriesDatasetsFieldsArgs = { + entriesIds: string[]; + datasetsIds: string[]; + workbookId: WorkbookId; +}; + +export type GetWidgetsDatasetsFieldsItem = { + entryId: string; + datasetId?: string; + datasetFields?: string[]; +}; + +export type GetWidgetsDatasetsFieldsResponse = GetWidgetsDatasetsFieldsItem[]; + +export type GetWidgetsDatasetsFieldsArgs = { + entriesIds: string[]; + workbookId: WorkbookId; +}; + +export type UpdateDashV1Result = z.infer; + +export type CreateDashV1Result = z.infer; + +export type DeleteDashResult = z.infer; + +export type GetDashV1Result = z.infer; + +export type DashV1 = z.infer; diff --git a/src/shared/schema/mix/types/entries.ts b/src/shared/schema/mix/types/entries.ts new file mode 100644 index 0000000..83156ae --- /dev/null +++ b/src/shared/schema/mix/types/entries.ts @@ -0,0 +1,127 @@ +import type {AppContext} from '@gravity-ui/nodekit'; +import type {Required} from 'utility-types'; + +import type {StringParams, WorkbookId} from '../../../types'; +import type {DeleteConnectionResponse, DeleteDatasetResponse} from '../../bi/types'; +import type {CheckStatReportExistsArgs, CheckStatReportExistsResponse} from '../../stat-api/types'; +import type { + DeleteUSEntryResponse, + EntryByKeyPattern, + GetEntriesArgs, + GetEntriesResponse, + GetEntryByKeyArgs, + GetEntryByKeyResponse, + GetEntryMetaArgs, + GetEntryMetaResponse, + GetRelationsEntry, +} from '../../us/types'; + +export type DeleteEntryResponse = + | DeleteUSEntryResponse + | DeleteConnectionResponse + | DeleteDatasetResponse; + +export interface DeleteEntryArgs { + entryId: string; + scope: string; + lockToken?: string; +} + +export interface GetPublicationPreviewEntry extends Required { + lockPublication: boolean; + lockPublicationReason: null | string; +} + +export type GetPublicationPreviewResponse = GetPublicationPreviewEntry[]; + +export interface GetPublicationPreviewArgs { + entryId: string; + workbookId: WorkbookId; +} + +export type GetEntryRelationsResponse = GetRelationsEntry[]; +export type GetEntryRelationsArgs = { + entryId: string; + workbookId: WorkbookId; + direction?: 'child' | 'parent'; +}; + +export type MixedSwitchPublicationStatusArgsUnversionedData = { + publicAuthor?: { + text?: string; + link?: string; + }; +}; + +export interface MixedSwitchPublicationStatusArgs { + entries: { + entryId: string; + publish: boolean; + scope: string; + }[]; + mainEntry?: { + entryId: string; + unversionedData: MixedSwitchPublicationStatusArgsUnversionedData; + }; + workbookId: WorkbookId; +} + +export type ResolveEntryByLinkComponentResponse = { + entry: GetEntryByKeyResponse | GetEntryMetaResponse; + params: StringParams; +}; + +export type ResolveEntryByLinkResponse = ResolveEntryByLinkComponentResponse; + +export type ResolveEntryByLinkComponentArgs = { + originalUrl: string; + ctx: AppContext; + getEntryMeta: (args: GetEntryMetaArgs) => Promise; + getEntryByKey: (args: GetEntryByKeyArgs) => Promise; + checkReportExists?: (args: CheckStatReportExistsArgs) => Promise; +}; + +export type ResolveEntryByLinkArgs = { + url: string; +}; + +export type GetEntryMetaStatusResponse = { + code: 'OK' | 'NOT_FOUND' | 'FORBIDDEN' | 'UNHANDLED'; +}; + +export type GetEntryMetaStatusArgs = { + entryId: string; + bindedWorkbookId?: string; + bindedDatasetId?: string; +}; + +export type GetEntriesInFolderArgs = { + folderId: string; +}; + +export type GetEntriesInFolderResponse = EntryByKeyPattern[]; + +export type GetBatchEntriesByIdsArgs = Omit & {ids: string[]}; + +export type GetBatchEntriesByIdsResponse = Pick; + +export interface EnrichedLinkNode { + entryId: string; + description?: string; + links: Record; +} + +export interface EnrichedLinksTree { + [entryId: string]: { + description?: string; + links: Record; + }; +} + +export interface GetEnrichedLinksTreeResponse { + linksTree: EnrichedLinksTree; +} + +export interface GetEnrichedLinksTreeArgs { + entryId: string; +} diff --git a/src/shared/schema/mix/types/index.ts b/src/shared/schema/mix/types/index.ts new file mode 100644 index 0000000..d7d5b43 --- /dev/null +++ b/src/shared/schema/mix/types/index.ts @@ -0,0 +1,6 @@ +export * from './navigation'; +export * from './entries'; +export * from './markdown'; +export * from './dash'; +export * from './ql'; +export * from './wizard'; diff --git a/src/shared/schema/mix/types/markdown.ts b/src/shared/schema/mix/types/markdown.ts new file mode 100644 index 0000000..4aa2506 --- /dev/null +++ b/src/shared/schema/mix/types/markdown.ts @@ -0,0 +1,17 @@ +import type {RenderHtmlOutput} from '../../../modules/markdown/markdown'; + +export type RenderMarkdownResponse = RenderHtmlOutput; + +export type RenderMarkdownArgs = { + text: string; + lang: string; +}; + +export type BatchRenderMarkdownResponse = Record; + +export type BatchRenderMarkdownArgs = { + texts: Record; + lang: string; +}; + +export type MarkdownContextAction = (args: {text: string; lang: string}) => RenderHtmlOutput; diff --git a/src/shared/schema/mix/types/navigation.ts b/src/shared/schema/mix/types/navigation.ts new file mode 100644 index 0000000..15403c7 --- /dev/null +++ b/src/shared/schema/mix/types/navigation.ts @@ -0,0 +1,24 @@ +import type {PLACE} from '../../../constants'; +import type { + EntriesCommonArgs, + GetEntriesEntryWithExcludedLockedAndPermissions, + GetFavoritesEntryWithPermissions, + ListDirectoryBreadCrumb, + ListDirectoryEntryWithPermissions, +} from '../../us/types'; + +export type NavigationEntry = + | GetEntriesEntryWithExcludedLockedAndPermissions + | ListDirectoryEntryWithPermissions + | GetFavoritesEntryWithPermissions; + +export interface GetNavigationListResponse { + hasNextPage: boolean; + entries: NavigationEntry[]; + breadCrumbs: ListDirectoryBreadCrumb[]; +} + +export interface GetNavigationListArgs extends EntriesCommonArgs { + place: (typeof PLACE)[keyof typeof PLACE]; + path?: string; +} diff --git a/src/shared/schema/mix/types/ql.ts b/src/shared/schema/mix/types/ql.ts new file mode 100644 index 0000000..26eecbe --- /dev/null +++ b/src/shared/schema/mix/types/ql.ts @@ -0,0 +1,7 @@ +import type z from 'zod'; + +import type {deleteQLChartResultSchema, getQLChartResultSchema} from '../schemas/ql'; + +export type GetQLChartResult = z.infer; + +export type DeleteQLChartResult = z.infer; diff --git a/src/shared/schema/mix/types/wizard.ts b/src/shared/schema/mix/types/wizard.ts new file mode 100644 index 0000000..a3a2643 --- /dev/null +++ b/src/shared/schema/mix/types/wizard.ts @@ -0,0 +1,7 @@ +import type z from 'zod'; + +import type {deleteWizardChartResultSchema, getWizardChartResultSchema} from '../schemas/wizard'; + +export type GetWizardResult = z.infer; + +export type DeleteWizardResult = z.infer; diff --git a/src/shared/schema/stat-api/types/index.ts b/src/shared/schema/stat-api/types/index.ts new file mode 100644 index 0000000..34a63df --- /dev/null +++ b/src/shared/schema/stat-api/types/index.ts @@ -0,0 +1,14 @@ +export type CheckStatReportExistsResponse = + | { + exists: true; + _path: string; + } + | { + exists: false; + reason: null | string; + dir_exists: boolean; + }; + +export type CheckStatReportExistsArgs = { + name: string; +}; diff --git a/src/shared/schema/types.ts b/src/shared/schema/types.ts new file mode 100644 index 0000000..223d71e --- /dev/null +++ b/src/shared/schema/types.ts @@ -0,0 +1,4 @@ +export * from './bi/types'; +export * from './mix/types'; +export * from './us/types'; +export * from './meta-manager/types'; diff --git a/src/shared/schema/us-private/actions/entries.ts b/src/shared/schema/us-private/actions/entries.ts new file mode 100644 index 0000000..33cb095 --- /dev/null +++ b/src/shared/schema/us-private/actions/entries.ts @@ -0,0 +1,74 @@ +import {WORKBOOK_ID_HEADER} from '../../../constants'; +import {createAction} from '../../gateway-utils'; +import type {GetEntryMetaArgs, GetEntryMetaResponse, GetEntryResponse} from '../../us/types'; +import {filterUrlFragment} from '../../utils'; +import type {PrivateGetEntryArgs, ProxyCreateEntryArgs} from '../types'; +const PRIVATE_PATH_PREFIX = '/private'; + +export const entriesActions = { + _getEntryMeta: createAction({ + method: 'GET', + path: ({entryId}) => `${PRIVATE_PATH_PREFIX}/entries/${entryId}/meta`, + params: (_, headers) => ({ + headers, + }), + }), + _getEntry: createAction({ + method: 'GET', + path: ({entryId}) => `${PRIVATE_PATH_PREFIX}/entries/${entryId}`, + params: (_, headers) => ({ + headers, + }), + }), + _proxyGetEntry: createAction({ + method: 'GET', + path: ({entryId}) => `${PRIVATE_PATH_PREFIX}/entries/${filterUrlFragment(entryId)}`, + params: ({entryId: _entryId, workbookId, ...query}, headers) => { + return { + query, + headers: { + ...headers, + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + }, + }; + }, + }), + _proxyCreateEntry: createAction({ + method: 'POST', + path: () => `${PRIVATE_PATH_PREFIX}/entries/`, + params: ( + { + workbookId, + data, + name, + type, + scope, + mode, + links, + key, + recursion, + includePermissionsInfo, + annotation, + }, + headers, + ) => ({ + headers: { + ...headers, + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + }, + body: { + workbookId, + data, + name, + type, + scope, + mode, + links, + recursion, + includePermissionsInfo, + annotation, + ...(key ? {key} : {}), + }, + }), + }), +}; diff --git a/src/shared/schema/us-private/actions/index.ts b/src/shared/schema/us-private/actions/index.ts new file mode 100644 index 0000000..8dd65f6 --- /dev/null +++ b/src/shared/schema/us-private/actions/index.ts @@ -0,0 +1,7 @@ +import {entriesActions} from './entries'; +import {tenantActions} from './tenant'; + +export const actions = { + ...entriesActions, + ...tenantActions, +}; diff --git a/src/shared/schema/us-private/actions/tenant.ts b/src/shared/schema/us-private/actions/tenant.ts new file mode 100644 index 0000000..13ad236 --- /dev/null +++ b/src/shared/schema/us-private/actions/tenant.ts @@ -0,0 +1,14 @@ +import {createAction} from '../../gateway-utils'; +import type {GetTenantDetailsArgs, GetTenantDetailsResponse} from '../../us/types'; + +const PRIVATE_PATH_PREFIX = '/private'; + +export const tenantActions = { + _getTenantDetails: createAction({ + method: 'GET', + path: ({tenantId}) => `${PRIVATE_PATH_PREFIX}/tenants/${tenantId}/details`, + params: (_, headers) => ({ + headers, + }), + }), +}; diff --git a/src/shared/schema/us-private/index.ts b/src/shared/schema/us-private/index.ts new file mode 100644 index 0000000..e528314 --- /dev/null +++ b/src/shared/schema/us-private/index.ts @@ -0,0 +1,20 @@ +import type {BaseSchema} from '@gravity-ui/gateway'; + +import {registry} from '../../../server/registry'; +import {getServiceEndpoints} from '../../endpoints/schema'; + +import {actions} from './actions'; + +export default { + actions, + endpoints: getServiceEndpoints('us'), + serviceName: 'us-private', + getAuthArgs: (req, res) => { + const {getAuthArgsUSPrivate} = registry.common.auth.getAll(); + return getAuthArgsUSPrivate(req, res); + }, + getAuthHeaders: (params) => { + const {getAuthHeadersUSPrivate} = registry.common.auth.getAll(); + return getAuthHeadersUSPrivate(params); + }, +} satisfies BaseSchema[string]; diff --git a/src/shared/schema/us-private/types/entries.ts b/src/shared/schema/us-private/types/entries.ts new file mode 100644 index 0000000..cad7ffc --- /dev/null +++ b/src/shared/schema/us-private/types/entries.ts @@ -0,0 +1,20 @@ +import type {EntryAnnotationArgs} from '../../..'; +import type {EntryFieldData, EntryFieldLinks, GetEntryArgs} from '../../us/types'; + +export interface PrivateGetEntryArgs extends GetEntryArgs { + branch?: 'published' | 'saved'; +} + +export interface ProxyCreateEntryArgs { + workbookId?: string; + data: EntryFieldData; + name: string; + type: string; + scope: string; + mode: string; + links: EntryFieldLinks; + key?: string; + includePermissionsInfo?: boolean; + recursion?: boolean; + annotation?: EntryAnnotationArgs; +} diff --git a/src/shared/schema/us-private/types/index.ts b/src/shared/schema/us-private/types/index.ts new file mode 100644 index 0000000..53d4292 --- /dev/null +++ b/src/shared/schema/us-private/types/index.ts @@ -0,0 +1 @@ +export * from './entries'; diff --git a/src/shared/schema/us/actions/collections/create-collection.ts b/src/shared/schema/us/actions/collections/create-collection.ts new file mode 100644 index 0000000..ca1159a --- /dev/null +++ b/src/shared/schema/us/actions/collections/create-collection.ts @@ -0,0 +1,30 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {collectionSchema} from '../../schemas/collections'; +import {datalensOperationSchema} from '../../schemas/operation'; + +const createCollectionArgsSchema = z.object({ + title: z.string(), + description: z.string().optional(), + parentId: z.string().nullable(), +}); + +export const createCollectionResultSchema = collectionSchema.extend({ + operation: datalensOperationSchema.optional(), +}); + +export const createCollection = createTypedAction( + { + paramsSchema: createCollectionArgsSchema, + resultSchema: createCollectionResultSchema, + }, + { + method: 'POST', + path: () => '/v1/collections', + params: ({title, description, parentId}, headers) => ({ + body: {title, description, parentId}, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/collections/delete-collection.ts b/src/shared/schema/us/actions/collections/delete-collection.ts new file mode 100644 index 0000000..1732cf6 --- /dev/null +++ b/src/shared/schema/us/actions/collections/delete-collection.ts @@ -0,0 +1,24 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {collectionSchema} from '../../schemas/collections'; + +const deleteCollectionArgsSchema = z.object({ + collectionId: z.string(), +}); + +export const deleteCollectionResultSchema = z.object({ + collections: z.array(collectionSchema), +}); + +export const deleteCollection = createTypedAction( + { + paramsSchema: deleteCollectionArgsSchema, + resultSchema: deleteCollectionResultSchema, + }, + { + method: 'DELETE', + path: ({collectionId}) => `/v1/collections/${collectionId}`, + params: (_, headers) => ({headers}), + }, +); diff --git a/src/shared/schema/us/actions/collections/delete-collections.ts b/src/shared/schema/us/actions/collections/delete-collections.ts new file mode 100644 index 0000000..f79bbb7 --- /dev/null +++ b/src/shared/schema/us/actions/collections/delete-collections.ts @@ -0,0 +1,27 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {collectionSchema} from '../../schemas/collections'; + +const deleteCollectionsArgsSchema = z.object({ + collectionIds: z.array(z.string()), +}); + +export const deleteCollectionsResultSchema = z.object({ + collections: z.array(collectionSchema), +}); + +export const deleteCollections = createTypedAction( + { + paramsSchema: deleteCollectionsArgsSchema, + resultSchema: deleteCollectionsResultSchema, + }, + { + method: 'DELETE', + path: () => '/v1/delete-collections', + params: ({collectionIds}, headers) => ({ + body: {collectionIds}, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/collections/get-collection-breadcrumbs.ts b/src/shared/schema/us/actions/collections/get-collection-breadcrumbs.ts new file mode 100644 index 0000000..997f12e --- /dev/null +++ b/src/shared/schema/us/actions/collections/get-collection-breadcrumbs.ts @@ -0,0 +1,27 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {collectionPermissionsSchema, collectionSchema} from '../../schemas/collections'; + +const getCollectionBreadcrumbsArgsSchema = z.object({ + collectionId: z.string(), + includePermissionsInfo: z.boolean().optional(), +}); + +export const getCollectionBreadcrumbsResultSchema = z.array( + collectionSchema.extend({ + permissions: collectionPermissionsSchema.optional(), + }), +); + +export const getCollectionBreadcrumbs = createTypedAction( + { + paramsSchema: getCollectionBreadcrumbsArgsSchema, + resultSchema: getCollectionBreadcrumbsResultSchema, + }, + { + method: 'GET', + path: ({collectionId}) => `/v1/collections/${collectionId}/breadcrumbs`, + params: ({includePermissionsInfo}, headers) => ({headers, query: {includePermissionsInfo}}), + }, +); diff --git a/src/shared/schema/us/actions/collections/get-collection.ts b/src/shared/schema/us/actions/collections/get-collection.ts new file mode 100644 index 0000000..a8b27e2 --- /dev/null +++ b/src/shared/schema/us/actions/collections/get-collection.ts @@ -0,0 +1,25 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {collectionPermissionsSchema, collectionSchema} from '../../schemas/collections'; + +export const getCollectionArgsSchema = z.object({ + collectionId: z.string(), + includePermissionsInfo: z.boolean().optional(), +}); + +export const getCollectionResultSchema = collectionSchema.extend({ + permissions: collectionPermissionsSchema.optional(), +}); + +export const getCollection = createTypedAction( + { + paramsSchema: getCollectionArgsSchema, + resultSchema: getCollectionResultSchema, + }, + { + method: 'GET', + path: ({collectionId}) => `/v1/collections/${collectionId}`, + params: ({includePermissionsInfo}, headers) => ({query: {includePermissionsInfo}, headers}), + }, +); diff --git a/src/shared/schema/us/actions/collections/get-collections-by-ids.ts b/src/shared/schema/us/actions/collections/get-collections-by-ids.ts new file mode 100644 index 0000000..3d1acf6 --- /dev/null +++ b/src/shared/schema/us/actions/collections/get-collections-by-ids.ts @@ -0,0 +1,20 @@ +import {createTypedAction} from '../../../gateway-utils'; +import { + getCollectionsByIdsArgsSchema, + getCollectionsByIdsResultSchema, +} from '../../schemas/collections/get-collections-by-ids'; + +export const getCollectionsByIds = createTypedAction( + { + paramsSchema: getCollectionsByIdsArgsSchema, + resultSchema: getCollectionsByIdsResultSchema, + }, + { + method: 'POST', + path: () => '/v1/collections-get-list-by-ids', + params: ({collectionIds}, headers) => ({ + body: {collectionIds}, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/collections/get-root-collection-permissions.ts b/src/shared/schema/us/actions/collections/get-root-collection-permissions.ts new file mode 100644 index 0000000..65e57d6 --- /dev/null +++ b/src/shared/schema/us/actions/collections/get-root-collection-permissions.ts @@ -0,0 +1,22 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; + +export const getRootCollectionPermissionsResultSchema = z.object({ + createCollectionInRoot: z.boolean(), + createWorkbookInRoot: z.boolean(), +}); + +export const getRootCollectionPermissions = createTypedAction( + { + paramsSchema: undefined, + resultSchema: getRootCollectionPermissionsResultSchema, + }, + { + method: 'GET', + path: () => '/v1/root-collection-permissions', + params: (_, headers) => ({ + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/collections/get-structure-items.ts b/src/shared/schema/us/actions/collections/get-structure-items.ts new file mode 100644 index 0000000..17b5e9c --- /dev/null +++ b/src/shared/schema/us/actions/collections/get-structure-items.ts @@ -0,0 +1,85 @@ +import z from 'zod'; + +import {getEntryNameByKey} from '../../../../modules'; +import {createTypedAction} from '../../../gateway-utils'; +import type {GetStructureItemsArgs, GetStructureItemsResponse} from '../../../us/types/collections'; +import {collectionPermissionsSchema, collectionSchema} from '../../schemas/collections'; +import {workbookPermissionsSchema, workbookSchema} from '../../schemas/workbooks'; + +const getStructureItemsArgsSchema = z.object({ + collectionId: z.string().nullable(), + page: z.string().nullable().optional(), + filterString: z.string().optional(), + orderField: z.enum(['title', 'createdAt', 'updatedAt']).optional(), + orderDirection: z.enum(['asc', 'desc']).optional(), + onlyMy: z.boolean().optional(), + mode: z.enum(['all', 'onlyCollections', 'onlyWorkbooks']).optional(), + pageSize: z.number().optional(), + includePermissionsInfo: z.boolean().optional(), +}); + +export const collectionItemSchema = collectionSchema.extend({ + entity: z.literal('collection'), + permissions: collectionPermissionsSchema.optional(), +}); + +export const workbookItemSchema = workbookSchema.extend({ + entity: z.literal('workbook'), + permissions: workbookPermissionsSchema.optional(), +}); + +const getStructureItemsResultSchema = z.object({ + items: z.discriminatedUnion('entity', [collectionItemSchema, workbookItemSchema]).array(), + nextPageToken: z.string().nullable().optional(), +}); + +export const getStructureItems = createTypedAction< + GetStructureItemsResponse, + GetStructureItemsArgs +>( + { + paramsSchema: getStructureItemsArgsSchema, + resultSchema: getStructureItemsResultSchema, + }, + { + method: 'GET', + path: () => '/v1/structure-items', + params: ( + { + collectionId, + includePermissionsInfo, + filterString, + page, + pageSize, + orderField, + orderDirection, + onlyMy, + mode, + }, + headers, + ) => ({ + query: { + collectionId, + includePermissionsInfo, + filterString, + // null is passed from query parameters + page: page === null ? 'null' : page, + pageSize, + orderField, + orderDirection, + onlyMy, + mode, + }, + headers, + }), + transformResponseData: (data) => ({ + ...data, + items: data.items.map((item) => { + if ('displayKey' in item) { + return {...item, title: getEntryNameByKey({key: item.displayKey})}; + } + return item; + }), + }), + }, +); diff --git a/src/shared/schema/us/actions/collections/index.ts b/src/shared/schema/us/actions/collections/index.ts new file mode 100644 index 0000000..ea00c5a --- /dev/null +++ b/src/shared/schema/us/actions/collections/index.ts @@ -0,0 +1,25 @@ +import {createCollection} from './create-collection'; +import {deleteCollection} from './delete-collection'; +import {deleteCollections} from './delete-collections'; +import {getCollection} from './get-collection'; +import {getCollectionBreadcrumbs} from './get-collection-breadcrumbs'; +import {getCollectionsByIds} from './get-collections-by-ids'; +import {getRootCollectionPermissions} from './get-root-collection-permissions'; +import {getStructureItems} from './get-structure-items'; +import {moveCollection} from './move-collection'; +import {moveCollections} from './move-collections'; +import {updateCollection} from './update-collection'; + +export const collectionsActions = { + getRootCollectionPermissions, + createCollection, + getCollection, + getCollectionsByIds, + getStructureItems, + getCollectionBreadcrumbs, + deleteCollection, + updateCollection, + moveCollection, + moveCollections, + deleteCollections, +}; diff --git a/src/shared/schema/us/actions/collections/move-collection.ts b/src/shared/schema/us/actions/collections/move-collection.ts new file mode 100644 index 0000000..4b19e05 --- /dev/null +++ b/src/shared/schema/us/actions/collections/move-collection.ts @@ -0,0 +1,30 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {collectionSchema} from '../../schemas/collections'; + +const moveCollectionArgsSchema = z.object({ + collectionId: z.string(), + parentId: z.string().nullable(), + title: z.string().optional(), +}); + +export const moveCollectionResultSchema = collectionSchema; + +export const moveCollection = createTypedAction( + { + paramsSchema: moveCollectionArgsSchema, + resultSchema: moveCollectionResultSchema, + }, + { + method: 'POST', + path: ({collectionId}) => `/v1/collections/${collectionId}/move`, + params: ({parentId, title}, headers) => ({ + body: { + parentId, + title, + }, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/collections/move-collections.ts b/src/shared/schema/us/actions/collections/move-collections.ts new file mode 100644 index 0000000..81a1d98 --- /dev/null +++ b/src/shared/schema/us/actions/collections/move-collections.ts @@ -0,0 +1,28 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {collectionSchema} from '../../schemas/collections'; + +const moveCollectionsArgsSchema = z.object({ + collectionIds: z.array(z.string()), + parentId: z.string().nullable(), +}); + +export const moveCollectionsResultSchema = z.object({ + collections: z.array(collectionSchema), +}); + +export const moveCollections = createTypedAction( + { + paramsSchema: moveCollectionsArgsSchema, + resultSchema: moveCollectionsResultSchema, + }, + { + method: 'POST', + path: () => '/v1/move-collections', + params: ({collectionIds, parentId}, headers) => ({ + body: {collectionIds, parentId}, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/collections/update-collection.ts b/src/shared/schema/us/actions/collections/update-collection.ts new file mode 100644 index 0000000..fd4ba4b --- /dev/null +++ b/src/shared/schema/us/actions/collections/update-collection.ts @@ -0,0 +1,30 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {collectionSchema} from '../../schemas/collections'; + +const updateCollectionArgsSchema = z.object({ + collectionId: z.string(), + title: z.string().optional(), + description: z.string().optional(), +}); + +export const updateCollectionResultSchema = collectionSchema; + +export const updateCollection = createTypedAction( + { + paramsSchema: updateCollectionArgsSchema, + resultSchema: updateCollectionResultSchema, + }, + { + method: 'POST', + path: ({collectionId}) => `/v1/collections/${collectionId}/update`, + params: ({title, description}, headers) => ({ + body: { + title, + description, + }, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/color-palettes.ts b/src/shared/schema/us/actions/color-palettes.ts new file mode 100644 index 0000000..5310fb7 --- /dev/null +++ b/src/shared/schema/us/actions/color-palettes.ts @@ -0,0 +1,44 @@ +import {createAction} from '../../gateway-utils'; +import type { + CreateColorPaletteArgs, + CreateColorPaletteResponse, + DeleteColorPaletteArgs, + DeleteColorPaletteResponse, + GetColorPalettesListArgs, + GetColorPalettesListResponse, + UpdateColorPaletteArgs, + UpdateColorPaletteResponse, +} from '../types'; + +const PATH_PREFIX = '/v1/color-palettes'; + +export const colorPalettesActions = { + createColorPalette: createAction({ + method: 'POST', + path: () => PATH_PREFIX, + params: (body, headers) => ({ + body, + headers, + }), + }), + + getColorPalettesList: createAction({ + method: 'GET', + path: () => PATH_PREFIX, + params: (_args, headers) => ({ + headers, + }), + }), + + updateColorPalette: createAction({ + method: 'POST', + path: ({colorPaletteId}) => `${PATH_PREFIX}/${colorPaletteId}/update`, + params: (body, headers) => ({body, headers}), + }), + + deleteColorPalette: createAction({ + method: 'DELETE', + path: ({colorPaletteId}) => `${PATH_PREFIX}/${colorPaletteId}`, + params: (_, headers) => ({headers}), + }), +}; diff --git a/src/shared/schema/us/actions/editor.ts b/src/shared/schema/us/actions/editor.ts new file mode 100644 index 0000000..86086e6 --- /dev/null +++ b/src/shared/schema/us/actions/editor.ts @@ -0,0 +1,77 @@ +import {EntryUpdateMode} from '../../..'; +import {createAction} from '../../gateway-utils'; +import {filterUrlFragment} from '../../utils'; +import type { + CreateEditorChartArgs, + CreateEditorChartResponse, + UpdateEditorChartArgs, + UpdateEditorChartResponse, +} from '../types'; + +const PATH_PREFIX = '/v1'; + +export const editorActions = { + _createEditorChart: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/entries`, + params: ( + { + version, + type, + key, + data, + meta = {}, + name, + workbookId, + mode = EntryUpdateMode.Publish, + links, + description = '', + annotation, + }, + headers, + ) => { + return { + body: { + version, + scope: 'widget', + type, + key, + meta, + data, + name, + workbookId, + mode, + links, + annotation: { + description: annotation?.description ?? description, + }, + }, + headers, + }; + }, + }), + _updateEditorChart: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX}/entries/${filterUrlFragment(entryId)}`, + + params: ( + {version, data, mode, revId, meta = {}, links, annotation, description = ''}, + headers, + ) => { + return { + body: { + version, + mode, + meta, + data, + revId, + links, + annotation: { + description: annotation?.description ?? description, + }, + }, + headers, + }; + }, + }), +}; diff --git a/src/shared/schema/us/actions/embeds/create-embed.ts b/src/shared/schema/us/actions/embeds/create-embed.ts new file mode 100644 index 0000000..265c3b6 --- /dev/null +++ b/src/shared/schema/us/actions/embeds/create-embed.ts @@ -0,0 +1,17 @@ +import {createTypedAction} from '../../../gateway-utils'; +import {createEmbedArgsSchema, createEmbedResultSchema} from '../../schemas/embeds'; + +export const createEmbed = createTypedAction( + { + paramsSchema: createEmbedArgsSchema, + resultSchema: createEmbedResultSchema, + }, + { + method: 'POST', + path: () => '/v1/embeds', + params: (params, headers) => ({ + body: params, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/embeds/delete-embed.ts b/src/shared/schema/us/actions/embeds/delete-embed.ts new file mode 100644 index 0000000..776b3c6 --- /dev/null +++ b/src/shared/schema/us/actions/embeds/delete-embed.ts @@ -0,0 +1,16 @@ +import {createTypedAction} from '../../../gateway-utils'; +import {deleteEmbedArgsSchema, deleteEmbedResultSchema} from '../../schemas/embeds'; + +export const deleteEmbed = createTypedAction( + { + paramsSchema: deleteEmbedArgsSchema, + resultSchema: deleteEmbedResultSchema, + }, + { + method: 'DELETE', + path: ({embedId}) => `/v1/embeds/${embedId}`, + params: (_, headers) => ({ + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/embeds/index.ts b/src/shared/schema/us/actions/embeds/index.ts new file mode 100644 index 0000000..687f506 --- /dev/null +++ b/src/shared/schema/us/actions/embeds/index.ts @@ -0,0 +1,9 @@ +import {createEmbed} from './create-embed'; +import {deleteEmbed} from './delete-embed'; +import {listEmbeds} from './list-embeds'; + +export const embedsActions = { + createEmbed, + listEmbeds, + deleteEmbed, +}; diff --git a/src/shared/schema/us/actions/embeds/list-embeds.ts b/src/shared/schema/us/actions/embeds/list-embeds.ts new file mode 100644 index 0000000..fe870e0 --- /dev/null +++ b/src/shared/schema/us/actions/embeds/list-embeds.ts @@ -0,0 +1,17 @@ +import {createTypedAction} from '../../../gateway-utils'; +import {listEmbedsArgsSchema, listEmbedsResultSchema} from '../../schemas/embeds'; + +export const listEmbeds = createTypedAction( + { + paramsSchema: listEmbedsArgsSchema, + resultSchema: listEmbedsResultSchema, + }, + { + method: 'GET', + path: () => '/v1/embeds', + params: ({entryId}, headers) => ({ + query: {entryId}, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/entries/create-entry.ts b/src/shared/schema/us/actions/entries/create-entry.ts new file mode 100644 index 0000000..64330c9 --- /dev/null +++ b/src/shared/schema/us/actions/entries/create-entry.ts @@ -0,0 +1,39 @@ +import {EntryUpdateMode} from '../../../..'; +import {createAction} from '../../../gateway-utils'; +import type {CreateEntryArgs, CreateEntryResponse} from '../../types'; + +export const _createEntry = createAction({ + method: 'POST', + path: () => '/v1/entries', + params: ( + { + scope, + type, + key, + data, + meta = {}, + name, + workbookId, + mode = EntryUpdateMode.Publish, + links, + annotation, + }, + headers, + ) => { + return { + body: { + scope, + type, + key, + meta, + data, + name, + workbookId, + mode, + links, + annotation, + }, + headers, + }; + }, +}); diff --git a/src/shared/schema/us/actions/entries/get-entries-relations.ts b/src/shared/schema/us/actions/entries/get-entries-relations.ts new file mode 100644 index 0000000..b25cfa5 --- /dev/null +++ b/src/shared/schema/us/actions/entries/get-entries-relations.ts @@ -0,0 +1,52 @@ +import z from 'zod'; + +import {EntryScope} from '../../../..'; +import {permissionsSchema} from '../../../../zod-schemas/permissions'; +import {sharedEntryPermissionsSchema} from '../../../../zod-schemas/shared-entry-permissions'; +import {createTypedAction} from '../../../gateway-utils'; + +const GET_ENTRIES_RELATIONS_LINK_DIRECTION = { + FROM: 'from', + TO: 'to', +} as const; + +const getEntriesRelationsArgsSchema = z.strictObject({ + entryIds: z.array(z.string()), + linkDirection: z.enum(GET_ENTRIES_RELATIONS_LINK_DIRECTION).optional(), + includePermissionsInfo: z.boolean().optional(), + limit: z.number().optional(), + pageToken: z.string().optional(), + scope: z.enum(EntryScope).optional(), +}); + +const entryRelationSchema = z.object({ + entryId: z.string(), + key: z.string().nullable(), + scope: z.enum(EntryScope), + type: z.string(), + createdAt: z.string(), + public: z.boolean(), + tenantId: z.string().nullable(), + workbookId: z.string().nullable(), + collectionId: z.string().nullable(), + isLocked: z.boolean().optional(), + permissions: permissionsSchema.optional(), + fullPermissions: sharedEntryPermissionsSchema.optional(), +}); + +const getEntriesRelationsResultSchema = z.object({ + relations: z.array(entryRelationSchema), + nextPageToken: z.string().optional(), +}); + +export const getEntriesRelations = createTypedAction( + { + paramsSchema: getEntriesRelationsArgsSchema, + resultSchema: getEntriesRelationsResultSchema, + }, + { + method: 'POST', + path: () => '/v1/get-entries-relations', + params: (params, headers) => ({body: params, headers}), + }, +); diff --git a/src/shared/schema/us/actions/entries/get-entries.ts b/src/shared/schema/us/actions/entries/get-entries.ts new file mode 100644 index 0000000..10b7eb7 --- /dev/null +++ b/src/shared/schema/us/actions/entries/get-entries.ts @@ -0,0 +1,29 @@ +import {getEntryNameByKey} from '../../../../modules'; +import {createTypedAction} from '../../../gateway-utils'; +import {defaultParamsSerializer} from '../../../utils'; +import { + getEntriesArgsSchema, + getEntriesResultSchema, + getEntriesTransformedSchema, +} from '../../schemas/entries/get-entries'; + +export const getEntries = createTypedAction( + { + paramsSchema: getEntriesArgsSchema, + resultSchema: getEntriesResultSchema, + transformedSchema: getEntriesTransformedSchema, + }, + { + method: 'GET', + path: () => '/v1/entries', + params: (query, headers) => ({query, headers}), + transformResponseData: (data) => ({ + hasNextPage: Boolean(data.nextPageToken), + entries: data.entries.map((entry) => ({ + ...entry, + name: 'key' in entry ? getEntryNameByKey({key: entry.key}) : '', + })), + }), + paramsSerializer: defaultParamsSerializer, + }, +); diff --git a/src/shared/schema/us/actions/entries/get-entry.ts b/src/shared/schema/us/actions/entries/get-entry.ts new file mode 100644 index 0000000..f7676f7 --- /dev/null +++ b/src/shared/schema/us/actions/entries/get-entry.ts @@ -0,0 +1,36 @@ +import { + DATASET_ID_HEADER, + DL_COMPONENT_HEADER, + DlComponentHeader, + WORKBOOK_ID_HEADER, +} from '../../../..'; +import {createAction} from '../../../gateway-utils'; +import {filterUrlFragment} from '../../../utils'; +import type {GetEntryArgs, GetEntryResponse} from '../../types'; + +export const getEntry = createAction({ + method: 'GET', + path: ({entryId}) => `/v1/entries/${filterUrlFragment(entryId)}`, + params: ( + { + entryId: _entryId, + workbookId, + includeDlComponentUiData, + bindedDatasetId, + includeFavorite = true, + ...query + }, + headers, + ) => ({ + query: { + ...query, + includeFavorite, + }, + headers: { + ...headers, + ...(includeDlComponentUiData ? {[DL_COMPONENT_HEADER]: DlComponentHeader.UI} : {}), + ...(workbookId ? {[WORKBOOK_ID_HEADER]: workbookId} : {}), + ...(bindedDatasetId ? {[DATASET_ID_HEADER]: bindedDatasetId} : {}), + }, + }), +}); diff --git a/src/shared/schema/us/actions/entries/index.ts b/src/shared/schema/us/actions/entries/index.ts new file mode 100644 index 0000000..425506d --- /dev/null +++ b/src/shared/schema/us/actions/entries/index.ts @@ -0,0 +1,360 @@ +import {omit, uniqBy} from 'lodash'; + +import { + DATASET_ID_HEADER, + TIMEOUT_60_SEC, + TIMEOUT_90_SEC, + WORKBOOK_ID_HEADER, +} from '../../../../constants'; +import {getEntryNameByKey, normalizeDestination} from '../../../../modules'; +import {Feature} from '../../../../types/feature'; +import {createAction} from '../../../gateway-utils'; +import {filterUrlFragment} from '../../../utils'; +import type { + CopyEntriesToWorkbookArgs, + CopyEntriesToWorkbookResponse, + CopyEntryArgs, + CopyEntryResponse, + CopyWorkbookEntryArgs, + CopyWorkbookEntryResponse, + CreateFolderArgs, + CreateFolderResponse, + DeleteSharedEntriesArgs, + DeleteSharedEntriesResponse, + DeleteUSEntryArgs, + DeleteUSEntryResponse, + EntityBindingsArgs, + EntityBindingsResponse, + GetEntriesAnnotationArgs, + GetEntriesAnnotationResponse, + GetEntriesByKeyPatternArgs, + GetEntriesByKeyPatternResponse, + GetEntryByKeyArgs, + GetEntryByKeyResponse, + GetEntryMetaArgs, + GetEntryMetaResponse, + GetRelationsArgs, + GetRelationsGraphArgs, + GetRelationsGraphResponse, + GetRelationsOutput, + GetRelationsResponse, + GetRevisionsArgs, + GetRevisionsOutput, + GetRevisionsResponse, + GetSharedEntryBindingsArgs, + GetSharedEntryBindingsResponse, + GetSharedEntryInfoArgs, + GetSharedEntryInfoResponse, + GetSharedEntryWorkbookRelationsArgs, + GetSharedEntryWorkbookRelationsResponse, + MoveEntryArgs, + MoveEntryResponse, + MoveSharedEntriesArgs, + MoveSharedEntriesResponse, + MoveSharedEntryArgs, + RenameEntryArgs, + RenameEntryResponse, + SwitchPublicationStatusArgs, + SwitchPublicationStatusResponse, +} from '../../types'; + +import {_createEntry} from './create-entry'; +import {getEntries} from './get-entries'; +import {getEntriesRelations} from './get-entries-relations'; +import {getEntry} from './get-entry'; +import {listDirectory} from './list-directory'; +import {_updateEntry} from './update-entry'; + +const PATH_PREFIX = '/v1'; +const PATH_PREFIX_V2 = '/v2'; +const PRIVATE_PATH_PREFIX = '/private'; + +export const entriesActions = { + _createEntry, + _updateEntry, + getEntriesRelations, + listDirectory, + getEntries, + getEntry, + _getEntryWithAudit: getEntry, + getEntryByKey: createAction({ + method: 'GET', + path: () => `${PATH_PREFIX}/entriesByKey`, + params: (query, headers) => ({query, headers}), + }), + getEntryMeta: createAction({ + method: 'GET', + path: ({entryId}) => `${PATH_PREFIX}/entries/${filterUrlFragment(entryId)}/meta`, + params: ({bindedWorkbookId, bindedDatasetId}, headers) => ({ + headers: { + ...headers, + ...(bindedWorkbookId ? {[WORKBOOK_ID_HEADER]: bindedWorkbookId} : {}), + ...(bindedDatasetId ? {[DATASET_ID_HEADER]: bindedDatasetId} : {}), + }, + }), + }), + getRevisions: createAction({ + method: 'GET', + path: ({entryId}) => `${PATH_PREFIX}/entries/${filterUrlFragment(entryId)}/revisions`, + params: (args, headers, {ctx}) => { + let updatedAfter; + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + if (!isEnabledServerFeature(Feature.RevisionsListNoLimit) && !args.revIds) { + const date = new Date(); + date.setMonth(date.getMonth() - 3); + updatedAfter = date.toISOString(); + } + return { + query: { + ...omit(args, 'entryId'), + updatedAfter, + }, + headers, + }; + }, + transformResponseData: (data) => ({ + hasNextPage: Boolean(data.nextPageToken), + entries: data.entries, + }), + }), + getRelations: createAction({ + method: 'GET', + path: ({entryId}) => `${PATH_PREFIX}/entries/${filterUrlFragment(entryId)}/relations`, + params: (args, headers) => ({ + query: omit(args, 'entryId', 'excludeUnregistredDlsEntries'), + headers, + }), + transformResponseData: (data, {args, ctx}) => { + let uniqRelations = uniqBy( + data.map((relationEntry) => ({ + ...relationEntry, + name: getEntryNameByKey({key: relationEntry.key}), + })), + (relationEntry) => relationEntry.entryId, + ); + if (args.excludeUnregistredDlsEntries) { + const isEnabledServerFeature = ctx.get('isEnabledServerFeature'); + if (isEnabledServerFeature(Feature.UseYqlFolderKey)) { + const yqlFolderKey = 'yql/charts/'; + uniqRelations = uniqRelations.filter( + ({key}) => !key.toLowerCase().startsWith(yqlFolderKey), + ); + } + } + return uniqRelations; + }, + }), + moveEntry: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX}/entries/${filterUrlFragment(entryId)}/move`, + params: (args, headers) => { + const body: Omit = { + destination: normalizeDestination(args.destination), + }; + if (args.name) { + body.name = args.name; + } + return {body, headers}; + }, + timeout: TIMEOUT_60_SEC, + }), + copyEntry: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX}/entries/${filterUrlFragment(entryId)}/copy`, + params: (args, headers) => { + const body: Omit = { + destination: normalizeDestination(args.destination), + }; + if (args.name) { + body.name = args.name; + } + return {body, headers}; + }, + }), + copyWorkbookEntry: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX_V2}/entries/${filterUrlFragment(entryId)}/copy`, + params: (args, headers) => { + const body: Omit = {}; + + if (args.name) { + body.name = args.name; + } + + return {body, headers}; + }, + }), + copyEntriesToWorkbook: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX_V2}/copy-entries`, + params: (args, headers) => ({ + body: {workbookId: args.workbookId, entryIds: args.entryIds}, + headers, + }), + }), + renameEntry: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX}/entries/${filterUrlFragment(entryId)}/rename`, + params: ({name}, headers) => ({body: {name}, headers}), + timeout: TIMEOUT_60_SEC, + }), + createFolder: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/entries`, + params: ({key}, headers) => { + return { + body: { + scope: 'folder', + type: '', + key, + meta: {}, + data: {}, + }, + headers, + }; + }, + }), + _deleteUSEntry: createAction({ + method: 'DELETE', + path: ({entryId}) => `${PATH_PREFIX}/entries/${filterUrlFragment(entryId)}`, + params: ({lockToken, scope, types}, headers) => ({ + query: {lockToken, scope, types}, + headers, + }), + }), + _switchPublicationStatus: createAction< + SwitchPublicationStatusResponse, + SwitchPublicationStatusArgs + >({ + method: 'POST', + path: () => `${PRIVATE_PATH_PREFIX}/entries/switchPublicationStatus`, + params: ({entries, mainEntry}, headers) => ({ + body: {entries, mainEntry}, + headers, + }), + }), + _getEntriesByKeyPattern: createAction< + GetEntriesByKeyPatternResponse, + GetEntriesByKeyPatternArgs + >({ + method: 'GET', + path: () => `${PRIVATE_PATH_PREFIX}/getEntriesByKeyPattern`, + params: ({keyPattern}, headers) => ({query: {keyPattern}, headers}), + }), + getRelationsGraph: createAction({ + method: 'GET', + path: ({entryId}) => `${PATH_PREFIX}/entries/${filterUrlFragment(entryId)}/relations-graph`, + timeout: TIMEOUT_90_SEC, + }), + getEntriesAnnotation: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/get-entries-annotation`, + params: (params, headers) => ({ + body: params, + headers, + }), + }), + getSharedEntryDelegation: createAction< + EntityBindingsResponse, + Omit + >({ + method: 'GET', + path: () => `${PATH_PREFIX}/entity-bindings`, + params: ({sourceId, targetId, bindedWorkbookId}, headers) => ({ + query: { + sourceId, + targetId, + }, + headers: { + ...headers, + ...(bindedWorkbookId ? {[WORKBOOK_ID_HEADER]: bindedWorkbookId} : {}), + }, + }), + }), + createSharedEntryBinding: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/entity-bindings/create`, + params: (params, headers) => ({ + body: params, + headers, + }), + }), + updateSharedEntryBinding: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/entity-bindings/update`, + params: (params, headers) => ({ + body: params, + headers, + }), + }), + deleteSharedEntryBinding: createAction< + EntityBindingsResponse, + Omit + >({ + method: 'DELETE', + path: () => `${PATH_PREFIX}/entity-bindings/delete`, + params: (params, headers) => ({ + query: params, + headers, + }), + }), + getSharedEntryBindings: createAction< + GetSharedEntryBindingsResponse, + GetSharedEntryBindingsArgs + >({ + method: 'GET', + path: ({entryId}) => `${PATH_PREFIX}/shared-entries/${entryId}/entity-bindings`, + params: ( + {page, pageSize, entryAs, mode, filterString, includePermissionsInfo}, + headers, + ) => ({ + query: { + pageSize, + page, + entryAs, + mode, + filterString, + includePermissionsInfo, + }, + headers, + }), + }), + getSharedEntryWorkbookRelations: createAction< + GetSharedEntryWorkbookRelationsResponse, + GetSharedEntryWorkbookRelationsArgs + >({ + method: 'GET', + path: ({entryId}) => `${PATH_PREFIX}/shared-entries/${entryId}/workbook-relations`, + params: ({scope, workbookId}, headers) => ({ + query: { + workbookId, + scope, + }, + headers, + }), + }), + moveSharedEntry: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX}/shared-entries/${filterUrlFragment(entryId)}/move`, + params: ({collectionId, name}, headers) => ({headers, body: {collectionId, name}}), + timeout: TIMEOUT_60_SEC, + }), + moveSharedEntries: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/shared-entries/move-entries`, + params: ({collectionId, entryIds}, headers) => ({headers, body: {collectionId, entryIds}}), + timeout: TIMEOUT_60_SEC, + }), + deleteSharedEntries: createAction({ + method: 'DELETE', + path: () => `${PATH_PREFIX}/shared-entries/delete-entries`, + params: ({entryIds}, headers) => ({headers, body: {entryIds}}), + timeout: TIMEOUT_60_SEC, + }), + getSharedEntryInfo: createAction({ + method: 'GET', + path: ({entryId}) => `${PATH_PREFIX}/shared-entries/${entryId}/info`, + params: ({includePermissionsInfo}, headers) => ({headers, query: {includePermissionsInfo}}), + timeout: TIMEOUT_60_SEC, + }), +}; diff --git a/src/shared/schema/us/actions/entries/list-directory.ts b/src/shared/schema/us/actions/entries/list-directory.ts new file mode 100644 index 0000000..39952ca --- /dev/null +++ b/src/shared/schema/us/actions/entries/list-directory.ts @@ -0,0 +1,30 @@ +import {getEntryNameByKey} from '../../../../modules'; +import {createTypedAction} from '../../../gateway-utils'; +import {defaultParamsSerializer} from '../../../utils'; +import { + listDirectoryArgsSchema, + listDirectoryResultSchema, + listDirectoryTransformedSchema, +} from '../../schemas/entries/list-directory'; + +export const listDirectory = createTypedAction( + { + paramsSchema: listDirectoryArgsSchema, + resultSchema: listDirectoryResultSchema, + transformedSchema: listDirectoryTransformedSchema, + }, + { + method: 'GET', + path: () => '/v1/navigation', + params: (query, headers) => ({query, headers}), + transformResponseData: (data) => ({ + hasNextPage: Boolean(data.nextPageToken), + breadCrumbs: data.breadCrumbs, + entries: data.entries.map((entry) => ({ + ...entry, + name: getEntryNameByKey({key: entry.key}), + })), + }), + paramsSerializer: defaultParamsSerializer, + }, +); diff --git a/src/shared/schema/us/actions/entries/update-entry.ts b/src/shared/schema/us/actions/entries/update-entry.ts new file mode 100644 index 0000000..5717d9a --- /dev/null +++ b/src/shared/schema/us/actions/entries/update-entry.ts @@ -0,0 +1,25 @@ +import {EntryUpdateMode} from '../../../..'; +import {createAction} from '../../../gateway-utils'; +import {filterUrlFragment} from '../../../utils'; +import type {UpdateEntryArgs, UpdateEntryResponse} from '../../types'; + +export const _updateEntry = createAction({ + method: 'POST', + path: ({entryId}) => `/v1/entries/${filterUrlFragment(entryId)}`, + params: ( + {data, meta = {}, revId, mode = EntryUpdateMode.Publish, links, annotation}, + headers, + ) => { + return { + body: { + mode, + meta, + data, + revId, + links, + annotation, + }, + headers, + }; + }, +}); diff --git a/src/shared/schema/us/actions/favorites.ts b/src/shared/schema/us/actions/favorites.ts new file mode 100644 index 0000000..2dd6039 --- /dev/null +++ b/src/shared/schema/us/actions/favorites.ts @@ -0,0 +1,49 @@ +import {getEntryNameByKey} from '../../../modules'; +import {createAction} from '../../gateway-utils'; +import {defaultParamsSerializer, filterUrlFragment} from '../../utils'; +import type { + AddFavoriteArgs, + AddFavoriteResponse, + DeleteFavoriteArgs, + DeleteFavoriteResponse, + GetFavoritesArgs, + GetFavoritesOutput, + GetFavoritesResponse, + RenameFavoriteArgs, + RenameFavoriteResponse, +} from '../types'; + +const PATH_PREFIX = '/v1'; + +export const favoritesActions = { + addFavorite: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX}/favorites/${filterUrlFragment(entryId)}`, + params: (_, headers) => ({headers}), + }), + deleteFavorite: createAction({ + method: 'DELETE', + path: ({entryId}) => `${PATH_PREFIX}/favorites/${filterUrlFragment(entryId)}`, + params: (_, headers) => ({headers}), + }), + renameFavorite: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX}/favorites/${entryId}/rename`, + params: ({name}, headers) => ({body: {name}, headers}), + }), + getFavorites: createAction({ + method: 'GET', + path: () => `${PATH_PREFIX}/favorites`, + params: (query, headers) => ({query, headers}), + transformResponseData: (data) => ({ + hasNextPage: Boolean(data.nextPageToken), + entries: data.entries.map((entry) => ({ + ...entry, + isFavorite: true, + name: getEntryNameByKey({key: entry.key}), + displayAlias: entry.displayAlias ?? entry.alias, + })), + }), + paramsSerializer: defaultParamsSerializer, + }), +}; diff --git a/src/shared/schema/us/actions/index.ts b/src/shared/schema/us/actions/index.ts new file mode 100644 index 0000000..8dae166 --- /dev/null +++ b/src/shared/schema/us/actions/index.ts @@ -0,0 +1,33 @@ +import {collectionsActions} from './collections'; +import {colorPalettesActions} from './color-palettes'; +import {editorActions} from './editor'; +import {embedsActions} from './embeds'; +import {entriesActions} from './entries'; +import {favoritesActions} from './favorites'; +import {locksActions} from './locks'; +import {operationsActions} from './operations'; +import {presetsActions} from './presets'; +import {privateActions} from './private'; +import {stateActions} from './state'; +import {templateActions} from './template'; +import {tenantActions} from './tenant'; +import {userActions} from './user'; +import {workbooksActions} from './workbooks'; + +export const actions = { + ...entriesActions, + ...presetsActions, + ...locksActions, + ...favoritesActions, + ...editorActions, + ...stateActions, + ...userActions, + ...templateActions, + ...privateActions, + ...collectionsActions, + ...workbooksActions, + ...colorPalettesActions, + ...embedsActions, + ...operationsActions, + ...tenantActions, +}; diff --git a/src/shared/schema/us/actions/locks.ts b/src/shared/schema/us/actions/locks.ts new file mode 100644 index 0000000..d1cfb55 --- /dev/null +++ b/src/shared/schema/us/actions/locks.ts @@ -0,0 +1,36 @@ +import {createAction} from '../../gateway-utils'; +import {filterUrlFragment} from '../../utils'; +import type { + CreateLockArgs, + CreateLockResponse, + DeleteLockArgs, + DeleteLockResponse, + ExtendLockArgs, + ExtendLockResponse, +} from '../types'; + +const PATH_PREFIX = '/v1'; + +export const locksActions = { + createLock: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX}/locks/${filterUrlFragment(entryId)}`, + params: ({data: body}, headers) => ({body, headers}), + }), + extendLock: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX}/locks/${filterUrlFragment(entryId)}/extend`, + params: ({data: body}, headers) => ({body, headers}), + }), + deleteLock: createAction({ + method: 'DELETE', + path: ({entryId}) => `${PATH_PREFIX}/locks/${filterUrlFragment(entryId)}`, + params: ({params: query}, headers) => { + if (typeof query !== 'object') { + return {headers}; + } + + return {query, headers}; + }, + }), +}; diff --git a/src/shared/schema/us/actions/operations.ts b/src/shared/schema/us/actions/operations.ts new file mode 100644 index 0000000..dc8edff --- /dev/null +++ b/src/shared/schema/us/actions/operations.ts @@ -0,0 +1,27 @@ +import {createAction} from '../../gateway-utils'; +import type { + FetchDatalensOperationArgs, + GetDatalensOperationArgs, + GetDatalensOperationResponse, +} from '../types'; + +const OPERATIONS_PATH_PREFIX = '/v1/operation'; + +export const operationsActions = { + // TODO: DLAPI-797 remove this action + getOperation: createAction({ + method: 'GET', + path: ({operationId}) => `${OPERATIONS_PATH_PREFIX}/${operationId}`, + params: (_, headers) => ({ + headers, + }), + }), + fetchOperation: createAction({ + method: 'POST', + path: ({operationId}) => `${OPERATIONS_PATH_PREFIX}/${operationId}`, + params: ({meta}, headers) => ({ + body: {meta}, + headers, + }), + }), +}; diff --git a/src/shared/schema/us/actions/presets.ts b/src/shared/schema/us/actions/presets.ts new file mode 100644 index 0000000..eb55224 --- /dev/null +++ b/src/shared/schema/us/actions/presets.ts @@ -0,0 +1,15 @@ +import _ from 'lodash'; + +import {createAction} from '../../gateway-utils'; +import {filterUrlFragment} from '../../utils'; +import type {GetPresetArgs, GetPresetResponse} from '../types'; + +const PATH_PREFIX = '/v1'; + +export const presetsActions = { + getPreset: createAction({ + method: 'GET', + path: ({presetId}) => `${PATH_PREFIX}/presets/${filterUrlFragment(presetId)}`, + params: (args, headers) => ({query: _.omit(args, 'presetId'), headers}), + }), +}; diff --git a/src/shared/schema/us/actions/private.ts b/src/shared/schema/us/actions/private.ts new file mode 100644 index 0000000..4c32f0f --- /dev/null +++ b/src/shared/schema/us/actions/private.ts @@ -0,0 +1,12 @@ +import {createAction} from '../../gateway-utils'; +import type {EnsurePersonalFolderIsPresentResponse} from '../types'; + +const PATH_PREFIX = '/private'; + +export const privateActions = { + _ensurePersonalFolderIsPresent: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/ensurePersonalFolderPresent`, + params: (_, headers) => ({headers}), + }), +}; diff --git a/src/shared/schema/us/actions/state.ts b/src/shared/schema/us/actions/state.ts new file mode 100644 index 0000000..7698385 --- /dev/null +++ b/src/shared/schema/us/actions/state.ts @@ -0,0 +1,24 @@ +import {createAction} from '../../gateway-utils'; +import {filterUrlFragment} from '../../utils'; +import type { + CreateDashStateArgs, + CreateDashStateResponse, + GetDashStateArgs, + GetDashStateResponse, +} from '../types'; + +const PATH_PREFIX = '/v1'; + +export const stateActions = { + getDashState: createAction({ + method: 'GET', + path: ({entryId, hash}) => + `${PATH_PREFIX}/states/${filterUrlFragment(entryId)}/${filterUrlFragment(hash)}`, + params: (_, headers) => ({headers}), + }), + createDashState: createAction({ + method: 'POST', + path: ({entryId}) => `${PATH_PREFIX}/states/${filterUrlFragment(entryId)}`, + params: ({data}, headers) => ({body: {data}, headers}), + }), +}; diff --git a/src/shared/schema/us/actions/template.ts b/src/shared/schema/us/actions/template.ts new file mode 100644 index 0000000..3b0b5e5 --- /dev/null +++ b/src/shared/schema/us/actions/template.ts @@ -0,0 +1,14 @@ +import {TIMEOUT_60_SEC} from '../../../constants'; +import {createAction} from '../../gateway-utils'; +import type {CopyTemplateArgs, CopyTemplateResponse} from '../types'; + +const PATH_PREFIX = '/v1'; + +export const templateActions = { + copyTemplate: createAction({ + method: 'POST', + path: () => `${PATH_PREFIX}/copyTemplate`, + params: (body, headers) => ({body, headers}), + timeout: TIMEOUT_60_SEC, + }), +}; diff --git a/src/shared/schema/us/actions/tenant.ts b/src/shared/schema/us/actions/tenant.ts new file mode 100644 index 0000000..d0c1159 --- /dev/null +++ b/src/shared/schema/us/actions/tenant.ts @@ -0,0 +1,18 @@ +import {createAction} from '../../gateway-utils'; +import type {SetDefaultColorPaletteArgs, SetDefaultColorPaletteResponse} from '../types'; + +const PATH_PREFIX = '/v1/tenants'; + +export const tenantActions = { + setDefaultColorPalette: createAction< + SetDefaultColorPaletteResponse, + SetDefaultColorPaletteArgs + >({ + method: 'POST', + path: () => `${PATH_PREFIX}/set-default-color-palette`, + params: (body, headers) => ({ + body, + headers, + }), + }), +}; diff --git a/src/shared/schema/us/actions/user.ts b/src/shared/schema/us/actions/user.ts new file mode 100644 index 0000000..218df5d --- /dev/null +++ b/src/shared/schema/us/actions/user.ts @@ -0,0 +1,20 @@ +import type {DLUserSettings} from '../../../types'; +import {createAction} from '../../gateway-utils'; + +const PATH_PREFIX = '/v1'; + +export const userActions = { + updateUserSettings: createAction< + {userSettings: DLUserSettings}, + {newSettings: Partial} + >({ + method: 'POST', + path: () => `${PATH_PREFIX}/userSettings`, + params: (body, headers) => ({body, headers}), + }), + getUserSettings: createAction<{userSettings: DLUserSettings}>({ + method: 'GET', + path: () => `${PATH_PREFIX}/userSettings`, + params: (_, headers) => ({headers}), + }), +}; diff --git a/src/shared/schema/us/actions/workbooks/copy-workbook.ts b/src/shared/schema/us/actions/workbooks/copy-workbook.ts new file mode 100644 index 0000000..e608c9f --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/copy-workbook.ts @@ -0,0 +1,10 @@ +import {TIMEOUT_60_SEC} from '../../../../constants'; +import {createAction} from '../../../gateway-utils'; +import type {CopyWorkbookArgs, CopyWorkbookResponse} from '../../types'; + +export const copyWorkbook = createAction({ + method: 'POST', + path: ({workbookId}) => `/v2/workbooks/${workbookId}/copy`, + params: ({collectionId, title}, headers) => ({body: {collectionId, title}, headers}), + timeout: TIMEOUT_60_SEC, +}); diff --git a/src/shared/schema/us/actions/workbooks/create-workbook.ts b/src/shared/schema/us/actions/workbooks/create-workbook.ts new file mode 100644 index 0000000..65140be --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/create-workbook.ts @@ -0,0 +1,30 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {datalensOperationSchema} from '../../schemas/operation'; +import {workbookSchema} from '../../schemas/workbooks'; + +const createWorkbookArgsSchema = z.object({ + collectionId: z.string().nullable().optional(), + title: z.string(), + description: z.string().optional(), +}); + +export const createWorkbookResultSchema = workbookSchema.extend({ + operation: datalensOperationSchema, +}); + +export const createWorkbook = createTypedAction( + { + paramsSchema: createWorkbookArgsSchema, + resultSchema: createWorkbookResultSchema, + }, + { + method: 'POST', + path: () => '/v2/workbooks', + params: ({collectionId, title, description}, headers) => ({ + body: {collectionId, title, description}, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/workbooks/delete-workbook.ts b/src/shared/schema/us/actions/workbooks/delete-workbook.ts new file mode 100644 index 0000000..e04f8ee --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/delete-workbook.ts @@ -0,0 +1,22 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {workbookSchema} from '../../schemas/workbooks'; + +const deleteWorkbookArgsSchema = z.object({ + workbookId: z.string(), +}); + +export const deleteWorkbookResultSchema = workbookSchema; + +export const deleteWorkbook = createTypedAction( + { + paramsSchema: deleteWorkbookArgsSchema, + resultSchema: deleteWorkbookResultSchema, + }, + { + method: 'DELETE', + path: ({workbookId}) => `/v2/workbooks/${workbookId}`, + params: (_, headers) => ({headers}), + }, +); diff --git a/src/shared/schema/us/actions/workbooks/delete-workbooks.ts b/src/shared/schema/us/actions/workbooks/delete-workbooks.ts new file mode 100644 index 0000000..a95b87a --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/delete-workbooks.ts @@ -0,0 +1,27 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {workbookSchema} from '../../schemas/workbooks'; + +const deleteWorkbooksArgsSchema = z.object({ + workbookIds: z.array(z.string()), +}); + +export const deleteWorkbooksResultSchema = z.object({ + workbooks: z.array(workbookSchema), +}); + +export const deleteWorkbooks = createTypedAction( + { + paramsSchema: deleteWorkbooksArgsSchema, + resultSchema: deleteWorkbooksResultSchema, + }, + { + method: 'DELETE', + path: () => '/v2/delete-workbooks', + params: ({workbookIds}, headers) => ({ + body: {workbookIds}, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/workbooks/get-workbook-entries.ts b/src/shared/schema/us/actions/workbooks/get-workbook-entries.ts new file mode 100644 index 0000000..18d4988 --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/get-workbook-entries.ts @@ -0,0 +1,33 @@ +import {createTypedAction} from '../../../gateway-utils'; +import {defaultParamsSerializer} from '../../../utils'; +import { + getWorkbookEntriesArgsSchema, + getWorkbookEntriesResultSchema, +} from '../../schemas/workbooks/get-workbook-entries'; + +export const getWorkbookEntries = createTypedAction( + { + paramsSchema: getWorkbookEntriesArgsSchema, + resultSchema: getWorkbookEntriesResultSchema, + }, + { + method: 'GET', + path: ({workbookId}) => `/v2/workbooks/${workbookId}/entries`, + params: ( + {includePermissionsInfo, page, pageSize, createdBy, orderBy, filters, scope}, + headers, + ) => ({ + query: { + includePermissionsInfo, + page, + pageSize, + orderBy, + filters, + scope, + createdBy, + }, + headers, + }), + paramsSerializer: defaultParamsSerializer, + }, +); diff --git a/src/shared/schema/us/actions/workbooks/get-workbook-shared-entries.ts b/src/shared/schema/us/actions/workbooks/get-workbook-shared-entries.ts new file mode 100644 index 0000000..cfc5606 --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/get-workbook-shared-entries.ts @@ -0,0 +1,29 @@ +import {makeUserId} from '../../../../modules'; +import {createAction} from '../../../gateway-utils'; +import {defaultParamsSerializer} from '../../../utils'; +import type {GetWorkbookEntriesArgs, GetWorkbookSharedEntriesResponse} from '../../types'; + +export const getWorkbookSharedEntries = createAction< + GetWorkbookSharedEntriesResponse, + GetWorkbookEntriesArgs +>({ + method: 'GET', + path: ({workbookId}) => `/v2/workbooks/${workbookId}/shared-entries`, + params: ( + {includePermissionsInfo, page, pageSize, onlyMy, orderBy, filters, scope}, + headers, + {ctx}, + ) => ({ + query: { + includePermissionsInfo, + page, + pageSize, + orderBy, + filters, + scope, + createdBy: onlyMy ? makeUserId(ctx.get('userId')!) : undefined, + }, + headers, + }), + paramsSerializer: defaultParamsSerializer, +}); diff --git a/src/shared/schema/us/actions/workbooks/get-workbook.ts b/src/shared/schema/us/actions/workbooks/get-workbook.ts new file mode 100644 index 0000000..25521cc --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/get-workbook.ts @@ -0,0 +1,25 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {workbookPermissionsSchema, workbookSchema} from '../../schemas/workbooks'; + +export const getWorkbookArgsSchema = z.object({ + workbookId: z.string(), + includePermissionsInfo: z.boolean().optional(), +}); + +export const getWorkbookResultSchema = workbookSchema.extend({ + permissions: workbookPermissionsSchema, +}); + +export const getWorkbook = createTypedAction( + { + paramsSchema: getWorkbookArgsSchema, + resultSchema: getWorkbookResultSchema, + }, + { + method: 'GET', + path: ({workbookId}) => `/v2/workbooks/${workbookId}`, + params: ({includePermissionsInfo}, headers) => ({query: {includePermissionsInfo}, headers}), + }, +); diff --git a/src/shared/schema/us/actions/workbooks/get-workbooks-by-ids.ts b/src/shared/schema/us/actions/workbooks/get-workbooks-by-ids.ts new file mode 100644 index 0000000..9e1ee9c --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/get-workbooks-by-ids.ts @@ -0,0 +1,20 @@ +import {createTypedAction} from '../../../gateway-utils'; +import { + getWorkbooksByIdsArgsSchema, + getWorkbooksByIdsResultSchema, +} from '../../schemas/workbooks/get-workbooks-by-ids'; + +export const getWorkbooksByIds = createTypedAction( + { + paramsSchema: getWorkbooksByIdsArgsSchema, + resultSchema: getWorkbooksByIdsResultSchema, + }, + { + method: 'POST', + path: () => '/v2/workbooks-get-list-by-ids', + params: ({workbookIds}, headers) => ({ + body: {workbookIds}, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/workbooks/get-workbooks-list.ts b/src/shared/schema/us/actions/workbooks/get-workbooks-list.ts new file mode 100644 index 0000000..5709eca --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/get-workbooks-list.ts @@ -0,0 +1,52 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {workbookPermissionsSchema, workbookSchema} from '../../schemas/workbooks'; + +const getWorkbooksListArgsSchema = z + .object({ + collectionId: z.string().nullable().optional(), + includePermissionsInfo: z.boolean().optional(), + filterString: z.string().optional(), + page: z.number().optional(), + pageSize: z.number().optional(), + orderField: z.enum(['title', 'createdAt', 'updatedAt']).optional(), + orderDirection: z.enum(['asc', 'desc']).optional(), + onlyMy: z.boolean().optional(), + }) + .optional(); + +const getWorkbooksListResultSchema = z.object({ + workbooks: z.array( + workbookSchema.extend({ + permissions: workbookPermissionsSchema.optional(), + }), + ), + nextPageToken: z.string().optional(), +}); + +export const getWorkbooksList = createTypedAction( + { + paramsSchema: getWorkbooksListArgsSchema, + resultSchema: getWorkbooksListResultSchema, + }, + { + method: 'GET', + path: () => '/v2/workbooks', + params: (args, headers) => ({ + headers, + query: args + ? { + collectionId: args.collectionId, + includePermissionsInfo: args.includePermissionsInfo, + filterString: args.filterString, + page: args.page, + pageSize: args.pageSize, + orderField: args.orderField, + orderDirection: args.orderDirection, + onlyMy: args.onlyMy, + } + : undefined, + }), + }, +); diff --git a/src/shared/schema/us/actions/workbooks/index.ts b/src/shared/schema/us/actions/workbooks/index.ts new file mode 100644 index 0000000..a9d721c --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/index.ts @@ -0,0 +1,31 @@ +import {copyWorkbook} from './copy-workbook'; +import {createWorkbook} from './create-workbook'; +import {deleteWorkbook} from './delete-workbook'; +import {deleteWorkbooks} from './delete-workbooks'; +import {getWorkbook} from './get-workbook'; +import {getWorkbookEntries} from './get-workbook-entries'; +import {getWorkbookSharedEntries} from './get-workbook-shared-entries'; +import {getWorkbooksByIds} from './get-workbooks-by-ids'; +import {getWorkbooksList} from './get-workbooks-list'; +import {migrateEntriesToWorkbookByCopy} from './migrate-entries-to-workbook-by-copy'; +import {migrateEntriesToWorkbookByTransfer} from './migrate-entries-to-workbook-by-transfer'; +import {moveWorkbook} from './move-workbook'; +import {moveWorkbooks} from './move-workbooks'; +import {updateWorkbook} from './update-workbook'; + +export const workbooksActions = { + createWorkbook, + getWorkbook, + getWorkbooksByIds, + getWorkbooksList, + updateWorkbook, + moveWorkbook, + moveWorkbooks, + deleteWorkbook, + copyWorkbook, + deleteWorkbooks, + getWorkbookEntries, + getWorkbookSharedEntries, + migrateEntriesToWorkbookByTransfer, + migrateEntriesToWorkbookByCopy, +}; diff --git a/src/shared/schema/us/actions/workbooks/migrate-entries-to-workbook-by-copy.ts b/src/shared/schema/us/actions/workbooks/migrate-entries-to-workbook-by-copy.ts new file mode 100644 index 0000000..c933682 --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/migrate-entries-to-workbook-by-copy.ts @@ -0,0 +1,11 @@ +import {createAction} from '../../../gateway-utils'; +import type {MigrateEntriesToWorkbookArgs, MigrateEntriesToWorkbookResponse} from '../../types'; + +export const migrateEntriesToWorkbookByCopy = createAction< + MigrateEntriesToWorkbookResponse, + MigrateEntriesToWorkbookArgs +>({ + method: 'POST', + path: ({workbookId}) => `/v2/workbooks/${workbookId}/migrate-copied-entries`, + params: ({entryIds}, headers) => ({body: {entryIds}, headers}), +}); diff --git a/src/shared/schema/us/actions/workbooks/migrate-entries-to-workbook-by-transfer.ts b/src/shared/schema/us/actions/workbooks/migrate-entries-to-workbook-by-transfer.ts new file mode 100644 index 0000000..db8740e --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/migrate-entries-to-workbook-by-transfer.ts @@ -0,0 +1,11 @@ +import {createAction} from '../../../gateway-utils'; +import type {MigrateEntriesToWorkbookArgs, MigrateEntriesToWorkbookResponse} from '../../types'; + +export const migrateEntriesToWorkbookByTransfer = createAction< + MigrateEntriesToWorkbookResponse, + MigrateEntriesToWorkbookArgs +>({ + method: 'POST', + path: ({workbookId}) => `/v2/workbooks/${workbookId}/migrate-entries`, + params: ({entryIds}, headers) => ({body: {entryIds}, headers}), +}); diff --git a/src/shared/schema/us/actions/workbooks/move-workbook.ts b/src/shared/schema/us/actions/workbooks/move-workbook.ts new file mode 100644 index 0000000..59bd8ec --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/move-workbook.ts @@ -0,0 +1,24 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {workbookSchema} from '../../schemas/workbooks'; + +const moveWorkbookArgsSchema = z.object({ + workbookId: z.string(), + collectionId: z.string().nullable(), + title: z.string().optional(), +}); + +export const moveWorkbookResultSchema = workbookSchema; + +export const moveWorkbook = createTypedAction( + { + paramsSchema: moveWorkbookArgsSchema, + resultSchema: moveWorkbookResultSchema, + }, + { + method: 'POST', + path: ({workbookId}) => `/v2/workbooks/${workbookId}/move`, + params: ({collectionId, title}, headers) => ({body: {collectionId, title}, headers}), + }, +); diff --git a/src/shared/schema/us/actions/workbooks/move-workbooks.ts b/src/shared/schema/us/actions/workbooks/move-workbooks.ts new file mode 100644 index 0000000..c29505f --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/move-workbooks.ts @@ -0,0 +1,28 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {workbookSchema} from '../../schemas/workbooks'; + +const moveWorkbooksArgsSchema = z.object({ + workbookIds: z.array(z.string()), + collectionId: z.string().nullable(), +}); + +export const moveWorkbooksResultSchema = z.object({ + workbooks: z.array(workbookSchema), +}); + +export const moveWorkbooks = createTypedAction( + { + paramsSchema: moveWorkbooksArgsSchema, + resultSchema: moveWorkbooksResultSchema, + }, + { + method: 'POST', + path: () => '/v2/move-workbooks', + params: ({workbookIds, collectionId}, headers) => ({ + body: {workbookIds, collectionId}, + headers, + }), + }, +); diff --git a/src/shared/schema/us/actions/workbooks/update-workbook.ts b/src/shared/schema/us/actions/workbooks/update-workbook.ts new file mode 100644 index 0000000..76907c2 --- /dev/null +++ b/src/shared/schema/us/actions/workbooks/update-workbook.ts @@ -0,0 +1,24 @@ +import z from 'zod'; + +import {createTypedAction} from '../../../gateway-utils'; +import {workbookSchema} from '../../schemas/workbooks'; + +const updateWorkbookArgsSchema = z.object({ + workbookId: z.string(), + title: z.string().optional(), + description: z.string().optional(), +}); + +export const updateWorkbookResultSchema = workbookSchema; + +export const updateWorkbook = createTypedAction( + { + paramsSchema: updateWorkbookArgsSchema, + resultSchema: updateWorkbookResultSchema, + }, + { + method: 'POST', + path: ({workbookId}) => `/v2/workbooks/${workbookId}/update`, + params: ({title, description}, headers) => ({body: {title, description}, headers}), + }, +); diff --git a/src/shared/schema/us/index.ts b/src/shared/schema/us/index.ts new file mode 100644 index 0000000..28b8cdd --- /dev/null +++ b/src/shared/schema/us/index.ts @@ -0,0 +1,9 @@ +import {getServiceEndpoints} from '../../endpoints/schema'; + +import {actions} from './actions'; + +export default { + actions, + endpoints: getServiceEndpoints('us'), + serviceName: 'us', +}; diff --git a/src/shared/schema/us/schemas/collections/get-collections-by-ids.ts b/src/shared/schema/us/schemas/collections/get-collections-by-ids.ts new file mode 100644 index 0000000..23e9f42 --- /dev/null +++ b/src/shared/schema/us/schemas/collections/get-collections-by-ids.ts @@ -0,0 +1,9 @@ +import z from 'zod'; + +import {collectionSchema} from './index'; + +export const getCollectionsByIdsArgsSchema = z.object({ + collectionIds: z.array(z.string()).min(1).max(1000), +}); + +export const getCollectionsByIdsResultSchema = z.array(collectionSchema); diff --git a/src/shared/schema/us/schemas/collections/index.ts b/src/shared/schema/us/schemas/collections/index.ts new file mode 100644 index 0000000..c8bb739 --- /dev/null +++ b/src/shared/schema/us/schemas/collections/index.ts @@ -0,0 +1,28 @@ +import z from 'zod'; + +export const collectionPermissionsSchema = z.object({ + listAccessBindings: z.boolean(), + updateAccessBindings: z.boolean(), + createSharedEntry: z.boolean(), + createCollection: z.boolean(), + createWorkbook: z.boolean(), + limitedView: z.boolean(), + view: z.boolean(), + update: z.boolean(), + copy: z.boolean(), + move: z.boolean(), + delete: z.boolean(), +}); + +export const collectionSchema = z.object({ + collectionId: z.string(), + title: z.string(), + description: z.string().nullable(), + parentId: z.string().nullable(), + tenantId: z.string(), + createdBy: z.string(), + createdAt: z.string(), + updatedBy: z.string(), + updatedAt: z.string(), + meta: z.record(z.string(), z.unknown()), +}); diff --git a/src/shared/schema/us/schemas/embeds/create-embed.ts b/src/shared/schema/us/schemas/embeds/create-embed.ts new file mode 100644 index 0000000..cfe11ba --- /dev/null +++ b/src/shared/schema/us/schemas/embeds/create-embed.ts @@ -0,0 +1,28 @@ +import z from 'zod'; + +import {registerComponentId} from '../../../../components/public-api/utils'; + +import {embedSchema, embedSettingsSchema} from './embed'; + +export const createEmbedArgsSchema = z + .object({ + title: z.string().describe('Name of the embedding.'), + embeddingSecretId: z + .string() + .describe('ID of the key for embedding used for authentication.'), + entryId: z.string().describe('ID of the entry to be privately embedded.'), + depsIds: z.array(z.string()).describe('Array of dependency entry IDs.'), + unsignedParams: z + .array(z.string()) + .describe('Array of unsigned parameters to be provided in the embedding link.'), + privateParams: z + .array(z.string()) + .describe('Array of signed parameters that are provided as part of the token.'), + publicParamsMode: z.boolean().describe('Whether default parameters mode is enabled.'), + settings: embedSettingsSchema, + }) + .openapi(registerComponentId('CreateEmbedArgs'), { + title: 'CreateEmbedArgs', + }); + +export const createEmbedResultSchema = embedSchema; diff --git a/src/shared/schema/us/schemas/embeds/delete-embed.ts b/src/shared/schema/us/schemas/embeds/delete-embed.ts new file mode 100644 index 0000000..ecde06a --- /dev/null +++ b/src/shared/schema/us/schemas/embeds/delete-embed.ts @@ -0,0 +1,19 @@ +import z from 'zod'; + +import {registerComponentId} from '../../../../components/public-api/utils'; + +export const deleteEmbedArgsSchema = z + .object({ + embedId: z.string().describe('ID of the embedding to delete.'), + }) + .openapi(registerComponentId('DeleteEmbedArgs'), { + title: 'DeleteEmbedArgs', + }); + +export const deleteEmbedResultSchema = z + .object({ + embedId: z.string().describe('ID of the deleted embedding.'), + }) + .openapi(registerComponentId('DeleteEmbedResult'), { + title: 'DeleteEmbedResult', + }); diff --git a/src/shared/schema/us/schemas/embeds/embed.ts b/src/shared/schema/us/schemas/embeds/embed.ts new file mode 100644 index 0000000..caf3c85 --- /dev/null +++ b/src/shared/schema/us/schemas/embeds/embed.ts @@ -0,0 +1,40 @@ +import z from 'zod'; + +import {registerComponentId} from '../../../../components/public-api/utils'; + +export const embedSettingsSchema = z + .object({ + enableExport: z + .boolean() + .optional() + .describe('Whether data export is enabled for the embedding.'), + }) + .openapi(registerComponentId('EmbedSettings'), { + title: 'EmbedSettings', + }); + +export const embedSchema = z + .object({ + embedId: z.string().describe('Unique identifier of the embedding.'), + title: z.string().describe('Name of the embedding.'), + embeddingSecretId: z + .string() + .describe('ID of the key for embedding used for authentication.'), + entryId: z.string().describe('ID of the entry being privately embedded.'), + depsIds: z.array(z.string()).describe('Array of dependency entry IDs.'), + unsignedParams: z + .array(z.string()) + .describe('Array of unsigned parameters to be provided in the embedding link.'), + privateParams: z + .array(z.string()) + .describe('Array of signed parameters that are provided as part of the token.'), + createdBy: z.string().describe('ID of the user who created the embedding.'), + createdAt: z.string().describe('Timestamp when the embedding was created.'), + updatedAt: z.string().describe('Timestamp when the embedding was last updated.'), + updatedBy: z.string().describe('ID of the user who was the last to update the embedding.'), + publicParamsMode: z.boolean().describe('Whether default parameters mode is enabled.'), + settings: embedSettingsSchema, + }) + .openapi(registerComponentId('Embed'), { + title: 'Embed', + }); diff --git a/src/shared/schema/us/schemas/embeds/index.ts b/src/shared/schema/us/schemas/embeds/index.ts new file mode 100644 index 0000000..6e2fc8c --- /dev/null +++ b/src/shared/schema/us/schemas/embeds/index.ts @@ -0,0 +1,4 @@ +export * from './embed'; +export * from './create-embed'; +export * from './list-embeds'; +export * from './delete-embed'; diff --git a/src/shared/schema/us/schemas/embeds/list-embeds.ts b/src/shared/schema/us/schemas/embeds/list-embeds.ts new file mode 100644 index 0000000..5f634aa --- /dev/null +++ b/src/shared/schema/us/schemas/embeds/list-embeds.ts @@ -0,0 +1,15 @@ +import z from 'zod'; + +import {registerComponentId} from '../../../../components/public-api/utils'; + +import {embedSchema} from './embed'; + +export const listEmbedsArgsSchema = z + .object({ + entryId: z.string().describe('ID of the entry to list embeddings for.'), + }) + .openapi(registerComponentId('ListEmbedsArgs'), { + title: 'ListEmbedsArgs', + }); + +export const listEmbedsResultSchema = z.array(embedSchema).describe('Array of embeddings.'); diff --git a/src/shared/schema/us/schemas/entries/get-entries.ts b/src/shared/schema/us/schemas/entries/get-entries.ts new file mode 100644 index 0000000..f604cc8 --- /dev/null +++ b/src/shared/schema/us/schemas/entries/get-entries.ts @@ -0,0 +1,86 @@ +import z from 'zod'; + +import {permissionsSchema} from '../../../../zod-schemas/permissions'; + +export const getEntriesArgsSchema = z + .object({ + excludeLocked: z.boolean().optional(), + includeData: z.boolean().optional(), + includeLinks: z.boolean().optional(), + filters: z + .object({ + name: z.string().optional(), + }) + .optional(), + orderBy: z + .object({ + field: z.enum(['createdAt', 'name']), + direction: z.enum(['desc', 'asc']), + }) + .optional(), + createdBy: z.union([z.string(), z.array(z.string())]).optional(), + page: z.number().optional(), + pageSize: z.number().optional(), + includePermissionsInfo: z.boolean().optional(), + ignoreWorkbookEntries: z.boolean().optional(), + }) + .and( + z.union([ + z.object({ + scope: z.string(), + ids: z.union([z.string(), z.array(z.string())]).optional(), + }), + z.object({ + scope: z.string().optional(), + ids: z.union([z.string(), z.array(z.string())]), + }), + ]), + ); + +const getEntriesLockedEntry = z.object({ + isLocked: z.literal(true), + entryId: z.string(), + scope: z.string(), + type: z.string(), +}); + +const getEntriesEntry = z.object({ + entryId: z.string(), + key: z.string(), + scope: z.string(), + type: z.string(), + meta: z.record(z.string(), z.unknown()).nullable(), + createdAt: z.string(), + updatedAt: z.string(), + createdBy: z.string(), + updatedBy: z.string(), + savedId: z.string(), + publishedId: z.string().nullable(), + hidden: z.boolean(), + workbookId: z.string().nullable(), + workbookTitle: z.string().nullable().optional(), + collectionId: z.string().nullable(), + collectionTitle: z.string().nullable().optional(), + isFavorite: z.boolean(), + isLocked: z.literal(false).optional(), + permissions: permissionsSchema.optional(), + links: z.record(z.string(), z.string()).nullable(), + data: z.record(z.string(), z.unknown()).optional(), +}); + +export const getEntriesEntryOutputSchema = z.union([getEntriesLockedEntry, getEntriesEntry]); + +export const getEntriesEntryResponseSchema = z.union([ + getEntriesLockedEntry.extend({name: z.string()}), + getEntriesEntry.extend({name: z.string()}), +]); + +export const getEntriesResultSchema = z.object({ + nextPageToken: z.string().optional(), + entries: z.array(getEntriesEntryOutputSchema), +}); + +export const getEntriesTransformedSchema = z.object({ + hasNextPage: z.boolean(), + entries: z.array(getEntriesEntryResponseSchema), +}); diff --git a/src/shared/schema/us/schemas/entries/list-directory.ts b/src/shared/schema/us/schemas/entries/list-directory.ts new file mode 100644 index 0000000..d4e53bd --- /dev/null +++ b/src/shared/schema/us/schemas/entries/list-directory.ts @@ -0,0 +1,69 @@ +import z from 'zod'; + +import {permissionsSchema} from '../../../../zod-schemas/permissions'; + +export const listDirectoryArgsSchema = z.object({ + path: z.string().optional(), + createdBy: z.union([z.string(), z.array(z.string())]).optional(), + orderBy: z + .object({ + field: z.enum(['createdAt', 'name']), + direction: z.enum(['desc', 'asc']), + }) + .optional(), + filters: z + .object({ + name: z.string().optional(), + }) + .optional(), + page: z.number().optional(), + pageSize: z.number().optional(), + includePermissionsInfo: z.boolean().optional(), +}); + +export const listDirectoryBreadCrumbSchema = z.object({ + title: z.string(), + path: z.string(), + entryId: z.string(), + isLocked: z.boolean(), + permissions: permissionsSchema, +}); + +export const listDirectoryEntryOutputSchema = z.object({ + entryId: z.string(), + key: z.string(), + scope: z.string(), + type: z.string(), + meta: z.record(z.string(), z.unknown()).nullable(), + createdAt: z.string(), + updatedAt: z.string(), + createdBy: z.string(), + updatedBy: z.string(), + savedId: z.string(), + publishedId: z.string().nullable(), + hidden: z.boolean(), + workbookId: z.string(), + workbookTitle: z.string().nullable().optional(), + collectionId: z.string().nullable(), + collectionTitle: z.string().nullable().optional(), + isFavorite: z.boolean(), + isLocked: z.boolean(), + permissions: permissionsSchema.optional(), + name: z.string(), +}); + +export const listDirectoryEntryResponseSchema = listDirectoryEntryOutputSchema.extend({ + name: z.string(), +}); + +export const listDirectoryResultSchema = z.object({ + nextPageToken: z.boolean(), + breadCrumbs: z.array(listDirectoryBreadCrumbSchema), + entries: z.array(listDirectoryEntryOutputSchema), +}); + +export const listDirectoryTransformedSchema = z.object({ + hasNextPage: z.boolean(), + breadCrumbs: z.array(listDirectoryBreadCrumbSchema), + entries: z.array(listDirectoryEntryResponseSchema), +}); diff --git a/src/shared/schema/us/schemas/operation/index.ts b/src/shared/schema/us/schemas/operation/index.ts new file mode 100644 index 0000000..549a8c4 --- /dev/null +++ b/src/shared/schema/us/schemas/operation/index.ts @@ -0,0 +1,16 @@ +import z from 'zod'; + +const timeSchema = z.object({ + seconds: z.string(), + nanos: z.number().optional(), +}); + +export const datalensOperationSchema = z.object({ + id: z.string(), + description: z.string(), + createdBy: z.string(), + createdAt: timeSchema, + modifiedAt: timeSchema, + metadata: z.object({}), + done: z.boolean(), +}); diff --git a/src/shared/schema/us/schemas/workbooks/get-workbook-entries.ts b/src/shared/schema/us/schemas/workbooks/get-workbook-entries.ts new file mode 100644 index 0000000..9c6c397 --- /dev/null +++ b/src/shared/schema/us/schemas/workbooks/get-workbook-entries.ts @@ -0,0 +1,53 @@ +import z from 'zod'; + +import {EntryScope} from '../../../..'; +import {permissionsSchema} from '../../../../zod-schemas/permissions'; + +export const workbookEntrySchema = z.object({ + entryId: z.string(), + scope: z.enum(EntryScope), + type: z.string(), + key: z.string().nullable(), + displayKey: z.string().nullable(), + createdBy: z.string(), + createdAt: z.string(), + updatedBy: z.string(), + updatedAt: z.string(), + savedId: z.string().nullable(), + publishedId: z.string().nullable(), + revId: z.string(), + meta: z.record(z.string(), z.unknown()).nullable(), + hidden: z.boolean().nullable(), + workbookId: z.string().nullable(), + collectionId: z.string().nullable(), + tenantId: z.string().nullable(), + isFavorite: z.boolean(), + isLocked: z.boolean(), + permissions: permissionsSchema.optional(), + mirrored: z.boolean().nullable(), +}); + +export const getWorkbookEntriesArgsSchema = z.object({ + workbookId: z.string(), + includePermissionsInfo: z.boolean().optional(), + page: z.number().optional(), + pageSize: z.number().optional(), + createdBy: z.string().optional(), + scope: z.union([z.string(), z.array(z.string())]).optional(), + orderBy: z + .object({ + field: z.enum(['name', 'createdAt']), + direction: z.enum(['asc', 'desc']), + }) + .optional(), + filters: z + .object({ + name: z.string(), + }) + .optional(), +}); + +export const getWorkbookEntriesResultSchema = z.object({ + entries: z.array(workbookEntrySchema), + nextPageToken: z.string().optional(), +}); diff --git a/src/shared/schema/us/schemas/workbooks/get-workbooks-by-ids.ts b/src/shared/schema/us/schemas/workbooks/get-workbooks-by-ids.ts new file mode 100644 index 0000000..ce55f53 --- /dev/null +++ b/src/shared/schema/us/schemas/workbooks/get-workbooks-by-ids.ts @@ -0,0 +1,9 @@ +import z from 'zod'; + +import {workbookSchema} from './index'; + +export const getWorkbooksByIdsArgsSchema = z.object({ + workbookIds: z.array(z.string()).min(1).max(1000), +}); + +export const getWorkbooksByIdsResultSchema = z.array(workbookSchema); diff --git a/src/shared/schema/us/schemas/workbooks/index.ts b/src/shared/schema/us/schemas/workbooks/index.ts new file mode 100644 index 0000000..672a078 --- /dev/null +++ b/src/shared/schema/us/schemas/workbooks/index.ts @@ -0,0 +1,34 @@ +import z from 'zod'; + +import {WORKBOOK_STATUS} from '../../../../constants/workbooks'; + +export const workbookSchema = z.object({ + workbookId: z.string(), + collectionId: z.string().nullable(), + title: z.string(), + description: z.string().nullable(), + tenantId: z.string(), + meta: z + .object({ + importId: z.string().optional(), + }) + .and(z.record(z.string(), z.unknown())), + createdBy: z.string(), + createdAt: z.string(), + updatedBy: z.string(), + updatedAt: z.string(), + status: z.enum(WORKBOOK_STATUS).optional(), +}); + +export const workbookPermissionsSchema = z.object({ + listAccessBindings: z.boolean(), + updateAccessBindings: z.boolean(), + limitedView: z.boolean(), + view: z.boolean(), + update: z.boolean(), + copy: z.boolean(), + move: z.boolean(), + publish: z.boolean(), + embed: z.boolean(), + delete: z.boolean(), +}); diff --git a/src/shared/schema/us/types/access-bindings.ts b/src/shared/schema/us/types/access-bindings.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/schema/us/types/collections.ts b/src/shared/schema/us/types/collections.ts new file mode 100644 index 0000000..ab40d7f --- /dev/null +++ b/src/shared/schema/us/types/collections.ts @@ -0,0 +1,131 @@ +import type z from 'zod'; + +import type {CollectionItemEntities} from '../../../constants'; +import type {createCollectionResultSchema} from '../actions/collections/create-collection'; +import type {deleteCollectionResultSchema} from '../actions/collections/delete-collection'; +import type {deleteCollectionsResultSchema} from '../actions/collections/delete-collections'; +import type { + getCollectionArgsSchema, + getCollectionResultSchema, +} from '../actions/collections/get-collection'; +import type {getCollectionBreadcrumbsResultSchema} from '../actions/collections/get-collection-breadcrumbs'; +import type {getRootCollectionPermissionsResultSchema} from '../actions/collections/get-root-collection-permissions'; +import type {moveCollectionResultSchema} from '../actions/collections/move-collection'; +import type {moveCollectionsResultSchema} from '../actions/collections/move-collections'; +import type {updateCollectionResultSchema} from '../actions/collections/update-collection'; + +import type { + SharedEntryFieldsWithOptionalPermissions, + SharedEntryFieldsWithPermissions, +} from './fields'; +import type {OrderBasicField, OrderDirection} from './sort'; +import type { + ExtendedWorkbookWithOptionalPermissions, + ExtendedWorkbookWithPermissions, +} from './workbooks'; + +export type GetStructureItemsMode = 'all' | 'onlyCollections' | 'onlyWorkbooks' | 'onlyEntries'; + +export type CollectionPermissions = { + listAccessBindings: boolean; + updateAccessBindings: boolean; + createSharedEntry: boolean; + createCollection: boolean; + createWorkbook: boolean; + limitedView: boolean; + view: boolean; + update: boolean; + copy: boolean; + move: boolean; + delete: boolean; +}; + +export type Collection = { + collectionId: string; + title: string; + description: string | null; + parentId: string | null; + tenantId: string; + createdBy: string; + createdAt: string; + updatedBy: string; + updatedAt: string; + meta: Record; +}; + +export type CollectionWithPermissions = Collection & { + permissions: CollectionPermissions; +}; + +export type CollectionWithOptionalPermissions = Collection & { + permissions?: CollectionPermissions; +}; + +export type ExtendedCollection = Collection & { + entity?: typeof CollectionItemEntities.COLLECTION; +}; + +export type ExtendedCollectionWithPermissions = CollectionWithPermissions & { + entity?: typeof CollectionItemEntities.COLLECTION; +}; + +export type ExtendedCollectionWithOptionalPermissions = Omit< + ExtendedCollectionWithPermissions, + 'permissions' +> & { + permissions?: CollectionPermissions; +}; + +export type GetRootCollectionPermissionsResponse = z.infer< + typeof getRootCollectionPermissionsResultSchema +>; + +export type CreateCollectionResponse = z.infer; + +export type GetCollectionArgs = z.infer; + +export type GetCollectionResponse = z.infer; + +export type GetStructureItemsArgs = { + collectionId: string | null; + page?: string | null; + filterString?: string; + orderField?: OrderBasicField; + orderDirection?: OrderDirection; + onlyMy?: boolean; + mode?: GetStructureItemsMode; + pageSize?: number; + includePermissionsInfo?: boolean; +}; + +export type StructureItemWithPermissions = + | ExtendedCollectionWithPermissions + | ExtendedWorkbookWithPermissions + | SharedEntryFieldsWithPermissions; + +export type StructureItemWithOptionalPermissions = + | ExtendedCollectionWithOptionalPermissions + | ExtendedWorkbookWithOptionalPermissions + | SharedEntryFieldsWithOptionalPermissions; + +export type StructureItem = + | ExtendedCollectionWithOptionalPermissions + | ExtendedWorkbookWithOptionalPermissions + | SharedEntryFieldsWithOptionalPermissions; + +export type GetStructureItemsResponse = { + items: StructureItemWithOptionalPermissions[]; + nextPageToken?: string | null; +}; + +export type GetCollectionBreadcrumbsResponse = z.infer; + +export type DeleteCollectionResponse = z.infer; + +export type MoveCollectionResponse = z.infer; + +export type MoveCollectionsResponse = z.infer; + +export type DeleteCollectionsResponse = z.infer; + +export type UpdateCollectionResponse = z.infer; diff --git a/src/shared/schema/us/types/color-palettes.ts b/src/shared/schema/us/types/color-palettes.ts new file mode 100644 index 0000000..2c560f6 --- /dev/null +++ b/src/shared/schema/us/types/color-palettes.ts @@ -0,0 +1,30 @@ +import type {ColorPalette} from '../../../types/color-palettes'; + +export type CreateColorPaletteArgs = { + displayName: string; + colors: string[]; + isDefault: boolean; + isGradient: boolean; +}; + +export type CreateColorPaletteResponse = ColorPalette; + +export type GetColorPalettesListArgs = undefined; + +export type GetColorPalettesListResponse = ColorPalette[]; + +export type UpdateColorPaletteArgs = { + colorPaletteId: string; + displayName: string; + colors: string[]; + isDefault: boolean; + isGradient: boolean; +}; + +export type UpdateColorPaletteResponse = ColorPalette; + +export type DeleteColorPaletteArgs = { + colorPaletteId: string; +}; + +export type DeleteColorPaletteResponse = {status: 'ok'}; diff --git a/src/shared/schema/us/types/common.ts b/src/shared/schema/us/types/common.ts new file mode 100644 index 0000000..b72702f --- /dev/null +++ b/src/shared/schema/us/types/common.ts @@ -0,0 +1,18 @@ +export interface EntriesFilters { + name?: string; +} + +export interface EntriesOrderBy { + field: 'createdAt' | 'name'; + direction: 'desc' | 'asc'; +} + +export interface EntriesCommonArgs { + filters?: EntriesFilters; + orderBy?: EntriesOrderBy; + createdBy?: string | string[]; + page?: number; + pageSize?: number; + includePermissionsInfo?: boolean; + ignoreWorkbookEntries?: boolean; +} diff --git a/src/shared/schema/us/types/editor.ts b/src/shared/schema/us/types/editor.ts new file mode 100644 index 0000000..192af4b --- /dev/null +++ b/src/shared/schema/us/types/editor.ts @@ -0,0 +1,37 @@ +import type {EntryAnnotationArgs, EntryUpdateMode} from '../../..'; + +import type {EntryFieldData, EntryFieldLinks, EntryFieldMeta, EntryFields} from './fields'; + +export interface CreateEditorChartResponse extends EntryFields { + links: EntryFieldLinks; +} + +export interface CreateEditorChartArgs { + version?: number; + type: string; + data: EntryFieldData; + key?: string; + meta?: EntryFieldMeta; + workbookId?: string; + name?: string; + mode?: EntryUpdateMode; + links?: EntryFieldLinks; + description?: string; + annotation?: EntryAnnotationArgs; +} + +export interface UpdateEditorChartResponse extends EntryFields { + links?: EntryFieldLinks; +} + +export interface UpdateEditorChartArgs { + version?: number; + entryId: string; + mode: 'save' | 'publish'; + data: EntryFieldData; + revId?: string; + meta?: EntryFieldMeta; + links?: EntryFieldLinks; + description?: string; + annotation?: EntryAnnotationArgs; +} diff --git a/src/shared/schema/us/types/embeds.ts b/src/shared/schema/us/types/embeds.ts new file mode 100644 index 0000000..c3fd85f --- /dev/null +++ b/src/shared/schema/us/types/embeds.ts @@ -0,0 +1,25 @@ +import type z from 'zod'; + +import type { + createEmbedArgsSchema, + createEmbedResultSchema, + deleteEmbedArgsSchema, + deleteEmbedResultSchema, + embedSchema, + listEmbedsArgsSchema, + listEmbedsResultSchema, +} from '../schemas/embeds'; + +export type Embed = z.infer; + +export type CreateEmbedArgs = z.infer; + +export type CreateEmbedResponse = z.infer; + +export type ListEmbedsArgs = z.infer; + +export type ListEmbedsResponse = z.infer; + +export type DeleteEmbedArgs = z.infer; + +export type DeleteEmbedResponse = z.infer; diff --git a/src/shared/schema/us/types/entries.ts b/src/shared/schema/us/types/entries.ts new file mode 100644 index 0000000..8b01e91 --- /dev/null +++ b/src/shared/schema/us/types/entries.ts @@ -0,0 +1,405 @@ +import type z from 'zod'; + +import type { + CollectionItemEntities, + EntryAnnotationArgs, + EntryScope, + EntryUpdateMode, + WorkbookId, +} from '../../..'; +import type {Permissions} from '../../../types'; +import type { + getEntriesEntryResponseSchema, + getEntriesTransformedSchema, +} from '../schemas/entries/get-entries'; +import type { + listDirectoryBreadCrumbSchema, + listDirectoryEntryResponseSchema, + listDirectoryTransformedSchema, +} from '../schemas/entries/list-directory'; + +import type {EntriesCommonArgs} from './common'; +import type { + EntryFieldData, + EntryFieldLinks, + EntryFieldMeta, + EntryFields, + EntryMetaFields, + EntryNavigationFields, + EntryRelationFields, + SharedEntryPermissions, + SharedEntryRelationFields, +} from './fields'; + +export interface GetEntryResponse extends EntryFields { + isFavorite: boolean; + permissions?: Permissions; + fullPermissions?: SharedEntryPermissions; + isLocked?: boolean; + links?: EntryFieldLinks; + parentDashEntryId?: string; + parentDashName?: string; +} + +export interface GetEntryArgs { + entryId: string; + workbookId?: WorkbookId; + revId?: string; + branch?: string; + includePermissionsInfo?: boolean; + includeLinks?: boolean; + includeFavorite?: boolean; + includeDlComponentUiData?: boolean; + bindedDatasetId?: string | null; +} + +export interface PrivateGetEntryArgs extends GetEntryArgs { + usMasterToken: string; + branch?: 'published' | 'saved'; +} + +export interface ProxyCreateEntryArgs { + usMasterToken?: string; + workbookId?: string; + data: EntryFieldData; + name: string; + type: string; + scope: string; + mode: string; + links: EntryFieldLinks; + key?: string; + includePermissionsInfo?: boolean; + recursion?: boolean; +} + +export interface GetEntryByKeyResponse extends Omit {} +export interface GetEntryByKeyArgs extends Omit { + key: string; +} + +export interface GetEntryMetaResponse extends EntryMetaFields {} +export interface GetEntryMetaArgs { + entryId: string; + bindedWorkbookId?: string | null; + bindedDatasetId?: string | null; +} + +export interface GetRevisionsEntry extends EntryNavigationFields { + revId: string; +} +export interface GetRevisionsOutput { + entries: GetRevisionsEntry[]; + nextPageToken?: string; +} +export interface GetRevisionsResponse { + entries: GetRevisionsEntry[]; + hasNextPage: boolean; +} +export interface GetRevisionsArgs { + entryId: string; + pageSize?: number; + page?: number; + revIds?: string[]; +} + +export type ListDirectoryEntryResponse = z.infer; + +export type ListDirectoryEntryWithPermissions = Omit & { + permissions: Permissions; +}; + +export type ListDirectoryBreadCrumb = z.infer; + +export type ListDirectoryResponse = z.infer; + +export type GetEntriesEntryResponse = z.infer; + +export type GetEntriesEntryWithExcludedLocked = Extract< + GetEntriesEntryResponse, + {isLocked?: false} +>; + +export type GetEntriesEntryWithExcludedLockedAndPermissions = Omit< + GetEntriesEntryWithExcludedLocked, + 'permissions' +> & { + permissions: Permissions; +}; + +export type GetEntriesResponse = z.infer; + +export type GetEntriesArgs = EntriesCommonArgs & { + excludeLocked?: boolean; + includeData?: boolean; + includeLinks?: boolean; +} & ({scope: string; ids?: string | string[]} | {scope?: never; ids: string[]}); + +export type MoveEntryResponse = EntryFields[]; + +export type GetSharedEntryInfoResponse = Pick< + GetEntryResponse, + 'permissions' | 'fullPermissions' | 'entryId' | 'workbookId' | 'key' | 'type' +> & { + scope: EntryScope.Connection | EntryScope.Dataset; + collectionId: string; +}; + +export type DeleteSharedEntriesResponse = { + entries: EntryFields[]; +}; +export type MoveSharedEntriesResponse = { + entries: EntryFields[]; +}; + +export interface MoveEntryArgs { + entryId: string; + destination: string; + name?: string; +} + +export interface MoveSharedEntryArgs { + entryId: string; + collectionId: string; + name?: string; +} + +export interface DeleteSharedEntriesArgs { + entryIds: string[]; +} + +export interface GetSharedEntryInfoArgs { + entryId: string; + includePermissionsInfo: boolean; +} + +export interface MoveSharedEntriesArgs { + entryIds: string[]; + collectionId: string; +} + +export interface CopyEntry extends EntryFields { + links: EntryFieldLinks; + oldEntryId: string; +} + +export type CopyEntryResponse = CopyEntry[]; + +export interface CopyEntryArgs { + entryId: string; + destination: string; + name?: string; +} + +export type CopyWorkbookEntryResponse = EntryFields; + +export interface CopyWorkbookEntryArgs { + entryId: string; + name?: string; +} + +export type RenameEntryResponse = EntryFields[]; + +export interface RenameEntryArgs { + entryId: string; + name: string; +} + +export interface CreateFolderResponse extends EntryFields { + links: EntryFieldLinks; +} + +export interface CreateFolderArgs { + key: string; +} + +export interface DeleteUSEntryResponse extends EntryFields { + deletedAt: string; + isDeleted: boolean; +} + +export interface DeleteUSEntryArgs { + entryId: string; + lockToken?: string; + scope?: EntryScope; + types?: string[]; +} + +interface GetRelationsEntryOutput extends EntryRelationFields { + createdAt: string; + permissions?: Permissions; +} + +export interface GetRelationsEntry extends GetRelationsEntryOutput { + name: string; +} + +export type GetRelationsOutput = GetRelationsEntryOutput[]; + +export type GetRelationsResponse = GetRelationsEntry[]; + +export interface GetRelationsArgs { + entryId: string; + direction?: 'parent' | 'child'; + includePermissionsInfo?: boolean; + excludeUnregistredDlsEntries?: boolean; +} + +export interface SwitchPublicationStatusEntry extends EntryMetaFields { + public: boolean; + isLocked: boolean; +} + +export type SwitchPublicationStatusResponse = SwitchPublicationStatusEntry[]; + +export interface SwitchPublicationStatusArgs { + entries: { + entryId: string; + publish: boolean; + }[]; + mainEntry?: { + entryId: string; + unversionedData: unknown; + }; +} + +export type EntryByKeyPattern = Pick; + +export type GetEntriesByKeyPatternResponse = EntryByKeyPattern[]; + +export type GetEntriesByKeyPatternArgs = { + keyPattern: string; +}; + +export type GetRelationsGraphArgs = { + entryId: string; +}; + +export type GetRelationsGraphResponse = EntryRelationFields[]; + +export interface CopyEntriesToWorkbookArgs { + entryIds: string[]; + workbookId: string; +} + +export interface CopyEntriesToWorkbookResponse { + workbookId: string; +} + +export type GetEntriesAnnotationResponse = Array< + | { + entryId: string; + result: { + scope: EntryScope; + type: string; + annotation: { + description?: string; + }; + }; + } + | { + entryId: string; + error: { + code: string; + message: string; + details?: unknown; + }; + } +>; + +export interface GetEntriesAnnotationArgs { + entryIds: string[]; + scope?: EntryScope; + type?: string; +} + +export interface CreateEntryResponse extends EntryFields { + links: EntryFieldLinks; +} + +export interface CreateEntryArgs { + scope: EntryScope; + type: string; + data: EntryFieldData; + key?: string; + meta?: EntryFieldMeta; + workbookId?: string; + name?: string; + mode?: EntryUpdateMode; + links?: EntryFieldLinks; + description?: string; + annotation?: EntryAnnotationArgs; +} + +export interface UpdateEntryResponse extends EntryFields { + links?: EntryFieldLinks; +} + +export interface UpdateEntryArgs { + entryId: string; + mode: EntryUpdateMode; + data: EntryFieldData; + revId?: string; + meta?: EntryFieldMeta; + links?: EntryFieldLinks; + description?: string; + annotation?: EntryAnnotationArgs; + skipSyncLinks?: boolean; +} + +export interface EntityBindingsResponse { + sourceId: string; + targetId: string; + isDelegated: boolean; +} + +export interface EntityBindingsArgs { + sourceId: string; + targetId: string; + delegation: boolean; + bindedWorkbookId?: string | null; +} + +export type GetSharedEntryBindingsArgs = { + entryId: string; + entryAs: 'target' | 'source'; + mode?: 'all' | 'onlyWorkbooks' | 'onlyEntries'; + filterString?: string; + page?: number; + pageSize?: number; + includePermissionsInfo?: boolean; +}; + +export type SharedEntry = { + entity: typeof CollectionItemEntities.ENTRY; + entryId: string; + scope: string; + type: string; + displayKey: string; + key: string; +}; + +type Workbook = { + entity: typeof CollectionItemEntities.WORKBOOK; + title: string; +}; + +export type SharedEntryBindingsItem = { + collectionTitle: string; + collectionId: string; + workbookId: WorkbookId; + isDelegated: boolean; +} & (SharedEntry | Workbook); + +export type GetSharedEntryBindingsResponse = { + items: SharedEntryBindingsItem[]; +}; + +export type GetSharedEntryWorkbookRelationsArgs = { + entryId: string; + workbookId: string; + scope?: `${EntryScope}`; +}; + +export type GetSharedEntryWorkbookRelationsResponse = { + relations: SharedEntryRelationFields[]; +}; diff --git a/src/shared/schema/us/types/favorites.ts b/src/shared/schema/us/types/favorites.ts new file mode 100644 index 0000000..82726f5 --- /dev/null +++ b/src/shared/schema/us/types/favorites.ts @@ -0,0 +1,60 @@ +import type {Permissions} from '../../../types'; + +import type {EntriesCommonArgs} from './common'; +import type {EntryFavoriteFields} from './fields'; + +export interface AddFavoriteResponse { + entryId: string; + createdAt: string; + login: string; + tenantId: string; +} + +export interface RenameFavoriteResponse { + entryId: string; + createdAt: string; + login: string; + tenantId: string; +} + +export interface AddFavoriteArgs { + entryId: string; +} + +export interface RenameFavoriteArgs { + entryId: string; + name: string | null; +} + +export type DeleteFavoriteResponse = AddFavoriteResponse[]; + +export interface DeleteFavoriteArgs { + entryId: string; +} + +export interface GetFavoritesEntryOutput extends EntryFavoriteFields { + isLocked: boolean; + permissions?: Permissions; +} + +export interface GetFavoritesEntryResponse extends GetFavoritesEntryOutput { + isFavorite: boolean; + name: string; +} + +export interface GetFavoritesEntryWithPermissions + extends Omit { + permissions: Permissions; + displayAlias: string | null; +} + +export interface GetFavoritesOutput { + nextPageToken?: string; + entries: GetFavoritesEntryOutput[]; +} +export interface GetFavoritesResponse { + hasNextPage: boolean; + entries: GetFavoritesEntryResponse[]; +} + +export interface GetFavoritesArgs extends EntriesCommonArgs {} diff --git a/src/shared/schema/us/types/fields.ts b/src/shared/schema/us/types/fields.ts new file mode 100644 index 0000000..c72746e --- /dev/null +++ b/src/shared/schema/us/types/fields.ts @@ -0,0 +1,155 @@ +import type {CollectionItemEntities} from '../../../constants'; +import type {CollectionId, EntryAnnotation, SharedScope, WorkbookId} from '../../../types'; + +export type EntryFieldData> = null | T; +export type EntryFieldLinks = null | Record; +export type EntryFieldMeta> = null | T; +export type EntryFieldPublishedId = null | string; + +// corresponds to RETURN_COLUMNS from US +export interface EntryFields { + version?: number | null; + displayAlias?: string | null; + createdAt: string; + createdBy: string; + data: EntryFieldData; + entryId: string; + hidden: boolean; + mirrored?: boolean; + key: string; + meta: EntryFieldMeta; + public: boolean; + publishedId: EntryFieldPublishedId; + revId: string; + savedId: string; + scope: string; + tenantId: string; + type: string; + updatedAt: string; + updatedBy: string; + unversionedData?: unknown; + workbookId: WorkbookId; + collectionId?: CollectionId; + annotation?: EntryAnnotation | null; +} + +// corresponds to RETURN_META_COLUMNS from US +export interface EntryMetaFields { + entryId: string; + scope: string; + type: string; + key: string; + meta: EntryFieldMeta; + savedId: string; + publishedId: EntryFieldPublishedId; + tenantId: string; + workbookId: WorkbookId; + collectionId: CollectionId; + accessDescription?: string; + supportDescription?: string; +} + +// corresponds to RETURN_NAVIGATION_COLUMNS from US +export interface EntryNavigationFields { + entryId: string; + scope: string; + type: string; + key: string; + alias?: string | null; + displayAlias?: string | null; + meta: EntryFieldMeta; + createdBy: string; + createdAt: string; + updatedBy: string; + updatedAt: string; + savedId: string; + publishedId: EntryFieldPublishedId; + hidden: boolean; + workbookId: WorkbookId; + workbookTitle?: string | null; +} + +export interface SharedEntryPermissions { + copy: boolean; + createEntryBinding: boolean; + createLimitedEntryBinding: boolean; + delete: boolean; + limitedView: boolean; + listAccessBindings: boolean; + move: boolean; + update: boolean; + updateAccessBindings: boolean; + view: boolean; +} + +export interface SharedEntryFields { + collectionId: string; + updatedAt: string; + workbookId: WorkbookId; + scope: SharedScope; + type: string; + key: string; + entryId: string; + entity: typeof CollectionItemEntities.ENTRY; + displayKey: string; + title: string; +} + +export interface SharedEntryFieldsWithPermissions extends SharedEntryFields { + permissions: SharedEntryPermissions; +} + +export interface SharedEntryFieldsWithOptionalPermissions + extends Omit { + permissions?: SharedEntryPermissions; +} + +// corresponds to RETURN_FAVORITES_COLUMNS from US +export interface EntryFavoriteFields { + entryId: string; + scope: string; + type: string; + key: string; + alias?: string | null; + displayAlias?: string | null; + createdBy: string; + updatedAt: string; + createdAt: string; + hidden: boolean; + workbookId: WorkbookId; + workbookTitle?: string | null; + collectionId: CollectionId; + collectionTitle?: string | null; +} + +// corresponds to RETURN_RELATION_COLUMNS from US +export interface EntryRelationFields { + entryId: string; + scope: string; + type: string; + key: string; + meta: EntryFieldMeta; + links: EntryFieldLinks; + depth: number; + tenantId: string; + public: boolean; + workbookId: WorkbookId; + isLocked: boolean; +} + +export type SharedEntryRelationFields = Omit & { + collectionId: string; + createdAt: string; +}; + +export interface TenantSettings { + defaultColorPaletteId?: string; +} + +export interface TenantFields { + tenantId: string; + createdAt: string; + enabled: boolean; + deleting: boolean; + settings: TenantSettings; +} diff --git a/src/shared/schema/us/types/index.ts b/src/shared/schema/us/types/index.ts new file mode 100644 index 0000000..18a503a --- /dev/null +++ b/src/shared/schema/us/types/index.ts @@ -0,0 +1,17 @@ +export * from './fields'; +export * from './common'; +export * from './entries'; +export * from './presets'; +export * from './locks'; +export * from './favorites'; +export * from './editor'; +export * from './state'; +export * from './template'; +export * from './private'; +export * from './workbooks'; +export * from './collections'; +export * from './color-palettes'; +export * from './embeds'; +export * from './operations'; +export * from './sort'; +export * from './tenant'; diff --git a/src/shared/schema/us/types/locks.ts b/src/shared/schema/us/types/locks.ts new file mode 100644 index 0000000..34498fc --- /dev/null +++ b/src/shared/schema/us/types/locks.ts @@ -0,0 +1,35 @@ +export interface CreateLockResponse { + lockToken: string; +} +export interface CreateLockArgs { + entryId: string; + data: { + duration: number; + force?: boolean; + }; +} + +export interface ExtendLockResponse { + entryId: string; + lockId: string; + lockToken: string; + expiryDate: string; + login: string; +} +export interface ExtendLockArgs { + entryId: string; + data: { + lockToken: string; + duration: number; + force?: boolean; + }; +} + +export interface DeleteLockResponse extends ExtendLockResponse {} +export interface DeleteLockArgs { + entryId: string; + params: { + lockToken: string; + force?: boolean; + }; +} diff --git a/src/shared/schema/us/types/operations.ts b/src/shared/schema/us/types/operations.ts new file mode 100644 index 0000000..d450383 --- /dev/null +++ b/src/shared/schema/us/types/operations.ts @@ -0,0 +1,56 @@ +import type z from 'zod'; + +import type {DATALENS_OPERATION} from '../../../constants/operations'; +import type {AccessBindingDelta} from '../../extensions/types'; +import type {datalensOperationSchema} from '../schemas/operation'; + +export type GetDatalensOperationArgs = { + operationId: string; +}; + +export type GetDatalensOperationResponse = z.infer; + +type CreateCollectionOperationMeta = { + type: typeof DATALENS_OPERATION.CREATE_COLLECTION; +}; + +type CreateWorkbookOperationMeta = { + type: typeof DATALENS_OPERATION.CREATE_WORKBOOK; +}; + +type CreateSharedEntryOperationMeta = { + type: typeof DATALENS_OPERATION.CREATE_SHARED_ENTRY; +}; + +type CopyWorkbookOperationMeta = { + type: typeof DATALENS_OPERATION.COPY_WORKBOOK; +}; + +type UpdateCollectionAccessBindingsOperationMeta = { + type: typeof DATALENS_OPERATION.UPDATE_COLLECTION_ACCESS_BINDINGS; + deltas: AccessBindingDelta[]; +}; + +type UpdateWorkbookAccessBindingsOperationMeta = { + type: typeof DATALENS_OPERATION.UPDATE_WORKBOOK_ACCESS_BINDINGS; + deltas: AccessBindingDelta[]; +}; + +type UpdateSharedEntryAccessBindingsOperationMeta = { + type: typeof DATALENS_OPERATION.UPDATE_SHARED_ENTRY_ACCESS_BINDINGS; + deltas: AccessBindingDelta[]; +}; + +type DatalensOperationMeta = + | CreateCollectionOperationMeta + | CreateWorkbookOperationMeta + | CreateSharedEntryOperationMeta + | CopyWorkbookOperationMeta + | UpdateCollectionAccessBindingsOperationMeta + | UpdateWorkbookAccessBindingsOperationMeta + | UpdateSharedEntryAccessBindingsOperationMeta; + +export type FetchDatalensOperationArgs = { + operationId: string; + meta: DatalensOperationMeta; +}; diff --git a/src/shared/schema/us/types/presets.ts b/src/shared/schema/us/types/presets.ts new file mode 100644 index 0000000..ee95586 --- /dev/null +++ b/src/shared/schema/us/types/presets.ts @@ -0,0 +1,6 @@ +import type {MonitoringPreset} from '../../../index'; + +export type GetPresetResponse = MonitoringPreset; +export interface GetPresetArgs { + presetId: string; +} diff --git a/src/shared/schema/us/types/private.ts b/src/shared/schema/us/types/private.ts new file mode 100644 index 0000000..45e1b62 --- /dev/null +++ b/src/shared/schema/us/types/private.ts @@ -0,0 +1,3 @@ +import type {EntryFields} from './fields'; + +export type EnsurePersonalFolderIsPresentResponse = EntryFields; diff --git a/src/shared/schema/us/types/sort.ts b/src/shared/schema/us/types/sort.ts new file mode 100644 index 0000000..6dd1f48 --- /dev/null +++ b/src/shared/schema/us/types/sort.ts @@ -0,0 +1,5 @@ +export type OrderBasicField = 'title' | 'createdAt' | 'updatedAt'; + +export type OrderDirection = 'asc' | 'desc'; + +export type OrderWorkbookEntriesField = 'name' | 'createdAt'; diff --git a/src/shared/schema/us/types/state.ts b/src/shared/schema/us/types/state.ts new file mode 100644 index 0000000..8e01c23 --- /dev/null +++ b/src/shared/schema/us/types/state.ts @@ -0,0 +1,22 @@ +import type {ItemsStateAndParams} from '@gravity-ui/dashkit'; + +export type GetDashStateResponse = { + createdAt: string; + entryId: string; + hash: string; + data: ItemsStateAndParams; +}; + +export interface GetDashStateArgs { + entryId: string; + hash: string; +} + +export interface CreateDashStateResponse { + hash: string; +} + +export interface CreateDashStateArgs { + entryId: string; + data: ItemsStateAndParams; +} diff --git a/src/shared/schema/us/types/template.ts b/src/shared/schema/us/types/template.ts new file mode 100644 index 0000000..ffa9664 --- /dev/null +++ b/src/shared/schema/us/types/template.ts @@ -0,0 +1,15 @@ +import type {EntryFieldMeta} from './fields'; + +export interface CopyTemplateResponse { + entryId: string; + workbookId?: string; +} + +export interface CopyTemplateArgs { + templateName: string; + connectionId?: string; + workbookId?: string; + path?: string; + postfix?: string; + meta?: NonNullable; +} diff --git a/src/shared/schema/us/types/tenant.ts b/src/shared/schema/us/types/tenant.ts new file mode 100644 index 0000000..3cfabb0 --- /dev/null +++ b/src/shared/schema/us/types/tenant.ts @@ -0,0 +1,20 @@ +import type {TenantFields, TenantSettings} from './fields'; + +export type SetDefaultColorPaletteArgs = { + defaultColorPaletteId: string; +}; + +export type SetDefaultColorPaletteResponse = TenantFields; + +export type GetTenantDetailsArgs = { + tenantId: string; +}; + +export type GetTenantDetailsResponse = TenantFields; + +export type ChangeTenantSettingArgs = { + key: keyof TenantSettings; + value: TenantSettings[keyof TenantSettings]; +}; + +export type ChangeTenantSettingResponse = TenantFields; diff --git a/src/shared/schema/us/types/workbooks.ts b/src/shared/schema/us/types/workbooks.ts new file mode 100644 index 0000000..a48c643 --- /dev/null +++ b/src/shared/schema/us/types/workbooks.ts @@ -0,0 +1,147 @@ +import type z from 'zod'; + +import type {CollectionItemEntities} from '../../../constants'; +import type {WorkbookStatus} from '../../../constants/workbooks'; +import type {createWorkbookResultSchema} from '../actions/workbooks/create-workbook'; +import type {deleteWorkbookResultSchema} from '../actions/workbooks/delete-workbook'; +import type {deleteWorkbooksResultSchema} from '../actions/workbooks/delete-workbooks'; +import type { + getWorkbookArgsSchema, + getWorkbookResultSchema, +} from '../actions/workbooks/get-workbook'; +import type {moveWorkbookResultSchema} from '../actions/workbooks/move-workbook'; +import type {moveWorkbooksResultSchema} from '../actions/workbooks/move-workbooks'; +import type {updateWorkbookResultSchema} from '../actions/workbooks/update-workbook'; +import type {workbookEntrySchema} from '../schemas/workbooks/get-workbook-entries'; + +import type {GetEntryResponse} from './entries'; +import type {SharedEntryPermissions} from './fields'; +import type {GetDatalensOperationResponse} from './operations'; +import type {OrderDirection, OrderWorkbookEntriesField} from './sort'; + +export type WorkbookPermission = { + listAccessBindings: boolean; + updateAccessBindings: boolean; + limitedView: boolean; + view: boolean; + update: boolean; + copy: boolean; + move: boolean; + publish: boolean; + embed: boolean; + delete: boolean; +}; + +export type Workbook = { + workbookId: string; + collectionId: string | null; + title: string; + description: string | null; + tenantId: string; + meta: {importId?: string; [key: string]: unknown}; + createdBy: string; + createdAt: string; + updatedBy: string; + updatedAt: string; + status?: WorkbookStatus; +}; + +export type WorkbookWithPermissions = Workbook & { + permissions: WorkbookPermission; +}; + +export type ExtendedWorkbook = Workbook & { + entity?: typeof CollectionItemEntities.WORKBOOK; +}; + +export type ExtendedWorkbookWithPermissions = WorkbookWithPermissions & { + entity?: typeof CollectionItemEntities.WORKBOOK; +}; + +export type ExtendedWorkbookWithOptionalPermissions = Omit< + ExtendedWorkbookWithPermissions, + 'permissions' +> & { + permissions?: WorkbookPermission; +}; + +export type CreateWorkbookResponse = z.infer; + +export type GetWorkbookArgs = z.infer; + +export type GetWorkbookResponse = z.infer; + +export type GetWorkbookEntriesArgs = { + workbookId: string; + includePermissionsInfo?: boolean; + page?: number; + pageSize?: number; + onlyMy?: boolean; + scope?: string | string[]; + orderBy?: { + field: OrderWorkbookEntriesField; + direction: OrderDirection; + }; + filters?: { + name: string; + }; +}; + +export type WorkbookEntryBase = z.infer; + +export interface SharedWorkbookEntry extends WorkbookEntryBase { + isDelegated: boolean; + fullPermissions?: SharedEntryPermissions; + isRestricted?: false; +} + +export interface RestrictedSharedWorkbookEntry + extends Pick< + SharedWorkbookEntry, + | 'entryId' + | 'collectionId' + | 'isDelegated' + | 'isLocked' + | 'scope' + | 'type' + | 'workbookId' + | 'permissions' + | 'fullPermissions' + > { + isRestricted: true; +} + +export type GetWorkbookEntriesResponse = { + entries: WorkbookEntryBase[]; + nextPageToken?: string; +}; + +export type GetWorkbookSharedEntriesResponse = { + entries: (SharedWorkbookEntry | RestrictedSharedWorkbookEntry)[]; + nextPageToken?: string; +}; + +export type UpdateWorkbookResponse = z.infer; + +export type MoveWorkbookResponse = z.infer; + +export type MoveWorkbooksResponse = z.infer; + +export type DeleteWorkbooksResponse = z.infer; + +export type DeleteWorkbookResponse = z.infer; + +export type CopyWorkbookArgs = { + workbookId: string; + collectionId?: string | null; + title?: string; +}; + +export type CopyWorkbookResponse = Workbook & {operation?: GetDatalensOperationResponse}; + +export type MigrateEntriesToWorkbookArgs = { + workbookId: string; + entryIds: string[]; +}; + +export type MigrateEntriesToWorkbookResponse = GetEntryResponse[]; diff --git a/src/shared/schema/utils.ts b/src/shared/schema/utils.ts new file mode 100644 index 0000000..1bb73b4 --- /dev/null +++ b/src/shared/schema/utils.ts @@ -0,0 +1,45 @@ +import type {ResponseError} from '@gravity-ui/gateway'; +import type {AxiosRequestConfig, AxiosResponse} from 'axios'; +import omit from 'lodash/omit'; +import qs from 'qs'; + +export function filterUrlFragment(input: string) { + return /^[a-zA-Z0-9._:-]*$/.test(input) ? input : ''; +} + +export function decodePathParams(params: TParams) { + const decodedParams: Record = {}; + + Object.keys(params).forEach((key) => { + decodedParams[key] = decodeURIComponent( + params[key as unknown as keyof TParams] as unknown as string, + ); + }); + + return decodedParams; +} + +export const defaultParamsSerializer: AxiosRequestConfig['paramsSerializer'] = (queryParams) => { + return qs.stringify(queryParams, {arrayFormat: 'repeat'}); +}; + +export function omitCloudAuthUnitHeaders( + headers: Record, + cloudIdHeader: string, + folderIdHeader: string, + tenantIdHeader: string, +): Record { + return omit(headers, cloudIdHeader, folderIdHeader, tenantIdHeader); +} + +export const transformResponseError = (response: ResponseError) => { + const data = (response.data || {}) as AxiosResponse['data']; + const {code, message} = data; + + return { + code, + message, + status: response.status, + details: {data: response.data}, + }; +}; diff --git a/src/shared/server/axios.ts b/src/shared/server/axios.ts new file mode 100644 index 0000000..c5826fd --- /dev/null +++ b/src/shared/server/axios.ts @@ -0,0 +1,15 @@ +import http from 'http'; +import https from 'https'; + +export const getAgents = ({family}: {family: 4 | 6}) => { + return { + httpAgent: new http.Agent({ + //@ts-ignore https://github.com/nodejs/node/blob/master/lib/_http_agent.js#L233 + family, + }), + httpsAgent: new https.Agent({ + //@ts-ignore https://github.com/nodejs/node/blob/master/lib/_http_agent.js#L233 + family, + }), + }; +}; diff --git a/src/shared/types/bi-api/index.ts b/src/shared/types/bi-api/index.ts new file mode 100644 index 0000000..cb50b32 --- /dev/null +++ b/src/shared/types/bi-api/index.ts @@ -0,0 +1 @@ +export * from './v2'; diff --git a/src/shared/types/bi-api/v2.ts b/src/shared/types/bi-api/v2.ts new file mode 100644 index 0000000..a2dea3f --- /dev/null +++ b/src/shared/types/bi-api/v2.ts @@ -0,0 +1,187 @@ +import type {ApiV2Annotations} from '../../constants'; +import type {DatasetUpdate, ParameterDefaultValue} from '../dataset'; +import type {Update} from '../wizard'; + +export type ApiV2Request = { + url: string; + method: 'POST' | 'GET'; + data: ApiV2RequestBody; + ui?: boolean; + cache?: number; +}; +export type ApiV2ResultDataRow = {data: string[]; legend: number[]}; +export type ApiV2ResultData = { + result_data: { + rows: ApiV2ResultDataRow[]; + }[]; + fields: ApiV2ResponseField[]; + blocks: { + query: string; + }[]; + data_export?: ApiV2DataExportField; + data_export_forbidden?: boolean; + pivot_data?: unknown; + notifications?: unknown[]; +}; + +export type ApiV2DataExportField = { + background: ApiV2DataExport; + basic: ApiV2DataExport; +}; +export type ApiV2DataExport = { + reason: null | ApiV2DataExportForbiddenReason[]; + allowed: boolean; +}; + +const API_V2_DATA_EXPORT_FORBIDDEN_REASONS = [ + 'DISABLED_EXPORT_DATASET', + 'DISABLED_EXPORT_CONNECTION', + 'DISABLED_EXPORT_TENANT', + 'PROHIBITED_EXPORT_TENANT', + 'PROHIBITED_EXPORT_CONNECTION', + 'PROHIBITED_EXPORT_PIVOT_TABLE', + 'PROHIBITED_EXPORT_QL_CHART', + 'PROHIBITED_EXPORT_TYPED_QUERY', +] as const; + +type ApiV2DataExportForbiddenReason = (typeof API_V2_DATA_EXPORT_FORBIDDEN_REASONS)[number]; + +export type ApiV2ResultField = { + data_type: string; + field_type: string; + id: string; + item_type: string; + legend_item_id: number; + role_spec: {role: string}; + title: string; +}; + +export type ApiV2RequestField = { + ref: ApiV2Ref; + block_id?: number; + legend_item_id?: number; + role_spec?: ApiV2RequestFieldRoleSpec; +}; +export type ApiV2RequestFieldRoleSpec = { + role: string; + annotation_type?: ApiV2Annotations; + level?: number; + template?: string; + prefix?: string; + dimension_values?: {legend_item_id: number; value: string}[]; + range_type?: 'min' | 'max'; + direction?: string; + + target_legend_item_ids?: number[]; +}; +export type ApiV2RequestPivotRoleSpec = {sorting?: ApiV2RequestFieldSorting} & Pick< + ApiV2RequestFieldRoleSpec, + 'annotation_type' | 'role' | 'direction' | 'target_legend_item_ids' +>; +export type ApiV2PivotRequestStructure = { + legend_item_ids: number[]; + role_spec?: ApiV2RequestPivotRoleSpec; +}; +export type ApiV2RequestPivot = { + structure: ApiV2PivotRequestStructure[]; + with_totals?: boolean; + totals?: ApiV2RequestPivotTotals; + pagination: ApiV2RequestPivotPagination; +}; +export type ApiV2RequestPivotPagination = { + offset_rows: number | undefined; + limit_rows: number | undefined; +}; +export type ApiV2ResponseField = { + id?: string; + title: string; + data_type: string; + ref: ApiV2Ref; + block_id?: number; + legend_item_id?: number; + role_spec?: { + role: string; + level?: number; + template?: string; + prefix?: string; + dimension_values?: {legend_item_id: number; value: string}[]; + }; +}; +export type ApiV2Filter = { + ref: ApiV2Ref; + operation: string; + values: string[]; + role_spec?: ApiV2RoleSpec; +}; +export type ApiV2OrderBy = { + ref: ApiV2Ref; + direction: string; + role_spec?: ApiV2RoleSpec; +}; +export type ApiV2RoleSpec = { + role: string; +}; +export type ApiV2Ref = + | { + type: 'id'; + id: string; + } + | { + type: 'title'; + title: string; + } + | { + type: 'measure_name'; + } + | { + type: 'placeholder'; + }; +export type ApiV2Parameter = { + ref: ApiV2Ref; + value: ParameterDefaultValue; + block_id?: number; +}; +export type ApiV2RequestBody = { + fields: ApiV2RequestField[]; + filters?: ApiV2Filter[]; + parameter_values?: ApiV2Parameter[]; + order_by?: ApiV2OrderBy[]; + updates?: Update[] | DatasetUpdate[]; + ignore_nonexistent_filters?: boolean; + disable_group_by?: boolean; + limit?: number; + offset?: number; + with_totals?: boolean; + autofill_legend?: boolean; + dataset_revision_id?: string; +}; +export type ApiV2RequestBodyPivot = Omit & { + pivot: ApiV2RequestPivot; + pivot_pagination: ApiV2RequestPivotPagination; +}; +export type ApiV2RequestFieldSortingSettings = { + header_values: Array<{value: string}>; + role_spec: { + role: 'data' | 'total'; + }; + direction: 'asc' | 'desc'; +}; +export type ApiV2RequestFieldSorting = { + column?: ApiV2RequestFieldSortingSettings; + row?: ApiV2RequestFieldSortingSettings; +}; +export type ApiV2RequestPivotTotalsSettings = { + level: number; +}; +export type ApiV2RequestPivotTotals = { + columns: ApiV2RequestPivotTotalsSettings[]; + rows: ApiV2RequestPivotTotalsSettings[]; +}; + +export type FieldRoleSpec = 'distinct' | 'range'; + +export type ApiV2BackgroundSettingsGuids = { + colorFieldGuid: string; + targetFieldGuid: string; + isContinuous: boolean; +}; diff --git a/src/shared/types/chartkit/common.ts b/src/shared/types/chartkit/common.ts new file mode 100644 index 0000000..45e5c2b --- /dev/null +++ b/src/shared/types/chartkit/common.ts @@ -0,0 +1,38 @@ +import type {StandardLonghandPropertiesHyphen} from 'csstype'; + +export type ChartKitCss = React.CSSProperties | StandardLonghandPropertiesHyphen; + +export type ChartkitGlobalSettings = { + highcharts?: { + enabled?: boolean; + /** Use as an external dependency (the files are loaded from CDN) */ + external?: boolean; + domain?: string; + protocol?: string; + /** List of highcharts modules to be downloaded from CDN. The setting is used only for the enabled "external" flag */ + modules?: string[]; + version?: string; + }; + + yandexMap?: { + enabled?: boolean; + }; +}; + +export type ChartComment = { + id: number; + creatorLogin: string; + createdDate: string; + modifierLogin: string; + modifiedDate: string; + feed: string; + date: string; + dateUntil?: string; + type: 'line-x' | 'band-x' | 'flag-x' | 'dot-x-y'; + text: string; + meta: Record; + params?: { + scale?: string | string[]; + region?: string | string[]; + }; +}; diff --git a/src/shared/types/chartkit/dl-chartkit.ts b/src/shared/types/chartkit/dl-chartkit.ts new file mode 100644 index 0000000..7a8322a --- /dev/null +++ b/src/shared/types/chartkit/dl-chartkit.ts @@ -0,0 +1,41 @@ +export type ChartsInsightsItemLevels = 'info' | 'warning' | 'critical'; + +export type ChartsInsightsItem = { + level: ChartsInsightsItemLevels; + title: string; + message: string; + locator: string; +}; + +type SetActionParamsEventHandler = { + type: 'setActionParams'; +}; + +type RunActivityEventHandler = { + type: 'runActivity'; +}; + +export type GoToEventHandler = { + type: 'goTo'; + target?: '_self' | '_blank'; +}; + +export type WidgetEventHandler = + | SetActionParamsEventHandler + | GoToEventHandler + | RunActivityEventHandler; + +export type GraphWidgetEventScope = 'point'; + +export type TableWidgetEventScope = 'row' | 'cell'; + +export type WidgetEvent = { + handler: WidgetEventHandler | WidgetEventHandler[]; + scope?: T; +}; + +export type GraphTooltipLine = { + originalValue: number; + seriesName: string; + value: string; +}; diff --git a/src/shared/types/chartkit/gravity-charts/index.ts b/src/shared/types/chartkit/gravity-charts/index.ts new file mode 100644 index 0000000..f25b483 --- /dev/null +++ b/src/shared/types/chartkit/gravity-charts/index.ts @@ -0,0 +1,11 @@ +import type {ChartData} from '@gravity-ui/chartkit/gravity-charts'; + +export * from './scatter'; + +export type ExtendedChartData = ChartData & { + custom?: { + tooltip?: { + headerLabel?: string; + }; + }; +}; diff --git a/src/shared/types/chartkit/gravity-charts/scatter.ts b/src/shared/types/chartkit/gravity-charts/scatter.ts new file mode 100644 index 0000000..4b199ae --- /dev/null +++ b/src/shared/types/chartkit/gravity-charts/scatter.ts @@ -0,0 +1,26 @@ +import type {WrappedHTML} from '../../..'; +import type {WrappedMarkup} from '../../../utils'; +import type {WrappedMarkdown} from '../../../utils/markdown'; +import type {SeriesExportSettings} from '../../wizard'; + +export type PointCustomData = { + name?: string | number | WrappedMarkdown | WrappedMarkup | WrappedHTML; + xLabel?: string | WrappedMarkdown | WrappedHTML; + yLabel?: string | WrappedMarkdown | WrappedHTML; + colorValue?: unknown; + cLabel?: string | null | WrappedMarkdown | WrappedHTML; + shapeValue?: unknown; + sLabel?: string | null | WrappedMarkdown | WrappedHTML | WrappedMarkup; + sizeValue?: unknown; + sizeLabel?: string | null | WrappedMarkdown | WrappedHTML; +}; + +export type ScatterSeriesCustomData = { + pointTitle?: string; + xTitle?: string; + yTitle?: string; + colorTitle?: string; + shapeTitle?: string; + sizeTitle?: string; + exportSettings?: SeriesExportSettings; +}; diff --git a/src/shared/types/chartkit/index.ts b/src/shared/types/chartkit/index.ts new file mode 100644 index 0000000..f5d0889 --- /dev/null +++ b/src/shared/types/chartkit/index.ts @@ -0,0 +1,4 @@ +export * from './common'; +export * from './dl-chartkit'; +export * from './table'; +export * from './gravity-charts'; diff --git a/src/shared/types/chartkit/table.ts b/src/shared/types/chartkit/table.ts new file mode 100644 index 0000000..b819a58 --- /dev/null +++ b/src/shared/types/chartkit/table.ts @@ -0,0 +1,155 @@ +import type {ReactElement} from 'react'; + +import type {Column} from '@gravity-ui/react-data-table'; + +import type {WrappedHTML} from '../charts'; +import type {StringParams} from '../common'; +import type {MarkupItem} from '../wizard'; + +import type {ChartKitCss} from './common'; + +type TableCellValue = MarkupItem | WrappedHTML | string | number | [number, number] | null; +export type TableValuesRow = { + values: TableCellValue[]; +}; +export type OnClickSetParams = { + action: 'setParams'; + args: StringParams; +}; + +export type OnClickShowMessage = { + action: 'showMsg'; + args: StringParams; +}; + +type TableCommonCellType = 'text' | 'date' | 'number' | 'diff' | 'diff_only' | 'markup' | 'bar'; +export type TableCellVeticalAlignment = 'top' | 'center' | 'bottom'; + +// interface used because it is expanded in chartkit.d.ts +export interface TableCommonCell { + value: TableCellValue; + treeNode?: string; + treeOffset?: number; + treeNodeState?: 'open' | 'closed'; + drillDownFilterValue?: string; + // formatted number from Wizard + formattedValue?: string; + css?: ChartKitCss; + // if the column type: 'text' + link?: { + href: string; + newWindow: boolean; + }; + onClick?: OnClickSetParams | OnClickShowMessage; + sortDirection?: 'asc' | 'desc' | null; + type?: TableCommonCellType; + fieldId?: string; + /** Reserved subspace to store options and values for customized functionality */ + custom?: Record; + verticalAlignment?: TableCellVeticalAlignment; +} + +export type BarTableCell = TableCommonCell & { + barColor?: string; + offset?: number; + showBar?: boolean; +} & BarViewOptions; +export type TableCell = TableCommonCell | BarTableCell | string; +export type TableCellsRow = { + cells: TableCell[]; +}; +export type TableRow = TableValuesRow | TableCellsRow; +export type CommonTableColumn = { + id?: string; + name: string; + formattedName?: ReactElement | WrappedHTML | string; + type: TableCommonCellType; + group?: boolean; + autogroup?: boolean; + markup?: MarkupItem; + css?: ChartKitCss; + width?: Column['width']; + /** @deprecated seems like only for type=grid */ + contentCss?: ChartKitCss; + sortable?: boolean; + allowGroupSort?: boolean; + /** Reserved subspace to store options and values for customized functionality */ + custom?: Record; + pinned?: boolean; + hint?: string; + verticalAlignment?: TableCellVeticalAlignment; +}; +export type TableColumnFormatter = { + format?: 'number' | 'percent'; + suffix?: string; + prefix?: string; + multiplier?: number; + precision?: number; + showRankDelimiter?: boolean; + unit?: 'auto' | 'k' | 'm' | 'b' | 't' | null; +}; +export type NumberViewOptions = { + view: 'number'; + formatter?: TableColumnFormatter; + precision?: number; +}; +export type BarViewOptions = { + view: 'bar'; + barHeight?: string | number; + min?: number; + max?: number; + align?: 'left' | 'right'; + showLabel?: boolean; + showSeparator?: boolean; + debug?: boolean; +}; +export type NumberTableColumn = CommonTableColumn & { + type: 'number'; +} & (NumberViewOptions | BarViewOptions); +export type DateTableColumn = CommonTableColumn & { + type: 'date'; + format?: string; + /** @deprecated */ + scale?: 'd' | 'w' | 'm' | 'h' | 'i' | 's' | 'q' | 'y'; +}; +type TextTableColumn = CommonTableColumn & { + type: 'text'; +}; +export type DiffTableColumn = CommonTableColumn & + NumberViewOptions & { + type: 'diff'; + diff_formatter?: TableColumnFormatter; + }; +type DiffOnlyTableColumn = CommonTableColumn & + Omit & { + type: 'diff_only'; + }; +// type bar derpecated, the type remained only for the fallback. +// Now everything is described in NumberTableColumn +export type BarTableColumn = CommonTableColumn & { + type: 'bar'; +}; +export type TableColumn = + | NumberTableColumn + | DateTableColumn + | TextTableColumn + | DiffTableColumn + | DiffOnlyTableColumn + | BarTableColumn; +type TableSubColumn = { + id?: string; + name: string; + formattedName?: ReactElement | WrappedHTML | string; + css?: ChartKitCss; + markup: MarkupItem; + sub: (TableColumn | TableSubColumn)[]; + width?: string; + // A reserved subspace to store options and values for customized functionality. + custom?: Record; + verticalAlignment?: TableCellVeticalAlignment; +}; +export type TableHead = TableColumn | TableSubColumn; +export type TableTitle = { + text: string; + style?: ChartKitCss; +}; diff --git a/src/shared/types/charts.ts b/src/shared/types/charts.ts new file mode 100644 index 0000000..0c36671 --- /dev/null +++ b/src/shared/types/charts.ts @@ -0,0 +1,355 @@ +import type {Highcharts} from '@gravity-ui/chartkit/highcharts'; +import type {ToastTheme} from '@gravity-ui/uikit'; + +import type {WRAPPED_HTML_KEY} from '../constants'; +import type {WidgetSize} from '../constants/charts'; +import type {WRAPPED_FN_KEY} from '../constants/ui-sandbox'; +import type {ChartsInsight, IntervalPart} from '../modules'; + +import type {StringParams} from './common'; +import type {UISandboxWrappedFunction} from './ui-sandbox'; + +const REQUIRED_THEME_VALUES = ['dark', 'light'] as const; +const OPTIONAL_THEME_VALUES = ['dark-hc', 'light-hc'] as const; +type RequiredThemes = Record<(typeof REQUIRED_THEME_VALUES)[number], Record>; +type OptionalThemes = Partial< + Record<(typeof OPTIONAL_THEME_VALUES)[number], Record> +>; + +export type ChartKitHtmlItem = { + tag: string; + style?: Record; + attributes?: Record; + content?: ChartKitHtmlItem | ChartKitHtmlItem[] | string; + theme?: RequiredThemes & OptionalThemes; +}; + +export type WrappedHTML = {[WRAPPED_HTML_KEY]: ChartKitHtmlItem | ChartKitHtmlItem[] | string}; + +export interface IChartEditor { + /** + * Return current user login + */ + getUserLogin(): string; + + /** + * Return current user lang + */ + getUserLang(): string; + + /** + * Return current user lang + */ + getLang(): string; + + /** + * Return translation for key with current user lang + */ + getTranslation( + keyset: string, + key: string, + params?: Record, + ): string; + + /** + * Return added secrets {key: value, ...} + */ + getSecrets(): {[key: string]: string}; + + /** + * Return data from tab 'Shared' + */ + getSharedData(): {[key: string]: object}; + + /** + * Return dash widget config + */ + getWidgetConfig(): DashWidgetConfig['widgetConfig']; + + /** + * Return data from tab 'Params' + */ + getParams(): StringParams; + + /** + * Return actionParams data + */ + getActionParams(): StringParams; + + /** + * Return data from tab 'Params' + */ + getParam(paramName: string): string | string[]; + /** + * Return current page number (only for tabs 'Sources' & 'Prepare') + */ + getCurrentPage(): number; + + /** + * Return current clicked column id and sort order (only for tab 'Sources') + */ + getSortParams(): SortParams; + + /** + * Return \n + * \t'__relative_-7d' return 7 day before\n + * years - y; month - M; weeks - w; days - d; hours - h; minutes - m; seconds - s; milliseconds - ms; + */ + resolveRelative(relativeStr: string, intervalPart?: IntervalPart): string | null; + + /** + * Return {from: , to: }\n + * \t'__interval___relative_-7d___relative_+30min' return {from: <7 day before>, to: }\n + * \t'__interval_2018-10-10___relative_-2w' return {from: '2018-10-10 00:00:00', to: <2 weeks before>}\n + * years - y; month - M; weeks - w; days - d; hours - h; minutes - m; seconds - s; milliseconds - ms; + */ + resolveInterval(intervalStr: string): {from: string; to: string} | null; + + resolveOperation(input: string): any; + + /** + * Update ChartKit config (only for tab 'Prepare') + */ + updateConfig(updatedFragment: object): void; + + setChartsInsights(input: ChartsInsight[]): void; + + getLoadedData(): { + [key: string]: any; + }; + + getLoadedDataStats(): { + [key: string]: any; + }; + + setError({code, details}: {code: string; details?: any}): void; + _setError({code, details}: {code: string; details?: any}): void; + + updateHighchartsConfig(config: {[key: string]: any}): void; + + setDataSourceInfo(key: string, value: object): void; + + setExtra(key: string, value: any): void; + + updateParams(params: object): void; + + updateActionParams(params: object): void; + + updateLibraryConfig(config: Record): void; + + setSideHtml(html: string): void; + + setSideMarkdown(markdown: string): void; + + setExportFilename(filename: string): void; + + wrapFn(value: any): {[WRAPPED_FN_KEY]: UISandboxWrappedFunction}; + + generateHtml(value: ChartKitHtmlItem): WrappedHTML; + + attachHandler( + handlerConfig: Record, + ): Record & {__chartkitHandler: true}; + attachFormatter( + formatterConfig: Record, + ): Record & {__chartkitFormatter: true}; + + getId?(key: string): string; +} + +export interface Link { + id: string; + fields: Record; +} + +export interface LinkField { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +} + +export interface PayloadFilter { + column: string; + operation: string; + values: string[]; + isDrillDown?: boolean; +} + +export interface FilterBody { + operation: { + code: string; + }; + value: string | string[]; +} + +export interface UrlRequest { + url: string; + data?: Record; + cache?: number; + method: 'GET' | 'POST'; + hideInInspector?: boolean; +} + +export interface SortParams { + columnId?: string; + order?: number; + meta?: Record; +} + +export interface ExtendedSeriesLineOptions extends Highcharts.SeriesLineOptions { + title: string; + shapeValue?: string; + lineColor?: Highcharts.GradientColorObject; +} + +export type ExportingColumnOptions = { + title?: string; + dataType?: string; + format?: string; +}; + +/** Additional settings for exporting the chart to xlsx/csv. + * Used in exporting.csv.columnHeaderFormatter + */ +type CustomExportingCsvOptions = { + categoryHeader?: ExportingColumnOptions; + + /** the key is the key from exporting.csv.columnHeaderFormatter (from series..keys)*/ + columnHeaderMap?: Record; +}; + +export interface ExtendedExportingCsvOptions + extends Omit { + custom?: CustomExportingCsvOptions; + + columnHeaderFormatter?: string; +} + +export type WidgetSizeType = (typeof WidgetSize)[keyof typeof WidgetSize]; + +/** + * Config of dash widget item, used for settings of actionParams (enabled filtering charts in section) + * @enable boolean - value of filtering chart widget settings + * @fields string[] - list of dataset fields (or chartEditor params) which is used for actionParams + * (will be set in dash relation dialog later), if undefined - means that using full fileds list + */ +export type DashWidgetConfig = { + widgetConfig?: { + actionParams?: { + enable?: boolean; + fields?: string[]; + }; + enableExport?: boolean; + size?: WidgetSizeType; + }; +}; + +export interface Timings { + configResolving: number | null; + dataFetching: number | null; + jsExecution: number | null; +} + +export interface ChartsStats extends Timings { + url: string; + requestId: string; + groupId: string | null; + scope: 'dash' | 'preview' | 'snapter' | null; + entryId: string; + query: string; + chartKind: 'editor' | 'wizard' | 'ql' | 'unknown'; + type: string; + // type: 'graph' | 'table' + widgetRendering: number | null; + yandexMapAPIWaiting?: number | null; + sourcesCount: number; + // type: 'graph' + graphType: string | null; + mixedGraphType: 0 | 1 | null; + pointsCount: number | null; + seriesCount: number | null; + // type: 'table' + columnsCount: number | null; + rowsCount: number | null; +} + +export const MARKUP_TYPE = { + none: 'none', + markdown: 'md', + html: 'html', +}; + +export type MarkupType = (typeof MARKUP_TYPE)[keyof typeof MARKUP_TYPE]; + +type ChartActivityToastResult = { + action: 'toast'; + title?: string; + type?: ToastTheme; + content?: string; +}; + +type ChartActivityPopupResult = { + action: 'popup'; + title: string; + content: string; +}; + +type ChartActivitySetParamsResult = { + action: 'setParams'; + params: StringParams; +}; + +export type ChartActivitiesActionResult = + | ChartActivityToastResult + | ChartActivitySetParamsResult + | ChartActivityPopupResult; + +export type ChartActivityResponseData = { + data?: ChartActivitiesActionResult | ChartActivitiesActionResult[]; + error?: { + code?: string; + message: string; + details?: { + stackTrace?: string; + }; + }; + settings?: {logError: 'toast' | 'ignore'}; + logs_v2?: string; + requestId?: string; + traceId?: string; +}; + +export type TrendLineSettings = { + method?: 'linear' | 'quadratic' | 'cubic'; + dashStyle?: string; + lineWidth?: number; + /** new series linked with original (e.g., shown/hidden together)) */ + linked?: boolean; +}; + +export type SmoothingLineSettings = { + method?: 'sma'; + windowSize?: number; + dashStyle?: string; + lineWidth?: number; + /** new series linked with original (e.g., shown/hidden together)) */ + linked?: boolean; +}; + +export type ChartStateWarning = 'dataWithNull'; + +export type ChartStateSettings = { + trends?: { + enabled?: boolean; + settings?: TrendLineSettings; + }; + smoothing?: { + enabled?: boolean; + settings?: SmoothingLineSettings; + }; +}; diff --git a/src/shared/types/color-palettes.ts b/src/shared/types/color-palettes.ts new file mode 100644 index 0000000..4d905bd --- /dev/null +++ b/src/shared/types/color-palettes.ts @@ -0,0 +1,7 @@ +export type ColorPalette = { + colorPaletteId: string; + displayName: string; + colors: string[]; + isDefault: boolean; + isGradient: boolean; +}; diff --git a/src/shared/types/common-update.ts b/src/shared/types/common-update.ts new file mode 100644 index 0000000..73ccf77 --- /dev/null +++ b/src/shared/types/common-update.ts @@ -0,0 +1,21 @@ +import type {DatasetField} from './dataset'; +import type {Field} from './wizard'; + +export type UpdateAction = + | 'add_field' + | 'add' + | 'update_field' + | 'update' + | 'delete' + | 'delete_field'; + +export type CommonUpdateField = Partial & Pick; + +export type CommonUpdate = T extends Field + ? { + action: UpdateAction; + field: CommonUpdateField; + debug_info?: string; + deleteUpdateAfterValidation?: boolean; + } + : {action: UpdateAction; field: CommonUpdateField}; diff --git a/src/shared/types/common.ts b/src/shared/types/common.ts new file mode 100644 index 0000000..bf2757d --- /dev/null +++ b/src/shared/types/common.ts @@ -0,0 +1,360 @@ +import type {RenderParams} from '@gravity-ui/app-layout'; +import type {TableSettingsData, Theme, ThemeSettings} from '@gravity-ui/uikit'; + +import type {UserRole} from '../components/auth/constants/role'; +import type {AuthPageSettings} from '../components/auth/types/layout'; +import type {AppEnvironment, AppInstallation, DeviceType, Language} from '../constants'; +import type {Palette} from '../constants/colors'; +import type {Endpoints} from '../endpoints'; +import type {ConnectorIconData} from '../schema/types'; + +import type {ChartkitGlobalSettings} from './chartkit'; +import type {DashData, DashStateData} from './dash'; +import type {EntryPublicAuthor} from './entry'; +import type {FeatureConfig} from './feature'; +import type {MenuItemsIdsType} from './menu'; +import type {MetricaCounterConfig} from './metrica'; +import type {PushServiceConfig} from './notification'; +import type {WidgetType} from './widget'; + +export type Dictionary = Record; + +export interface StringParams extends Dictionary {} + +export type Lang = 'en' | 'ru'; + +export interface LandingLayoutPageError { + errorType: string; + title?: string; + isHtmlInTitle?: boolean; + isHtmlInDescription?: boolean; + description?: string; + pageTitle?: RenderParams<{DL: DLGlobalData}>['title']; + pageMeta?: RenderParams<{DL: DLGlobalData}>['meta']; + pageLinks?: RenderParams<{DL: DLGlobalData}>['links']; + showDebugInfo?: boolean; +} + +export interface LandingPageEntryMeta { + entryId: string; + key: string; + scope: string; + type: string; + accessDescription?: string; + supportDescription?: string; +} + +export interface LandingPageSettings extends LandingLayoutPageError { + entryMeta?: LandingPageEntryMeta; +} + +export interface DLUserSettings { + email?: string; + emailConfirmed?: boolean; + phone?: string; + cloudId?: string; + folderId?: string; + language?: Language; + isDefaultLanguage?: boolean; + theme?: Theme; + themeSettings?: ThemeSettings; + isCompact?: boolean; + orgId?: string; + chartsInsightsLocators?: string; + projectId?: string; + dateFormat?: string | 'auto'; + timeFormat?: '12' | '24'; + + dlNavFilterOrderByField?: 'name' | 'createdAt'; + dlNavFilterOrderByDirection?: 'asc' | 'desc'; + dlNavFilterCreatedBy?: string; + dlDebugMode?: boolean; + dlStartInDataLens?: boolean; // CHARTS-6086 + dlStartInDataLensExt?: boolean; // CHARTS-6146 + dlFieldEditorDocShown?: boolean; // CHARTS-6168 + dlGsheetAuthHintShown?: boolean; // CHARTS-6811 + + // DC specific user settings + crmContactCreated?: boolean; + + mail_tech?: boolean; + mail_billing?: boolean; + mail_info?: boolean; + mail_feature?: boolean; + mail_event?: boolean; + mail_promo?: boolean; + mail_testing?: boolean; + + signupInfo?: { + name?: string; + firstname: string; + lastname: string; + email: string; + phone: string; + country: string; + currency: string; + countryName?: string; + company: string; + jobtitle: string; + }; + + tableSettings?: { + [tableId: string]: TableSettingsData; + }; + + communicationsCheck?: { + lastPhoneShowDate: number; + lastEmailShowDate: number; + lastAnyShowDate: number; + }; + + awsMpData?: null | { + token: string; + createdAt: string; + }; +} + +export interface DLUserAccount { + avatarId: string; + displayName: string; + email: string; + federationId: string; + lang: Lang; + login: string; + id: string; + uid: string; + avatarHost: string; + formattedLogin: string; +} + +export interface DLUser extends DLUserAccount { + accounts: DLUserAccount[]; + passportHost: string; + formattedLogin: string; + staffLogin?: string; + canCreateClouds?: boolean; + isFederationUser?: boolean; + isGlobalFederationUser?: boolean; + isLocalFederationUser?: boolean; + withNavigation?: boolean; + roles?: `${UserRole}`[]; + idpType: string | null; +} + +export type TenantSettings = { + defaultColorPaletteId?: string; +}; + +export type MainLayoutConfigData = { + intLandingConfigEntryId?: string; +}; + +type DocPathNameKey = 'datasetSubsql' | 'qlCreateChart' | 'functionsPath'; +export type DocPathName = Partial>; + +export type AIConfig = { + defaultModel: string; + availableModels: string[]; +}; + +export type DLGlobalData = { + deviceType: DeviceType; + requestId: string; + env: AppEnvironment; + installationType: AppInstallation; + serviceName: string; + user: DLUser; + userSettings: DLUserSettings; + iamUserId: string; + currentTenantId?: string; + clouds?: any[]; // TODO@types + endpoints: Endpoints['ui']; + features: FeatureConfig; + dynamicFeatures?: FeatureConfig; + displaySuperuserSwitch?: boolean; + intLandingConfigEntryId?: string; + meta?: Record; + landingPageSettings?: LandingPageSettings; + authPageSettings?: AuthPageSettings; + push?: PushServiceConfig; + metricaCounters?: MetricaCounterConfig[]; + isLanding?: boolean; + publicScope?: 'dash' | 'widget'; + ymapApiKey?: string; + oauthEndpoint?: string; + tenantMode?: { + foldersEnabled: boolean; + features?: Record; + }; + userIsOrgAdmin?: boolean; + tenantSettings?: TenantSettings; + allowLanguages?: Language[]; + langRegion?: string; + widgetMenuGroupConfig?: Array>; + title?: string; + publicAuthor?: EntryPublicAuthor; + iamResources?: { + collection: { + roles: { + admin: string; + editor: string; + viewer: string; + limitedViewer?: string; + entryBindingCreator?: string; + limitedEntryBindingCreator?: string; + }; + }; + workbook: { + roles: { + admin: string; + editor: string; + viewer: string; + limitedViewer?: string; + }; + }; + sharedEntry: { + roles: { + admin: string; + editor: string; + viewer: string; + limitedViewer?: string; + entryBindingCreator?: string; + limitedEntryBindingCreator?: string; + }; + }; + }; + // sorted roles from the role with the most rights to the role with the least + orderedAuthRoles?: `${UserRole}`[]; + embed?: + | { + mode: 'chart'; + } + | boolean; + apiPrefix?: string; + docPathName?: DocPathName; + chartkitSettings?: ChartkitGlobalSettings; + defaultColorPaletteId?: string; + extraPalettes?: Record; + headersMap?: Record; + hideNavigation?: boolean; + connectorIcons?: ConnectorIconData[]; + releaseVersion?: string; + docsUrl?: string; + isAuthEnabled?: boolean; + authManageLocalUsersDisabled?: boolean; + authSignupDisabled?: boolean; + authCookieName?: string; + aiConfig?: AIConfig; +} & MainLayoutConfigData; + +export type ContactDialogSettings = { + inputPhoneImageUrl?: string; + phoneVerifyCodeImageUrl?: string; + inputEmailImageUrl?: string; + emailVerifyCodeImageUrl?: string; + telegramCodeImageUrl?: string; +}; + +export type DSAPIErrorCode = + | 'ERR.UNKNOWN' + | 'ERR.DS_API.US.ACCESS_DENIED' + | 'ERR.DS_API.FIELD.NOT_FOUND' + | 'ERR.DS_API.DB' + | 'ERR.DS_API.DATABASE_UNAVAILABLE' + | 'ERR.DS_API' + | 'ERR.DS_API.DB.COLUMN_DOES_NOT_EXIST' + | 'ERR.DS_API.DB.METRICA' + | 'ERR.DS_API.FILTER.INVALID_VALUE' + | 'ERR.DS_API.DB.SOURCE_DOES_NOT_EXIST' + | 'ERR.DS_API.DB.SOURCE_ERROR.TIMEOUT' + | 'ERR.DS_API.SOURCE_CONFIG.TABLE_NOT_CONFIGURED' + | 'ERR.DS_API.ROW_COUNT_LIMIT' + | 'ERR.DS_API.US.OBJ_NOT_FOUND' + | 'ERR.DS_API.CLIQUE_STOPPED' + | 'ERR.DS_API.TABLE_NOT_READY' + | 'ERR.DS_API.FORMULA' + | 'ERR.DS_API.DB.CHYT.INVALID_SORTED_JOIN' + | 'ERR.DS_API.US.BAD_REQUEST'; + +/** @deprecated use StringParams from datalens-shared*/ +export interface Params extends Dictionary {} + +export enum EntryScope { + Dash = 'dash', + Widget = 'widget', + Dataset = 'dataset', + Folder = 'folder', + Connection = 'connection', +} +export type SharedScope = EntryScope.Dataset | EntryScope.Connection; +export interface EntryAnnotation { + description?: string; +} + +export type EntryAnnotationArgs = Required; + +export interface Entry { + entryId: string; + key: string; + scope: EntryScope; + type: EntryType; + data: EntryData; + links: Dictionary; + meta: object; + workbookId?: string; + mode?: EntryUpdateMode; + annotation?: EntryAnnotation | null; +} + +export type CreateEntryRequest = Partial> & + Required<{key: string; data: EntryData}> & { + description?: string; + annotation?: EntryAnnotationArgs; + }; + +export type UpdateEntryRequest = Omit< + T, + 'entryId' | 'scope' | 'type' | 'updatedAt' | 'savedId' | 'publishedId' +> & { + description?: string; + annotation?: EntryAnnotationArgs; +}; + +export type EntryData = DashData; // | WidgetData | DatasetData | ConnectionData | FolderData + +// WidgetType when scope=widget +export type EntryType = '' | WidgetType; + +export interface EntryReadParams { + revId?: string; + includePermissionsInfo: string; + includeLinks: string; + includeFavorite?: boolean; + branch?: string; +} + +export enum EntryUpdateMode { + Save = 'save', + Publish = 'publish', +} + +export interface State { + entryId: string; + hash: string; + data: StateData; +} + +export type StateData = DashStateData; + +export interface Lock { + lockId: string; + entryId: string; + lockToken: string; + expiryDate: string; + login: string; +} + +export enum DeveloperModeCheckStatus { + Allowed = 'allowed', + Forbidden = 'forbidden', +} diff --git a/src/shared/types/config/ql/index.ts b/src/shared/types/config/ql/index.ts new file mode 100644 index 0000000..d6daf17 --- /dev/null +++ b/src/shared/types/config/ql/index.ts @@ -0,0 +1,56 @@ +import type {MonitoringPresetV1, MonitoringPresetV2, QLEntryDataShared} from '../../ql/common'; + +import type {QlConfigV1} from './v1'; +import type {QlConfigV2} from './v2'; +import type {QlConfigV3} from './v3'; +import type {QlConfigV4} from './v4'; +import type {QlConfigV5} from './v5'; +import type {QlConfigV6} from './v6'; +import type { + QLParamIntervalV7, + QLParamV7, + QLPreviewTableDataColumnV7, + QLPreviewTableDataRowV7, + QLPreviewTableDataV7, + QLQueryV7, + QLRequestParamV7, + QLResultEntryMetadataDataColumnOrGroupV7, + QLResultEntryMetadataDataColumnV7, + QLResultEntryMetadataDataGroupV7, + QlConfigV7, +} from './v7'; + +export type QlConfig = QlConfigV7; + +export type QlPreviousConfig = + | QLEntryDataShared + | QlConfigV6 + | QlConfigV5 + | QlConfigV4 + | QlConfigV3 + | QlConfigV2 + | QlConfigV1; + +export type QlExtendedConfig = QlConfig | QlPreviousConfig; + +export type QLConfigQuery = QLQueryV7; + +export type QlConfigResultEntryMetadataDataGroup = QLResultEntryMetadataDataGroupV7; + +export type QlConfigResultEntryMetadataDataColumn = QLResultEntryMetadataDataColumnV7; + +export type QlConfigParamInterval = QLParamIntervalV7; + +export type QlConfigParam = QLParamV7; + +export type QlConfigRequestParam = QLRequestParamV7; + +export type QlConfigResultEntryMetadataDataColumnOrGroup = QLResultEntryMetadataDataColumnOrGroupV7; + +export type QlConfigPreviewTableDataColumn = QLPreviewTableDataColumnV7; + +export type QlConfigPreviewTableDataRow = QLPreviewTableDataRowV7; + +export type QlConfigPreviewTableData = QLPreviewTableDataV7; + +export type MonitoringPreset = MonitoringPresetV1 | MonitoringPresetV2; diff --git a/src/shared/types/config/ql/v1.ts b/src/shared/types/config/ql/v1.ts new file mode 100644 index 0000000..6c4d127 --- /dev/null +++ b/src/shared/types/config/ql/v1.ts @@ -0,0 +1,100 @@ +import type {QLChartType} from '../../../constants'; +import type {QlConfigVersions} from '../../ql/versions'; +import type {CommonSharedExtraSettings, Field, ShapesConfig, Shared} from '../../wizard'; + +export interface QLResultEntryMetadataDataGroupV1 { + group: boolean; + undragable: boolean; + name: string; + capacity?: number; + size: number; + id?: string; + allowedTypes?: string[]; + pseudo?: boolean; +} + +export interface QLResultEntryMetadataDataColumnV1 { + // typeName -- legacy type that is incompatible with Field + // How to turn on wizard ql common visualization -- you can delete + typeName: string; + + // biType -- a new type compatible with Field and general visualization section + biType?: string; + + name: string; + id?: string; + undragable?: boolean; + pseudo?: boolean; +} + +export interface QLParamIntervalV1 { + from: string | undefined; + to: string | undefined; +} + +export interface QLParamV1 { + name: string; + type: string; + defaultValue: string | string[] | QLParamIntervalV1 | undefined; + overridenValue?: string | string[] | QLParamIntervalV1; +} + +// Description of the request parameters. +export interface QLRequestParamV1 { + type_name: string; + value: string | string[]; +} + +export type QLResultEntryMetadataDataColumnOrGroupV1 = + | QLResultEntryMetadataDataGroupV1 + | QLResultEntryMetadataDataColumnV1; + +interface QLEntryDataSharedConnectionV1 { + entryId: string; + type: string; +} + +export interface QLQueryV1 { + value: string; + hidden?: boolean; + params: QLParamV1[]; +} + +export interface QlConfigV1 { + version: QlConfigVersions.V1; + // The type of template used to generate a chart at the ChartsEngine level, a low-level thing + type: string; + + // Chart type should be selected in UI + chartType: QLChartType; + queryValue: string; + queries: QLQueryV1[]; + extraSettings?: CommonSharedExtraSettings; + visualization: Shared['visualization'] & {highchartsId?: string}; + params: QLParamV1[]; + connection: QLEntryDataSharedConnectionV1; + order?: QLResultEntryMetadataDataColumnOrGroupV1[]; + preview?: boolean; + + colors?: Field[]; + colorsConfig?: any; + labels?: Field[]; + tooltips?: Field[]; + shapes?: Field[]; + shapesConfig?: ShapesConfig; + + available?: Field[]; +} + +export interface QLPreviewTableDataColumnV1 { + name: string; +} + +export interface QLPreviewTableDataRowV1 { + [key: string]: number | string | null; +} + +export interface QLPreviewTableDataV1 { + columns?: QLPreviewTableDataColumnV1[]; + data?: QLPreviewTableDataRowV1[]; +} diff --git a/src/shared/types/config/ql/v2.ts b/src/shared/types/config/ql/v2.ts new file mode 100644 index 0000000..b08f6f7 --- /dev/null +++ b/src/shared/types/config/ql/v2.ts @@ -0,0 +1,101 @@ +import type {QLChartType} from '../../../constants'; +import type {QlConfigVersions} from '../../ql/versions'; +import type {CommonSharedExtraSettings, Field, ShapesConfig, Shared} from '../../wizard'; + +export interface QLResultEntryMetadataDataGroupV2 { + group: boolean; + undragable: boolean; + name: string; + capacity?: number; + size: number; + id?: string; + allowedTypes?: string[]; + pseudo?: boolean; +} + +export interface QLResultEntryMetadataDataColumnV2 { + // typeName -- legacy type that is incompatible with Field + // How to turn on wizard ql common visualization -- you can delete + typeName: string; + + // biType -- a new type compatible with Field and general visualization section + biType?: string; + + name: string; + id?: string; + undragable?: boolean; + pseudo?: boolean; +} + +export interface QLParamIntervalV2 { + from: string | undefined; + to: string | undefined; +} + +export interface QLParamV2 { + name: string; + type: string; + defaultValue: string | string[] | QLParamIntervalV2 | undefined; + overridenValue?: string | string[] | QLParamIntervalV2; +} + +// Description of the request parameters. +export interface QLRequestParamV2 { + type_name: string; + value: string | string[]; +} + +export type QLResultEntryMetadataDataColumnOrGroupV2 = + | QLResultEntryMetadataDataGroupV2 + | QLResultEntryMetadataDataColumnV2; + +interface QLEntryDataSharedConnectionV2 { + entryId: string; + type: string; +} + +export interface QLQueryV2 { + value: string; + hidden?: boolean; + params: QLParamV2[]; + queryName: string; +} + +export interface QlConfigV2 { + version: QlConfigVersions.V2; + // The type of template used to generate a chart at the ChartsEngine level, a low-level thing + type: string; + + // Chart type should be selected in UI + chartType: QLChartType; + queryValue: string; + queries: QLQueryV2[]; + extraSettings?: CommonSharedExtraSettings; + visualization: Shared['visualization'] & {highchartsId?: string}; + params: QLParamV2[]; + connection: QLEntryDataSharedConnectionV2; + order?: QLResultEntryMetadataDataColumnOrGroupV2[] | null; + preview?: boolean; + + colors?: Field[]; + colorsConfig?: any; + labels?: Field[]; + tooltips?: Field[]; + shapes?: Field[]; + shapesConfig?: ShapesConfig; + + available?: Field[]; +} + +export interface QLPreviewTableDataColumnV2 { + name: string; +} + +export interface QLPreviewTableDataRowV2 { + [key: string]: number | string | null; +} + +export interface QLPreviewTableDataV2 { + columns?: QLPreviewTableDataColumnV2[]; + data?: QLPreviewTableDataRowV2[]; +} diff --git a/src/shared/types/config/ql/v3.ts b/src/shared/types/config/ql/v3.ts new file mode 100644 index 0000000..6f9b44f --- /dev/null +++ b/src/shared/types/config/ql/v3.ts @@ -0,0 +1,99 @@ +import type {QLChartType} from '../../../constants'; +import type {QlConfigVersions} from '../../ql/versions'; +import type {CommonSharedExtraSettings, Field, ShapesConfig, Shared} from '../../wizard'; + +export interface QLResultEntryMetadataDataGroupV3 { + group: boolean; + undragable: boolean; + name: string; + capacity?: number; + size: number; + id?: string; + allowedTypes?: string[]; + pseudo?: boolean; +} + +export interface QLResultEntryMetadataDataColumnV3 { + typeName: string; + + // biType -- a new type compatible with Field and general visualization section + biType?: string; + + name: string; + id?: string; + undragable?: boolean; + pseudo?: boolean; +} + +export interface QLParamIntervalV3 { + from: string | undefined; + to: string | undefined; +} + +export interface QLParamV3 { + name: string; + type: string; + defaultValue: string | string[] | QLParamIntervalV3 | undefined; + overridenValue?: string | string[] | QLParamIntervalV3; +} + +// Description of the request parameters. +export interface QLRequestParamV3 { + type_name: string; + value: string | string[]; +} + +export type QLResultEntryMetadataDataColumnOrGroupV3 = + | QLResultEntryMetadataDataGroupV3 + | QLResultEntryMetadataDataColumnV3; + +interface QLEntryDataSharedConnectionV3 { + entryId: string; + type: string; +} + +export interface QLQueryV3 { + value: string; + hidden?: boolean; + params: QLParamV3[]; + queryName: string; +} + +export interface QlConfigV3 { + version: QlConfigVersions.V3; + // The type of template used to generate a chart at the ChartsEngine level, a low-level thing + type: string; + + // Chart type should be selected in UI + chartType: QLChartType; + queryValue: string; + queries: QLQueryV3[]; + extraSettings?: CommonSharedExtraSettings; + visualization: Shared['visualization'] & {highchartsId?: string}; + params: QLParamV3[]; + connection: QLEntryDataSharedConnectionV3; + order?: QLResultEntryMetadataDataColumnOrGroupV3[] | null; + preview?: boolean; + + colors: Field[]; + colorsConfig?: any; + labels: Field[]; + tooltips: Field[]; + shapes: Field[]; + shapesConfig?: ShapesConfig; + + available?: Field[]; +} + +export interface QLPreviewTableDataColumnV3 { + name: string; +} + +export interface QLPreviewTableDataRowV3 { + [key: string]: number | string | null; +} + +export interface QLPreviewTableDataV3 { + columns?: QLPreviewTableDataColumnV3[]; + data?: QLPreviewTableDataRowV3[]; +} diff --git a/src/shared/types/config/ql/v4.ts b/src/shared/types/config/ql/v4.ts new file mode 100644 index 0000000..c538769 --- /dev/null +++ b/src/shared/types/config/ql/v4.ts @@ -0,0 +1,106 @@ +import type {QLChartType} from '../../../constants'; +import type {QlConfigVersions} from '../../ql/versions'; +import type { + CommonSharedExtraSettings, + Field, + PointSizeConfig, + ShapesConfig, + Shared, +} from '../../wizard'; + +export interface QLResultEntryMetadataDataGroupV4 { + group: boolean; + undragable: boolean; + name: string; + capacity?: number; + size: number; + id?: string; + allowedTypes?: string[]; + pseudo?: boolean; +} + +export interface QLResultEntryMetadataDataColumnV4 { + typeName: string; + + // biType -- a new type compatible with Field and general visualization section + biType?: string; + + name: string; + id?: string; + undragable?: boolean; + pseudo?: boolean; +} + +export interface QLParamIntervalV4 { + from: string | undefined; + to: string | undefined; +} + +export interface QLParamV4 { + name: string; + type: string; + defaultValue: string | string[] | QLParamIntervalV4 | undefined; + overridenValue?: string | string[] | QLParamIntervalV4; +} + +// Description of the request parameters. +export interface QLRequestParamV4 { + type_name: string; + value: string | string[]; +} + +export type QLResultEntryMetadataDataColumnOrGroupV4 = + | QLResultEntryMetadataDataGroupV4 + | QLResultEntryMetadataDataColumnV4; + +interface QLEntryDataSharedConnectionV4 { + entryId: string; + type: string; +} + +export interface QLQueryV4 { + value: string; + hidden?: boolean; + params: QLParamV4[]; + queryName: string; +} + +export interface QlConfigV4 { + version: QlConfigVersions.V4; + // The type of template used to generate a chart at the ChartsEngine level, a low-level thing + type: string; + + // Chart type should be selected in UI + chartType: QLChartType; + queryValue: string; + queries: QLQueryV4[]; + extraSettings?: CommonSharedExtraSettings; + visualization: Shared['visualization'] & {highchartsId?: string}; + geopointsConfig?: PointSizeConfig; + params: QLParamV4[]; + connection: QLEntryDataSharedConnectionV4; + order?: QLResultEntryMetadataDataColumnOrGroupV4[] | null; + preview?: boolean; + + colors: Field[]; + colorsConfig?: any; + labels: Field[]; + tooltips: Field[]; + shapes: Field[]; + shapesConfig?: ShapesConfig; + + available?: Field[]; +} + +export interface QLPreviewTableDataColumnV4 { + name: string; +} + +export interface QLPreviewTableDataRowV4 { + [key: string]: number | string | null; +} + +export interface QLPreviewTableDataV4 { + columns?: QLPreviewTableDataColumnV4[]; + data?: QLPreviewTableDataRowV4[]; +} diff --git a/src/shared/types/config/ql/v5.ts b/src/shared/types/config/ql/v5.ts new file mode 100644 index 0000000..e63757f --- /dev/null +++ b/src/shared/types/config/ql/v5.ts @@ -0,0 +1,106 @@ +import type {QLChartType} from '../../../constants'; +import type {QlConfigVersions} from '../../ql/versions'; +import type { + CommonSharedExtraSettings, + Field, + PointSizeConfig, + ShapesConfig, + Shared, +} from '../../wizard'; + +export interface QLResultEntryMetadataDataGroupV5 { + group: boolean; + undragable: boolean; + name: string; + capacity?: number; + size: number; + id?: string; + allowedTypes?: string[]; + pseudo?: boolean; +} + +export interface QLResultEntryMetadataDataColumnV5 { + typeName: string; + + // biType -- a new type compatible with Field and general visualization section + biType?: string; + + name: string; + id?: string; + undragable?: boolean; + pseudo?: boolean; +} + +export interface QLParamIntervalV5 { + from: string | undefined; + to: string | undefined; +} + +export interface QLParamV5 { + name: string; + type: string; + defaultValue: string | string[] | QLParamIntervalV5 | undefined; + overridenValue?: string | string[] | QLParamIntervalV5; +} + +// Description of the request parameters. +export interface QLRequestParamV5 { + type_name: string; + value: string | string[]; +} + +export type QLResultEntryMetadataDataColumnOrGroupV5 = + | QLResultEntryMetadataDataGroupV5 + | QLResultEntryMetadataDataColumnV5; + +interface QLEntryDataSharedConnectionV5 { + entryId: string; + type: string; +} + +export interface QLQueryV5 { + value: string; + hidden?: boolean; + params: QLParamV5[]; + queryName: string; +} + +export interface QlConfigV5 { + version: QlConfigVersions.V5; + // The type of template used to generate a chart at the ChartsEngine level, a low-level thing + type: string; + + // Chart type should be selected in UI + chartType: QLChartType; + queryValue: string; + queries: QLQueryV5[]; + extraSettings?: CommonSharedExtraSettings; + visualization: Shared['visualization'] & {highchartsId?: string}; + geopointsConfig?: PointSizeConfig; + params: QLParamV5[]; + connection: QLEntryDataSharedConnectionV5; + order?: QLResultEntryMetadataDataColumnOrGroupV5[] | null; + preview?: boolean; + + colors: Field[]; + colorsConfig?: any; + labels: Field[]; + tooltips: Field[]; + shapes: Field[]; + shapesConfig?: ShapesConfig; + + available?: Field[]; +} + +export interface QLPreviewTableDataColumnV5 { + name: string; +} + +export interface QLPreviewTableDataRowV5 { + [key: string]: number | string | null; +} + +export interface QLPreviewTableDataV5 { + columns?: QLPreviewTableDataColumnV5[]; + data?: QLPreviewTableDataRowV5[]; +} diff --git a/src/shared/types/config/ql/v6.ts b/src/shared/types/config/ql/v6.ts new file mode 100644 index 0000000..3bb3fdb --- /dev/null +++ b/src/shared/types/config/ql/v6.ts @@ -0,0 +1,104 @@ +import type {QLChartType} from '../../../constants'; +import type {TableHead, TableRow} from '../../chartkit'; +import type {QlConfigVersions} from '../../ql/versions'; +import type { + CommonSharedExtraSettings, + Field, + PointSizeConfig, + ShapesConfig, + Shared, +} from '../../wizard'; + +export interface QLResultEntryMetadataDataGroupV6 { + group: boolean; + undragable: boolean; + name: string; + capacity?: number; + size: number; + id?: string; + allowedTypes?: string[]; + pseudo?: boolean; +} + +export interface QLResultEntryMetadataDataColumnV6 { + typeName: string; + + // biType -- a new type compatible with Field and general visualization section + biType?: string; + + name: string; + id?: string; + undragable?: boolean; + pseudo?: boolean; +} + +export interface QLParamIntervalV6 { + from: string | undefined; + to: string | undefined; +} + +export interface QLParamV6 { + name: string; + type: string; + defaultValue: string | string[] | QLParamIntervalV6 | undefined; + overridenValue?: string | string[] | QLParamIntervalV6; +} + +// Description of the request parameters. +export interface QLRequestParamV6 { + type_name: string; + value: string | string[]; +} + +export type QLResultEntryMetadataDataColumnOrGroupV6 = + | QLResultEntryMetadataDataGroupV6 + | QLResultEntryMetadataDataColumnV6; + +interface QLEntryDataSharedConnectionV6 { + entryId: string; + type: string; + dataExportForbidden?: boolean; +} + +export interface QLQueryV6 { + value: string; + hidden?: boolean; + params: QLParamV6[]; + queryName: string; +} + +export interface QlConfigV6 { + version: QlConfigVersions.V6; + // The type of template used to generate a chart at the ChartsEngine level, a low-level thing + type: string; + + // Chart type should be selected in UI + chartType: QLChartType; + queryValue: string; + queries: QLQueryV6[]; + extraSettings?: CommonSharedExtraSettings; + visualization: Shared['visualization'] & {highchartsId?: string}; + geopointsConfig?: PointSizeConfig; + params: QLParamV6[]; + connection: QLEntryDataSharedConnectionV6; + order?: QLResultEntryMetadataDataColumnOrGroupV6[] | null; + preview?: boolean; + + colors: Field[]; + colorsConfig?: any; + labels: Field[]; + tooltips: Field[]; + shapes: Field[]; + shapesConfig?: ShapesConfig; + + available?: Field[]; +} + +export interface QLPreviewTableDataColumnV6 {} + +export interface QLPreviewTableDataRowV6 {} + +export interface QLPreviewTableDataV6 { + columns?: Array; + data?: Array; +} diff --git a/src/shared/types/config/ql/v7.ts b/src/shared/types/config/ql/v7.ts new file mode 100644 index 0000000..4094249 --- /dev/null +++ b/src/shared/types/config/ql/v7.ts @@ -0,0 +1,104 @@ +import type {QLChartType} from '../../../constants'; +import type {TableHead, TableRow} from '../../chartkit'; +import type {QlConfigVersions} from '../../ql/versions'; +import type { + CommonSharedExtraSettings, + Field, + PointSizeConfig, + ShapesConfig, + Shared, +} from '../../wizard'; + +export interface QLResultEntryMetadataDataGroupV7 { + group: boolean; + undragable: boolean; + name: string; + capacity?: number; + size: number; + id?: string; + allowedTypes?: string[]; + pseudo?: boolean; +} + +export interface QLResultEntryMetadataDataColumnV7 { + typeName: string; + + // biType -- a new type compatible with Field and general visualization section + biType?: string; + + name: string; + id?: string; + undragable?: boolean; + pseudo?: boolean; +} + +export interface QLParamIntervalV7 { + from: string | undefined; + to: string | undefined; +} + +export interface QLParamV7 { + name: string; + type: string; + defaultValue: string | string[] | QLParamIntervalV7 | undefined; + overridenValue?: string | string[] | QLParamIntervalV7; +} + +// Description of the request parameters. +export interface QLRequestParamV7 { + type_name: string; + value: string | string[]; +} + +export type QLResultEntryMetadataDataColumnOrGroupV7 = + | QLResultEntryMetadataDataGroupV7 + | QLResultEntryMetadataDataColumnV7; + +interface QLEntryDataSharedConnectionV7 { + entryId: string; + type: string; + dataExportForbidden?: boolean; +} + +export interface QLQueryV7 { + value: string; + hidden?: boolean; + params: QLParamV7[]; + queryName: string; +} + +export interface QlConfigV7 { + version: QlConfigVersions.V7; + // The type of template used to generate a chart at the ChartsEngine level, a low-level thing + type: string; + + // Chart type should be selected in UI + chartType: QLChartType; + queryValue: string; + queries: QLQueryV7[]; + extraSettings?: CommonSharedExtraSettings; + visualization: Shared['visualization'] & {highchartsId?: string}; + geopointsConfig?: PointSizeConfig; + params: QLParamV7[]; + connection: QLEntryDataSharedConnectionV7; + order?: QLResultEntryMetadataDataColumnOrGroupV7[] | null; + preview?: boolean; + + colors: Field[]; + colorsConfig?: any; + labels: Field[]; + tooltips: Field[]; + shapes: Field[]; + shapesConfig?: ShapesConfig; + + available?: Field[]; +} + +export interface QLPreviewTableDataColumnV7 {} + +export interface QLPreviewTableDataRowV7 {} + +export interface QLPreviewTableDataV7 { + columns?: Array; + data?: Array; +} diff --git a/src/shared/types/config/wizard/index.ts b/src/shared/types/config/wizard/index.ts new file mode 100644 index 0000000..1788bb3 --- /dev/null +++ b/src/shared/types/config/wizard/index.ts @@ -0,0 +1,116 @@ +import type {Shared, SharedData} from '../../index'; + +import type {V10ChartsConfig} from './v10'; +import type {V11ChartsConfig} from './v11'; +import type {V12ChartsConfig} from './v12'; +import type {V13ChartsConfig} from './v13'; +import type {V14ChartsConfig} from './v14'; +import type { + V15ChartsConfig, + V15ChartsConfigDatasetField, + V15ColorsConfig, + V15CommonPlaceholders, + V15CommonSharedExtraSettings, + V15Field, + V15Filter, + V15Formatting, + V15HierarchyField, + V15Layer, + V15PlaceholderSettings, + V15ShapesConfig, + V15TooltipConfig, + V15Update, +} from './v15'; +import type {V2ChartsConfig} from './v2'; +import type {V3ChartsConfig} from './v3'; +import type {V4ChartsConfig} from './v4'; +import type {V5ChartsConfig} from './v5'; +import type {V6ChartsConfig} from './v6'; +import type {V7ChartsConfig} from './v7'; +import type {V8ChartsConfig} from './v8'; +import type {V9ChartsConfig} from './v9'; + +export * from './v2'; +export * from './v3'; +export * from './v4'; +export * from './v5'; +export * from './v6'; +export * from './v7'; +export * from './v8'; +export * from './v9'; +export * from './v10'; +export * from './v11'; +export * from './v12'; +export * from './v13'; +export * from './v14'; +export * from './v15'; + +export type ChartsConfig = V15ChartsConfig; +export type PreviousChartsConfigs = + | Shared + | V2ChartsConfig + | V3ChartsConfig + | V4ChartsConfig + | V5ChartsConfig + | V6ChartsConfig + | V7ChartsConfig + | V8ChartsConfig + | V9ChartsConfig + | V10ChartsConfig + | V11ChartsConfig + | V12ChartsConfig + | V13ChartsConfig + | V14ChartsConfig; +export type CommonPlaceholders = V15CommonPlaceholders; + +export type ExtendedChartsConfig = ChartsConfig | PreviousChartsConfigs; + +export type ServerChartsConfig = ChartsConfig & { + sharedData: SharedData; +}; + +export type ServerPlaceholder = ChartsConfig['visualization']['placeholders'][0]; + +export type ServerVisualization = ChartsConfig['visualization']; + +export type ServerColor = ChartsConfig['colors'][0]; + +export type ServerSort = ChartsConfig['sort'][0]; + +export type ServerTooltip = ChartsConfig['tooltips'][0]; + +export type ServerLabel = ChartsConfig['labels'][0]; + +export type ServerShape = ChartsConfig['shapes'][0]; + +export type ServerLayerSettings = NonNullable< + ChartsConfig['visualization']['layers'] +>[0]['layerSettings']; + +export type ServerFieldFormatting = V15Formatting; + +export type ServerShapesConfig = V15ShapesConfig; + +export type ServerColorsConfig = V15ColorsConfig; + +export type ServerPointSizeConfig = NonNullable; + +export type ServerField = V15Field; + +export type ServerFieldUpdate = V15Update; + +export type ServerFilter = V15Filter; + +export type ServerDatasetField = V15ChartsConfigDatasetField; + +export type ServerVisualizationLayer = V15Layer; + +export type ServerLink = ChartsConfig['links'][0]; + +export type ServerHierarchy = V15HierarchyField; + +export type ServerCommonSharedExtraSettings = V15CommonSharedExtraSettings; + +export type ServerPlaceholderSettings = V15PlaceholderSettings; + +export type ServerTooltipConfig = V15TooltipConfig; diff --git a/src/shared/types/config/wizard/v10.ts b/src/shared/types/config/wizard/v10.ts new file mode 100644 index 0000000..261e132 --- /dev/null +++ b/src/shared/types/config/wizard/v10.ts @@ -0,0 +1,299 @@ +import type {ColorMode} from '../../../constants'; +import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; +import type {NumberFormatType, NumberFormatUnit} from '../../formatting'; +import type { + AxisLabelFormatMode, + AxisMode, + AxisNullsMode, + ChartsConfigVersion, + ColumnSettings, + HintSettings, + LabelsPositions, + TableBarsSettings, + TableFieldBackgroundSettings, + TableSubTotalsSettings, +} from '../../wizard'; + +export type V10ChartsConfig = { + title?: string; + colors: V10Color[]; + colorsConfig?: V10ColorsConfig; + extraSettings: V10CommonSharedExtraSettings | undefined; + filters: V10Filter[]; + geopointsConfig?: V10PointSizeConfig; + hierarchies: V10HierarchyField[]; + labels: V10Label[]; + links: V10Link[]; + sort: V10Sort[]; + tooltips: V10Tooltip[]; + type: 'datalens'; + updates: V10Update[]; + visualization: V10Visualization; + shapes: V10Shape[]; + shapesConfig?: V10ShapesConfig; + version: ChartsConfigVersion.V10; + datasetsIds: string[]; + datasetsPartialFields: V10ChartsConfigDatasetField[][]; + segments: V10Field[]; + chartType?: string; +}; + +export type V10Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export interface V10CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V10NavigatorSettings; + enableGPTInsights?: boolean; + labelsPosition?: LabelsPositions; + pinnedColumns?: number; +} + +export type V10NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V10Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V10ClientOnlyFields; + +export type V10Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V10ClientOnlyFields; + +export type V10Link = { + id: string; + fields: Record; +}; + +export type V10LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V10Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V10Layer[]; + placeholders: V10Placeholder[]; +}; + +export type V10LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V10CommonPlaceholders = { + colors: V10Color[]; + labels: V10Label[]; + tooltips: V10Tooltip[]; + filters: V10Filter[]; + sort: V10Sort[]; + shapes?: V10Shape[]; + colorsConfig?: V10ColorsConfig; + geopointsConfig?: V10PointSizeConfig; + shapesConfig?: V10ShapesConfig; +}; + +export type V10Layer = { + id: string; + commonPlaceholders: V10CommonPlaceholders; + layerSettings: V10LayerSettings; + placeholders: V10Placeholder[]; +}; + +export type V10PlaceholderSettings = { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisFormatMode?: AxisLabelFormatMode; + axisModeMap?: Record; + disableAxisMode?: boolean; +}; + +export type V10Placeholder = { + id: string; + settings?: V10PlaceholderSettings; + required?: boolean; + capacity?: number; + items: V10Field[]; +}; + +export type V10Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V10Formatting; + calc_mode: DatasetFieldCalcMode; +} & V10ClientOnlyFields; + +export type V10Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V10Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V10Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V10ClientOnlyFields; + +export type V10Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V10Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V10Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V10HierarchyField = { + data_type: string; + fields: V10Field[]; + type: string; +}; + +export type V10PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V10Field = { + data_type: string; + fields?: V10Field[]; + type: string; + title: string; + guid: string; + formatting?: V10Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + subTotalsSettings?: TableSubTotalsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; + hintSettings?: HintSettings; +} & V10ClientOnlyFields; + +export type V10ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; + colorMode?: ColorMode; +}; + +export type V10ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V10ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +export type V10ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; +}; diff --git a/src/shared/types/config/wizard/v11.ts b/src/shared/types/config/wizard/v11.ts new file mode 100644 index 0000000..e0264fc --- /dev/null +++ b/src/shared/types/config/wizard/v11.ts @@ -0,0 +1,318 @@ +import type {WidgetSizeType} from '../../..'; +import type {ColorMode} from '../../../constants'; +import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; +import type {NumberFormatType, NumberFormatUnit} from '../../formatting'; +import type { + AxisLabelFormatMode, + AxisMode, + AxisNullsMode, + ChartsConfigVersion, + ColumnSettings, + HintSettings, + IndicatorTitleMode, + LabelsPositions, + TableBarsSettings, + TableFieldBackgroundSettings, + TableSubTotalsSettings, +} from '../../wizard'; + +export type V11ChartsConfig = { + title?: string; + colors: V11Color[]; + colorsConfig?: V11ColorsConfig; + extraSettings: V11CommonSharedExtraSettings | undefined; + filters: V11Filter[]; + geopointsConfig?: V11PointSizeConfig; + hierarchies: V11HierarchyField[]; + labels: V11Label[]; + links: V11Link[]; + sort: V11Sort[]; + tooltips: V11Tooltip[]; + tooltipConfig?: V11TooltipConfig; + type: 'datalens'; + updates: V11Update[]; + visualization: V11Visualization; + shapes: V11Shape[]; + shapesConfig?: V11ShapesConfig; + version: ChartsConfigVersion.V11; + datasetsIds: string[]; + datasetsPartialFields: V11ChartsConfigDatasetField[][]; + segments: V11Field[]; + chartType?: string; +}; + +export type V11Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export interface V11CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + indicatorTitleMode?: IndicatorTitleMode; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + tooltip?: 'show' | 'hide'; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + pivotInlineSort?: 'on' | 'off'; + stacking?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V11NavigatorSettings; + enableGPTInsights?: boolean; + labelsPosition?: LabelsPositions; + pinnedColumns?: number; + size?: WidgetSizeType; +} + +export type V11NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V11Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V11ClientOnlyFields; + +export type V11Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V11ClientOnlyFields; + +export type V11Link = { + id: string; + fields: Record; +}; + +export type V11LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V11Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V11Layer[]; + placeholders: V11Placeholder[]; +}; + +export type V11LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V11CommonPlaceholders = { + colors: V11Color[]; + labels: V11Label[]; + tooltips: V11Tooltip[]; + filters: V11Filter[]; + sort: V11Sort[]; + shapes?: V11Shape[]; + colorsConfig?: V11ColorsConfig; + geopointsConfig?: V11PointSizeConfig; + shapesConfig?: V11ShapesConfig; + tooltipConfig?: V11TooltipConfig; +}; + +export type V11Layer = { + id: string; + commonPlaceholders: V11CommonPlaceholders; + layerSettings: V11LayerSettings; + placeholders: V11Placeholder[]; +}; + +export type V11PlaceholderSettings = { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisFormatMode?: AxisLabelFormatMode; + axisModeMap?: Record; + disableAxisMode?: boolean; + /* Whether axis, including axis title, line, ticks and labels, should be visible + * @default 'show' + **/ + axisVisibility?: 'show' | 'hide'; +}; + +export type V11Placeholder = { + id: string; + settings?: V11PlaceholderSettings; + required?: boolean; + capacity?: number; + items: V11Field[]; +}; + +export type V11Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V11Formatting; + calc_mode: DatasetFieldCalcMode; +} & V11ClientOnlyFields; + +export type V11Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V11ClientOnlyFields; + +export type V11Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V11Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V11ClientOnlyFields; + +export type V11Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V11Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V11Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V11HierarchyField = { + data_type: string; + fields: V11Field[]; + type: string; +}; + +export type V11PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V11Field = { + data_type: string; + fields?: V11Field[]; + type: string; + title: string; + guid: string; + formatting?: V11Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + subTotalsSettings?: TableSubTotalsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; + hintSettings?: HintSettings; +} & V11ClientOnlyFields; + +export type V11ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; + colorMode?: ColorMode; +}; + +export type V11ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V11TooltipConfig = { + color?: 'on' | 'off'; + fieldTitle?: 'on' | 'off'; +}; + +export type V11ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +export type V11ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; + isMarkdown?: boolean; +}; diff --git a/src/shared/types/config/wizard/v12.ts b/src/shared/types/config/wizard/v12.ts new file mode 100644 index 0000000..f9a5c3d --- /dev/null +++ b/src/shared/types/config/wizard/v12.ts @@ -0,0 +1,331 @@ +import type { + GradientNullMode, + MapCenterModes, + MarkupType, + WidgetSizeType, + ZoomModes, +} from '../../..'; +import type {ColorMode} from '../../../constants'; +import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; +import type {NumberFormatType, NumberFormatUnit} from '../../formatting'; +import type { + AxisLabelFormatMode, + AxisMode, + AxisNullsMode, + ChartsConfigVersion, + ColumnSettings, + HintSettings, + IndicatorTitleMode, + LabelsPositions, + TableBarsSettings, + TableFieldBackgroundSettings, + TableSubTotalsSettings, +} from '../../wizard'; + +export type V12ChartsConfig = { + title?: string; + colors: V12Color[]; + colorsConfig?: V12ColorsConfig; + extraSettings: V12CommonSharedExtraSettings | undefined; + filters: V12Filter[]; + geopointsConfig?: V12PointSizeConfig; + hierarchies: V12HierarchyField[]; + labels: V12Label[]; + links: V12Link[]; + sort: V12Sort[]; + tooltips: V12Tooltip[]; + tooltipConfig?: V12TooltipConfig; + type: 'datalens'; + updates: V12Update[]; + visualization: V12Visualization; + shapes: V12Shape[]; + shapesConfig?: V12ShapesConfig; + version: ChartsConfigVersion.V12; + datasetsIds: string[]; + datasetsPartialFields: V12ChartsConfigDatasetField[][]; + segments: V12Field[]; + chartType?: string; +}; + +export type V12Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export interface V12CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + indicatorTitleMode?: IndicatorTitleMode; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + metricFontColorPalette?: string; + tooltip?: 'show' | 'hide'; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + pivotInlineSort?: 'on' | 'off'; + stacking?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V12NavigatorSettings; + enableGPTInsights?: boolean; + labelsPosition?: LabelsPositions; + pinnedColumns?: number; + size?: WidgetSizeType; + zoomMode?: ZoomModes; + zoomValue?: number | null; + mapCenterMode?: MapCenterModes; + mapCenterValue?: string | null; + preserveWhiteSpace?: boolean; +} + +export type V12NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V12Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V12ClientOnlyFields; + +export type V12Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V12ClientOnlyFields; + +export type V12Link = { + id: string; + fields: Record; +}; + +export type V12LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V12Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V12Layer[]; + placeholders: V12Placeholder[]; +}; + +export type V12LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V12CommonPlaceholders = { + colors: V12Color[]; + labels: V12Label[]; + tooltips: V12Tooltip[]; + filters: V12Filter[]; + sort: V12Sort[]; + shapes?: V12Shape[]; + colorsConfig?: V12ColorsConfig; + geopointsConfig?: V12PointSizeConfig; + shapesConfig?: V12ShapesConfig; + tooltipConfig?: V12TooltipConfig; +}; + +export type V12Layer = { + id: string; + commonPlaceholders: V12CommonPlaceholders; + layerSettings: V12LayerSettings; + placeholders: V12Placeholder[]; +}; + +export type V12PlaceholderSettings = { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisFormatMode?: AxisLabelFormatMode; + axisModeMap?: Record; + disableAxisMode?: boolean; + /* Whether axis, including axis title, line, ticks and labels, should be visible + * @default 'show' + **/ + axisVisibility?: 'show' | 'hide'; +}; + +export type V12Placeholder = { + id: string; + settings?: V12PlaceholderSettings; + required?: boolean; + capacity?: number; + items: V12Field[]; +}; + +export type V12Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V12Formatting; + calc_mode: DatasetFieldCalcMode; +} & V12ClientOnlyFields; + +export type V12Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V12ClientOnlyFields; + +export type V12Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V12Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V12ClientOnlyFields; + +export type V12Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V12Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V12Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V12HierarchyField = { + data_type: string; + fields: V12Field[]; + type: string; +}; + +export type V12PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V12Field = { + data_type: string; + fields?: V12Field[]; + type: string; + title: string; + guid: string; + formatting?: V12Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + subTotalsSettings?: TableSubTotalsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; + hintSettings?: HintSettings; +} & V12ClientOnlyFields; + +export type V12ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; + colorMode?: ColorMode; + nullMode?: GradientNullMode; +}; + +export type V12ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V12TooltipConfig = { + color?: 'on' | 'off'; + fieldTitle?: 'on' | 'off'; +}; + +export type V12ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +export type V12ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; + markupType?: MarkupType; +}; diff --git a/src/shared/types/config/wizard/v13.ts b/src/shared/types/config/wizard/v13.ts new file mode 100644 index 0000000..1eb2b97 --- /dev/null +++ b/src/shared/types/config/wizard/v13.ts @@ -0,0 +1,331 @@ +import type { + GradientNullMode, + MapCenterModes, + MarkupType, + MetricFontSettings, + WidgetSizeType, + ZoomModes, +} from '../../..'; +import type {ColorMode} from '../../../constants'; +import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; +import type {NumberFormatType, NumberFormatUnit} from '../../formatting'; +import type { + AxisLabelFormatMode, + AxisMode, + AxisNullsMode, + ChartsConfigVersion, + ColumnSettings, + HintSettings, + IndicatorTitleMode, + LabelsPositions, + TableBarsSettings, + TableFieldBackgroundSettings, + TableSubTotalsSettings, +} from '../../wizard'; + +export type V13ChartsConfig = { + title?: string; + colors: V13Color[]; + colorsConfig?: V13ColorsConfig; + extraSettings: V13CommonSharedExtraSettings | undefined; + filters: V13Filter[]; + geopointsConfig?: V13PointSizeConfig; + hierarchies: V13HierarchyField[]; + labels: V13Label[]; + links: V13Link[]; + sort: V13Sort[]; + tooltips: V13Tooltip[]; + tooltipConfig?: V13TooltipConfig; + type: 'datalens'; + updates: V13Update[]; + visualization: V13Visualization; + shapes: V13Shape[]; + shapesConfig?: V13ShapesConfig; + version: ChartsConfigVersion.V13; + datasetsIds: string[]; + datasetsPartialFields: V13ChartsConfigDatasetField[][]; + segments: V13Field[]; + chartType?: string; +}; + +export type V13Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export type V13CommonSharedExtraSettings = { + title?: string; + titleMode?: 'show' | 'hide'; + indicatorTitleMode?: IndicatorTitleMode; + legendMode?: 'show' | 'hide'; + tooltip?: 'show' | 'hide'; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + pivotInlineSort?: 'on' | 'off'; + stacking?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V13NavigatorSettings; + enableGPTInsights?: boolean; + labelsPosition?: LabelsPositions; + pinnedColumns?: number; + size?: WidgetSizeType; + zoomMode?: ZoomModes; + zoomValue?: number | null; + mapCenterMode?: MapCenterModes; + mapCenterValue?: string | null; + preserveWhiteSpace?: boolean; +} & MetricFontSettings; + +export type V13NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V13Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V13ClientOnlyFields; + +export type V13Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V13ClientOnlyFields; + +export type V13Link = { + id: string; + fields: Record; +}; + +export type V13LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V13Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V13Layer[]; + placeholders: V13Placeholder[]; +}; + +export type V13LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V13CommonPlaceholders = { + colors: V13Color[]; + labels: V13Label[]; + tooltips: V13Tooltip[]; + filters: V13Filter[]; + sort: V13Sort[]; + shapes?: V13Shape[]; + colorsConfig?: V13ColorsConfig; + geopointsConfig?: V13PointSizeConfig; + shapesConfig?: V13ShapesConfig; + tooltipConfig?: V13TooltipConfig; +}; + +export type V13Layer = { + id: string; + commonPlaceholders: V13CommonPlaceholders; + layerSettings: V13LayerSettings; + placeholders: V13Placeholder[]; +}; + +export type V13PlaceholderSettings = { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisFormatMode?: AxisLabelFormatMode; + axisLabelFormating?: V13Formatting; + axisLabelDateFormat?: string; + axisModeMap?: Record; + disableAxisMode?: boolean; + /* Whether axis, including axis title, line, ticks and labels, should be visible + * @default 'show' + **/ + axisVisibility?: 'show' | 'hide'; +}; + +export type V13Placeholder = { + id: string; + settings?: V13PlaceholderSettings; + required?: boolean; + capacity?: number; + items: V13Field[]; +}; + +export type V13Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V13Formatting; + calc_mode: DatasetFieldCalcMode; +} & V13ClientOnlyFields; + +export type V13Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V13ClientOnlyFields; + +export type V13Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V13Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V13ClientOnlyFields; + +export type V13Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V13Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V13Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V13HierarchyField = { + data_type: string; + fields: V13Field[]; + type: string; +}; + +export type V13PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V13Field = { + data_type: string; + fields?: V13Field[]; + type: string; + title: string; + guid: string; + formatting?: V13Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + subTotalsSettings?: TableSubTotalsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; + hintSettings?: HintSettings; +} & V13ClientOnlyFields; + +export type V13ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; + colorMode?: ColorMode; + nullMode?: GradientNullMode; +}; + +export type V13ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V13TooltipConfig = { + color?: 'on' | 'off'; + fieldTitle?: 'on' | 'off'; +}; + +export type V13ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +export type V13ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; + markupType?: MarkupType; +}; diff --git a/src/shared/types/config/wizard/v14.ts b/src/shared/types/config/wizard/v14.ts new file mode 100644 index 0000000..fa49338 --- /dev/null +++ b/src/shared/types/config/wizard/v14.ts @@ -0,0 +1,332 @@ +import type { + GradientNullMode, + MapCenterModes, + MarkupType, + MetricFontSettings, + WidgetSizeType, + ZoomModes, +} from '../../..'; +import type {ColorMode} from '../../../constants'; +import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; +import type {NumberFormatType, NumberFormatUnit} from '../../formatting'; +import type { + AxisLabelFormatMode, + AxisMode, + AxisNullsMode, + ChartsConfigVersion, + ColumnSettings, + HintSettings, + IndicatorTitleMode, + LabelsPositions, + TableBarsSettings, + TableFieldBackgroundSettings, + TableSubTotalsSettings, +} from '../../wizard'; + +export type V14ChartsConfig = { + title?: string; + colors: V14Color[]; + colorsConfig?: V14ColorsConfig; + extraSettings: V14CommonSharedExtraSettings | undefined; + filters: V14Filter[]; + geopointsConfig?: V14PointSizeConfig; + hierarchies: V14HierarchyField[]; + labels: V14Label[]; + links: V14Link[]; + sort: V14Sort[]; + tooltips: V14Tooltip[]; + tooltipConfig?: V14TooltipConfig; + type: 'datalens'; + updates: V14Update[]; + visualization: V14Visualization; + shapes: V14Shape[]; + shapesConfig?: V14ShapesConfig; + version: ChartsConfigVersion.V14; + datasetsIds: string[]; + datasetsPartialFields: V14ChartsConfigDatasetField[][]; + segments: V14Field[]; + chartType?: string; +}; + +export type V14Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export type V14CommonSharedExtraSettings = { + title?: string; + titleMode?: 'show' | 'hide'; + indicatorTitleMode?: IndicatorTitleMode; + legendMode?: 'show' | 'hide'; + tooltip?: 'show' | 'hide'; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + pivotInlineSort?: 'on' | 'off'; + stacking?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V14NavigatorSettings; + enableGPTInsights?: boolean; + labelsPosition?: LabelsPositions; + pinnedColumns?: number; + size?: WidgetSizeType; + zoomMode?: ZoomModes; + zoomValue?: number | null; + mapCenterMode?: MapCenterModes; + mapCenterValue?: string | null; + preserveWhiteSpace?: boolean; +} & MetricFontSettings; + +export type V14NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V14Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V14ClientOnlyFields; + +export type V14Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V14ClientOnlyFields; + +export type V14Link = { + id: string; + fields: Record; +}; + +export type V14LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V14Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V14Layer[]; + placeholders: V14Placeholder[]; +}; + +export type V14LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V14CommonPlaceholders = { + colors: V14Color[]; + labels: V14Label[]; + tooltips: V14Tooltip[]; + filters: V14Filter[]; + sort: V14Sort[]; + shapes?: V14Shape[]; + colorsConfig?: V14ColorsConfig; + geopointsConfig?: V14PointSizeConfig; + shapesConfig?: V14ShapesConfig; + tooltipConfig?: V14TooltipConfig; +}; + +export type V14Layer = { + id: string; + commonPlaceholders: V14CommonPlaceholders; + layerSettings: V14LayerSettings; + placeholders: V14Placeholder[]; +}; + +export type V14PlaceholderSettings = { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisLabelFormating?: V14Formatting; + axisLabelDateFormat?: string; + axisFormatMode?: AxisLabelFormatMode; + axisModeMap?: Record; + disableAxisMode?: boolean; + /* Whether axis, including axis title, line, ticks and labels, should be visible + * @default 'show' + **/ + axisVisibility?: 'show' | 'hide'; +}; + +export type V14Placeholder = { + id: string; + settings?: V14PlaceholderSettings; + required?: boolean; + capacity?: number; + items: V14Field[]; +}; + +export type V14Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V14Formatting; + calc_mode: DatasetFieldCalcMode; +} & V14ClientOnlyFields; + +export type V14Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V14ClientOnlyFields; + +export type V14Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V14Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V14ClientOnlyFields; + +export type V14Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V14Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V14Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V14HierarchyField = { + data_type: string; + fields: V14Field[]; + type: string; +}; + +export type V14PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V14Field = { + data_type: string; + fields?: V14Field[]; + type: string; + title: string; + guid: string; + formatting?: V14Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + subTotalsSettings?: TableSubTotalsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; + hintSettings?: HintSettings; + ui_settings?: string; +} & V14ClientOnlyFields; + +export type V14ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; + colorMode?: ColorMode; + nullMode?: GradientNullMode; +}; + +export type V14ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V14TooltipConfig = { + color?: 'on' | 'off'; + fieldTitle?: 'on' | 'off'; +}; + +export type V14ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +export type V14ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; + markupType?: MarkupType; +}; diff --git a/src/shared/types/config/wizard/v15.ts b/src/shared/types/config/wizard/v15.ts new file mode 100644 index 0000000..0923cf5 --- /dev/null +++ b/src/shared/types/config/wizard/v15.ts @@ -0,0 +1,342 @@ +import type { + GradientNullMode, + LineCap, + LineJoin, + MapCenterModes, + MarkupType, + MetricFontSettings, + WidgetSizeType, + ZoomModes, +} from '../../..'; +import type {ColorMode} from '../../../constants'; +import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; +import type {NumberFormatType, NumberFormatUnit} from '../../formatting'; +import type { + AxisLabelFormatMode, + AxisMode, + AxisNullsMode, + ChartsConfigVersion, + ColumnSettings, + HintSettings, + IndicatorTitleMode, + LabelsPositions, + TableBarsSettings, + TableFieldBackgroundSettings, + TableSubTotalsSettings, +} from '../../wizard'; + +export type V15ChartsConfig = { + title?: string; + colors: V15Color[]; + colorsConfig?: V15ColorsConfig; + extraSettings: V15CommonSharedExtraSettings | undefined; + filters: V15Filter[]; + geopointsConfig?: V15PointSizeConfig; + hierarchies: V15HierarchyField[]; + labels: V15Label[]; + links: V15Link[]; + sort: V15Sort[]; + tooltips: V15Tooltip[]; + tooltipConfig?: V15TooltipConfig; + type: 'datalens'; + updates: V15Update[]; + visualization: V15Visualization; + shapes: V15Shape[]; + shapesConfig?: V15ShapesConfig; + version: ChartsConfigVersion.V15; + datasetsIds: string[]; + datasetsPartialFields: V15ChartsConfigDatasetField[][]; + segments: V15Field[]; + chartType?: string; +}; + +export type V15Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export type V15CommonSharedExtraSettings = { + title?: string; + titleMode?: 'show' | 'hide'; + indicatorTitleMode?: IndicatorTitleMode; + legendMode?: 'show' | 'hide'; + tooltip?: 'show' | 'hide'; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + pivotInlineSort?: 'on' | 'off'; + stacking?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V15NavigatorSettings; + enableGPTInsights?: boolean; + labelsPosition?: LabelsPositions; + pinnedColumns?: number; + size?: WidgetSizeType; + zoomMode?: ZoomModes; + zoomValue?: number | null; + mapCenterMode?: MapCenterModes; + mapCenterValue?: string | null; + preserveWhiteSpace?: boolean; +} & MetricFontSettings; + +export type V15NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V15Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V15ClientOnlyFields; + +export type V15Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V15ClientOnlyFields; + +export type V15Link = { + id: string; + fields: Record; +}; + +export type V15LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V15Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V15Layer[]; + placeholders: V15Placeholder[]; +}; + +export type V15LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V15CommonPlaceholders = { + colors: V15Color[]; + labels: V15Label[]; + tooltips: V15Tooltip[]; + filters: V15Filter[]; + sort: V15Sort[]; + shapes?: V15Shape[]; + colorsConfig?: V15ColorsConfig; + geopointsConfig?: V15PointSizeConfig; + shapesConfig?: V15ShapesConfig; + tooltipConfig?: V15TooltipConfig; +}; + +export type V15Layer = { + id: string; + commonPlaceholders: V15CommonPlaceholders; + layerSettings: V15LayerSettings; + placeholders: V15Placeholder[]; +}; + +export type V15PlaceholderSettings = { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisLabelFormating?: V15Formatting; + axisLabelDateFormat?: string; + axisFormatMode?: AxisLabelFormatMode; + axisModeMap?: Record; + disableAxisMode?: boolean; + /* Whether axis, including axis title, line, ticks and labels, should be visible + * @default 'show' + **/ + axisVisibility?: 'show' | 'hide'; +}; + +export type V15Placeholder = { + id: string; + settings?: V15PlaceholderSettings; + required?: boolean; + capacity?: number; + items: V15Field[]; +}; + +export type V15Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V15Formatting; + calc_mode: DatasetFieldCalcMode; +} & V15ClientOnlyFields; + +export type V15Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V15ClientOnlyFields; + +export type V15Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V15Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V15ClientOnlyFields; + +export type V15Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V15Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V15Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V15HierarchyField = { + data_type: string; + fields: V15Field[]; + type: string; +}; + +export type V15PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V15Field = { + data_type: string; + fields?: V15Field[]; + type: string; + title: string; + guid: string; + formatting?: V15Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + subTotalsSettings?: TableSubTotalsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; + hintSettings?: HintSettings; + ui_settings?: string; +} & V15ClientOnlyFields; + +export type V15ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; + colorMode?: ColorMode; + nullMode?: GradientNullMode; +}; + +export type LineShapeSettings = { + lineWidth?: number | 'auto'; + linecap?: LineCap; + linejoin?: LineJoin; +}; + +export type V15ShapesConfig = { + fieldGuid?: string; + mountedShapes?: Record; + lineSettings?: Record; + commonLineSettings?: LineShapeSettings; +}; + +export type V15TooltipConfig = { + color?: 'on' | 'off'; + fieldTitle?: 'on' | 'off'; +}; + +export type V15ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +export type V15ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; + markupType?: MarkupType; +}; diff --git a/src/shared/types/config/wizard/v2.ts b/src/shared/types/config/wizard/v2.ts new file mode 100644 index 0000000..201c65b --- /dev/null +++ b/src/shared/types/config/wizard/v2.ts @@ -0,0 +1,262 @@ +import type {AxisNullsMode, ChartsConfigVersion, DatasetFieldCalcMode} from '../../index'; + +export type V2ChartsConfig = { + title?: string; + colors: V2Color[]; + colorsConfig: V2ColorsConfig; + extraSettings: V2CommonSharedExtraSettings; + filters: V2Filter[]; + geopointsConfig?: V2PointSizeConfig; + hierarchies: V2HierarchyField[]; + labels: V2Label[]; + links: V2Link[]; + sort: V2Sort[]; + tooltips: V2Tooltip[]; + type: 'datalens'; + updates: V2Update[]; + visualization: V2Visualization; + shapes: V2Shape[]; + shapesConfig?: V2ShapesConfig; + version: ChartsConfigVersion.V2; + datasetsIds: string[]; + datasetsPartialFields: V2ChartsConfigDatasetField[][]; +}; + +type V2Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +interface V2CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V2NavigatorSettings; +} + +type V2NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V2Filter = { + guid: string; + type: string; + datasetId: string; + disabled?: string; + title: string; + calc_mode: DatasetFieldCalcMode; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; +} & V2ClientOnlyFields; + +type V2Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + calc_mode: DatasetFieldCalcMode; +} & V2ClientOnlyFields; + +type V2Link = { + id: string; + fields: Record; +}; + +type V2LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +type V2Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V2Layer[]; + placeholders: V2Placeholder[]; +}; + +type V2LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V2Layer = { + id: string; + commonPlaceholders: { + colors: V2Color[]; + labels: V2Label[]; + tooltips: V2Tooltip[]; + filters: V2Filter[]; + colorsConfig: V2ColorsConfig; + geopointsConfig?: V2PointSizeConfig; + sort: V2Sort[]; + }; + layerSettings: V2LayerSettings; + placeholders: V2Placeholder[]; +}; + +type V2Placeholder = { + id: string; + settings?: { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + }; + items: V2Field[]; +}; + +type V2Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V2Formatting; + calc_mode: DatasetFieldCalcMode; +} & V2ClientOnlyFields; + +type V2Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +type V2Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V2Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V2ClientOnlyFields; + +export type V2Formatting = { + format?: 'number' | 'percent'; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: 'auto' | 'k' | 'm' | 'b' | 't'; + precision?: number; +}; + +type V2Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V2Formatting; + format?: string; + labelMode?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V2HierarchyField = { + data_type: string; + fields: V2Field[]; + type: string; +}; + +type V2PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V2Field = { + data_type: string; + fields?: V2Field[]; + type: string; + title: string; + guid: string; + formatting?: V2Formatting; + format?: string; + labelMode?: string; + datasetId: string; + dateMode?: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; +} & V2ClientOnlyFields; + +type V2ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; +}; + +type V2ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V2ChartsConfigDatasetField = { + guid: string; + title: string; +}; + +type V2ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; +}; diff --git a/src/shared/types/config/wizard/v3.ts b/src/shared/types/config/wizard/v3.ts new file mode 100644 index 0000000..e07652a --- /dev/null +++ b/src/shared/types/config/wizard/v3.ts @@ -0,0 +1,271 @@ +import type { + AxisNullsMode, + ChartsConfigVersion, + DatasetFieldCalcMode, + ParameterDefaultValue, +} from '../../index'; + +export type V3ChartsConfig = { + title?: string; + colors: V3Color[]; + colorsConfig: V3ColorsConfig; + extraSettings: V3CommonSharedExtraSettings; + filters: V3Filter[]; + geopointsConfig?: V3PointSizeConfig; + hierarchies: V3HierarchyField[]; + labels: V3Label[]; + links: V3Link[]; + sort: V3Sort[]; + tooltips: V3Tooltip[]; + type: 'datalens'; + updates: V3Update[]; + visualization: V3Visualization; + shapes: V3Shape[]; + shapesConfig?: V3ShapesConfig; + version: ChartsConfigVersion.V3; + datasetsIds: string[]; + datasetsPartialFields: V3ChartsConfigDatasetField[][]; +}; + +type V3Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +interface V3CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V3NavigatorSettings; +} + +type V3NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V3Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V3ClientOnlyFields; + +type V3Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V3ClientOnlyFields; + +type V3Link = { + id: string; + fields: Record; +}; + +type V3LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +type V3Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V3Layer[]; + placeholders: V3Placeholder[]; +}; + +type V3LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V3CommonPlaceholders = { + colors: V3Color[]; + labels: V3Label[]; + tooltips: V3Tooltip[]; + filters: V3Filter[]; + sort: V3Sort[]; + colorsConfig: V3ColorsConfig; + geopointsConfig?: V3PointSizeConfig; +}; + +export type V3Layer = { + id: string; + commonPlaceholders: V3CommonPlaceholders; + layerSettings: V3LayerSettings; + placeholders: V3Placeholder[]; +}; + +type V3Placeholder = { + id: string; + settings?: { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + }; + items: V3Field[]; +}; + +type V3Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V3Formatting; + calc_mode: DatasetFieldCalcMode; +} & V3ClientOnlyFields; + +type V3Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +type V3Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V3Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V3ClientOnlyFields; + +export type V3Formatting = { + format?: 'number' | 'percent'; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: 'auto' | 'k' | 'm' | 'b' | 't'; + precision?: number; +}; + +export type V3Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V3Formatting; + format?: string; + labelMode?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V3HierarchyField = { + data_type: string; + fields: V3Field[]; + type: string; +}; + +type V3PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V3Field = { + data_type: string; + fields?: V3Field[]; + type: string; + title: string; + guid: string; + formatting?: V3Formatting; + format?: string; + labelMode?: string; + datasetId: string; + dateMode?: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; +} & V3ClientOnlyFields; + +type V3ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; +}; + +type V3ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V3ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +type V3ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; +}; diff --git a/src/shared/types/config/wizard/v4.ts b/src/shared/types/config/wizard/v4.ts new file mode 100644 index 0000000..f2dd651 --- /dev/null +++ b/src/shared/types/config/wizard/v4.ts @@ -0,0 +1,286 @@ +import type { + AxisLabelFormatMode, + ChartsConfigVersion, + ColumnSettings, + DatasetFieldCalcMode, + NumberFormatType, + NumberFormatUnit, + ParameterDefaultValue, + TableBarsSettings, +} from '../../index'; +import type {AxisNullsMode, TableFieldBackgroundSettings} from '../../wizard'; + +export type V4ChartsConfig = { + title?: string; + colors: V4Color[]; + colorsConfig?: V4ColorsConfig; + extraSettings: V4CommonSharedExtraSettings; + filters: V4Filter[]; + geopointsConfig?: V4PointSizeConfig; + hierarchies: V4HierarchyField[]; + labels: V4Label[]; + links: V4Link[]; + sort: V4Sort[]; + tooltips: V4Tooltip[]; + type: 'datalens'; + updates: V4Update[]; + visualization: V4Visualization; + shapes: V4Shape[]; + shapesConfig?: V4ShapesConfig; + version: ChartsConfigVersion.V4; + datasetsIds: string[]; + datasetsPartialFields: V4ChartsConfigDatasetField[][]; + segments: V4Field[]; + chartType?: string; +}; + +type V4Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export interface V4CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V4NavigatorSettings; +} + +type V4NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V4Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V4ClientOnlyFields; + +export type V4Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V4ClientOnlyFields; + +type V4Link = { + id: string; + fields: Record; +}; + +type V4LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V4Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V4Layer[]; + placeholders: V4Placeholder[]; +}; + +type V4LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V4CommonPlaceholders = { + colors: V4Color[]; + labels: V4Label[]; + tooltips: V4Tooltip[]; + filters: V4Filter[]; + sort: V4Sort[]; + shapes?: V4Shape[]; + colorsConfig?: V4ColorsConfig; + geopointsConfig?: V4PointSizeConfig; + shapesConfig?: V4ShapesConfig; +}; + +export type V4Layer = { + id: string; + commonPlaceholders: V4CommonPlaceholders; + layerSettings: V4LayerSettings; + placeholders: V4Placeholder[]; +}; + +export type V4Placeholder = { + id: string; + settings?: { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisFormatMode?: AxisLabelFormatMode; + }; + required?: boolean; + capacity?: number; + items: V4Field[]; +}; + +export type V4Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V4Formatting; + calc_mode: DatasetFieldCalcMode; +} & V4ClientOnlyFields; + +export type V4Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V4Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V4Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V4ClientOnlyFields; + +export type V4Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V4Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V4Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V4HierarchyField = { + data_type: string; + fields: V4Field[]; + type: string; +}; + +type V4PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V4Field = { + data_type: string; + fields?: V4Field[]; + type: string; + title: string; + guid: string; + formatting?: V4Formatting; + format?: string; + datasetId: string; + dateMode?: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; +} & V4ClientOnlyFields; + +export type V4ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; +}; + +type V4ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V4ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +type V4ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; +}; diff --git a/src/shared/types/config/wizard/v5.ts b/src/shared/types/config/wizard/v5.ts new file mode 100644 index 0000000..51c79f7 --- /dev/null +++ b/src/shared/types/config/wizard/v5.ts @@ -0,0 +1,287 @@ +import type { + AxisLabelFormatMode, + AxisMode, + ChartsConfigVersion, + ColumnSettings, + DatasetFieldCalcMode, + NumberFormatType, + NumberFormatUnit, + ParameterDefaultValue, + TableBarsSettings, +} from '../../index'; +import type {AxisNullsMode, TableFieldBackgroundSettings} from '../../wizard'; + +export type V5ChartsConfig = { + title?: string; + colors: V5Color[]; + colorsConfig?: V5ColorsConfig; + extraSettings: V5CommonSharedExtraSettings; + filters: V5Filter[]; + geopointsConfig?: V5PointSizeConfig; + hierarchies: V5HierarchyField[]; + labels: V5Label[]; + links: V5Link[]; + sort: V5Sort[]; + tooltips: V5Tooltip[]; + type: 'datalens'; + updates: V5Update[]; + visualization: V5Visualization; + shapes: V5Shape[]; + shapesConfig?: V5ShapesConfig; + version: ChartsConfigVersion.V5; + datasetsIds: string[]; + datasetsPartialFields: V5ChartsConfigDatasetField[][]; + segments: V5Field[]; + chartType?: string; +}; + +type V5Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export interface V5CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V5NavigatorSettings; +} + +type V5NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V5Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V5ClientOnlyFields; + +export type V5Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V5ClientOnlyFields; + +type V5Link = { + id: string; + fields: Record; +}; + +type V5LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V5Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V5Layer[]; + placeholders: V5Placeholder[]; +}; + +type V5LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V5CommonPlaceholders = { + colors: V5Color[]; + labels: V5Label[]; + tooltips: V5Tooltip[]; + filters: V5Filter[]; + sort: V5Sort[]; + shapes?: V5Shape[]; + colorsConfig?: V5ColorsConfig; + geopointsConfig?: V5PointSizeConfig; + shapesConfig?: V5ShapesConfig; +}; + +export type V5Layer = { + id: string; + commonPlaceholders: V5CommonPlaceholders; + layerSettings: V5LayerSettings; + placeholders: V5Placeholder[]; +}; + +export type V5Placeholder = { + id: string; + settings?: { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisFormatMode?: AxisLabelFormatMode; + axisMode?: AxisMode; + }; + required?: boolean; + capacity?: number; + items: V5Field[]; +}; + +export type V5Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V5Formatting; + calc_mode: DatasetFieldCalcMode; +} & V5ClientOnlyFields; + +export type V5Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V5Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V5Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V5ClientOnlyFields; + +export type V5Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V5Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V5Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V5HierarchyField = { + data_type: string; + fields: V5Field[]; + type: string; +}; + +type V5PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V5Field = { + data_type: string; + fields?: V5Field[]; + type: string; + title: string; + guid: string; + formatting?: V5Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; +} & V5ClientOnlyFields; + +export type V5ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; +}; + +type V5ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V5ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +type V5ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; +}; diff --git a/src/shared/types/config/wizard/v6.ts b/src/shared/types/config/wizard/v6.ts new file mode 100644 index 0000000..0de5272 --- /dev/null +++ b/src/shared/types/config/wizard/v6.ts @@ -0,0 +1,286 @@ +import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; +import type {NumberFormatType, NumberFormatUnit} from '../../formatting'; +import type { + AxisLabelFormatMode, + AxisMode, + AxisNullsMode, + ChartsConfigVersion, + ColumnSettings, + TableBarsSettings, + TableFieldBackgroundSettings, +} from '../../wizard'; + +export type V6ChartsConfig = { + title?: string; + colors: V6Color[]; + colorsConfig?: V6ColorsConfig; + extraSettings: V6CommonSharedExtraSettings; + filters: V6Filter[]; + geopointsConfig?: V6PointSizeConfig; + hierarchies: V6HierarchyField[]; + labels: V6Label[]; + links: V6Link[]; + sort: V6Sort[]; + tooltips: V6Tooltip[]; + type: 'datalens'; + updates: V6Update[]; + visualization: V6Visualization; + shapes: V6Shape[]; + shapesConfig?: V6ShapesConfig; + version: ChartsConfigVersion.V6; + datasetsIds: string[]; + datasetsPartialFields: V6ChartsConfigDatasetField[][]; + segments: V6Field[]; + chartType?: string; +}; + +export type V6Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export interface V6CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V6NavigatorSettings; +} + +export type V6NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V6Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V6ClientOnlyFields; + +export type V6Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V6ClientOnlyFields; + +export type V6Link = { + id: string; + fields: Record; +}; + +export type V6LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V6Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V6Layer[]; + placeholders: V6Placeholder[]; +}; + +export type V6LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V6CommonPlaceholders = { + colors: V6Color[]; + labels: V6Label[]; + tooltips: V6Tooltip[]; + filters: V6Filter[]; + sort: V6Sort[]; + shapes?: V6Shape[]; + colorsConfig?: V6ColorsConfig; + geopointsConfig?: V6PointSizeConfig; + shapesConfig?: V6ShapesConfig; +}; + +export type V6Layer = { + id: string; + commonPlaceholders: V6CommonPlaceholders; + layerSettings: V6LayerSettings; + placeholders: V6Placeholder[]; +}; + +export type V6Placeholder = { + id: string; + settings?: { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisFormatMode?: AxisLabelFormatMode; + axisMode?: AxisMode; + }; + required?: boolean; + capacity?: number; + items: V6Field[]; +}; + +export type V6Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V6Formatting; + calc_mode: DatasetFieldCalcMode; +} & V6ClientOnlyFields; + +export type V6Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V6Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V6Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V6ClientOnlyFields; + +export type V6Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V6Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V6Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V6HierarchyField = { + data_type: string; + fields: V6Field[]; + type: string; +}; + +export type V6PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V6Field = { + data_type: string; + fields?: V6Field[]; + type: string; + title: string; + guid: string; + formatting?: V6Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; +} & V6ClientOnlyFields; + +export type V6ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; +}; + +export type V6ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V6ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +export type V6ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; +}; diff --git a/src/shared/types/config/wizard/v7.ts b/src/shared/types/config/wizard/v7.ts new file mode 100644 index 0000000..6c0013b --- /dev/null +++ b/src/shared/types/config/wizard/v7.ts @@ -0,0 +1,288 @@ +import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; +import type {NumberFormatType, NumberFormatUnit} from '../../formatting'; +import type { + AxisLabelFormatMode, + AxisMode, + AxisNullsMode, + ChartsConfigVersion, + ColumnSettings, + TableBarsSettings, + TableFieldBackgroundSettings, + TableSubTotalsSettings, +} from '../../wizard'; + +export type V7ChartsConfig = { + title?: string; + colors: V7Color[]; + colorsConfig?: V7ColorsConfig; + extraSettings: V7CommonSharedExtraSettings; + filters: V7Filter[]; + geopointsConfig?: V7PointSizeConfig; + hierarchies: V7HierarchyField[]; + labels: V7Label[]; + links: V7Link[]; + sort: V7Sort[]; + tooltips: V7Tooltip[]; + type: 'datalens'; + updates: V7Update[]; + visualization: V7Visualization; + shapes: V7Shape[]; + shapesConfig?: V7ShapesConfig; + version: ChartsConfigVersion.V7; + datasetsIds: string[]; + datasetsPartialFields: V7ChartsConfigDatasetField[][]; + segments: V7Field[]; + chartType?: string; +}; + +export type V7Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export interface V7CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V7NavigatorSettings; +} + +export type V7NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V7Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V7ClientOnlyFields; + +export type V7Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V7ClientOnlyFields; + +export type V7Link = { + id: string; + fields: Record; +}; + +export type V7LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V7Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V7Layer[]; + placeholders: V7Placeholder[]; +}; + +export type V7LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V7CommonPlaceholders = { + colors: V7Color[]; + labels: V7Label[]; + tooltips: V7Tooltip[]; + filters: V7Filter[]; + sort: V7Sort[]; + shapes?: V7Shape[]; + colorsConfig?: V7ColorsConfig; + geopointsConfig?: V7PointSizeConfig; + shapesConfig?: V7ShapesConfig; +}; + +export type V7Layer = { + id: string; + commonPlaceholders: V7CommonPlaceholders; + layerSettings: V7LayerSettings; + placeholders: V7Placeholder[]; +}; + +export type V7Placeholder = { + id: string; + settings?: { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisFormatMode?: AxisLabelFormatMode; + axisModeMap?: Record; + }; + required?: boolean; + capacity?: number; + items: V7Field[]; +}; + +export type V7Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V7Formatting; + calc_mode: DatasetFieldCalcMode; +} & V7ClientOnlyFields; + +export type V7Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V7Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V7Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V7ClientOnlyFields; + +export type V7Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V7Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V7Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V7HierarchyField = { + data_type: string; + fields: V7Field[]; + type: string; +}; + +export type V7PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V7Field = { + data_type: string; + fields?: V7Field[]; + type: string; + title: string; + guid: string; + formatting?: V7Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + subTotalsSettings?: TableSubTotalsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; +} & V7ClientOnlyFields; + +export type V7ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; +}; + +export type V7ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V7ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +export type V7ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; +}; diff --git a/src/shared/types/config/wizard/v8.ts b/src/shared/types/config/wizard/v8.ts new file mode 100644 index 0000000..3a7f107 --- /dev/null +++ b/src/shared/types/config/wizard/v8.ts @@ -0,0 +1,293 @@ +import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; +import type {NumberFormatType, NumberFormatUnit} from '../../formatting'; +import type { + AxisLabelFormatMode, + AxisMode, + AxisNullsMode, + ChartsConfigVersion, + ColumnSettings, + LabelsPositions, + TableBarsSettings, + TableFieldBackgroundSettings, +} from '../../wizard'; +import type {TableSubTotalsSettings} from '../../wizard/sub-totals'; + +export type V8ChartsConfig = { + title?: string; + colors: V8Color[]; + colorsConfig?: V8ColorsConfig; + extraSettings: V8CommonSharedExtraSettings | undefined; + filters: V8Filter[]; + geopointsConfig?: V8PointSizeConfig; + hierarchies: V8HierarchyField[]; + labels: V8Label[]; + links: V8Link[]; + sort: V8Sort[]; + tooltips: V8Tooltip[]; + type: 'datalens'; + updates: V8Update[]; + visualization: V8Visualization; + shapes: V8Shape[]; + shapesConfig?: V8ShapesConfig; + version: ChartsConfigVersion.V8; + datasetsIds: string[]; + datasetsPartialFields: V8ChartsConfigDatasetField[][]; + segments: V8Field[]; + chartType?: string; +}; + +export type V8Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export interface V8CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V8NavigatorSettings; + enableGPTInsights?: boolean; + labelsPosition?: LabelsPositions; +} + +export type V8NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V8Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V8ClientOnlyFields; + +export type V8Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V8ClientOnlyFields; + +export type V8Link = { + id: string; + fields: Record; +}; + +export type V8LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V8Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V8Layer[]; + placeholders: V8Placeholder[]; +}; + +export type V8LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V8CommonPlaceholders = { + colors: V8Color[]; + labels: V8Label[]; + tooltips: V8Tooltip[]; + filters: V8Filter[]; + sort: V8Sort[]; + shapes?: V8Shape[]; + colorsConfig?: V8ColorsConfig; + geopointsConfig?: V8PointSizeConfig; + shapesConfig?: V8ShapesConfig; +}; + +export type V8Layer = { + id: string; + commonPlaceholders: V8CommonPlaceholders; + layerSettings: V8LayerSettings; + placeholders: V8Placeholder[]; +}; + +export type V8PlaceholderSettings = { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisFormatMode?: AxisLabelFormatMode; + axisModeMap?: Record; +}; + +export type V8Placeholder = { + id: string; + settings?: V8PlaceholderSettings; + required?: boolean; + capacity?: number; + items: V8Field[]; +}; + +export type V8Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V8Formatting; + calc_mode: DatasetFieldCalcMode; +} & V8ClientOnlyFields; + +export type V8Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V8Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V8Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V8ClientOnlyFields; + +export type V8Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V8Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V8Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V8HierarchyField = { + data_type: string; + fields: V8Field[]; + type: string; +}; + +export type V8PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V8Field = { + data_type: string; + fields?: V8Field[]; + type: string; + title: string; + guid: string; + formatting?: V8Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + subTotalsSettings?: TableSubTotalsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; +} & V8ClientOnlyFields; + +export type V8ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; +}; + +export type V8ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V8ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +export type V8ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; +}; diff --git a/src/shared/types/config/wizard/v9.ts b/src/shared/types/config/wizard/v9.ts new file mode 100644 index 0000000..da857ad --- /dev/null +++ b/src/shared/types/config/wizard/v9.ts @@ -0,0 +1,296 @@ +import type {ColorMode} from '../../../constants'; +import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; +import type {NumberFormatType, NumberFormatUnit} from '../../formatting'; +import type { + AxisLabelFormatMode, + AxisMode, + AxisNullsMode, + ChartsConfigVersion, + ColumnSettings, + LabelsPositions, + TableBarsSettings, + TableFieldBackgroundSettings, + TableSubTotalsSettings, +} from '../../wizard'; + +export type V9ChartsConfig = { + title?: string; + colors: V9Color[]; + colorsConfig?: V9ColorsConfig; + extraSettings: V9CommonSharedExtraSettings | undefined; + filters: V9Filter[]; + geopointsConfig?: V9PointSizeConfig; + hierarchies: V9HierarchyField[]; + labels: V9Label[]; + links: V9Link[]; + sort: V9Sort[]; + tooltips: V9Tooltip[]; + type: 'datalens'; + updates: V9Update[]; + visualization: V9Visualization; + shapes: V9Shape[]; + shapesConfig?: V9ShapesConfig; + version: ChartsConfigVersion.V9; + datasetsIds: string[]; + datasetsPartialFields: V9ChartsConfigDatasetField[][]; + segments: V9Field[]; + chartType?: string; +}; + +export type V9Update = { + action: 'add_field' | 'add' | 'update_field' | 'update' | 'delete' | 'delete_field'; + field: any; + debug_info?: string; +}; + +export interface V9CommonSharedExtraSettings { + title?: string; + titleMode?: 'show' | 'hide'; + legendMode?: 'show' | 'hide'; + metricFontSize?: string; + metricFontColor?: string; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + overlap?: 'on' | 'off'; + feed?: string; + navigatorSettings?: V9NavigatorSettings; + enableGPTInsights?: boolean; + labelsPosition?: LabelsPositions; +} + +export type V9NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: string; + periodSettings: { + type: string; + value: string; + period: string; + }; +}; + +export type V9Filter = { + guid: string; + datasetId: string; + disabled?: string; + filter: { + operation: { + code: string; + }; + value?: string | string[]; + }; + type: string; + title: string; + calc_mode: DatasetFieldCalcMode; +} & V9ClientOnlyFields; + +export type V9Sort = { + guid: string; + title: string; + source?: string; + datasetId: string; + direction: string; + data_type: string; + format?: string; + type: string; + default_value?: ParameterDefaultValue; +} & V9ClientOnlyFields; + +export type V9Link = { + id: string; + fields: Record; +}; + +export type V9LinkField = { + field: { + title: string; + guid: string; + }; + dataset: { + id: string; + realName: string; + }; +}; + +export type V9Visualization = { + id: string; + highchartsId?: string; + selectedLayerId?: string; + layers?: V9Layer[]; + placeholders: V9Placeholder[]; +}; + +export type V9LayerSettings = { + id: string; + name: string; + type: string; + alpha: number; + valid: boolean; +}; + +export type V9CommonPlaceholders = { + colors: V9Color[]; + labels: V9Label[]; + tooltips: V9Tooltip[]; + filters: V9Filter[]; + sort: V9Sort[]; + shapes?: V9Shape[]; + colorsConfig?: V9ColorsConfig; + geopointsConfig?: V9PointSizeConfig; + shapesConfig?: V9ShapesConfig; +}; + +export type V9Layer = { + id: string; + commonPlaceholders: V9CommonPlaceholders; + layerSettings: V9LayerSettings; + placeholders: V9Placeholder[]; +}; + +export type V9PlaceholderSettings = { + groupping?: 'disabled' | 'off'; + autoscale?: boolean; + scale?: 'auto' | 'manual'; + scaleValue?: '0-max' | [string, string]; + title?: 'auto' | 'manual' | 'off'; + titleValue?: 'string'; + type?: 'logarithmic'; + grid?: 'on' | 'off'; + gridStep?: 'manual'; + gridStepValue?: number; + hideLabels?: 'yes' | 'no'; + labelsView?: 'horizontal' | 'vertical' | 'angle'; + nulls?: AxisNullsMode; + holidays?: 'on' | 'off'; + axisFormatMode?: AxisLabelFormatMode; + axisModeMap?: Record; + disableAxisMode?: boolean; +}; + +export type V9Placeholder = { + id: string; + settings?: V9PlaceholderSettings; + required?: boolean; + capacity?: number; + items: V9Field[]; +}; + +export type V9Color = { + datasetId: string; + guid: string; + title: string; + type: string; + data_type: string; + formatting?: V9Formatting; + calc_mode: DatasetFieldCalcMode; +} & V9ClientOnlyFields; + +export type V9Shape = { + datasetId: string; + guid: string; + title: string; + originalTitle?: string; + type: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V9Tooltip = { + datasetId: string; + guid: string; + title: string; + formatting?: V9Formatting; + data_type: string; + calc_mode: DatasetFieldCalcMode; +} & V9ClientOnlyFields; + +export type V9Formatting = { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + precision?: number; + labelMode?: string; +}; + +export type V9Label = { + datasetId: string; + type: string; + title: string; + guid: string; + formatting?: V9Formatting; + format?: string; + data_type: string; + calc_mode: DatasetFieldCalcMode; +}; + +export type V9HierarchyField = { + data_type: string; + fields: V9Field[]; + type: string; +}; + +export type V9PointSizeConfig = { + radius: number; + minRadius: number; + maxRadius: number; +}; + +export type V9Field = { + data_type: string; + fields?: V9Field[]; + type: string; + title: string; + guid: string; + formatting?: V9Formatting; + format?: string; + datasetId: string; + source?: string; + datasetName?: string; + hideLabelMode?: string; + calc_mode: DatasetFieldCalcMode; + default_value?: ParameterDefaultValue; + barsSettings?: TableBarsSettings; + subTotalsSettings?: TableSubTotalsSettings; + backgroundSettings?: TableFieldBackgroundSettings; + columnSettings?: ColumnSettings; +} & V9ClientOnlyFields; + +export type V9ColorsConfig = { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: string; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; + colorMode?: ColorMode; +}; + +export type V9ShapesConfig = { + mountedShapes?: Record; + fieldGuid?: string; +}; + +export type V9ChartsConfigDatasetField = { + guid: string; + title: string; + calc_mode?: DatasetFieldCalcMode; +}; + +export type V9ClientOnlyFields = { + fakeTitle?: string; + originalTitle?: string; +}; diff --git a/src/shared/types/configs/index.ts b/src/shared/types/configs/index.ts new file mode 100644 index 0000000..45899e3 --- /dev/null +++ b/src/shared/types/configs/index.ts @@ -0,0 +1,22 @@ +export type IconId = + | 'visCombined' + | 'visDonut' + | 'visFlatTable' + | 'visGeolayers' + | 'visArea100p' + | 'visArea' + | 'visBar100p' + | 'visBar' + | 'visColumn100p' + | 'visColumn' + | 'visLines' + | 'visScatter' + | 'visMetric' + | 'visPie' + | 'visPivot' + | 'visTreemap' + | 'visFunnel' + | 'collectionColored' + | 'collectionColoredDark' + | 'collectionColoredBig' + | 'collectionColoredBigDark'; diff --git a/src/shared/types/connections.ts b/src/shared/types/connections.ts new file mode 100644 index 0000000..ad4c6b6 --- /dev/null +++ b/src/shared/types/connections.ts @@ -0,0 +1,29 @@ +import type {ConnectionQueryTypeValues} from '../constants'; + +export type ConnectionData = Record< + string, + string | number | boolean | unknown[] | null | undefined | Record +>; + +export type ConnectionOptions = { + allow_dashsql_usage: boolean; + allow_dataset_usage: boolean; + allow_typed_query_usage: boolean; + query_types: ConnectionQueryTypeOptions[]; +}; + +export type ConnectionQueryTypeOptions = { + query_type: ConnectionQueryTypeValues; + query_type_label: string; + allow_selector: boolean; + required_parameters: ConnectionRequiredParameter[]; +}; + +export type ConnectionRequiredParameter = { + name: string; + data_type: string; +}; + +export type ConnectionQueryContent = { + query: string; +}; diff --git a/src/shared/types/dash.ts b/src/shared/types/dash.ts new file mode 100644 index 0000000..aa08b56 --- /dev/null +++ b/src/shared/types/dash.ts @@ -0,0 +1,457 @@ +import type {ItemDropProps} from '@gravity-ui/dashkit'; +import type {ThemeType} from '@gravity-ui/uikit'; + +import type {ConnectionQueryTypeValues} from '../constants/connections'; +import type {Operations} from '../modules'; + +export type ImpactType = 'allTabs' | 'currentTab' | 'selectedTabs' | 'asGroup'; +export type ImpactTabsIds = string[] | null | undefined; + +import type { + ClientChartsConfig, + ConnectionQueryContent, + DatasetFieldType, + Dictionary, + Entry, + EntryScope, + HintSettings, + StringParams, + ValueOf, +} from './index'; + +export enum ControlType { + Dash = 'control_dash', +} + +export type DashChartRequestContext = Record; + +export enum DashTabItemType { + Title = 'title', + Text = 'text', + Widget = 'widget', + Control = 'control', + GroupControl = 'group_control', + Image = 'image', +} + +export const DashTabItemTitleSizes = { + XL: 'xl', + L: 'l', + M: 'm', + S: 's', + XS: 'xs', +} as const; + +export type DashTabItemTitleSize = ValueOf; + +export enum DashTabItemControlSourceType { + Dataset = 'dataset', + Connection = 'connection', + Manual = 'manual', + External = 'external', +} + +export enum DashTabItemControlElementType { + Select = 'select', + Date = 'date', + Input = 'input', + Checkbox = 'checkbox', +} + +export enum DashTabConnectionKind { + Ignore = 'ignore', +} + +export enum DashLoadPriority { + Charts = 'charts', + Selectors = 'selectors', +} + +export interface DashEntryCreateParams extends DashEntry, DashData {} + +export interface DashEntry extends Entry { + scope: EntryScope.Dash; + type: ''; + data: DashData; + savedId: string; + revId: string; + updatedAt: string; + publishedId: string; + asNew?: boolean; + fake?: boolean; +} + +export type DashWidgetsSettings = { + backgroundSettings?: BackgroundSettings; + borderRadius?: number; + internalMarginsEnabled?: boolean; +}; + +export type DashSettings = { + autoupdateInterval: null | number; + maxConcurrentRequests: null | number; + silentLoading: boolean; + dependentSelectors: boolean; + hideTabs: boolean; + hideDashTitle?: boolean; + expandTOC: boolean; + globalParams?: DashSettingsGlobalParams; + // used only for secure cases + signedGlobalParams?: DashSettingsGlobalParams; + loadPriority?: DashLoadPriority; + loadOnlyVisibleCharts?: boolean; + margins?: [number, number]; + enableAssistant?: boolean; + widgetsSettings?: DashWidgetsSettings; + backgroundSettings?: BackgroundSettings; +}; + +export interface DashData { + counter: number; + tabs: DashTab[]; + salt: string; + schemeVersion: number; + settings: DashSettings; + // only in old dashbords (now migrating to annotation) + description?: string; + accessDescription?: string; + supportDescription?: string; +} + +export type DashDragOptions = ItemDropProps; + +type OptionalDashDataSettings = Pick< + DashSettings, + 'margins' | 'enableAssistant' | 'widgetsSettings' | 'backgroundSettings' +>; + +// config with strict requirements of settings for new dash +// schemeVersion comes from server +export type FakeDashData = Omit & { + settings: Required> & + OptionalDashDataSettings; +}; + +export interface DashTabSettings { + fixedHeaderCollapsedDefault: boolean; +} +export interface DashTab { + id: string; + title: string; + items: DashTabItem[]; + layout: DashTabLayout[]; + aliases: DashTabAliases; + connections: DashTabConnection[]; + settings?: DashTabSettings; + globalItems?: DashGlobalItem[]; +} + +export type DashSettingsGlobalParams = Record; +export type DashWidgetsConfigsMapper = Record; + +export type DashGlobalItem = DashTabItemControl | DashTabItemGroupControl; + +export type DashTabItem = + | DashTabItemText + | DashTabItemTitle + | DashTabItemWidget + | DashTabItemControl + | DashTabItemGroupControl + | DashTabItemImage; + +export type ColorByTheme = Partial>; + +export type OldBackgroundSettings = { + enabled?: boolean; + color: string; +}; + +export type ColorSettings = ColorByTheme | string; +export type BackgroundSettings = {color?: ColorSettings}; + +export type TitleTextSettings = {color?: ColorSettings}; + +export interface DashTabItemBase { + id: string; + namespace: string; + type: DashTabItemType; + orderId?: number; + defaultOrderId?: number; + title?: string; +} + +export type DashTabItemBaseData = { + background?: OldBackgroundSettings; + backgroundSettings?: BackgroundSettings; + borderRadius?: number; + internalMarginsEnabled?: boolean; +}; + +export interface DashTabItemText extends DashTabItemBase { + type: DashTabItemType.Text; + data: DashTabItemBaseData & { + text: string; + autoHeight?: boolean; + }; +} + +export type DashTitleSize = + | DashTabItemTitleSize + | { + fontSize: number; + lineHeight?: number; + }; + +export interface DashTabItemTitle extends DashTabItemBase { + type: DashTabItemType.Title; + data: DashTabItemBaseData & { + text: string; + size: DashTitleSize; + showInTOC: boolean; + autoHeight?: boolean; + textColor?: string; + textSettings?: TitleTextSettings; + hint?: HintSettings; + }; +} + +export interface DashTabItemWidget extends DashTabItemBase { + type: DashTabItemType.Widget; + data: Omit & { + hideTitle: boolean; + tabs: DashTabItemWidgetTab[]; + }; +} + +export interface DashTabItemWidgetTab { + id: string; + title: string; + description: string; + hint?: string; + enableHint?: boolean; + enableDescription?: boolean; + chartId: string; + isDefault: boolean; + params: StringParams; + autoHeight?: boolean; + enableActionParams?: boolean; + background?: OldBackgroundSettings; +} + +export interface DashTabItemControl extends DashTabItemBase { + type: DashTabItemType.Control; + data: Omit; + defaults: StringParams; +} + +export interface DashTabItemControlBaseData { + id?: string; + title: string; + placementMode?: 'auto' | '%' | 'px'; + autoHeight?: boolean; + width?: string; + defaults?: StringParams; + namespace?: string; + impactType?: ImpactType; + impactTabsIds?: ImpactTabsIds; +} + +export interface DashTabItemControlData extends DashTabItemControlBaseData { + sourceType: DashTabItemControlSourceType; + source: + | DashTabItemControlDataset['source'] + | DashTabItemControlManual['source'] + | DashTabItemControlExternal['source']; +} + +export type DashTabItemControlSingle = (DashTabItemControlDataset | DashTabItemControlManual) & { + id: string; + namespace: string; +}; + +export type DashItemControlConnectionBaseSource = { + connectionId?: string; + connectionQueryType?: ConnectionQueryTypeValues; + connectionQueryContent?: ConnectionQueryContent; +}; + +export type DashTabItemControlConnection = DashTabItemControlBaseData & { + sourceType: DashTabItemControlSourceType.Connection; + source: DashItemControlConnectionBaseSource & DashTabItemControlElement; +}; + +export type DashItemControlDatasetBaseSource = { + datasetId: string; + datasetFieldId: string; + datasetFieldType?: DatasetFieldType; +}; + +export interface DashTabItemControlDataset extends DashTabItemControlData { + sourceType: DashTabItemControlSourceType.Dataset; + source: DashItemControlDatasetBaseSource & DashTabItemControlElement; +} + +export type DashItemControlManualBaseSource = { + fieldName: string; + acceptableValues?: + | { + // elementType: select + value: string; + title: string; + }[] + | { + // elementType: date + from: string; + to: string; + }; +}; + +export interface DashTabItemControlManual extends DashTabItemControlData { + sourceType: DashTabItemControlSourceType.Manual; + source: DashItemControlManualBaseSource & DashTabItemControlElement; +} + +export type DashTabItemControlElement = + | DashTabItemControlElementSelect + | DashTabItemControlElementInput + | DashTabItemControlElementDate + | DashTabItemControlElementCheckbox; + +export enum TitlePlacementOption { + Left = 'left', + Top = 'top', +} + +export const TitlePlacements = { + Hide: 'hide' as const, + Left: TitlePlacementOption.Left, + Top: TitlePlacementOption.Top, +}; + +export type TitlePlacement = ValueOf; + +export type AccentTypeValue = 'info' | null; + +export interface DashTabItemControlElementBase { + showTitle: boolean; + titlePlacement?: TitlePlacementOption; + elementType: DashTabItemControlElementType; + operation?: Operations; + showInnerTitle?: boolean; + innerTitle?: string; + fieldType?: string; + required?: boolean; + showHint?: boolean; + hint?: string; + accentType?: AccentTypeValue; +} + +export type UniversalDefaultValue = StringDefaultValue | string[]; +export type StringDefaultValue = string; + +export interface DashTabItemControlElementSelectBase { + multiselectable: boolean; +} + +export interface DashTabItemControlElementSelect + extends DashTabItemControlElementBase, + DashTabItemControlElementSelectBase { + elementType: DashTabItemControlElementType.Select; + defaultValue?: UniversalDefaultValue; +} + +export interface DashTabItemControlElementInput extends DashTabItemControlElementBase { + elementType: DashTabItemControlElementType.Input; + defaultValue?: StringDefaultValue; +} + +export interface DashTabItemControlElementDateBase { + isRange: boolean; +} + +export interface DashTabItemControlElementDate + extends DashTabItemControlElementBase, + DashTabItemControlElementDateBase { + elementType: DashTabItemControlElementType.Date; + defaultValue?: StringDefaultValue; +} + +export interface DashTabItemControlElementCheckbox extends DashTabItemControlElementBase { + elementType: DashTabItemControlElementType.Checkbox; + defaultValue: StringDefaultValue; +} + +export type DashItemControlExternalBaseSource = { + chartId: string; +}; + +export interface DashTabItemControlExternal extends DashTabItemControlData { + sourceType: DashTabItemControlSourceType.External; + source: DashItemControlExternalBaseSource; +} + +export interface DashTabItemGroupControl extends DashTabItemBase { + type: DashTabItemType.GroupControl; + data: DashTabItemGroupControlData; +} + +export interface DashTabItemGroupControlBaseData { + showGroupName: boolean; + groupName?: string; + autoHeight: boolean; + buttonApply: boolean; + buttonReset: boolean; + impactType?: ImpactType; + impactTabsIds?: ImpactTabsIds; + + updateControlsOnChange?: boolean; +} + +export interface DashTabItemGroupControlData extends DashTabItemGroupControlBaseData { + group: DashTabItemControlSingle[]; +} + +export interface DashTabLayout { + h: number; + i: string; + w: number; + x: number; + y: number; + parent?: string; +} + +export interface DashTabAliases extends Dictionary> {} + +export interface DashTabConnection { + from: string; + to: string; + kind: DashTabConnectionKind; +} + +export interface DashStateData + extends Dictionary<{ + params: StringParams; + state: { + // for chart state + tabId: string; + }; + }> {} + +export interface DashStats { + dashId: string; + dashTabId: string; + dashStateHash: string | null; + login?: string; + userId: string; + tenantId: string; + traceId?: string; +} + +export interface DashTabItemImage extends DashTabItemBase { + type: DashTabItemType.Image; + data: DashTabItemBaseData & { + src: string; + alt?: string; + preserveAspectRatio?: boolean; + }; +} diff --git a/src/shared/types/dataset.ts b/src/shared/types/dataset.ts new file mode 100644 index 0000000..13bdcb0 --- /dev/null +++ b/src/shared/types/dataset.ts @@ -0,0 +1,396 @@ +import type {ConnectorType, DATASET_CACHE_MODE} from '../constants'; +import type {SharedEntryPermissions} from '../schema'; +import type {CommonNumberFormattingOptions, Permissions, ValueOf} from '../types'; + +import type {CommonUpdate} from './common-update'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export enum DATASET_FIELD_TYPES { + DATE = 'date', + GENERICDATETIME = 'genericdatetime', + DATETIMETZ = 'datetimetz', + INTEGER = 'integer', + UINTEGER = 'uinteger', + STRING = 'string', + FLOAT = 'float', + BOOLEAN = 'boolean', + GEOPOINT = 'geopoint', + GEOPOLYGON = 'geopolygon', + MARKUP = 'markup', + HEATMAP = 'heatmap', + ARRAY_INT = 'array_int', + ARRAY_FLOAT = 'array_float', + ARRAY_STR = 'array_str', + UNSUPPORTED = 'unsupported', + // HIERARCHY - the value is used in the client part of the wizard, in the future it will come from the backend. + HIERARCHY = 'hierarchy', + TREE_STR = 'tree_str', + TREE_INT = 'tree_int', + TREE_FLOAT = 'tree_float', +} + +export const DATASET_IGNORED_DATA_TYPES = [ + DATASET_FIELD_TYPES.MARKUP, + DATASET_FIELD_TYPES.TREE_STR, + DATASET_FIELD_TYPES.TREE_INT, + DATASET_FIELD_TYPES.TREE_FLOAT, + DATASET_FIELD_TYPES.ARRAY_STR, + DATASET_FIELD_TYPES.ARRAY_INT, + DATASET_FIELD_TYPES.ARRAY_FLOAT, +]; + +export const AVAILABLE_FIELD_TYPES = [ + DATASET_FIELD_TYPES.INTEGER, + DATASET_FIELD_TYPES.STRING, + DATASET_FIELD_TYPES.FLOAT, + DATASET_FIELD_TYPES.BOOLEAN, + DATASET_FIELD_TYPES.DATE, + DATASET_FIELD_TYPES.GENERICDATETIME, +] as const; + +export const COMMON_FIELD_TYPES = [ + DATASET_FIELD_TYPES.ARRAY_FLOAT, + DATASET_FIELD_TYPES.ARRAY_INT, + DATASET_FIELD_TYPES.ARRAY_STR, + DATASET_FIELD_TYPES.DATE, + DATASET_FIELD_TYPES.FLOAT, + DATASET_FIELD_TYPES.GENERICDATETIME, + DATASET_FIELD_TYPES.GEOPOINT, + DATASET_FIELD_TYPES.GEOPOLYGON, + DATASET_FIELD_TYPES.INTEGER, + DATASET_FIELD_TYPES.MARKUP, + DATASET_FIELD_TYPES.STRING, + DATASET_FIELD_TYPES.UNSUPPORTED, +] as const; + +export type AvailableFieldType = (typeof AVAILABLE_FIELD_TYPES)[number]; +export type CommonFieldType = (typeof COMMON_FIELD_TYPES)[number]; + +export enum DatasetFieldAggregation { + None = 'none', + Sum = 'sum', + Avg = 'avg', + Min = 'min', + Max = 'max', + Count = 'count', + Countunique = 'countunique', +} + +export type DatasetFieldCalcMode = 'formula' | 'direct' | 'parameter'; + +export enum DatasetFieldType { + Dimension = 'DIMENSION', + Measure = 'MEASURE', + Pseudo = 'PSEUDO', + Parameter = 'PARAMETER', +} + +export type DatasetComponentError = { + id: string; + type: 'data_source' | 'field'; + errors: DatasetComponentErrorItem[]; +}; + +export type DatasetComponentErrorItem = { + code: string; + level: string; + message: string; + details: { + db_message?: string; + query?: string; + }; +}; + +export interface Dataset { + id: string; + realName: string; + is_favorite: boolean; + key: string; + options: DatasetOptions; + dataset: { + avatar_relations: DatasetAvatarRelation[]; + component_errors: { + items: DatasetComponentError[]; + }; + obligatory_filters: ObligatoryFilter[]; + preview_enabled: boolean; + result_schema: DatasetField[]; + result_schema_aux: { + inter_dependencies: { + deps: string[]; + }; + }; + rls: {[key: string]: string}; + rls2: {[key: string]: string}; + source_avatars: DatasetSourceAvatar[]; + source_features?: {}; + sources: DatasetSource[]; + revisionId?: string; + load_preview_by_default: boolean; + template_enabled: boolean; + data_export_forbidden?: boolean; + description?: string; + }; + workbook_id?: string; + collection_id?: string; + permissions?: Permissions; + full_permissions?: SharedEntryPermissions; + cache_invalidation_source: DatasetCacheInvalidationSource; + + // This part of the fields moved to the dataset field. right here saved for backward compatibility + avatar_relations: DatasetAvatarRelation[]; + component_errors: {items: DatasetComponentError[]}; + preview_enabled: boolean; + raw_schema?: DatasetRawSchema[]; + result_schema?: DatasetField[]; + rls: {[key: string]: string}; + source_avatars: DatasetSourceAvatar[]; + source_features: {}; + sources: DatasetSource[]; +} +export type DatasetWithDelegation = Dataset & {isDelegated?: boolean}; +export interface ObligatoryFilter { + id: string; + field_guid: string; + managed_by: string; + valid: boolean; + default_filters: ObligatoryDefaultFilter[]; +} + +export interface ObligatoryDefaultFilter { + column: string; + operation: string; + values: string[]; +} + +export const DATASET_VALUE_CONSTRAINT_TYPE = { + DEFAULT: 'default', + NULL: 'null', + REGEX: 'regex', +} as const; + +export type DatasetValueConstraintType = + (typeof DATASET_VALUE_CONSTRAINT_TYPE)[keyof typeof DATASET_VALUE_CONSTRAINT_TYPE]; + +export interface DatasetField { + aggregation: DatasetFieldAggregation; + type: DatasetFieldType; + calc_mode: DatasetFieldCalcMode; + default_value: ParameterDefaultValue | null; + initial_data_type: DATASET_FIELD_TYPES; + // type before aggregation + cast: DATASET_FIELD_TYPES; + // final type + data_type: DATASET_FIELD_TYPES; + description: string; + guid: string; + title: string; + managed_by: string; + source: string; + avatar_id: string; + formula?: string; + guid_formula?: string; + has_auto_aggregation: boolean; + aggregation_locked: boolean; + lock_aggregation: boolean; + virtual: boolean; + valid: boolean; + hidden: boolean; + autoaggregated: boolean; + template_enabled?: boolean; + value_constraint?: + | {type: typeof DATASET_VALUE_CONSTRAINT_TYPE.DEFAULT} + | {type: typeof DATASET_VALUE_CONSTRAINT_TYPE.NULL} + | {type: typeof DATASET_VALUE_CONSTRAINT_TYPE.REGEX; pattern: string} + | null; + ui_settings?: string; +} + +export type DatasetFieldColorConfig = { + palette?: string; + colors?: Record; +}; + +export type FieldUISettings = { + numberFormatting?: CommonNumberFormattingOptions; +} & DatasetFieldColorConfig; + +export interface DatasetFieldError { + guid: string; + title: string; + errors: { + column: number | null; + row: number | null; + message: string; + }[]; +} + +export type DatasetUpdate = CommonUpdate; + +export type DatasetOptionDataTypeItem = { + aggregations: DatasetFieldAggregation[]; + casts: DATASET_FIELD_TYPES[]; + type: string; + filter_operations: string[]; +}; + +export type DatasetOptionFieldItem = { + aggregations: DatasetFieldAggregation[]; + casts: DATASET_FIELD_TYPES[]; + guid: string; +}; + +export interface SourceListingOptions { + source_listing?: { + supports_source_search: boolean; + supports_source_pagination: boolean; + supports_db_name_listing: boolean; + db_name_label: string; + db_name_required_for_search: boolean; + }; +} + +export interface DatasetOptions { + connections: { + compatible_types: string[]; + items: { + id: string; + replacement_types: { + conn_type: ConnectorType; + }[]; + }[]; + max: number; + }; + syntax_highlighting_url: string; + sources: { + compatible_types: string[]; + items: { + schema_update_enabled: boolean; + id: string; + }[]; + }; + preview: { + enabled: boolean; + }; + source_avatars: { + items: { + schema_update_enabled: boolean; + id: string; + }[]; + max: number; + }; + schema_update_enabled: boolean; + supports_offset: boolean; + supported_functions: string[]; + data_types: {items: DatasetOptionDataTypeItem[]}; + fields: {items: DatasetOptionFieldItem[]}; + join: { + types: string[]; + operators: string[]; + }; +} + +export type DatasetRawSchema = { + user_type: string; + name: string; + title: string; + description: string; + nullable: boolean; + lock_aggregation: boolean; + has_auto_aggregation: boolean; + native_type: { + name: string; + conn_type?: string; + }; +}; + +export interface DatasetSource { + id: string; + connection_id: string; + ref_source_id?: string | null; + name?: string; + title: string; + source_type: string; + managed_by: string; + parameter_hash: string; + valid: boolean; + is_ref?: boolean; + virtual: boolean; + raw_schema: DatasetRawSchema[]; + group?: string[]; + parameters: { + table_name?: string; + db_version?: string; + db_name?: string | null; + }; +} + +export interface DatasetSourceAvatar { + id: string; + title: string; + source_id: string; + managed_by: string; + valid: boolean; + is_root: boolean; + virtual: boolean; +} + +export interface DatasetAvatarRelation { + id: string; + join_type: string; + left_avatar_id: string; + right_avatar_id: string; + managed_by: string; + virtual: boolean; + conditions: DatasetAvatarRelationCondition[]; + required: boolean; +} + +export interface DatasetAvatarRelationCondition { + operator: string; + type: string; + left: { + calc_mode: string; + source: string | null; + }; + right: { + calc_mode: string; + source: string | null; + }; +} + +export interface DatasetApiError { + datasetId: string; + error: { + code?: string; + message?: string; + }; +} + +export type DatasetCacheInvalidationSource = { + mode: ValueOf; + filters: ObligatoryFilter[]; + field: DatasetField | null; + sql: string | null; + cache_invalidation_error: { + code: string; + message: string; + title: string; + level: string; + locator: string; + } | null; + last_result_timestamp: string | null; + last_result_error: { + code: string; + message: string; + details: { + db_message: string; + }; + debug: unknown; + } | null; +}; + +export type DatasetSelectionMap = Record; + +export type ParameterDefaultValue = string | number | boolean | null; +export type DatasetRls = {[key: string]: string}; diff --git a/src/shared/types/entry.ts b/src/shared/types/entry.ts new file mode 100644 index 0000000..8d176e1 --- /dev/null +++ b/src/shared/types/entry.ts @@ -0,0 +1,23 @@ +import type { + RestrictedSharedWorkbookEntry, + SharedWorkbookEntry, + WorkbookEntryBase, +} from '../schema'; +import type {EntryMetaFields} from '../schema/us/types/fields'; + +export type MinimumEntryFields = Pick; + +export type EntryPublicAuthor = {text?: string; link?: string}; + +export type WorkbookId = string | null; +export type CollectionId = string | null; + +export type TransferIdMapping = Record; + +export type TransferNotification = { + level: 'info' | 'warning' | 'critical'; + code: string; + details?: object; +}; + +export type WorkbookEntry = WorkbookEntryBase | SharedWorkbookEntry | RestrictedSharedWorkbookEntry; diff --git a/src/shared/types/feature.ts b/src/shared/types/feature.ts new file mode 100644 index 0000000..afcd651 --- /dev/null +++ b/src/shared/types/feature.ts @@ -0,0 +1,112 @@ +export enum Feature { + ChartkitAlerts = 'chartkitAlerts', + UseConfigurableChartkit = 'UseConfigurableChartkit', + AsideHeaderEnabled = 'AsideHeaderEnabled', + FieldEditorDocSection = 'FieldEditorDocSection', + UsePublicDistincts = 'UsePublicDistincts', + EnablePublishEntry = 'EnablePublishEntry', + EnableChartEditorDocs = 'EnableChartEditorDocs', + EnableSaveAsEditorScript = 'EnableSaveAsEditorScript', + EnableCustomMonitoring = 'EnableCustomMonitoring', + EnableDashChartStat = 'EnableDashChartStat', + EnableAutocreateDataset = 'EnableAutocreateDataset', + ShowCreateEntryWithMenu = 'ShowCreateEntryWithMenu', + RevisionsListNoLimit = 'RevisionsListNoLimit', + AuthUpdateWithTimeout = 'AuthUpdateWithTimeout', + UseComponentHeader = 'UseComponentHeader', + FetchDocumentation = 'FetchDocumentation', + Comments = 'Comments', + // Check access rights when processing ChartEditor charts + ChartEditorDeveloperModeCheck = 'ChartEditorDeveloperModeCheck', + QLPrometheus = 'QLPrometheus', + QLMonitoring = 'QLMonitoring', + CollectionsEnabled = 'CollectionsEnabled', + CollectionsAccessEnabled = 'CollectionsAccessEnabled', + DashBoardSupportDescription = 'DashBoardSupportDescription', + // Show request body in the Inspector dialog + ShowInspectorDetails = 'ShowInspectorDetails', + // Prohibiting the serialization of functions in the chart configs + NoJsonFn = 'NoJsonFn', + DatasetsRLS = 'DatasetsRLS', + // The ability to upload xlsx files for file connections + XlsxFilesEnabled = 'XlsxFilesEnabled', + XlsxChartExportEnabled = 'XlsxChartExportEnabled', + HolidaysOnChart = 'HolidaysOnChart', + ReadOnlyMode = 'ReadOnlyMode', + MenuItemsFlatView = 'MenuItemsFlatView', + EntryMenuItemCopy = 'EntryMenuItemCopy', + EntryMenuItemMove = 'EntryMenuItemMove', + ExternalSelectors = 'ExternalSelectors', + DashBoardWidgetParamsStrictValidation = 'DashBoardWidgetParamsStrictValidation', + HideMultiDatasets = 'HideMultiDatasets', + ShouldCheckEditorAccess = 'ShouldCheckEditorAccess', + HideMultitenant = 'HideMultitenant', + EnableMobileHeader = 'EnableMobileHeader', + UseYqlFolderKey = 'UseYqlFolderKey', + ShowChartsEngineDebugInfo = 'ShowChartsEngineDebugInfo', + UseChartsEngineResponseConfig = 'UseChartsEngineResponseConfig', + UseChartsEngineLogin = 'UseChartsEngineLogin', + CopyEntriesToWorkbook = 'CopyEntriesToWorkbook', + QlAutoExecuteMonitoringChart = 'QlAutoExecuteMonitoringChart', + MultipleColorsInVisualization = 'MultipleColorsInVisualization', + ConnectionBasedControl = 'ConnectionBasedControl', + EnableServerlessEditor = 'EnableServerlessEditor', + EnableFooter = 'EnableFooter', + MassRemoveCollectionsWorkbooks = 'MassRemoveCollectionsWorkbooks', + EnableEmbedsInDialogShare = 'EnableEmbedsInDialogShare', + EnableEntryMenuItemShare = 'EnableEntryMenuItemShare', + NewTableWidgetForCE = 'NewTableWidgetForCE', + /** E2E tests mode with collections */ + CollectionsE2ETestsMode = 'CollectionsE2ETestsMode', + /** Disable the use of html and function in chart configs */ + DisableFnAndHtml = 'DisableFnAndHtml', + /** Enables export menu item for downloading workbook config and import button + * when creating a workbook */ + EnableExportWorkbookFile = 'EnableExportWorkbookFile', + /** Enable using RLS v2 config for datasets */ + EnableRLSV2 = 'EnableRLSV2', + /* Enable Dash server entry validation */ + DashServerValidationEnable = 'DashServerValidationEnable', + /* Enable Dash server entry migrations */ + DashServerMigrationEnable = 'DashServerMigrationEnable', + /** Enable custom dashboard gaps */ + EnableCustomDashMargins = 'EnableCustomDashMargins', + /** Enabled Dash elements auto-focus */ + EnableDashAutoFocus = 'EnableDashAutoFocus', + /** Enable export settings in connections & datasets */ + EnableExportSettings = 'EnableExportSettings', + /** Enable updating dataset settings by action ('load_preview_by_default' | 'template_enabled' | 'data_export_forbidden') */ + EnableUpdatingDsSettingsByAction = 'EnableUpdatingDsSettingsByAction', + /** Use GravityUI Charts as the default library for some wizard visualizations(pie, donut, treemap) */ + GravityChartsForPieAndTreemap = 'GravityChartsForPieAndTreemap', + /** Use GravityUI Charts as the default library for some wizard visualizations(scatter and bar-y) */ + GravityChartsForBarYAndScatter = 'GravityChartsForBarYAndScatter', + /** Use GravityUI Charts as the default library for some wizard visualizations(line, area and bar-x) */ + GravityChartsForLineAreaAndBarX = 'GravityChartsForLineAreaAndBarX', + /** Enable shared connections and datasets */ + EnableSharedEntries = 'EnableSharedEntries', + + EnableMobileFixedHeader = 'EnableMobileFixedHeader', + /** enabled redesign/moving to drawers existing settings */ + EnableCommonChartDashSettings = 'EnableCommonChartDashSettings', + /** enables new dash & widgets settings */ + EnableNewDashSettings = 'EnableNewDashSettings', + /** Enable color pickers with customization of values for light & dark themes separately */ + EnableDashColorPickersByTheme = 'EnableDashColorPickersByTheme', + /** Shows updated settings page */ + EnableNewServiceSettings = 'EnableNewServiceSettings', + /** Funnel chart visualization */ + FunnelChart = 'FunnelChart', + /** Replace static master token with dynamic one */ + UsDynamicMasterToken = 'UsDynamicMasterToken', + /** Enable using dynamic master token in proxy */ + UsDynamicMasterTokenInProxy = 'UsDynamicMasterTokenInProxy', + /** Сhart settings for displaying trend lines smoothing, etc. */ + ChartModeling = 'ChartModeling', + /** Route legacy HTTP sources through API Connector */ + UseSourceAlias = 'UseSourceAlias', + /** Enable early cache invalidation in datasets */ + EnableDatasetEarlyInvalidationCache = 'EnableDatasetEarlyInvalidationCache', +} + +export type FeatureConfig = Record; diff --git a/src/shared/types/formatting.ts b/src/shared/types/formatting.ts new file mode 100644 index 0000000..c7bdd82 --- /dev/null +++ b/src/shared/types/formatting.ts @@ -0,0 +1,27 @@ +export enum NumberFormatType { + Number = 'number', + Percent = 'percent', +} + +export enum NumberFormatUnit { + Auto = 'auto', + K = 'k', + M = 'm', + B = 'b', + T = 't', +} + +export interface CommonNumberFormattingOptions { + format?: NumberFormatType; + showRankDelimiter?: boolean; + prefix?: string; + postfix?: string; + unit?: NumberFormatUnit; + // for a situation where unit is specified, integers is set to 0 + precision?: number; + labelMode?: string; +} + +export type IntegerNumberFormattingOptions = CommonNumberFormattingOptions; + +export type FloatNumberFormattingOptions = CommonNumberFormattingOptions; diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts new file mode 100644 index 0000000..d856a1c --- /dev/null +++ b/src/shared/types/index.ts @@ -0,0 +1,30 @@ +export * from './common'; +export * from './configs'; +export * from './connections'; +export * from './dash'; +export * from './widget'; +export * from './dataset'; +export * from './wizard'; +export * from './ql/common'; +export * from './wizard/index'; +export * from './charts'; +export * from './utility-types'; +export * from './notification'; +export * from './metrica'; +export * from './feature'; +export * from './config/wizard'; +export * from './bi-api'; +export * from './color-palettes'; +export * from './common-update'; +export * from './menu'; +export * from './entry'; +export * from './chartkit/common'; +export * from './chartkit/dl-chartkit'; +export * from './chartkit/table'; +export * from './config/ql'; +export * from './typed-query-api'; +export * from './permissions'; +export * from './formatting'; + +// TODO: will be removed in CHARTS-10240 +export type GoogleOAuthClient = any; diff --git a/src/shared/types/language.ts b/src/shared/types/language.ts new file mode 100644 index 0000000..f98589f --- /dev/null +++ b/src/shared/types/language.ts @@ -0,0 +1,5 @@ +export type GetTranslationFn = ( + keyset: string, + key: string, + params?: Record, +) => string; diff --git a/src/shared/types/menu.ts b/src/shared/types/menu.ts new file mode 100644 index 0000000..95f26f9 --- /dev/null +++ b/src/shared/types/menu.ts @@ -0,0 +1,33 @@ +export const MenuItemsIds = { + EXPORT: 'export', + EXPORT_CSV: 'exportCsv', + EXPORT_XLSX: 'exportXlsx', + EXPORT_MARKDOWN: 'exportMarkdown', + EXPORT_WIKI: 'exportWiki', + EXPORT_SCREENSHOT: 'exportScreenshot', + INSPECTOR: 'inspector', + COMMENTS: 'comments', + SHOW_COMMENTS: 'showComments', + HIDE_COMMENTS: 'hideComments', + SCREENSHOT: 'screenshot', + NEW_WINDOW: 'openInNewWindow', + OPEN_AS_TABLE: 'openAsTable', + GET_LINK: 'getLink', + SOURCE: 'source', + EDIT: 'edit', + COPY: 'copy', + MOVE: 'move', + RENAME: 'rename', + DUPLICATE: 'duplicate', + DELETE: 'delete', + EMBEDDED: 'embedded', + SETTINGS: 'settings', + ALERTS: 'alerts', + FULLSCREEEN: 'fullscreen', + CHART_MODELING: 'modeling', + CHART_MODELING_TREND: 'trend', + CHART_MODELING_SMOOTHING: 'smoothing', + CHART_MODELING_SETTINGS: 'modelingSettings', +} as const; + +export type MenuItemsIdsType = (typeof MenuItemsIds)[keyof typeof MenuItemsIds]; diff --git a/src/shared/types/meta-manager/index.ts b/src/shared/types/meta-manager/index.ts new file mode 100644 index 0000000..b3bf1e1 --- /dev/null +++ b/src/shared/types/meta-manager/index.ts @@ -0,0 +1,6 @@ +import type {ValueOf} from '../..'; +import type {NOTIFICATION_LEVEL, PROCESS_STATUS} from '../../constants/meta-manager'; + +export type ProcessStatus = ValueOf; + +export type NotificationLevel = ValueOf; diff --git a/src/shared/types/metrica.ts b/src/shared/types/metrica.ts new file mode 100644 index 0000000..d8bd900 --- /dev/null +++ b/src/shared/types/metrica.ts @@ -0,0 +1,6 @@ +export type AvailableYMActions = 'reachGoal' | 'hit'; + +export interface MetricaCounterConfig { + id: number; + name: string; +} diff --git a/src/shared/types/notification.ts b/src/shared/types/notification.ts new file mode 100644 index 0000000..40048c3 --- /dev/null +++ b/src/shared/types/notification.ts @@ -0,0 +1,5 @@ +export interface PushServiceConfig { + host: string; + service: string; + client: string; +} diff --git a/src/shared/types/permissions.ts b/src/shared/types/permissions.ts new file mode 100644 index 0000000..9b5ed66 --- /dev/null +++ b/src/shared/types/permissions.ts @@ -0,0 +1,5 @@ +import type z from 'zod'; + +import type {permissionsSchema} from '../zod-schemas/permissions'; + +export type Permissions = z.infer; diff --git a/src/shared/types/ql/common.ts b/src/shared/types/ql/common.ts new file mode 100644 index 0000000..9ecae9b --- /dev/null +++ b/src/shared/types/ql/common.ts @@ -0,0 +1,182 @@ +import type {QLChartType} from '../../constants'; +import type {CommonSharedExtraSettings, ShapesConfig, Shared} from '../wizard/'; +import type {Field} from '../wizard/field'; + +export interface QLResultEntryMetadataDataGroup { + group: boolean; + undragable: boolean; + name: string; + capacity?: number; + size: number; + id?: string; + allowedTypes?: string[]; + pseudo?: boolean; +} + +export interface QLResultEntryMetadataDataColumn { + // typeName -- legacy type that is incompatible with Field + // How to turn on wizard ql common visualization -- you can delete + typeName: string; + + // biType -- a new type compatible with Field and general visualization section + biType?: string; + + name: string; + id?: string; + undragable?: boolean; + pseudo?: boolean; +} + +export interface QLParamInterval { + from: string | undefined; + to: string | undefined; +} + +export interface QLParam { + name: string; + type: string; + defaultValue: string | string[] | QLParamInterval | undefined; + overridenValue?: string | string[] | QLParamInterval; +} + +// Description of the request parameters. +export interface QLRequestParam { + type_name: string; + value: string | string[]; +} + +export type QLResultEntryMetadataDataColumnOrGroup = + | QLResultEntryMetadataDataGroup + | QLResultEntryMetadataDataColumn; + +interface QLEntryDataSharedConnection { + entryId: string; + type: string; +} + +export interface QLQuery { + value: string; + hidden?: boolean; + params: QLParam[]; +} + +export interface QLEntryDataShared { + version: undefined; + // The type of template used to generate a chart at the ChartsEngine level, a low-level thing + type: string; + + // Chart type should be selected in UI + chartType: QLChartType; + queryValue: string; + queries: QLQuery[]; + extraSettings?: CommonSharedExtraSettings; + visualization: Shared['visualization'] & {highchartsId?: string}; + params: QLParam[]; + connection: QLEntryDataSharedConnection; + order?: QLResultEntryMetadataDataColumnOrGroup[]; + preview?: boolean; + + colors?: Field[]; + colorsConfig?: any; + labels?: Field[]; + tooltips?: Field[]; + shapes?: Field[]; + shapesConfig?: ShapesConfig; + + available?: Field[]; +} + +export interface QLPreviewTableDataColumn { + name: string; +} + +export interface QLPreviewTableDataRow { + [key: string]: number | string | null; +} + +export interface QLPreviewTableData { + columns?: QLPreviewTableDataColumn[]; + data?: QLPreviewTableDataRow[]; +} + +export interface MonitoringPresetV1 { + data: { + v?: string; + chart: { + targets: { + query: string; + scopeId: string; + }[]; + settings: { + ['chart.type']: string; + ['other.normalize']: string; + }; + }; + redirectUrl?: string; + params: { + from: number; + to: number; + }; + }; + presetId: string; +} + +export interface MonitoringPresetV2 { + data: { + v: 'v2'; + widget: { + id: string; + title: string; + queries: { + targets: { + name: string; + query: string; + hidden: boolean; + textMode: boolean; + }[]; + }; + visualizationSettings: { + type: string; + title: string; + normalize: boolean; + showLabels: boolean; + aggregation: string; + interpolate: string; + tilesSettings: { + showTitle: boolean; + showValue: boolean; + sortField: string; + sortOrder: string; + }; + yaxisSettings: { + left: { + max: string; + min: string; + type: string; + title: string; + precision: number | null; + unitFormat: string; + }; + right: { + max: string; + min: string; + type: string; + title: string; + precision: number | null; + unitFormat: string; + }; + }; + colorSchemeSettings: { + scheme: string; + }; + }; + }; + redirectUrl: string; + scopeId: string; + params: { + from: number; + to: number; + }; + }; + presetId: string; +} diff --git a/src/shared/types/ql/versions.ts b/src/shared/types/ql/versions.ts new file mode 100644 index 0000000..7699f4e --- /dev/null +++ b/src/shared/types/ql/versions.ts @@ -0,0 +1,15 @@ +export enum QlConfigVersions { + V1 = '1', + // add required queryName field to queries + V2 = '2', + // shapes, colors, tooltips and labels are required + V3 = '3', + // A new 'dimensions' section has been added to the pie chart, the old section 'dimensions' has been moved to 'colors'. + V4 = '4', + // Rename 'default-palette' to classic20 + V5 = '5', + // rename the palette id (remove the word "palette" from the value) + V6 = '6', + // use standard visualization ids instead of d3 + V7 = '7', +} diff --git a/src/shared/types/typed-query-api/index.ts b/src/shared/types/typed-query-api/index.ts new file mode 100644 index 0000000..5b98253 --- /dev/null +++ b/src/shared/types/typed-query-api/index.ts @@ -0,0 +1 @@ +export * from './v1'; diff --git a/src/shared/types/typed-query-api/v1.ts b/src/shared/types/typed-query-api/v1.ts new file mode 100644 index 0000000..1116136 --- /dev/null +++ b/src/shared/types/typed-query-api/v1.ts @@ -0,0 +1,34 @@ +import type {ConnectionQueryTypeValues} from '../../constants'; +import type {ConnectionQueryContent} from '../connections'; + +export type ConnectionTypedQueryApiRequest = { + query_type: ConnectionQueryTypeValues; + query_content: ConnectionQueryContent; + parameters: ConnectionTypedQueryParameter[]; +}; + +export type ConnectionTypedQueryContent = { + query: string; +}; +export type ConnectionTypedQueryParameter = { + name: string; + data_type: string; + value: string; +}; + +export type ConnectionTypedQueryApiResponse = { + query_type: ConnectionQueryTypeValues; + data: { + headers: ConnectionTypedQueryApiDataHeader[]; + rows: ConnectionTypedQueryApiDataRow[]; + }; + debug_info: ConnectionTypedQueryApiDataDebug; +}; + +export type ConnectionTypedQueryApiDataHeader = { + name: string; + data_type: string; +}; + +export type ConnectionTypedQueryApiDataRow = Array; +export type ConnectionTypedQueryApiDataDebug = {}; diff --git a/src/shared/types/ui-sandbox.ts b/src/shared/types/ui-sandbox.ts new file mode 100644 index 0000000..086837a --- /dev/null +++ b/src/shared/types/ui-sandbox.ts @@ -0,0 +1,5 @@ +export type UISandboxWrappedFunction = { + fn: string; + args?: unknown; + libs?: string[]; +}; diff --git a/src/shared/types/utility-types.ts b/src/shared/types/utility-types.ts new file mode 100644 index 0000000..a897b0b --- /dev/null +++ b/src/shared/types/utility-types.ts @@ -0,0 +1,44 @@ +import type {Falsy} from 'utility-types'; + +export type EmptyObject = { + [K in any]: never; +}; + +export type Truthy = T extends Falsy ? never : T; + +// This `is guard` is convenient to use in map.filter(isTruthy) to filter truthy values +export function isTruthy(value: T): value is Truthy { + return Boolean(value); +} + +export type Nullable = T | null; + +export type NullableValues = { + [P in keyof T]: T[P] | null; +}; + +export type NestedPartial = Partial< + Omit & {[key in keyof T]: Partial} +>; + +// https://stackoverflow.com/a/47914631 +export type RecursivePartial = { + [P in keyof T]?: RecursivePartial; +}; + +export type PartialBy = Omit & Partial>; + +// https://stackoverflow.com/a/53050575 +type NoUndefinedField = { + [P in keyof T]-?: NoUndefinedField>; +}; + +export type WithRequired = T & {[P in K]-?: T[P]}; + +export type NonNullableBy = Omit & NoUndefinedField>; + +type ObjectKeys = `${Exclude}`; + +export const objectKeys = Object.keys as (value: T) => Array>; + +export type ValueOf = T[keyof T]; diff --git a/src/shared/types/widget.ts b/src/shared/types/widget.ts new file mode 100644 index 0000000..f2ea0b5 --- /dev/null +++ b/src/shared/types/widget.ts @@ -0,0 +1,51 @@ +export enum WizardType { + GraphWizardNode = 'graph_wizard_node', + TableWizardNode = 'table_wizard_node', + YmapWizardNode = 'ymap_wizard_node', + MetricWizardNode = 'metric_wizard_node', + MarkupWizardNode = 'markup_wizard_node', + TimeseriesWizardNode = 'timeseries_wizard_node', + GravityChartsWizardNode = 'd3_wizard_node', +} + +export enum LegacyEditorType { + Graph = 'graph', + Table = 'table', + Map = 'map', + Manager = 'manager', + Text = 'text', + Metric = 'metric', + BlankChart = 'blank-chart_node', +} + +export enum EditorType { + Module = 'module', + GraphNode = 'graph_node', + TableNode = 'table_node', + TextNode = 'text_node', + MetricNode = 'metric_node', + MapNode = 'map_node', + YmapNode = 'ymap_node', + ControlNode = 'control_node', + MarkdownNode = 'markdown_node', + MarkupNode = 'markup_node', + GravityChartsNode = 'd3_node', + AdvancedChartNode = 'advanced-chart_node', +} + +export enum WidgetKind { + Graph = 'graph', + Table = 'table', + Map = 'map', + Manager = 'manager', + Text = 'text', + Metric = 'metric', + Ymap = 'ymap', + Control = 'control', + Markdown = 'markdown', + Markup = 'markup', + GravityCharts = 'd3', + AdvancedChart = 'advanced-chart', +} + +export type WidgetType = LegacyEditorType | EditorType | WizardType; diff --git a/src/shared/types/wizard/background-settings.ts b/src/shared/types/wizard/background-settings.ts new file mode 100644 index 0000000..50ca20c --- /dev/null +++ b/src/shared/types/wizard/background-settings.ts @@ -0,0 +1,22 @@ +import type {ColorsConfig} from './index'; +type PaletteFields = 'mountedColors' | 'palette'; +type GradientFields = + | 'thresholdsMode' + | 'leftThreshold' + | 'middleThreshold' + | 'rightThreshold' + | 'gradientPalette' + | 'gradientMode' + | 'reversed' + | 'nullMode'; + +export interface TableFieldBackgroundSettings { + enabled: boolean; + colorFieldGuid: string; + settingsId: string; + settings: { + paletteState: Pick; + gradientState: Pick; + isContinuous: boolean; + }; +} diff --git a/src/shared/types/wizard/bars.ts b/src/shared/types/wizard/bars.ts new file mode 100644 index 0000000..b3af87d --- /dev/null +++ b/src/shared/types/wizard/bars.ts @@ -0,0 +1,95 @@ +import type {BarsAlignValues, BarsColorType, GradientType} from '../../constants'; + +export interface TableBarsSettings { + enabled: boolean; + colorSettings: BarGradientColorSettings | BarOneColorSettings | BarTwoColorSettings; + showLabels: boolean; + align: AlignValues; + scale: BarAutoScaleSettings | BarManualScaleSettings; + showBarsInTotals: boolean; +} + +export type AlignValues = BarsAlignValues.Left | BarsAlignValues.Right | BarsAlignValues.Default; + +export interface BarGradientColorSettings { + colorType: BarsColorType.Gradient; + settings: { + gradientType: GradientType; + thresholds: + | { + mode: 'manual'; + min: string; + mid?: string; + max: string; + } + | {mode: 'auto'}; + palette: string; + reversed: boolean; + }; +} + +// TODO: use either index or color +// type BarOneColorByIndex = { +// colorIndex: number; +// color?: undefined; +// }; + +// type BarOneColorByColor = { +// colorIndex?: undefined; +// color: string; +// }; + +export interface BarOneColorSettings { + colorType: BarsColorType.OneColor; + settings: { + palette: string; + colorIndex?: number; + color?: string; + }; +} + +// type BarTwoColumnsPositiveByIndex = { +// positiveColorIndex: number; +// positiveColor?: undefined; +// }; + +// type BarTwoColumnsPositiveByColor = { +// positiveColorIndex?: undefined; +// positiveColor: string; +// }; + +// type BarTwoColumnsNegativeByIndex = { +// negativeColorIndex: number; +// negativeColor?: undefined; +// }; + +// type BarTwoColumnsNegativeByColor = { +// negativeColorIndex?: undefined; +// negativeColor: string; +// }; + +// type BatTwoColorsSettings = (BarTwoColumnsPositiveByIndex | BarTwoColumnsPositiveByColor) & +// (BarTwoColumnsNegativeByIndex | BarTwoColumnsNegativeByColor); + +export interface BarTwoColorSettings { + colorType: BarsColorType.TwoColor; + settings: { + palette: string | undefined; + negativeColorIndex?: number; + negativeColor?: string; + positiveColorIndex?: number; + positiveColor?: string; + }; +} + +export interface BarAutoScaleSettings { + mode: 'auto'; +} + +export interface BarManualScaleSettings { + mode: 'manual'; + settings: { + min?: string; + max?: string; + }; +} diff --git a/src/shared/types/wizard/column.ts b/src/shared/types/wizard/column.ts new file mode 100644 index 0000000..d13eb5d --- /dev/null +++ b/src/shared/types/wizard/column.ts @@ -0,0 +1,17 @@ +export type ColumnSettings = { + width: WidthAutoSetting | WidthPercentSetting | WidthPixelSetting; +}; + +export type WidthAutoSetting = { + mode: 'auto'; +}; + +export type WidthPercentSetting = { + mode: 'percent'; + value: string; +}; + +export type WidthPixelSetting = { + mode: 'pixel'; + value: string; +}; diff --git a/src/shared/types/wizard/export.ts b/src/shared/types/wizard/export.ts new file mode 100644 index 0000000..5ea9694 --- /dev/null +++ b/src/shared/types/wizard/export.ts @@ -0,0 +1,14 @@ +import type {ServerFieldFormatting} from '../config/wizard'; + +export type ColumnExportSettings = { + id: string; + field: string; + name: string; + formatter?: ServerFieldFormatting; + format?: string; + type?: 'number' | 'text' | 'date' | 'genericdatetime'; +}; + +export type SeriesExportSettings = { + columns: ColumnExportSettings[]; +}; diff --git a/src/shared/types/wizard/field.ts b/src/shared/types/wizard/field.ts new file mode 100644 index 0000000..9cd007d --- /dev/null +++ b/src/shared/types/wizard/field.ts @@ -0,0 +1,166 @@ +import {MARKUP_TYPE} from '../charts'; +import type {FilterBody, MarkupType} from '../charts'; +import type {ServerField} from '../config/wizard'; +import type {DatasetField, DatasetFieldType} from '../dataset'; +import {DATASET_FIELD_TYPES} from '../dataset'; +import type { + CommonNumberFormattingOptions, + FloatNumberFormattingOptions, + IntegerNumberFormattingOptions, +} from '../formatting'; + +import type {TableFieldBackgroundSettings} from './background-settings'; +import type {TableBarsSettings} from './bars'; +import type {ColumnSettings} from './column'; +import type {TableSubTotalsSettings} from './sub-totals'; + +export interface WizardDatasetField extends DatasetField { + datasetName: string; + datasetId: string; + fakeTitle?: string; + grouping?: string; + id?: string; + format?: string; + originalTitle?: string; + conflict?: string; + direction?: string; + mode?: string; + disabled?: string; + className?: string; + new_id?: string; + originalFormula?: string; + originalSource?: string; + hideLabelMode?: 'hide' | 'show'; + formatting?: CommonNumberFormattingOptions; + originalDateCast: unknown; + undragable?: boolean; + transformed?: boolean; + quickFormula?: boolean; + unsaved?: boolean; + local?: boolean; + filter?: FilterBody; + barsSettings?: TableBarsSettings; + subTotalsSettings?: TableSubTotalsSettings; + columnSettings?: ColumnSettings; + backgroundSettings?: TableFieldBackgroundSettings; + distincts?: string[]; + hintSettings?: HintSettings; + markupType?: MarkupType; +} + +export type HintSettings = { + enabled?: boolean; + text?: string; +}; + +export interface FilterField extends WizardDatasetField { + filter: FilterBody; + is_default_filter?: boolean; +} + +export interface IntegerField extends WizardDatasetField { + data_type: DATASET_FIELD_TYPES.INTEGER | DATASET_FIELD_TYPES.UINTEGER; + formatting?: IntegerNumberFormattingOptions; +} + +export interface FloatField extends WizardDatasetField { + data_type: DATASET_FIELD_TYPES.FLOAT; + formatting?: FloatNumberFormattingOptions; +} + +export type NumberField = IntegerField | FloatField; + +export interface DateField extends WizardDatasetField { + data_type: DATASET_FIELD_TYPES.DATE; + format: string; +} + +export interface GenericDatetimeField extends WizardDatasetField { + data_type: DATASET_FIELD_TYPES.GENERICDATETIME; + format: string; +} + +export interface HierarchyField extends WizardDatasetField { + data_type: DATASET_FIELD_TYPES.HIERARCHY; + fields: Field[]; + type: DatasetFieldType.Pseudo; +} + +export interface MarkupField extends WizardDatasetField { + data_type: DATASET_FIELD_TYPES.MARKUP; +} + +export interface UnsupportedField extends WizardDatasetField { + data_type: DATASET_FIELD_TYPES.UNSUPPORTED; +} + +export type Field = + | HierarchyField + | IntegerField + | FloatField + | DateField + | GenericDatetimeField + | WizardDatasetField; + +export type FieldGuid = string; + +export function isIntegerField(field?: {data_type: string}): field is IntegerField { + return ( + field?.data_type === DATASET_FIELD_TYPES.INTEGER || + field?.data_type === DATASET_FIELD_TYPES.UINTEGER + ); +} + +export function isFloatField(field: {data_type: string}): field is FloatField { + return field.data_type === DATASET_FIELD_TYPES.FLOAT; +} + +export function isStringField(field: {data_type: string}) { + return field.data_type === DATASET_FIELD_TYPES.STRING; +} + +export function isMarkdownField(field?: {data_type: string; markupType?: MarkupType}) { + return Boolean(field && isStringField(field) && field.markupType === MARKUP_TYPE.markdown); +} + +export function isHtmlField(field?: {data_type: string; markupType?: MarkupType}) { + return Boolean(field && isStringField(field) && field.markupType === MARKUP_TYPE.html); +} + +export function isNumberField(field?: {data_type: string}): field is NumberField { + return Boolean(field && (isIntegerField(field) || isFloatField(field))); +} + +export function isMarkupDataType(dataType: string) { + return dataType === DATASET_FIELD_TYPES.MARKUP; +} + +export function isMarkupField(field?: {data_type: string}): field is MarkupField { + return Boolean(field && isMarkupDataType(field.data_type)); +} + +export function isUnsupportedDataType(dataType: string) { + return dataType === DATASET_FIELD_TYPES.UNSUPPORTED; +} + +export function isUnsupportedField(field?: {data_type: string}): field is UnsupportedField { + return Boolean(field && isUnsupportedDataType(field.data_type)); +} + +export function isDateType(dataType: string) { + return Boolean( + dataType === DATASET_FIELD_TYPES.DATE || + dataType === 'datetime' || + dataType === DATASET_FIELD_TYPES.GENERICDATETIME || + dataType === DATASET_FIELD_TYPES.DATETIMETZ, + ); +} + +export function isDateField(field?: { + data_type: string; +}): field is DateField | GenericDatetimeField { + return Boolean(field && isDateType(field?.data_type)); +} + +export const isFieldHierarchy = (field: Field | ServerField | undefined): field is HierarchyField => + field?.data_type === DATASET_FIELD_TYPES.HIERARCHY; diff --git a/src/shared/types/wizard/index.ts b/src/shared/types/wizard/index.ts new file mode 100644 index 0000000..dad0f11 --- /dev/null +++ b/src/shared/types/wizard/index.ts @@ -0,0 +1,539 @@ +import type {IconProps} from '@gravity-ui/uikit'; + +import type { + ChartsConfig, + Dataset, + FilterField, + HierarchyField, + Link, + ServerCommonSharedExtraSettings, + ServerDatasetField, + ServerShapesConfig, + ServerTooltipConfig, + WidgetSizeType, +} from '../'; +import type { + ColorMode, + GradientNullMode, + GradientType, + MapCenterModes, + NavigatorLinesMode, + NavigatorPeriod, + WizardVisualizationId, + ZoomModes, +} from '../../constants'; +import type {CommonUpdate, CommonUpdateField} from '../common-update'; +import type {IconId} from '../configs'; +import type {DatasetField} from '../dataset'; + +import type {Field} from './field'; +import type {Placeholder} from './placeholder'; +import type {ChartsConfigVersion} from './versions'; + +export * from './field'; +export * from './versions'; +export * from './bars'; +export * from './column'; +export * from './background-settings'; +export * from './misc'; +export * from './placeholder'; +export * from './sub-totals'; +export * from './export'; + +export type VisualizationIconProps = Omit & { + id: IconId; +}; + +export interface DrillDownData { + breadcrumbs: string[]; + filters: string[]; + level: number; + fields: Field[]; + isColorDrillDown?: boolean; +} + +export interface SharedData { + drillDownData?: DrillDownData; + metaHierarchy?: Record; +} +export type NavigatorSettings = { + navigatorMode: string; + isNavigatorAvailable: boolean; + selectedLines: string[]; + linesMode: NavigatorLinesMode; + periodSettings: NavigatorPeriod; +}; + +export interface ColorsConfig { + thresholdsMode?: string; + leftThreshold?: string; + middleThreshold?: string; + rightThreshold?: string; + gradientPalette?: string; + gradientMode?: GradientType; + polygonBorders?: string; + reversed?: boolean; + fieldGuid?: string; + mountedColors?: Record; + coloredByMeasure?: boolean; + palette?: string; + colorMode?: ColorMode; + nullMode?: GradientNullMode; +} + +export enum LabelsPositions { + Outside = 'outside', + Inside = 'inside', +} + +export const enum LegendDisplayMode { + Show = 'show', + Hide = 'hide', +} + +export const enum IndicatorTitleMode { + ByField = 'by-field', + Manual = 'manual', + Hide = 'hide', +} + +// TODO: use either index or color +// type MetricColorWithIndex = { +// metricFontColor?: undefined; +// metricFontColorIndex: number; +// }; + +// type MetricColorWithCustomColor = { +// metricFontColor: string; +// metricFontColorIndex?: undefined; +// }; + +// export type DefinedMetricFontSettings = {metricFontColorPalette: string; metricFontSize: string} & ( +// | MetricColorWithIndex +// | MetricColorWithCustomColor +// ); + +// export type MetricFontSettings = +// | DefinedMetricFontSettings +// | { +// metricFontColorPalette?: undefined; +// metricFontSize?: undefined; +// metricFontColor?: undefined; +// metricFontColorIndex?: undefined; +// }; + +export type MetricFontSettings = { + metricFontColorPalette?: string; + metricFontSize?: string; + metricFontColor?: string; + metricFontColorIndex?: number; +}; + +export type CommonSharedExtraSettings = { + title?: string; + titleMode?: 'show' | 'hide'; + indicatorTitleMode?: IndicatorTitleMode; + legendMode?: LegendDisplayMode; + overlap?: 'on' | 'off'; + tooltip?: ServerCommonSharedExtraSettings['tooltip']; + tooltipSum?: 'on' | 'off'; + limit?: number; + pagination?: 'on' | 'off'; + pivotInlineSort?: 'on' | 'off'; + // For old charts, navigatorMode was specified in the body of extraSettings + navigatorMode?: string; + navigatorSeriesName?: string; + totals?: 'on' | 'off'; + pivotFallback?: 'on' | 'off'; + feed?: string; + // For new charts, an object was created using the navigator settings. + navigatorSettings?: NavigatorSettings; + enableGPTInsights?: boolean; + labelsPosition?: LabelsPositions; + qlAutoExecuteChart?: 'on' | 'off'; + pinnedColumns?: number; + stacking?: 'on' | 'off'; + size?: WidgetSizeType; + zoomMode?: ZoomModes; + zoomValue?: number | null; + mapCenterMode?: MapCenterModes; + mapCenterValue?: string | null; + preserveWhiteSpace?: boolean; +} & MetricFontSettings; + +interface CommonShared { + visualization: { + id: unknown; + highchartsId?: string; + iconProps: VisualizationIconProps; + name?: string; + placeholders: unknown[]; + colorsRequired?: boolean; + allowColors?: boolean; + allowSegments?: boolean; + allowShapes?: boolean; + allowSort?: boolean; + allowLabels?: boolean; + allowToltips?: boolean; + availableLabelModes?: string[]; + hidden?: boolean; + }; + title: string; + extraSettings?: CommonSharedExtraSettings; + datasets?: Dataset[]; + dataset?: Dataset; + links: Link[]; + dimensions: DatasetField[]; + measures: DatasetField[]; + hierarchies: HierarchyField[]; + filters: FilterField[]; + colors: Field[]; + shapes: Field[]; + sort: Sort[]; + labels: Field[]; + tooltips: Field[]; + updates: Update[]; + colorsConfig: ColorsConfig; + sharedData: SharedData; + version?: ChartsConfigVersion.V1; + datasetsIds?: string[]; + datasetsPartialFields?: ServerDatasetField[][]; + wizardDataset?: Dataset | undefined; +} + +interface CommonSharedLayer extends CommonShared { + visualization: { + id: string; + iconProps: VisualizationIconProps; + name?: string; + placeholders: Placeholder[]; + colorsRequired?: boolean; + allowColors?: boolean; + allowSegments?: boolean; + allowShapes?: boolean; + allowSort?: boolean; + allowLabels?: boolean; + allowToltips?: boolean; + availableLabelModes?: string[]; + hidden?: boolean; + layerSettings: { + id: string; + name: string; + type: VisualizationLayerType; + alpha: number; + valid: boolean; + }; + }; +} + +export interface GraphShared extends CommonShared { + visualization: { + id: + | WizardVisualizationId.Line + | WizardVisualizationId.Area + | WizardVisualizationId.Area100p + | WizardVisualizationId.Column + | WizardVisualizationId.Column100p + | WizardVisualizationId.Bar + | WizardVisualizationId.Bar100p + | WizardVisualizationId.Pie + | WizardVisualizationId.Donut + | WizardVisualizationId.Scatter + | WizardVisualizationId.Treemap + | WizardVisualizationId.Funnel; + iconProps: VisualizationIconProps; + name: string; + hidden?: boolean; + highchartsId?: string; + placeholders: Placeholder[]; + colorsRequired?: boolean; + onDesignItemsChange?: (args: { + shapes?: Field[]; + colors?: Field[]; + prevColors?: Field[]; + isMultipleColorsSupported?: boolean; + visualization: GraphShared['visualization']; + }) => {shapes: Field[]; colors: Field[]}; + allowAvailable?: boolean; + allowColors?: boolean; + allowFilters?: boolean; + allowSegments?: boolean; + allowShapes?: boolean; + allowSort?: boolean; + allowLabels?: boolean; + allowToltips?: boolean; + allowComments?: boolean; + availableLabelModes?: string[]; + checkAllowedDesignItems?: (args: { + item: Field; + visualization?: Shared['visualization']; + designItems: Field[]; + isMultipleColorsSupported?: boolean; + }) => boolean; + checkAllowedLabels?: (item: Field) => boolean; + checkAllowedShapes?: (args: { + item: Field; + visualization?: Shared['visualization']; + designItems: Field[]; + }) => boolean; + checkAllowedSort?: ( + item: Field, + visualization: GraphShared['visualization'], + colors: Field[], + segments?: Field[], + ) => boolean; + colorsCapacity?: number; + shapesCapacity?: number; + type?: string; + }; + geopointsConfig: PointSizeConfig; + title: string; + colors: Field[]; + shapes: Field[]; + shapesConfig?: ShapesConfig; +} + +export interface PointSizeConfig { + radius: number; + minRadius: number; + maxRadius: number; +} + +export interface TableShared extends CommonShared { + visualization: { + id: 'pivotTable' | 'flatTable'; + type?: string; + name: string; + iconProps: VisualizationIconProps; + placeholders: Placeholder[]; + checkAllowedSort: (item: Field, visualization: TableShared['visualization']) => boolean; + checkAllowedDesignItems?: (args: { + item: Field; + visualization?: Shared['visualization']; + designItems: Field[]; + }) => boolean; + checkAllowedShapes?: (args: { + item: Field; + visualization?: Shared['visualization']; + designItems: Field[]; + }) => boolean; + colorsRequired?: boolean; + allowAvailable?: boolean; + allowColors?: boolean; + allowSegments?: boolean; + allowShapes?: boolean; + allowSort?: boolean; + allowLabels?: boolean; + allowToltips?: boolean; + allowFilters?: boolean; + availableLabelModes?: string[]; + colorsCapacity?: number; + shapesCapacity?: number; + }; +} + +interface MetricShared extends CommonShared { + visualization: { + id: 'metric'; + iconProps: VisualizationIconProps; + name: string; + placeholders: Placeholder[]; + colorsRequired?: boolean; + allowAvailable?: boolean; + allowColors?: boolean; + allowSegments?: boolean; + allowShapes?: boolean; + allowSort?: boolean; + allowLabels?: boolean; + allowToltips?: boolean; + allowFilters?: boolean; + availableLabelModes?: string[]; + colorsCapacity?: number; + shapesCapacity?: number; + checkAllowedDesignItems?: (args: { + item: Field; + visualization?: Shared['visualization']; + designItems: Field[]; + }) => boolean; + checkAllowedShapes?: (args: { + item: Field; + visualization?: Shared['visualization']; + designItems: Field[]; + }) => boolean; + }; +} + +export type GeoLayerType = + | 'geopoint' + | 'geopolygon' + | 'heatmap' + | 'polyline' + | 'geopoint-with-cluster'; + +export type VisualizationLayerType = GeoLayerType | 'line' | 'column' | 'area'; + +export interface VisualizationLayerShared extends CommonSharedLayer { + visualization: { + id: VisualizationLayerType; + layerSettings: { + id: string; + name: string; + type: VisualizationLayerType; + alpha: number; + valid: boolean; + }; + commonPlaceholders: { + filters: FilterField[]; + colors: Field[]; + labels: Field[]; + tooltips: Field[]; + sort: Sort[]; + shapes: Field[]; + colorsConfig?: ColorsConfig; + geopointsConfig?: PointSizeConfig; + shapesConfig?: ShapesConfig; + tooltipConfig?: ServerTooltipConfig; + }; + name: string; + iconProps: VisualizationIconProps; + type?: string; + placeholders: Placeholder[]; + colorsRequired?: boolean; + allowAvailable?: boolean; + allowColors?: boolean; + allowSegments?: boolean; + allowShapes?: boolean; + allowSort?: boolean; + allowLabels?: boolean; + allowToltips?: boolean; + allowFilters?: boolean; + allowLayerFilters?: boolean; + allowTooltips?: boolean; + availableLabelModes?: string[]; + checkAllowedDesignItems?: (args: { + item: Field; + visualization?: Shared['visualization']; + designItems: Field[]; + }) => boolean; + checkAllowedShapes?: (args: { + item: Field; + visualization?: Shared['visualization']; + designItems: Field[]; + }) => boolean; + checkAllowedTooltips?: (item: Field) => boolean; + checkAllowedLabels?: (item: Field) => boolean; + checkAllowedSort?: ( + item: Field, + visualization: VisualizationLayerShared['visualization'], + ) => boolean; + hidden?: boolean; + colorsCapacity?: number; + shapesCapacity?: number; + }; +} + +export interface VisualizationWithLayersShared extends CommonShared { + visualization: { + id: string; + highchartsId?: string; + type: string; + iconProps: VisualizationIconProps; + name: string; + layers: VisualizationLayerShared['visualization'][]; + selectedLayerId: string; + placeholders: Placeholder[]; + colorsRequired?: boolean; + allowAvailable?: boolean; + allowColors?: boolean; + allowSegments?: boolean; + allowShapes?: boolean; + allowSort?: boolean; + allowLabels?: boolean; + allowToltips?: boolean; + availableLabelModes?: string[]; + colorsCapacity?: number; + shapesCapacity?: number; + hidden?: boolean; + checkAllowedDesignItems?: (args: { + item: Field; + visualization?: Shared['visualization']; + designItems: Field[]; + }) => boolean; + checkAllowedShapes?: (args: { + item: Field; + visualization?: Shared['visualization']; + designItems: Field[]; + }) => boolean; + }; +} + +export type Shared = + | GraphShared + | TableShared + | MetricShared + | VisualizationWithLayersShared + | VisualizationLayerShared; + +export function isGraphShared(shared: Shared): shared is GraphShared { + return ( + shared.visualization.id === 'line' || + shared.visualization.id === 'area' || + shared.visualization.id === 'area100p' || + shared.visualization.id === 'column' || + shared.visualization.id === 'column100p' || + shared.visualization.id === 'bar' || + shared.visualization.id === 'bar100p' || + shared.visualization.id === 'pie' || + shared.visualization.id === 'donut' || + shared.visualization.id === 'scatter' || + shared.visualization.id === 'treemap' + ); +} + +export const isGraphScatterShared = (shared: Shared): shared is GraphShared => + shared.visualization.id === 'scatter'; + +export const isTableShared = (shared: Shared): shared is TableShared => + shared.visualization.id === 'flatTable' || shared.visualization.id === 'pivotTable'; + +export const isMetricShared = (shared: Shared): shared is MetricShared => + shared.visualization.id === 'metric'; + +export type Update = CommonUpdate; +export type UpdateField = CommonUpdateField; + +export type RawUpdate = Pick & { + field: Field; +}; + +export type Sort = Field & { + direction: string; +}; + +export type ShapesConfig = ServerShapesConfig; + +export type ClientChartsConfig = { + colors: Field[]; + colorsConfig: ColorsConfig; + extraSettings: CommonSharedExtraSettings; + filters: FilterField[]; + geopointsConfig: PointSizeConfig | undefined; + hierarchies: HierarchyField[]; + labels: Field[]; + links: Link[]; + sort: Sort[]; + tooltips: Field[]; + type: 'datalens'; + updates: Update[]; + visualization: Shared['visualization']; + shapes: Field[]; + shapesConfig: ShapesConfig; + version: ChartsConfig['version']; + datasetsIds: string[]; + datasetsPartialFields: ServerDatasetField[][]; + segments: Field[]; +}; + +export type ClientChartsConfigWithDataset = ClientChartsConfig & { + wizardDataset: Dataset | undefined; +}; diff --git a/src/shared/types/wizard/misc.ts b/src/shared/types/wizard/misc.ts new file mode 100644 index 0000000..5618622 --- /dev/null +++ b/src/shared/types/wizard/misc.ts @@ -0,0 +1,74 @@ +import type {WrappedHTML} from '../..'; +import type {CommonPlaceholders} from '../config/wizard'; + +export type CommonPlaceholdersKeys = keyof CommonPlaceholders; + +export type CommonPlaceholdersKeysWithoutConfigs = Exclude< + CommonPlaceholdersKeys, + 'colorsConfig' | 'geopointsConfig' | 'shapesConfig' | 'tooltipConfig' +>; + +export const MarkupItemTypes = { + Bold: 'bold', + Br: 'br', + Color: 'color', + Concat: 'concat', + Italics: 'italics', + Size: 'size', + Text: 'text', + Url: 'url', + UserInfo: 'user_info', + Image: 'img', + Tooltip: 'tooltip', +} as const; + +export type MarkupItemType = (typeof MarkupItemTypes)[keyof typeof MarkupItemTypes]; + +export interface MarkupItem { + type: MarkupItemType; + url?: string; + children?: (MarkupItem | string)[]; + content?: string | MarkupItem; + color?: string; + size?: string | number; + user_info?: 'name' | 'email'; + className?: string; + src?: string; + alt?: string; + width?: number; + height?: number; + placement?: string; + tooltip?: MarkupItem; +} + +export type HighchartsSeriesCustomObject = { + segmentTitle?: string | WrappedHTML; +}; + +export const enum AxisMode { + Discrete = 'discrete', + Continuous = 'continuous', +} + +export const enum AxisLabelFormatMode { + Auto = 'auto', + ByField = 'by-field', + Manual = 'manual', +} + +export const enum AxisNullsMode { + Ignore = 'ignore', + Connect = 'connect', + AsZero = 'as-0', + UsePrevious = 'use-previous', +} + +export const AxisAutoScaleModes = { + // historically, auto mode was called min-max, so it is written that way in the config + // todo: add renaming to the wizard config migration, when needs to add a new version + Auto: 'min-max', + MinMax: 'data-min-max', + ZeroMax: '0-max', +} as const; + +export type AxisAutoScaleMode = (typeof AxisAutoScaleModes)[keyof typeof AxisAutoScaleModes]; diff --git a/src/shared/types/wizard/placeholder.ts b/src/shared/types/wizard/placeholder.ts new file mode 100644 index 0000000..5fbb7ef --- /dev/null +++ b/src/shared/types/wizard/placeholder.ts @@ -0,0 +1,55 @@ +import type {IconProps} from '@gravity-ui/uikit'; + +import type {PlaceholderId} from '../../constants'; +import type {CommonNumberFormattingOptions} from '../formatting'; + +import type {Field} from './field'; + +import type {Sort} from './index'; + +export interface PlaceholderSettings { + scale?: string; + scaleValue?: string | [string, string]; + title?: string; + titleValue?: string; + type?: string; + grid?: string; + gridStep?: string; + gridStepValue?: number; + hideLabels?: string; + labelsView?: string; + nulls?: string; + holidays?: string; + polylinePoints?: string; + axisFormatMode?: string; + axisLabelFormating?: CommonNumberFormattingOptions; + axisLabelDateFormat?: string; + axisModeMap?: Record; + disableAxisMode?: boolean; + axisVisibility?: string; +} + +export type Placeholder = { + id: string; + type: string; + title: string; + iconProps: IconProps; + capacity?: number; + transform?: (item: Field) => Field; + allowedTypes?: Set; + checkAllowed?: (item: Field) => boolean; + items: Field[]; + settings?: Record; + required?: boolean; + onChange?: (params: { + placeholder: Placeholder; + visualization: any; + colors: Field[]; + sort: Sort[]; + shapes: Field[]; + placeholderId: PlaceholderId; + isQL: boolean; + }) => void; + allowedDataTypes?: Set; + allowedFinalTypes?: Set; +}; diff --git a/src/shared/types/wizard/sub-totals.ts b/src/shared/types/wizard/sub-totals.ts new file mode 100644 index 0000000..6bd05bc --- /dev/null +++ b/src/shared/types/wizard/sub-totals.ts @@ -0,0 +1,3 @@ +export type TableSubTotalsSettings = { + enabled: boolean; +}; diff --git a/src/shared/types/wizard/versions.ts b/src/shared/types/wizard/versions.ts new file mode 100644 index 0000000..1f28e6d --- /dev/null +++ b/src/shared/types/wizard/versions.ts @@ -0,0 +1,36 @@ +export enum ChartsConfigVersion { + // Initial version + V1 = '1', + // The dataset and datasets fields have been removed from the config. Instead, only partial knowledge about the dataset has been added. + // New datasetsIds, datasetsPartialFields fields are now saved in the config. + // Based on them, we prepare Wizard widget and data in urls + // datasetsIds: string[]; datasetsPartialFields: ChartsConfigDatasetField[][] + V2 = '2', + // The sort field for geo visualizations should be stored in commonPlaceholders, not just in the chart config. + V3 = '3', + // Moved the labelMode fields from the top level of the field to formatting.labelMode + V4 = '4', + // Added axisMode to placeholder settings + // Removed dateMode from Date|Datetime|GenericDatetime type fields + V5 = '5', + // Measure Names fields were deleted from sort placeholder for pivot tables charts. + V6 = '6', + // axisMode replaced to object where keys are guids of fields and values are 'continuous' | 'discrete' + V7 = '7', + // move totals setting from chart settings to field settings for pivot table chart. + V8 = '8', + // shapes in the scatter chart are specified by field in shape section + V9 = '9', + // A new 'dimensions' section has been added to the pie chart, the old section 'dimensions' has been moved to 'colors'. + V10 = '10', + // Added new field for displaying the title in the indicator charts. + V11 = '11', + // isMarkdown boolean field is converted to a string markupType. + V12 = '12', + // rename 'default-palette' value to classic20 + V13 = '13', + // rename the palette id (remove the word "palette" from the value) + V14 = '14', + // use standard visualization ids instead of d3 + V15 = '15', +} diff --git a/src/shared/utils/__tests__/date-time.test.ts b/src/shared/utils/__tests__/date-time.test.ts new file mode 100644 index 0000000..826b688 --- /dev/null +++ b/src/shared/utils/__tests__/date-time.test.ts @@ -0,0 +1,10 @@ +import {getUtcDateTime} from '../date-time'; + +describe('getUtcDateTime', function () { + it.each([ + ['2022-09-01T09:00:00', '2022-09-01T09:00:00.000Z'], + ['2022-09-01T09:00:00Z', '2022-09-01T09:00:00.000Z'], + ])(`should return correct ISO string for: %s`, (input, result) => { + expect(getUtcDateTime(input)?.toISOString()).toEqual(result); + }); +}); diff --git a/src/shared/utils/array/assert-non-empty-array.ts b/src/shared/utils/array/assert-non-empty-array.ts new file mode 100644 index 0000000..f74d567 --- /dev/null +++ b/src/shared/utils/array/assert-non-empty-array.ts @@ -0,0 +1,5 @@ +export function assertNonEmptyArray(arr: T[], message?: string): asserts arr is [T, ...T[]] { + if (arr.length === 0) { + throw new Error(message ?? 'Array must not be empty!'); + } +} diff --git a/src/shared/utils/array/index.ts b/src/shared/utils/array/index.ts new file mode 100644 index 0000000..7c3135d --- /dev/null +++ b/src/shared/utils/array/index.ts @@ -0,0 +1 @@ +export {assertNonEmptyArray} from './assert-non-empty-array'; diff --git a/src/shared/utils/dash.ts b/src/shared/utils/dash.ts new file mode 100644 index 0000000..5cc06ef --- /dev/null +++ b/src/shared/utils/dash.ts @@ -0,0 +1,60 @@ +import type { + BackgroundSettings, + ColorByTheme, + ColorSettings, + OldBackgroundSettings, + TitleTextSettings, +} from '../types/dash'; + +export function isOldBackgroundSettings(value: unknown): value is OldBackgroundSettings { + return ( + typeof value === 'object' && + value !== null && + 'color' in value && + typeof value.color === 'string' && + ('enabled' in value + ? typeof value.enabled === 'boolean' || value.enabled === undefined + : true) + ); +} + +export function isColorByTheme(value: unknown): value is ColorByTheme { + return ( + typeof value === 'object' && + value !== null && + Object.entries(value).every( + ([key, val]) => + ['light', 'dark'].includes(key) && ['string', 'undefined'].includes(typeof val), + ) + ); +} + +export function isColorSettings(value: unknown): value is ColorSettings { + return typeof value === 'string' || isColorByTheme(value); +} +function isSettingsWithColor(value: unknown): value is {color?: ColorSettings} { + return ( + typeof value === 'object' && + value !== null && + 'color' in value && + (value.color === undefined || + typeof value.color === 'string' || + isColorSettings(value.color)) + ); +} + +export function isBackgroundSettings(value: unknown): value is BackgroundSettings { + return isSettingsWithColor(value); +} + +export function isTextSettings(value: unknown): value is TitleTextSettings { + return isSettingsWithColor(value); +} + +export const getAllTabItems = (tab?: {items: T[]; globalItems?: T[]} | null) => { + if (!tab) { + return []; + } + + return tab.items.concat(tab.globalItems || []); +}; diff --git a/src/shared/utils/dataset.ts b/src/shared/utils/dataset.ts new file mode 100644 index 0000000..609e230 --- /dev/null +++ b/src/shared/utils/dataset.ts @@ -0,0 +1,31 @@ +import isEmpty from 'lodash/isEmpty'; + +import {isDimensionField} from '../modules/helpers'; +import {type DatasetField, type FieldUISettings, isNumberField} from '../types'; + +export const getFieldUISettings = ({field}: {field: {ui_settings?: string} | undefined}) => { + const value = field?.ui_settings; + let result: FieldUISettings | null = null; + try { + if (value) { + result = JSON.parse(value); + } + } catch (e) { + console.error('Incorrect ui_settings value', e); + } + + return result; +}; + +export const isDisplaySettingsAvailable = ({field}: {field: DatasetField}) => { + const isNumberFormattingAvailable = isNumberField(field); + const isColoringAvailable = isDimensionField(field); + + return isNumberFormattingAvailable || isColoringAvailable; +}; + +export const isFieldWithDisplaySettings = ({field}: {field: DatasetField}) => { + const settings = getFieldUISettings({field}); + + return [settings?.numberFormatting, settings?.colors].some((d) => !isEmpty(d)); +}; diff --git a/src/shared/utils/date-time.ts b/src/shared/utils/date-time.ts new file mode 100644 index 0000000..95fd1e8 --- /dev/null +++ b/src/shared/utils/date-time.ts @@ -0,0 +1,6 @@ +import dayjs from '@gravity-ui/date-utils/build/dayjs'; + +// https://github.com/gravity-ui/date-utils/issues/49 +export function getUtcDateTime(value: string) { + return dayjs.utc(value); +} diff --git a/src/shared/utils/entry.ts b/src/shared/utils/entry.ts new file mode 100644 index 0000000..257e8a9 --- /dev/null +++ b/src/shared/utils/entry.ts @@ -0,0 +1,11 @@ +import {ENTRY_TYPES} from '..'; + +export const isEditorEntryType = (type: string): type is (typeof ENTRY_TYPES.editor)[number] => { + return (ENTRY_TYPES.editor as string[]).includes(type); +}; + +export const isLegacyEditorEntryType = ( + type: string, +): type is (typeof ENTRY_TYPES.legacyEditor)[number] => { + return (ENTRY_TYPES.legacyEditor as string[]).includes(type); +}; diff --git a/src/shared/utils/feature.ts b/src/shared/utils/feature.ts new file mode 100644 index 0000000..9124141 --- /dev/null +++ b/src/shared/utils/feature.ts @@ -0,0 +1,19 @@ +import type {AppContext} from '@gravity-ui/nodekit'; +import merge from 'lodash/merge'; + +import type {FeatureConfig} from '../types'; + +export const getServerFeatures = (ctx: AppContext): FeatureConfig => { + return merge({}, ctx.config.features, ctx.dynamicConfig?.features); +}; + +/** + * @deprecated You should use ctx.get('isEnabledServerFeature') instead + */ +export const isEnabledServerFeature = (ctx: AppContext, feature: string) => { + return getServerFeatures(ctx)[feature]; +}; + +export const getEnabledServerFeatureWithBoundedContext = (ctx: AppContext) => { + return (feature: string) => getServerFeatures(ctx)[feature]; +}; diff --git a/src/shared/utils/functions-registry.ts b/src/shared/utils/functions-registry.ts new file mode 100644 index 0000000..4faf1fa --- /dev/null +++ b/src/shared/utils/functions-registry.ts @@ -0,0 +1,36 @@ +import {objectKeys} from '../'; + +export const createFunctionsRegistry = function >( + functionsMap: FunctionsMap, +) { + const map: {[key in keyof typeof functionsMap]?: (typeof functionsMap)[key]} = {}; + + const internalRegister = (id: T, fn: any): void => { + map[id] = fn; + }; + + const registry = { + register(registerMap: typeof map): void { + Object.entries(registerMap).forEach(([id, fn]) => { + internalRegister(id, fn); + }); + }, + get(id: T): NonNullable<(typeof map)[T]> { + const fn = map[id]; + if (!fn) { + throw new Error(`Function with ${String(id)} does not exist`); + } + + return fn; + }, + getAll() { + return map as FunctionsMap; + }, + }; + + objectKeys(functionsMap).forEach((id) => { + internalRegister(id, functionsMap[id]); + }); + + return registry; +}; diff --git a/src/shared/utils/index.ts b/src/shared/utils/index.ts new file mode 100644 index 0000000..4bdf36e --- /dev/null +++ b/src/shared/utils/index.ts @@ -0,0 +1,8 @@ +export * from './visualization-check'; +export * from './feature'; +export * from './wizard'; +export * from './date-time'; +export * from './markup'; +export * from './markdown'; +export * from './dataset'; +export * from './dash'; diff --git a/src/shared/utils/makeFunctionTemplate.ts b/src/shared/utils/makeFunctionTemplate.ts new file mode 100644 index 0000000..2e2bf05 --- /dev/null +++ b/src/shared/utils/makeFunctionTemplate.ts @@ -0,0 +1,3 @@ +export const makeFunctionTemplate = (options?: {isReduxThunkActionTemplate?: boolean}): T => { + return (options?.isReduxThunkActionTemplate ? () => () => {} : () => {}) as T; +}; diff --git a/src/shared/utils/manualError.ts b/src/shared/utils/manualError.ts new file mode 100644 index 0000000..3b053f7 --- /dev/null +++ b/src/shared/utils/manualError.ts @@ -0,0 +1,39 @@ +type ManualErrorArgs = { + message: string; + originalError?: Error; + code?: string; + status?: number; + details?: Record; + debug?: Record; + extra?: {hideRetry?: boolean}; +}; + +type ManualErrorFields = Omit; + +export class ManualError extends Error implements ManualErrorFields { + code?: string; + status?: number; + details?: Record; + debug?: Record; + extra?: ManualErrorArgs['extra']; + _manualError = true; + + constructor({message, originalError, code, status, details, debug, extra}: ManualErrorArgs) { + super(message); + + this.code = code; + this.status = status; + this.details = details; + this.debug = debug; + this.extra = extra; + + if (originalError) { + this.name = originalError.name; + this.stack = originalError.stack; + } + } +} + +export function isManualError(error: any): error is ManualError { + return Boolean((error as ManualError)._manualError); +} diff --git a/src/shared/utils/markdown.ts b/src/shared/utils/markdown.ts new file mode 100644 index 0000000..3caf27b --- /dev/null +++ b/src/shared/utils/markdown.ts @@ -0,0 +1,7 @@ +import {WRAPPED_MARKDOWN_KEY} from '../constants'; + +export function wrapMarkdownValue(value: string) { + return {[WRAPPED_MARKDOWN_KEY]: value}; +} + +export type WrappedMarkdown = ReturnType; diff --git a/src/shared/utils/markup.ts b/src/shared/utils/markup.ts new file mode 100644 index 0000000..2d80138 --- /dev/null +++ b/src/shared/utils/markup.ts @@ -0,0 +1,7 @@ +import {WRAPPED_MARKUP_KEY} from '../constants'; + +export function wrapMarkupValue(value: unknown) { + return {[WRAPPED_MARKUP_KEY]: value}; +} + +export type WrappedMarkup = ReturnType; diff --git a/src/shared/utils/openapi/index.ts b/src/shared/utils/openapi/index.ts new file mode 100644 index 0000000..30bbaef --- /dev/null +++ b/src/shared/utils/openapi/index.ts @@ -0,0 +1 @@ +export const makeSchemaRef = (name: string): string => `#/components/schemas/${name}`; diff --git a/src/shared/utils/palettes.ts b/src/shared/utils/palettes.ts new file mode 100644 index 0000000..0708697 --- /dev/null +++ b/src/shared/utils/palettes.ts @@ -0,0 +1,21 @@ +export function getColorByColorSettings({ + currentColors, + colorIndex, + color, + fallbackIndex = 0, +}: { + currentColors: string[]; + colorIndex?: number; + color?: string; + fallbackIndex?: number; +}) { + if (typeof colorIndex === 'number') { + return currentColors[colorIndex] || currentColors[fallbackIndex]; + } + + if (color) { + return color; + } + + return currentColors[fallbackIndex]; +} diff --git a/src/shared/utils/ui-sandbox.ts b/src/shared/utils/ui-sandbox.ts new file mode 100644 index 0000000..18efc7f --- /dev/null +++ b/src/shared/utils/ui-sandbox.ts @@ -0,0 +1,16 @@ +import {WRAPPED_HTML_KEY} from '../constants'; +import type {ChartKitHtmlItem, WrappedHTML} from '../types'; + +export function wrapHtml(value: ChartKitHtmlItem | string) { + return { + [WRAPPED_HTML_KEY]: value, + }; +} + +export function isWrappedHTML(value: unknown): value is WrappedHTML { + if (!value || typeof value !== 'object') { + return false; + } + + return WRAPPED_HTML_KEY in value; +} diff --git a/src/shared/utils/visualization-check.ts b/src/shared/utils/visualization-check.ts new file mode 100644 index 0000000..9acf32a --- /dev/null +++ b/src/shared/utils/visualization-check.ts @@ -0,0 +1,74 @@ +import type {FeatureConfig, Link} from '../../shared'; +import {Feature, WizardVisualizationId} from '../../shared'; +import {QLChartType} from '../constants'; + +export function isMonitoringOrPrometheusChart(chartType: string | null | undefined) { + return chartType === QLChartType.Monitoringql || chartType === QLChartType.Promql; +} + +export function isYAGRVisualization(chartType: string, visualizationId: string) { + const isMonitoringOrPrometheus = isMonitoringOrPrometheusChart(chartType); + + return ( + isMonitoringOrPrometheus && + ['line', 'area', 'area100p', 'column', 'column100p'].includes(visualizationId) + ); +} + +export function getItemLinkWithDatasets( + item: {guid: string; datasetId: string}, + datasetId: string, + links: Link[], +) { + const targetLink = + item.datasetId && + links.find((link) => { + return link.fields[item.datasetId]?.field.guid === item.guid && link.fields[datasetId]; + }); + + return targetLink; +} + +export function isGravityChartsVisualization({ + id, + features, +}: { + id: string; + features?: FeatureConfig; +}) { + if (id === WizardVisualizationId.Funnel) { + return true; + } + + const isPieOrTreemap = [ + WizardVisualizationId.Pie, + WizardVisualizationId.Donut, + WizardVisualizationId.Treemap, + ].includes(id as WizardVisualizationId); + if (isPieOrTreemap && features?.[Feature.GravityChartsForPieAndTreemap]) { + return true; + } + + const isScatterOrBarY = [ + WizardVisualizationId.Bar, + WizardVisualizationId.Bar100p, + WizardVisualizationId.Scatter, + ].includes(id as WizardVisualizationId); + if (isScatterOrBarY && features?.[Feature.GravityChartsForBarYAndScatter]) { + return true; + } + + const isLineAreaOrBarX = [ + WizardVisualizationId.Line, + WizardVisualizationId.Area, + WizardVisualizationId.Area100p, + WizardVisualizationId.Column, + WizardVisualizationId.Column100p, + WizardVisualizationId.CombinedChart, + ].includes(id as WizardVisualizationId); + if (isLineAreaOrBarX && features?.[Feature.GravityChartsForLineAreaAndBarX]) { + return true; + } + + return false; +} diff --git a/src/shared/utils/wizard.ts b/src/shared/utils/wizard.ts new file mode 100644 index 0000000..3240b93 --- /dev/null +++ b/src/shared/utils/wizard.ts @@ -0,0 +1,33 @@ +import {WizardVisualizationId} from '../constants'; +import type {ExtendedChartsConfig, VisualizationWithLayersShared} from '../types'; + +export function isVisualizationWithLayers( + visualization: ExtendedChartsConfig['visualization'] | undefined, +): visualization is VisualizationWithLayersShared['visualization'] { + return [WizardVisualizationId.Geolayer, WizardVisualizationId.CombinedChart].includes( + visualization?.id as WizardVisualizationId, + ); +} + +export function mapStringToCoordinates(value: string) { + return value.split(',').reduce((acc, val) => { + const res = Number(val); + if (!Number.isNaN(res)) { + acc.push(res); + } + + return acc; + }, []); +} + +export function validateCoordinatesValue(value: string) { + let coordinates = []; + + try { + coordinates = mapStringToCoordinates(value); + } catch (e) { + return false; + } + + return coordinates.length === 2; +} diff --git a/src/shared/zod-schemas/dash.ts b/src/shared/zod-schemas/dash.ts new file mode 100644 index 0000000..44c2e70 --- /dev/null +++ b/src/shared/zod-schemas/dash.ts @@ -0,0 +1,305 @@ +import * as z from 'zod'; + +import { + CONTROLS_PLACEMENT_MODE, + DASH_CURRENT_SCHEME_VERSION, + DashLoadPriority, + DashTabConnectionKind, + DashTabItemControlElementType, + DashTabItemControlSourceType, + DashTabItemTitleSizes, + DashTabItemType, +} from '..'; + +const DASH_DEFAULT_NAMESPACE = 'default'; + +// Text definition +const textSchema = z.object({ + text: z.string(), +}); + +// Title definition +const titleSchema = z.object({ + text: z.string(), + size: z.enum(DashTabItemTitleSizes), + showInTOC: z.boolean(), +}); + +// Widget definition +const widgetSchema = z.object({ + hideTitle: z.boolean(), + tabs: z.array( + z.object({ + id: z.string().min(1), + title: z.string().min(1), + description: z.string(), + chartId: z.string().min(1), + isDefault: z.boolean(), + params: z.record(z.any(), z.any()), + autoHeight: z.boolean().optional(), + }), + ), +}); + +// Control element type definition +const controlElementTypeSchema = z + .object({ + required: z.boolean().optional(), + showHint: z.boolean().optional(), + showTitle: z.boolean(), + elementType: z.enum(DashTabItemControlElementType), + }) + .and( + z.discriminatedUnion('elementType', [ + z.object({ + elementType: z.literal(DashTabItemControlElementType.Select), + defaultValue: z.union([z.string(), z.array(z.string())]).optional(), + multiselectable: z.boolean(), + }), + z.object({ + elementType: z.literal(DashTabItemControlElementType.Date), + defaultValue: z.string().optional(), + isRange: z.boolean(), + }), + z.object({ + elementType: z.literal(DashTabItemControlElementType.Input), + defaultValue: z.string().optional(), + }), + z.object({ + elementType: z.literal(DashTabItemControlElementType.Checkbox), + defaultValue: z.string(), + }), + ]), + ); + +// Control source dataset definition +const controlSourceDatasetSchema = controlElementTypeSchema.and( + z.object({ + datasetId: z.string().min(1), + datasetFieldId: z.string().min(1), + }), +); + +// Control source manual definition +const controlSourceManualSchema = controlElementTypeSchema.and( + z.object({ + fieldName: z.string().min(1), + acceptableValues: z + .union([ + // elementType: select + z.array( + z.object({ + value: z.string(), + title: z.string(), + }), + ), + // elementType: date + z.object({ + from: z.string(), + to: z.string(), + }), + ]) + .optional(), + }), +); + +// Control source external definition +const controlSourceExternalSchema = z.object({ + chartId: z.string().min(1), + text: z.string().optional(), + autoHeight: z.boolean().optional(), +}); + +// Control definition +const controlSchema = z + .object({ + title: z.string().min(1), + sourceType: z.enum(DashTabItemControlSourceType), + // 'allTabs' = global selectors + // 'currentTab' = regular selector that is visible only on one tab + // 'selectedTabs' + impactTabsIds = selector is visible on the selected tabs + impactType: z + .enum(['allTabs', 'currentTab', 'selectedTabs']) + .optional() + .describe('Type that regulate which tabs the selector will be displayed on'), + impactTabsIds: z + .array(z.string()) + .optional() + .nullable() + .describe( + 'List of tabs ids that regulate which tabs the selector will be displayed on. Depends on impactType field', + ), + }) + .and( + z.discriminatedUnion('sourceType', [ + z.object({ + sourceType: z.literal(DashTabItemControlSourceType.Dataset), + source: controlSourceDatasetSchema, + }), + z.object({ + sourceType: z.literal(DashTabItemControlSourceType.Manual), + source: controlSourceManualSchema, + }), + z.object({ + sourceType: z.literal(DashTabItemControlSourceType.External), + source: controlSourceExternalSchema, + }), + ]), + ); + +const stringDefaultsSchema = z.record(z.string(), z.union([z.string(), z.array(z.string())])); + +// Group control items definition +const groupControlItemsSchema = z + .object({ + id: z.string().min(1), + title: z.string().min(1), + namespace: z.literal(DASH_DEFAULT_NAMESPACE), + sourceType: z.union([ + z.literal(DashTabItemControlSourceType.Dataset), + z.literal(DashTabItemControlSourceType.Manual), + ]), + defaults: stringDefaultsSchema, + placementMode: z.enum(CONTROLS_PLACEMENT_MODE).optional(), + width: z.string().optional(), + // 'allTabs' = global selectors + // 'currentTab' = regular selector that is visible only on one tab + // 'selectedTabs' + impactTabsIds = selector is visible on the selected tabs + impactType: z + .enum(['allTabs', 'currentTab', 'selectedTabs', 'asGroup']) + .optional() + .describe('Type that regulate which tabs the selector will be displayed on'), + impactTabsIds: z + .array(z.string()) + .optional() + .nullable() + .describe( + 'List of tabs ids that regulate which tabs the selector will be displayed on. Depends on impactType field', + ), + }) + .and( + z.discriminatedUnion('sourceType', [ + z.object({ + sourceType: z.literal(DashTabItemControlSourceType.Dataset), + source: controlSourceDatasetSchema, + }), + z.object({ + sourceType: z.literal(DashTabItemControlSourceType.Manual), + source: controlSourceManualSchema, + }), + ]), + ); + +// Group control definition +const groupControlSchema = z.object({ + group: z.array(groupControlItemsSchema), + autoHeight: z.boolean(), + buttonApply: z.boolean(), + buttonReset: z.boolean(), + showGroupName: z.boolean(), + updateControlsOnChange: z.boolean().optional(), +}); + +// Layout item definition +const layoutItemSchema = z.object({ + i: z.string().min(1), + h: z.number(), + w: z.number(), + x: z.number(), + y: z.number(), + parent: z.string().optional(), +}); + +// Connection definition +const connectionSchema = z.object({ + from: z.string().min(1), + to: z.string().min(1), + kind: z.enum(DashTabConnectionKind), +}); + +const tabItemSharedSchema = z.object({ + id: z.string().min(1), + namespace: z.literal(DASH_DEFAULT_NAMESPACE), +}); + +const tabControlItemSchema = z.object({ + type: z.literal(DashTabItemType.Control), + data: controlSchema, + defaults: stringDefaultsSchema, +}); + +const tabGroupControlItemSchema = z.object({ + type: z.literal(DashTabItemType.GroupControl), + data: groupControlSchema, +}); + +// Tab item definition +const tabItemSchema = tabItemSharedSchema.and( + z.discriminatedUnion('type', [ + z.object({ + type: z.literal(DashTabItemType.Text), + data: textSchema, + }), + z.object({ + type: z.literal(DashTabItemType.Title), + data: titleSchema, + }), + z.object({ + type: z.literal(DashTabItemType.Widget), + data: widgetSchema, + }), + tabControlItemSchema, + tabGroupControlItemSchema, + ]), +); + +const globalItemSchema = tabItemSharedSchema.and( + z.discriminatedUnion('type', [tabControlItemSchema, tabGroupControlItemSchema]), +); + +// Alias definition +const aliasRecordSchema = z.array(z.string().min(1)).max(2).min(2); + +// Tab definition +const tabSchema = z + .object({ + id: z.string().min(1), + title: z.string().min(1), + items: z.array(tabItemSchema), + layout: z.array(layoutItemSchema), + connections: z.array(connectionSchema), + aliases: z + .object({ + [DASH_DEFAULT_NAMESPACE]: z.array(aliasRecordSchema).optional(), + }) + .strict(), + globalItems: z.array(globalItemSchema).optional(), + }) + .strict(); + +// Settings definition +const settingsSchema = z.object({ + autoupdateInterval: z.union([z.number().min(30), z.null()]), + maxConcurrentRequests: z.union([z.number().min(1), z.null()]), + loadPriority: z.enum(DashLoadPriority).optional(), + silentLoading: z.boolean(), + dependentSelectors: z.boolean(), + globalParams: z.record(z.any(), z.any()).optional(), + hideTabs: z.boolean(), + hideDashTitle: z.boolean().optional(), + expandTOC: z.boolean(), + assistantEnabled: z.boolean().optional(), +}); + +// Data definition +export const dataSchema = z.object({ + counter: z.number().int().min(1), + salt: z.string().min(1), + schemeVersion: z.literal(DASH_CURRENT_SCHEME_VERSION), + tabs: z.array(tabSchema), + settings: settingsSchema, + supportDescription: z.string().optional(), + accessDescription: z.string().optional(), +}); + +export const dashMetaSchema = z.record(z.string(), z.union([z.string(), z.boolean()])).nullable(); diff --git a/src/shared/zod-schemas/entry.ts b/src/shared/zod-schemas/entry.ts new file mode 100644 index 0000000..96bd2df --- /dev/null +++ b/src/shared/zod-schemas/entry.ts @@ -0,0 +1,31 @@ +import {z} from 'zod'; + +export const keyOrWorkbookIdNameSchema = z + .object({ + key: z.string().optional(), + workbookId: z.string().optional(), + name: z.string().optional(), + }) + .refine( + (data) => { + const {key, workbookId, name} = data; + + const isKeyProvided = key !== undefined; + const isWorkbookIdProvided = workbookId !== undefined; + const isNameProvided = name !== undefined; + + if (isKeyProvided && !isWorkbookIdProvided && !isNameProvided) { + return true; + } + + if (!isKeyProvided && isWorkbookIdProvided && isNameProvided) { + return true; + } + + return false; + }, + { + message: + "For folder entries provide only 'key', for workbook entries provide only 'workbookId' and 'name' together.", + }, + ); diff --git a/src/shared/zod-schemas/permissions.ts b/src/shared/zod-schemas/permissions.ts new file mode 100644 index 0000000..ad397fa --- /dev/null +++ b/src/shared/zod-schemas/permissions.ts @@ -0,0 +1,8 @@ +import z from 'zod'; + +export const permissionsSchema = z.object({ + execute: z.boolean(), + read: z.boolean(), + edit: z.boolean(), + admin: z.boolean(), +}); diff --git a/src/shared/zod-schemas/shared-entry-permissions.ts b/src/shared/zod-schemas/shared-entry-permissions.ts new file mode 100644 index 0000000..b849499 --- /dev/null +++ b/src/shared/zod-schemas/shared-entry-permissions.ts @@ -0,0 +1,14 @@ +import z from 'zod'; + +export const sharedEntryPermissionsSchema = z.object({ + listAccessBindings: z.boolean(), + updateAccessBindings: z.boolean(), + limitedView: z.boolean(), + view: z.boolean(), + update: z.boolean(), + copy: z.boolean(), + move: z.boolean(), + delete: z.boolean(), + createEntryBinding: z.boolean(), + createLimitedEntryBinding: z.boolean(), +}); diff --git a/src/ui/.ci-eslintrc b/src/ui/.ci-eslintrc new file mode 100644 index 0000000..b10d549 --- /dev/null +++ b/src/ui/.ci-eslintrc @@ -0,0 +1,17 @@ +{ + "extends": "@gravity-ui/eslint-config/client", + "rules": { + "camelcase": 0, + "react/display-name": [1], + "no-console": ["error", { "allow": ["warn", "error"] }] + }, + "overrides": [ + { + "files": ["src/ui/datalens/**/*", "src/ui/entries/**/*", "src/ui/store/**/*"], + "rules": { + "sort-imports": [0], + "import/order": [0] + } + } + ] +} diff --git a/src/ui/.eslintrc b/src/ui/.eslintrc new file mode 100644 index 0000000..fe75cda --- /dev/null +++ b/src/ui/.eslintrc @@ -0,0 +1,17 @@ +{ + "extends": "@gravity-ui/eslint-config/client", + "rules": { + "camelcase": 0, + "react/display-name": [1], + "valid-jsdoc": 0 + }, + "overrides": [ + { + "files": ["datalens/**/*", "entries/**/*", "store/**/*"], + "rules": { + "sort-imports": [0], + "import/order": [0] + } + } + ] +} diff --git a/src/ui/assets/icons/alert.svg b/src/ui/assets/icons/alert.svg new file mode 100644 index 0000000..c216325 --- /dev/null +++ b/src/ui/assets/icons/alert.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/arr-reverse.svg b/src/ui/assets/icons/arr-reverse.svg new file mode 100644 index 0000000..75ddbe5 --- /dev/null +++ b/src/ui/assets/icons/arr-reverse.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/arr-right.svg b/src/ui/assets/icons/arr-right.svg new file mode 100644 index 0000000..2944901 --- /dev/null +++ b/src/ui/assets/icons/arr-right.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/arrow-left.svg b/src/ui/assets/icons/arrow-left.svg new file mode 100644 index 0000000..b1c1546 --- /dev/null +++ b/src/ui/assets/icons/arrow-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/arrow-right.svg b/src/ui/assets/icons/arrow-right.svg new file mode 100644 index 0000000..fed3a93 --- /dev/null +++ b/src/ui/assets/icons/arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/avatar-middle.png b/src/ui/assets/icons/avatar-middle.png new file mode 100644 index 0000000000000000000000000000000000000000..6bff46abcf3bc90e817b903c8b211d856f401dcb GIT binary patch literal 8353 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 literal 0 HcmV?d00001 diff --git a/src/ui/assets/icons/branded-question.svg b/src/ui/assets/icons/branded-question.svg new file mode 100644 index 0000000..9af1cd6 --- /dev/null +++ b/src/ui/assets/icons/branded-question.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/assets/icons/broken.svg b/src/ui/assets/icons/broken.svg new file mode 100644 index 0000000..90cec10 --- /dev/null +++ b/src/ui/assets/icons/broken.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/browse.svg b/src/ui/assets/icons/browse.svg new file mode 100644 index 0000000..1e165c3 --- /dev/null +++ b/src/ui/assets/icons/browse.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/calendar-2.svg b/src/ui/assets/icons/calendar-2.svg new file mode 100644 index 0000000..abc8807 --- /dev/null +++ b/src/ui/assets/icons/calendar-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/chart-monitoringql.svg b/src/ui/assets/icons/chart-monitoringql.svg new file mode 100644 index 0000000..87ca492 --- /dev/null +++ b/src/ui/assets/icons/chart-monitoringql.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/ui/assets/icons/chart-promql.svg b/src/ui/assets/icons/chart-promql.svg new file mode 100644 index 0000000..8039b34 --- /dev/null +++ b/src/ui/assets/icons/chart-promql.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/ui/assets/icons/charts-visualisations/vis-area-100p.svg b/src/ui/assets/icons/charts-visualisations/vis-area-100p.svg new file mode 100644 index 0000000..c21248c --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-area-100p.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/charts-visualisations/vis-area.svg b/src/ui/assets/icons/charts-visualisations/vis-area.svg new file mode 100644 index 0000000..57eb327 --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-area.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/charts-visualisations/vis-bar-100p.svg b/src/ui/assets/icons/charts-visualisations/vis-bar-100p.svg new file mode 100644 index 0000000..ae09d2e --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-bar-100p.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/charts-visualisations/vis-bar.svg b/src/ui/assets/icons/charts-visualisations/vis-bar.svg new file mode 100644 index 0000000..8db062c --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-bar.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/charts-visualisations/vis-column-100p.svg b/src/ui/assets/icons/charts-visualisations/vis-column-100p.svg new file mode 100644 index 0000000..2678569 --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-column-100p.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/charts-visualisations/vis-column.svg b/src/ui/assets/icons/charts-visualisations/vis-column.svg new file mode 100644 index 0000000..27d16aa --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-column.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/charts-visualisations/vis-donut.svg b/src/ui/assets/icons/charts-visualisations/vis-donut.svg new file mode 100644 index 0000000..657f963 --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-donut.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/charts-visualisations/vis-flat-table.svg b/src/ui/assets/icons/charts-visualisations/vis-flat-table.svg new file mode 100644 index 0000000..68d0a2a --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-flat-table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/charts-visualisations/vis-geolayers.svg b/src/ui/assets/icons/charts-visualisations/vis-geolayers.svg new file mode 100644 index 0000000..87e267e --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-geolayers.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/charts-visualisations/vis-geopoint.svg b/src/ui/assets/icons/charts-visualisations/vis-geopoint.svg new file mode 100644 index 0000000..df56c30 --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-geopoint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/charts-visualisations/vis-geopolygon.svg b/src/ui/assets/icons/charts-visualisations/vis-geopolygon.svg new file mode 100644 index 0000000..58e5c49 --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-geopolygon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/charts-visualisations/vis-heatmap.svg b/src/ui/assets/icons/charts-visualisations/vis-heatmap.svg new file mode 100644 index 0000000..324f23a --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-heatmap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/charts-visualisations/vis-lines.svg b/src/ui/assets/icons/charts-visualisations/vis-lines.svg new file mode 100644 index 0000000..78b95e6 --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-lines.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/charts-visualisations/vis-metric.svg b/src/ui/assets/icons/charts-visualisations/vis-metric.svg new file mode 100644 index 0000000..135c39d --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-metric.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/charts-visualisations/vis-pie.svg b/src/ui/assets/icons/charts-visualisations/vis-pie.svg new file mode 100644 index 0000000..205ee07 --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-pie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/charts-visualisations/vis-pivot.svg b/src/ui/assets/icons/charts-visualisations/vis-pivot.svg new file mode 100644 index 0000000..b931ca7 --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-pivot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/charts-visualisations/vis-scatter.svg b/src/ui/assets/icons/charts-visualisations/vis-scatter.svg new file mode 100644 index 0000000..d5e01b0 --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-scatter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/charts-visualisations/vis-treemap.svg b/src/ui/assets/icons/charts-visualisations/vis-treemap.svg new file mode 100644 index 0000000..de0d736 --- /dev/null +++ b/src/ui/assets/icons/charts-visualisations/vis-treemap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/check-folder.svg b/src/ui/assets/icons/check-folder.svg new file mode 100644 index 0000000..07c7f72 --- /dev/null +++ b/src/ui/assets/icons/check-folder.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/assets/icons/choveryt.svg b/src/ui/assets/icons/choveryt.svg new file mode 100644 index 0000000..a163fcc --- /dev/null +++ b/src/ui/assets/icons/choveryt.svg @@ -0,0 +1,23 @@ + + + + ic-choveryt + Created with Sketch. + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ui/assets/icons/clickhouse.svg b/src/ui/assets/icons/clickhouse.svg new file mode 100644 index 0000000..b43163f --- /dev/null +++ b/src/ui/assets/icons/clickhouse.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/collections/collection-colored-big-dark.svg b/src/ui/assets/icons/collections/collection-colored-big-dark.svg new file mode 100644 index 0000000..4d4b3ce --- /dev/null +++ b/src/ui/assets/icons/collections/collection-colored-big-dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/ui/assets/icons/collections/collection-colored-big.svg b/src/ui/assets/icons/collections/collection-colored-big.svg new file mode 100644 index 0000000..f657114 --- /dev/null +++ b/src/ui/assets/icons/collections/collection-colored-big.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/ui/assets/icons/collections/collection-colored-dark.svg b/src/ui/assets/icons/collections/collection-colored-dark.svg new file mode 100644 index 0000000..1c81602 --- /dev/null +++ b/src/ui/assets/icons/collections/collection-colored-dark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/ui/assets/icons/collections/collection-colored.svg b/src/ui/assets/icons/collections/collection-colored.svg new file mode 100644 index 0000000..756c158 --- /dev/null +++ b/src/ui/assets/icons/collections/collection-colored.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/ui/assets/icons/collections/collection.svg b/src/ui/assets/icons/collections/collection.svg new file mode 100644 index 0000000..c3a3093 --- /dev/null +++ b/src/ui/assets/icons/collections/collection.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/ui/assets/icons/collections/grid.svg b/src/ui/assets/icons/collections/grid.svg new file mode 100644 index 0000000..5ad69f1 --- /dev/null +++ b/src/ui/assets/icons/collections/grid.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/collections/workbook-colored.svg b/src/ui/assets/icons/collections/workbook-colored.svg new file mode 100644 index 0000000..c50f507 --- /dev/null +++ b/src/ui/assets/icons/collections/workbook-colored.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/assets/icons/collections/workbook.svg b/src/ui/assets/icons/collections/workbook.svg new file mode 100644 index 0000000..f20e77c --- /dev/null +++ b/src/ui/assets/icons/collections/workbook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/assets/icons/connected.svg b/src/ui/assets/icons/connected.svg new file mode 100644 index 0000000..78837f1 --- /dev/null +++ b/src/ui/assets/icons/connected.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/connections/appmetrica.svg b/src/ui/assets/icons/connections/appmetrica.svg new file mode 100644 index 0000000..3a86df5 --- /dev/null +++ b/src/ui/assets/icons/connections/appmetrica.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/connections/choveryt.svg b/src/ui/assets/icons/connections/choveryt.svg new file mode 100644 index 0000000..a60746d --- /dev/null +++ b/src/ui/assets/icons/connections/choveryt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/connections/chydb.svg b/src/ui/assets/icons/connections/chydb.svg new file mode 100644 index 0000000..cef502a --- /dev/null +++ b/src/ui/assets/icons/connections/chydb.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/connections/clickhouse.svg b/src/ui/assets/icons/connections/clickhouse.svg new file mode 100644 index 0000000..b43163f --- /dev/null +++ b/src/ui/assets/icons/connections/clickhouse.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/connections/csv.svg b/src/ui/assets/icons/connections/csv.svg new file mode 100644 index 0000000..d1800ba --- /dev/null +++ b/src/ui/assets/icons/connections/csv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/connections/greenplum.svg b/src/ui/assets/icons/connections/greenplum.svg new file mode 100644 index 0000000..6542801 --- /dev/null +++ b/src/ui/assets/icons/connections/greenplum.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/connections/metrica.svg b/src/ui/assets/icons/connections/metrica.svg new file mode 100644 index 0000000..e8ef2c6 --- /dev/null +++ b/src/ui/assets/icons/connections/metrica.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/connections/monitoring.svg b/src/ui/assets/icons/connections/monitoring.svg new file mode 100644 index 0000000..0f64fd9 --- /dev/null +++ b/src/ui/assets/icons/connections/monitoring.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/assets/icons/connections/mssql.svg b/src/ui/assets/icons/connections/mssql.svg new file mode 100644 index 0000000..d232d63 --- /dev/null +++ b/src/ui/assets/icons/connections/mssql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/connections/mysql.svg b/src/ui/assets/icons/connections/mysql.svg new file mode 100644 index 0000000..b917e36 --- /dev/null +++ b/src/ui/assets/icons/connections/mysql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/connections/oracle.svg b/src/ui/assets/icons/connections/oracle.svg new file mode 100644 index 0000000..221f042 --- /dev/null +++ b/src/ui/assets/icons/connections/oracle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/connections/postgres.svg b/src/ui/assets/icons/connections/postgres.svg new file mode 100644 index 0000000..5e84fc8 --- /dev/null +++ b/src/ui/assets/icons/connections/postgres.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/connections/undefined.svg b/src/ui/assets/icons/connections/undefined.svg new file mode 100644 index 0000000..07b2992 --- /dev/null +++ b/src/ui/assets/icons/connections/undefined.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/ui/assets/icons/connections/ydb.svg b/src/ui/assets/icons/connections/ydb.svg new file mode 100644 index 0000000..38c4c2c --- /dev/null +++ b/src/ui/assets/icons/connections/ydb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/connector.svg b/src/ui/assets/icons/connector.svg new file mode 100644 index 0000000..ff3647e --- /dev/null +++ b/src/ui/assets/icons/connector.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/ui/assets/icons/copy-xmark.svg b/src/ui/assets/icons/copy-xmark.svg new file mode 100644 index 0000000..975b72b --- /dev/null +++ b/src/ui/assets/icons/copy-xmark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/assets/icons/cross.svg b/src/ui/assets/icons/cross.svg new file mode 100644 index 0000000..cfbac8e --- /dev/null +++ b/src/ui/assets/icons/cross.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/ui/assets/icons/csv.svg b/src/ui/assets/icons/csv.svg new file mode 100644 index 0000000..f2a49e4 --- /dev/null +++ b/src/ui/assets/icons/csv.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/dimensions.svg b/src/ui/assets/icons/dimensions.svg new file mode 100644 index 0000000..a675a35 --- /dev/null +++ b/src/ui/assets/icons/dimensions.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/ui/assets/icons/dl-auth-logo-dark.svg b/src/ui/assets/icons/dl-auth-logo-dark.svg new file mode 100644 index 0000000..b5f012b --- /dev/null +++ b/src/ui/assets/icons/dl-auth-logo-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/dl-auth-logo-light.svg b/src/ui/assets/icons/dl-auth-logo-light.svg new file mode 100644 index 0000000..33e4d6f --- /dev/null +++ b/src/ui/assets/icons/dl-auth-logo-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/error-close.svg b/src/ui/assets/icons/error-close.svg new file mode 100644 index 0000000..2a85140 --- /dev/null +++ b/src/ui/assets/icons/error-close.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/error.svg b/src/ui/assets/icons/error.svg new file mode 100644 index 0000000..9b832fa --- /dev/null +++ b/src/ui/assets/icons/error.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/ui/assets/icons/file-diff.svg b/src/ui/assets/icons/file-diff.svg new file mode 100644 index 0000000..2f628cd --- /dev/null +++ b/src/ui/assets/icons/file-diff.svg @@ -0,0 +1,7 @@ + + + + diff --git a/src/ui/assets/icons/filter-tooltip.svg b/src/ui/assets/icons/filter-tooltip.svg new file mode 100644 index 0000000..4fec754 --- /dev/null +++ b/src/ui/assets/icons/filter-tooltip.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/folder-selection.svg b/src/ui/assets/icons/folder-selection.svg new file mode 100644 index 0000000..c6e2105 --- /dev/null +++ b/src/ui/assets/icons/folder-selection.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/folder.svg b/src/ui/assets/icons/folder.svg new file mode 100644 index 0000000..6bcbf58 --- /dev/null +++ b/src/ui/assets/icons/folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/funnel-clear.svg b/src/ui/assets/icons/funnel-clear.svg new file mode 100644 index 0000000..0952a9d --- /dev/null +++ b/src/ui/assets/icons/funnel-clear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/blue-gray-red.svg b/src/ui/assets/icons/gradients/blue-gray-red.svg new file mode 100644 index 0000000..3365b24 --- /dev/null +++ b/src/ui/assets/icons/gradients/blue-gray-red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/blue-yellow-red.svg b/src/ui/assets/icons/gradients/blue-yellow-red.svg new file mode 100644 index 0000000..6ddc5cd --- /dev/null +++ b/src/ui/assets/icons/gradients/blue-yellow-red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/blue.svg b/src/ui/assets/icons/gradients/blue.svg new file mode 100644 index 0000000..191e64d --- /dev/null +++ b/src/ui/assets/icons/gradients/blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/cyan.svg b/src/ui/assets/icons/gradients/cyan.svg new file mode 100644 index 0000000..57eb044 --- /dev/null +++ b/src/ui/assets/icons/gradients/cyan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/golden.svg b/src/ui/assets/icons/gradients/golden.svg new file mode 100644 index 0000000..02ebffd --- /dev/null +++ b/src/ui/assets/icons/gradients/golden.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/gray.svg b/src/ui/assets/icons/gradients/gray.svg new file mode 100644 index 0000000..126870c --- /dev/null +++ b/src/ui/assets/icons/gradients/gray.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/green-blue.svg b/src/ui/assets/icons/gradients/green-blue.svg new file mode 100644 index 0000000..c3573fc --- /dev/null +++ b/src/ui/assets/icons/gradients/green-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/green.svg b/src/ui/assets/icons/gradients/green.svg new file mode 100644 index 0000000..57eb044 --- /dev/null +++ b/src/ui/assets/icons/gradients/green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/oceanic.svg b/src/ui/assets/icons/gradients/oceanic.svg new file mode 100644 index 0000000..5d18007 --- /dev/null +++ b/src/ui/assets/icons/gradients/oceanic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/orange-blue-green.svg b/src/ui/assets/icons/gradients/orange-blue-green.svg new file mode 100644 index 0000000..55e5e5c --- /dev/null +++ b/src/ui/assets/icons/gradients/orange-blue-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/orange-gray-blue.svg b/src/ui/assets/icons/gradients/orange-gray-blue.svg new file mode 100644 index 0000000..652ee1f --- /dev/null +++ b/src/ui/assets/icons/gradients/orange-gray-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/orange-violet-blue.svg b/src/ui/assets/icons/gradients/orange-violet-blue.svg new file mode 100644 index 0000000..b938e17 --- /dev/null +++ b/src/ui/assets/icons/gradients/orange-violet-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/orange-yellow.svg b/src/ui/assets/icons/gradients/orange-yellow.svg new file mode 100644 index 0000000..0283ebd --- /dev/null +++ b/src/ui/assets/icons/gradients/orange-yellow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/pink-gray-green.svg b/src/ui/assets/icons/gradients/pink-gray-green.svg new file mode 100644 index 0000000..624e4b9 --- /dev/null +++ b/src/ui/assets/icons/gradients/pink-gray-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/red-blue.svg b/src/ui/assets/icons/gradients/red-blue.svg new file mode 100644 index 0000000..22484e3 --- /dev/null +++ b/src/ui/assets/icons/gradients/red-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/red-orange-green.svg b/src/ui/assets/icons/gradients/red-orange-green.svg new file mode 100644 index 0000000..2980527 --- /dev/null +++ b/src/ui/assets/icons/gradients/red-orange-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/red.svg b/src/ui/assets/icons/gradients/red.svg new file mode 100644 index 0000000..3dfca12 --- /dev/null +++ b/src/ui/assets/icons/gradients/red.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/traffic-light.svg b/src/ui/assets/icons/gradients/traffic-light.svg new file mode 100644 index 0000000..9170d13 --- /dev/null +++ b/src/ui/assets/icons/gradients/traffic-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/violet-blue-green.svg b/src/ui/assets/icons/gradients/violet-blue-green.svg new file mode 100644 index 0000000..489ffa0 --- /dev/null +++ b/src/ui/assets/icons/gradients/violet-blue-green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/violet-orange.svg b/src/ui/assets/icons/gradients/violet-orange.svg new file mode 100644 index 0000000..92429f2 --- /dev/null +++ b/src/ui/assets/icons/gradients/violet-orange.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/violet.svg b/src/ui/assets/icons/gradients/violet.svg new file mode 100644 index 0000000..26a2392 --- /dev/null +++ b/src/ui/assets/icons/gradients/violet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/yellow-green-blue.svg b/src/ui/assets/icons/gradients/yellow-green-blue.svg new file mode 100644 index 0000000..a9d5041 --- /dev/null +++ b/src/ui/assets/icons/gradients/yellow-green-blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/gradients/yellow.svg b/src/ui/assets/icons/gradients/yellow.svg new file mode 100644 index 0000000..bf3bf9d --- /dev/null +++ b/src/ui/assets/icons/gradients/yellow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/greenplum.svg b/src/ui/assets/icons/greenplum.svg new file mode 100644 index 0000000..f6d5ad4 --- /dev/null +++ b/src/ui/assets/icons/greenplum.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/header-nav.svg b/src/ui/assets/icons/header-nav.svg new file mode 100644 index 0000000..b1f50d7 --- /dev/null +++ b/src/ui/assets/icons/header-nav.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/header-star-active.svg b/src/ui/assets/icons/header-star-active.svg new file mode 100644 index 0000000..9cbf125 --- /dev/null +++ b/src/ui/assets/icons/header-star-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/header-star-inactive.svg b/src/ui/assets/icons/header-star-inactive.svg new file mode 100644 index 0000000..b70e0d8 --- /dev/null +++ b/src/ui/assets/icons/header-star-inactive.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/id-square.svg b/src/ui/assets/icons/id-square.svg new file mode 100644 index 0000000..b8c1ece --- /dev/null +++ b/src/ui/assets/icons/id-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/info.svg b/src/ui/assets/icons/info.svg new file mode 100644 index 0000000..8eccc2a --- /dev/null +++ b/src/ui/assets/icons/info.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/legacy-wizard.svg b/src/ui/assets/icons/legacy-wizard.svg new file mode 100644 index 0000000..f703e1a --- /dev/null +++ b/src/ui/assets/icons/legacy-wizard.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/ui/assets/icons/line-cap-butt.svg b/src/ui/assets/icons/line-cap-butt.svg new file mode 100644 index 0000000..67a67fd --- /dev/null +++ b/src/ui/assets/icons/line-cap-butt.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/assets/icons/line-cap-round.svg b/src/ui/assets/icons/line-cap-round.svg new file mode 100644 index 0000000..81c48d6 --- /dev/null +++ b/src/ui/assets/icons/line-cap-round.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/assets/icons/line-join-miter.svg b/src/ui/assets/icons/line-join-miter.svg new file mode 100644 index 0000000..b831d05 --- /dev/null +++ b/src/ui/assets/icons/line-join-miter.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/line-join-round.svg b/src/ui/assets/icons/line-join-round.svg new file mode 100644 index 0000000..adb324b --- /dev/null +++ b/src/ui/assets/icons/line-join-round.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/lock.svg b/src/ui/assets/icons/lock.svg new file mode 100644 index 0000000..9c8bb38 --- /dev/null +++ b/src/ui/assets/icons/lock.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/ui/assets/icons/logo-ya-dl.svg b/src/ui/assets/icons/logo-ya-dl.svg new file mode 100644 index 0000000..0aa7e62 --- /dev/null +++ b/src/ui/assets/icons/logo-ya-dl.svg @@ -0,0 +1,5 @@ + + + diff --git a/src/ui/assets/icons/menu.svg b/src/ui/assets/icons/menu.svg new file mode 100644 index 0000000..c8a226a --- /dev/null +++ b/src/ui/assets/icons/menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/monitoring.svg b/src/ui/assets/icons/monitoring.svg new file mode 100644 index 0000000..1829b18 --- /dev/null +++ b/src/ui/assets/icons/monitoring.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/mono-collection.svg b/src/ui/assets/icons/mono-collection.svg new file mode 100644 index 0000000..7575553 --- /dev/null +++ b/src/ui/assets/icons/mono-collection.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/mono-service-settings.svg b/src/ui/assets/icons/mono-service-settings.svg new file mode 100644 index 0000000..a6ad1ca --- /dev/null +++ b/src/ui/assets/icons/mono-service-settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/mssql.svg b/src/ui/assets/icons/mssql.svg new file mode 100644 index 0000000..e168b0c --- /dev/null +++ b/src/ui/assets/icons/mssql.svg @@ -0,0 +1,15 @@ + + + + ic-mssql + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/src/ui/assets/icons/mysql.svg b/src/ui/assets/icons/mysql.svg new file mode 100644 index 0000000..80511ea --- /dev/null +++ b/src/ui/assets/icons/mysql.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/okay.svg b/src/ui/assets/icons/okay.svg new file mode 100644 index 0000000..6755ea7 --- /dev/null +++ b/src/ui/assets/icons/okay.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/ui/assets/icons/os-logo.svg b/src/ui/assets/icons/os-logo.svg new file mode 100644 index 0000000..3ae4b6b --- /dev/null +++ b/src/ui/assets/icons/os-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/palettes/datalens-classic20.svg b/src/ui/assets/icons/palettes/datalens-classic20.svg new file mode 100644 index 0000000..0c8880d --- /dev/null +++ b/src/ui/assets/icons/palettes/datalens-classic20.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/palettes/datalens-default20.svg b/src/ui/assets/icons/palettes/datalens-default20.svg new file mode 100644 index 0000000..9cc3ed7 --- /dev/null +++ b/src/ui/assets/icons/palettes/datalens-default20.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ui/assets/icons/palettes/datalens-neo20.svg b/src/ui/assets/icons/palettes/datalens-neo20.svg new file mode 100644 index 0000000..84ec548 --- /dev/null +++ b/src/ui/assets/icons/palettes/datalens-neo20.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ui/assets/icons/palettes/emerald-20.svg b/src/ui/assets/icons/palettes/emerald-20.svg new file mode 100644 index 0000000..ed24925 --- /dev/null +++ b/src/ui/assets/icons/palettes/emerald-20.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/palettes/golden-20.svg b/src/ui/assets/icons/palettes/golden-20.svg new file mode 100644 index 0000000..19e9530 --- /dev/null +++ b/src/ui/assets/icons/palettes/golden-20.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/palettes/neutral-20.svg b/src/ui/assets/icons/palettes/neutral-20.svg new file mode 100644 index 0000000..68b6366 --- /dev/null +++ b/src/ui/assets/icons/palettes/neutral-20.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/palettes/oceanic-20.svg b/src/ui/assets/icons/palettes/oceanic-20.svg new file mode 100644 index 0000000..63e30a1 --- /dev/null +++ b/src/ui/assets/icons/palettes/oceanic-20.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/palettes/traffic-light-9.svg b/src/ui/assets/icons/palettes/traffic-light-9.svg new file mode 100644 index 0000000..e6a3a93 --- /dev/null +++ b/src/ui/assets/icons/palettes/traffic-light-9.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/parameter-section.svg b/src/ui/assets/icons/parameter-section.svg new file mode 100644 index 0000000..e446b53 --- /dev/null +++ b/src/ui/assets/icons/parameter-section.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/ui/assets/icons/placeholder-tooltip.svg b/src/ui/assets/icons/placeholder-tooltip.svg new file mode 100644 index 0000000..84a7e46 --- /dev/null +++ b/src/ui/assets/icons/placeholder-tooltip.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/plus.svg b/src/ui/assets/icons/plus.svg new file mode 100644 index 0000000..00f5b2e --- /dev/null +++ b/src/ui/assets/icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/postgres.svg b/src/ui/assets/icons/postgres.svg new file mode 100644 index 0000000..5e84fc8 --- /dev/null +++ b/src/ui/assets/icons/postgres.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/preview-bottom.svg b/src/ui/assets/icons/preview-bottom.svg new file mode 100644 index 0000000..398faf9 --- /dev/null +++ b/src/ui/assets/icons/preview-bottom.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/src/ui/assets/icons/preview-side.svg b/src/ui/assets/icons/preview-side.svg new file mode 100644 index 0000000..df0309b --- /dev/null +++ b/src/ui/assets/icons/preview-side.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + diff --git a/src/ui/assets/icons/product.svg b/src/ui/assets/icons/product.svg new file mode 100644 index 0000000..0465a95 --- /dev/null +++ b/src/ui/assets/icons/product.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/public-filled.svg b/src/ui/assets/icons/public-filled.svg new file mode 100644 index 0000000..814876e --- /dev/null +++ b/src/ui/assets/icons/public-filled.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/ql-chart.svg b/src/ui/assets/icons/ql-chart.svg new file mode 100644 index 0000000..4a63ae2 --- /dev/null +++ b/src/ui/assets/icons/ql-chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/relations-alias.svg b/src/ui/assets/icons/relations-alias.svg new file mode 100644 index 0000000..d918cc7 --- /dev/null +++ b/src/ui/assets/icons/relations-alias.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/relations-any.svg b/src/ui/assets/icons/relations-any.svg new file mode 100644 index 0000000..161db5d --- /dev/null +++ b/src/ui/assets/icons/relations-any.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/relations-controls.svg b/src/ui/assets/icons/relations-controls.svg new file mode 100644 index 0000000..6e71815 --- /dev/null +++ b/src/ui/assets/icons/relations-controls.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/relations-no-data.svg b/src/ui/assets/icons/relations-no-data.svg new file mode 100644 index 0000000..735d868 --- /dev/null +++ b/src/ui/assets/icons/relations-no-data.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/relations-no-link.svg b/src/ui/assets/icons/relations-no-link.svg new file mode 100644 index 0000000..678115f --- /dev/null +++ b/src/ui/assets/icons/relations-no-link.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/relations.svg b/src/ui/assets/icons/relations.svg new file mode 100644 index 0000000..aed5068 --- /dev/null +++ b/src/ui/assets/icons/relations.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/relative-mode.svg b/src/ui/assets/icons/relative-mode.svg new file mode 100644 index 0000000..f8c7738 --- /dev/null +++ b/src/ui/assets/icons/relative-mode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/search-clear.svg b/src/ui/assets/icons/search-clear.svg new file mode 100644 index 0000000..08698ef --- /dev/null +++ b/src/ui/assets/icons/search-clear.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/assets/icons/shapes.svg b/src/ui/assets/icons/shapes.svg new file mode 100644 index 0000000..a92e2f3 --- /dev/null +++ b/src/ui/assets/icons/shapes.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/sort-asc.svg b/src/ui/assets/icons/sort-asc.svg new file mode 100644 index 0000000..a14d48d --- /dev/null +++ b/src/ui/assets/icons/sort-asc.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/sort-desc.svg b/src/ui/assets/icons/sort-desc.svg new file mode 100644 index 0000000..1900706 --- /dev/null +++ b/src/ui/assets/icons/sort-desc.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/source-table.svg b/src/ui/assets/icons/source-table.svg new file mode 100644 index 0000000..5bf93fb --- /dev/null +++ b/src/ui/assets/icons/source-table.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/ui/assets/icons/sync.svg b/src/ui/assets/icons/sync.svg new file mode 100644 index 0000000..9c39e20 --- /dev/null +++ b/src/ui/assets/icons/sync.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/tick.svg b/src/ui/assets/icons/tick.svg new file mode 100644 index 0000000..3b7fe78 --- /dev/null +++ b/src/ui/assets/icons/tick.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/ui/assets/icons/toast-attention.svg b/src/ui/assets/icons/toast-attention.svg new file mode 100644 index 0000000..c09ca6b --- /dev/null +++ b/src/ui/assets/icons/toast-attention.svg @@ -0,0 +1 @@ + diff --git a/src/ui/assets/icons/toggle-preview.svg b/src/ui/assets/icons/toggle-preview.svg new file mode 100644 index 0000000..d7639f1 --- /dev/null +++ b/src/ui/assets/icons/toggle-preview.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/undefined.svg b/src/ui/assets/icons/undefined.svg new file mode 100644 index 0000000..07b2992 --- /dev/null +++ b/src/ui/assets/icons/undefined.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/src/ui/assets/icons/vis-area-100p.svg b/src/ui/assets/icons/vis-area-100p.svg new file mode 100644 index 0000000..b8fd33b --- /dev/null +++ b/src/ui/assets/icons/vis-area-100p.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/ui/assets/icons/vis-area.svg b/src/ui/assets/icons/vis-area.svg new file mode 100644 index 0000000..2b99f98 --- /dev/null +++ b/src/ui/assets/icons/vis-area.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/vis-bar-100p.svg b/src/ui/assets/icons/vis-bar-100p.svg new file mode 100644 index 0000000..0f6244a --- /dev/null +++ b/src/ui/assets/icons/vis-bar-100p.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/ui/assets/icons/vis-bar.svg b/src/ui/assets/icons/vis-bar.svg new file mode 100644 index 0000000..7dbdd1a --- /dev/null +++ b/src/ui/assets/icons/vis-bar.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/vis-column-100p.svg b/src/ui/assets/icons/vis-column-100p.svg new file mode 100644 index 0000000..67c1b0e --- /dev/null +++ b/src/ui/assets/icons/vis-column-100p.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/ui/assets/icons/vis-column.svg b/src/ui/assets/icons/vis-column.svg new file mode 100644 index 0000000..822f0cf --- /dev/null +++ b/src/ui/assets/icons/vis-column.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/vis-combined.svg b/src/ui/assets/icons/vis-combined.svg new file mode 100644 index 0000000..cb825ed --- /dev/null +++ b/src/ui/assets/icons/vis-combined.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/ui/assets/icons/vis-donut.svg b/src/ui/assets/icons/vis-donut.svg new file mode 100644 index 0000000..8f31cdd --- /dev/null +++ b/src/ui/assets/icons/vis-donut.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/ui/assets/icons/vis-flat-table.svg b/src/ui/assets/icons/vis-flat-table.svg new file mode 100644 index 0000000..f926efc --- /dev/null +++ b/src/ui/assets/icons/vis-flat-table.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/vis-funnel.svg b/src/ui/assets/icons/vis-funnel.svg new file mode 100644 index 0000000..cc7cd2b --- /dev/null +++ b/src/ui/assets/icons/vis-funnel.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/ui/assets/icons/vis-geolayers.svg b/src/ui/assets/icons/vis-geolayers.svg new file mode 100644 index 0000000..d7c8626 --- /dev/null +++ b/src/ui/assets/icons/vis-geolayers.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/icons/vis-geopoint.svg b/src/ui/assets/icons/vis-geopoint.svg new file mode 100644 index 0000000..4f2521c --- /dev/null +++ b/src/ui/assets/icons/vis-geopoint.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/ui/assets/icons/vis-geopolygon.svg b/src/ui/assets/icons/vis-geopolygon.svg new file mode 100644 index 0000000..bf8c179 --- /dev/null +++ b/src/ui/assets/icons/vis-geopolygon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/ui/assets/icons/vis-heatmap.svg b/src/ui/assets/icons/vis-heatmap.svg new file mode 100644 index 0000000..a935b7d --- /dev/null +++ b/src/ui/assets/icons/vis-heatmap.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/ui/assets/icons/vis-lines.svg b/src/ui/assets/icons/vis-lines.svg new file mode 100644 index 0000000..04ee69f --- /dev/null +++ b/src/ui/assets/icons/vis-lines.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/vis-metric.svg b/src/ui/assets/icons/vis-metric.svg new file mode 100644 index 0000000..251fc0d --- /dev/null +++ b/src/ui/assets/icons/vis-metric.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/ui/assets/icons/vis-pie.svg b/src/ui/assets/icons/vis-pie.svg new file mode 100644 index 0000000..381fefd --- /dev/null +++ b/src/ui/assets/icons/vis-pie.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/vis-pivot.svg b/src/ui/assets/icons/vis-pivot.svg new file mode 100644 index 0000000..9892ed5 --- /dev/null +++ b/src/ui/assets/icons/vis-pivot.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/ui/assets/icons/vis-scatter.svg b/src/ui/assets/icons/vis-scatter.svg new file mode 100644 index 0000000..6503b35 --- /dev/null +++ b/src/ui/assets/icons/vis-scatter.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/ui/assets/icons/vis-treemap.svg b/src/ui/assets/icons/vis-treemap.svg new file mode 100644 index 0000000..f935513 --- /dev/null +++ b/src/ui/assets/icons/vis-treemap.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/ui/assets/icons/visualization.svg b/src/ui/assets/icons/visualization.svg new file mode 100644 index 0000000..f26e682 --- /dev/null +++ b/src/ui/assets/icons/visualization.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/ui/assets/icons/warning-colored.svg b/src/ui/assets/icons/warning-colored.svg new file mode 100644 index 0000000..71447c1 --- /dev/null +++ b/src/ui/assets/icons/warning-colored.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ui/assets/icons/yt.svg b/src/ui/assets/icons/yt.svg new file mode 100644 index 0000000..db31811 --- /dev/null +++ b/src/ui/assets/icons/yt.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/ui/assets/images/dl-auth-background-dark.jpg b/src/ui/assets/images/dl-auth-background-dark.jpg new file mode 100644 index 0000000000000000000000000000000000000000..939609b6eb9ccd17f12cbeeacda92993117312ae GIT binary patch literal 172463 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

A=bMn92#__J7JJV95mP+ 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%0A_Hxan8}4)k4g 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%0A>ACH-GEYPgLu`aCd!vvTLZNJsXZ?M=Aqt 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 z�TVi_Z*OCO?tvaXaJkJ)q+hr={k_$GaInml=L*9XIQ=K0>-q4ICaTnyjAY%;5g6 zY7AWEb-YSk6Pw=oTj&qpZ?{>uDn@bNxc}p{nc}G!?JF*#T5Vn}rl+K)4NC3AeIu@I zTeP@VWHxs0XrTX5HL2RJIf2lPzhD1;k@Ry@)ZOXg;+3-@q{A6EtB(zjE00O4zRvqI zlifMow^6qI+GbmEuh|QsOU2u2(u-e8UOpj^`F#jS?I^0!)Vs3kNbwZUvDvp1zk};+ zb_8X3N*+%moxXoK`dM_39&M8F;dA#q=Gj$v?%PtQ;ho-tM#aCUXY(HZxTU#dIy&IUuVe9m)yw-*!zRZzP~Ok!N({#FF=ieh>C3SH0CVF1Igxu045> zzovPylddW!N_VdWpP%XlF+D+J(Z(Wvm8rUEn=@voGWIx!7@pPs_)FK`H?+9!`<*gB zizY({DP8+5LDvnx@Qe#eDOhE@RHeyRT_7tyeWcM><}5idt@rn%AI@WQjflfc`1{X- z?<1I*H;P!|%D?a4(B1#2uDeSRv?KaZvH7Zq0e+Nsw~?+ZzsdeRhPBlx7@{NJFv zRWmA�FYd98h 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 B3Ge^_ literal 0 HcmV?d00001 diff --git a/src/ui/assets/images/dl-auth-background-light.jpg b/src/ui/assets/images/dl-auth-background-light.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3274c6f2ad2a1d581f948b26506deb7259c5a72a GIT binary patch literal 228700 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

*8;JcYXVn3rD@398C~B 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

>0Z?8ye>WeF9D6 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 \ No newline at end of file diff --git a/src/ui/assets/images/illustration/dark/404.svg b/src/ui/assets/images/illustration/dark/404.svg new file mode 100644 index 0000000..e4e2344 --- /dev/null +++ b/src/ui/assets/images/illustration/dark/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/dark/500.svg b/src/ui/assets/images/illustration/dark/500.svg new file mode 100644 index 0000000..cadbb67 --- /dev/null +++ b/src/ui/assets/images/illustration/dark/500.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/dark/bad_request.svg b/src/ui/assets/images/illustration/dark/bad_request.svg new file mode 100644 index 0000000..d611c5f --- /dev/null +++ b/src/ui/assets/images/illustration/dark/bad_request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/dark/empty_directory.svg b/src/ui/assets/images/illustration/dark/empty_directory.svg new file mode 100644 index 0000000..725e3d8 --- /dev/null +++ b/src/ui/assets/images/illustration/dark/empty_directory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/dark/empty_state.svg b/src/ui/assets/images/illustration/dark/empty_state.svg new file mode 100644 index 0000000..1728638 --- /dev/null +++ b/src/ui/assets/images/illustration/dark/empty_state.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/dark/no_accounts.svg b/src/ui/assets/images/illustration/dark/no_accounts.svg new file mode 100644 index 0000000..e951ef0 --- /dev/null +++ b/src/ui/assets/images/illustration/dark/no_accounts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/dark/no_permission.svg b/src/ui/assets/images/illustration/dark/no_permission.svg new file mode 100644 index 0000000..b34f235 --- /dev/null +++ b/src/ui/assets/images/illustration/dark/no_permission.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/dark/nothing_found.svg b/src/ui/assets/images/illustration/dark/nothing_found.svg new file mode 100644 index 0000000..8beac65 --- /dev/null +++ b/src/ui/assets/images/illustration/dark/nothing_found.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/dark/project.svg b/src/ui/assets/images/illustration/dark/project.svg new file mode 100644 index 0000000..3339104 --- /dev/null +++ b/src/ui/assets/images/illustration/dark/project.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/dark/success_operation.svg b/src/ui/assets/images/illustration/dark/success_operation.svg new file mode 100644 index 0000000..38372d0 --- /dev/null +++ b/src/ui/assets/images/illustration/dark/success_operation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/403.svg b/src/ui/assets/images/illustration/light/403.svg new file mode 100644 index 0000000..b22f2d6 --- /dev/null +++ b/src/ui/assets/images/illustration/light/403.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/404.svg b/src/ui/assets/images/illustration/light/404.svg new file mode 100644 index 0000000..1f551ea --- /dev/null +++ b/src/ui/assets/images/illustration/light/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/500.svg b/src/ui/assets/images/illustration/light/500.svg new file mode 100644 index 0000000..4cce6e7 --- /dev/null +++ b/src/ui/assets/images/illustration/light/500.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/bad_request.svg b/src/ui/assets/images/illustration/light/bad_request.svg new file mode 100644 index 0000000..acd64af --- /dev/null +++ b/src/ui/assets/images/illustration/light/bad_request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/empty_directory.svg b/src/ui/assets/images/illustration/light/empty_directory.svg new file mode 100644 index 0000000..fb20a73 --- /dev/null +++ b/src/ui/assets/images/illustration/light/empty_directory.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/empty_state.svg b/src/ui/assets/images/illustration/light/empty_state.svg new file mode 100644 index 0000000..6bffa04 --- /dev/null +++ b/src/ui/assets/images/illustration/light/empty_state.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/no_accounts.svg b/src/ui/assets/images/illustration/light/no_accounts.svg new file mode 100644 index 0000000..81daa56 --- /dev/null +++ b/src/ui/assets/images/illustration/light/no_accounts.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/no_permission.svg b/src/ui/assets/images/illustration/light/no_permission.svg new file mode 100644 index 0000000..68a85c6 --- /dev/null +++ b/src/ui/assets/images/illustration/light/no_permission.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/nothing_found.svg b/src/ui/assets/images/illustration/light/nothing_found.svg new file mode 100644 index 0000000..a09630b --- /dev/null +++ b/src/ui/assets/images/illustration/light/nothing_found.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/project.svg b/src/ui/assets/images/illustration/light/project.svg new file mode 100644 index 0000000..4b2be98 --- /dev/null +++ b/src/ui/assets/images/illustration/light/project.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/assets/images/illustration/light/success_operation.svg b/src/ui/assets/images/illustration/light/success_operation.svg new file mode 100644 index 0000000..8c1c6fd --- /dev/null +++ b/src/ui/assets/images/illustration/light/success_operation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/ui/components/AccessRights/AccessRightsUrlOpen.tsx b/src/ui/components/AccessRights/AccessRightsUrlOpen.tsx new file mode 100644 index 0000000..9ee8384 --- /dev/null +++ b/src/ui/components/AccessRights/AccessRightsUrlOpen.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +import {registry} from 'ui/registry'; +import type {AccessRightsUrlOpenProps} from 'ui/registry/units/common/types/components/AccessRightsUrlOpen'; + +export const AccessRightsUrlOpen = ({history}: AccessRightsUrlOpenProps) => { + const {AccessRightsUrlOpenComponent} = registry.common.components.getAll(); + + return ; +}; diff --git a/src/ui/components/ActionPanel/ActionPanel.scss b/src/ui/components/ActionPanel/ActionPanel.scss new file mode 100644 index 0000000..3ae7bca --- /dev/null +++ b/src/ui/components/ActionPanel/ActionPanel.scss @@ -0,0 +1,54 @@ +@import '../../styles/variables'; + +$topExpandablePanelMargin: var(--g-spacing-2); + +.action-panel { + &__wrapper { + position: relative; + height: $actionPanelHeight; + min-height: $actionPanelHeight; + } + + &__container { + position: fixed; + z-index: 10; + right: 0; + box-sizing: border-box; + height: $actionPanelHeight; + flex-shrink: 0; + display: flex; + align-items: center; + padding: 0 $actionPanelHorizontalPadding; + border-bottom: 1px solid var(--g-color-line-generic); + background: var(--g-color-base-background); + + &_mobile { + overflow-x: auto; + overflow-y: hidden; + } + } + + &__create-widget-btn { + margin-left: auto; + margin-right: 10px; + } + + &__action-btn { + width: 132px; + flex: 0 0 132px; + } + + &__expandable-panel { + position: fixed; + right: var(--g-spacing-2); + top: calc(#{$actionPanelHeight} + #{$topExpandablePanelMargin}); + height: calc(100vh - #{$actionPanelHeight} - 2 * #{$topExpandablePanelMargin}); + } + + &__revisions-panel + &__expandable-panel { + top: calc(#{$actionPanelHeight} + #{$topExpandablePanelMargin} + #{$revisionPanelHeight}); + height: calc( + 100vh - #{$actionPanelHeight} - 2 * #{$topExpandablePanelMargin} - #{$revisionPanelHeight} + ); + } +} diff --git a/src/ui/components/ActionPanel/ActionPanel.tsx b/src/ui/components/ActionPanel/ActionPanel.tsx new file mode 100644 index 0000000..7da36e0 --- /dev/null +++ b/src/ui/components/ActionPanel/ActionPanel.tsx @@ -0,0 +1,302 @@ +import React from 'react'; + +import block from 'bem-cn-lite'; +import {I18n} from 'i18n'; +import {connect} from 'react-redux'; +import type {RouteComponentProps} from 'react-router-dom'; +import {withRouter} from 'react-router-dom'; +import type {Dispatch} from 'redux'; +import {bindActionCreators} from 'redux'; +import {ActionPanelQA, Feature} from 'shared'; +import { + cleanRevisions, + fetchEntryById, + setEntryContent, + setEntryCurrentRevId, + setRevisionsListMode, + setRevisionsMode, +} from 'store/actions/entryContent'; +import {selectAsideHeaderData} from 'store/selectors/asideHeader'; +import {selectEntryContent, selectIsRevisionsOpened} from 'store/selectors/entryContent'; +import {RevisionsListMode, RevisionsMode} from 'store/typings/entryContent'; +import type {FilterEntryContextMenuItems} from 'ui/components/EntryContextMenu'; +import {DL} from 'ui/constants/common'; +import {registry} from 'ui/registry'; +import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; + +import type {GetEntryResponse, GetRevisionsEntry} from '../../../shared/schema'; +import type {DatalensGlobalState} from '../../index'; +import {getSdk} from '../../libs/schematic-sdk'; +import type {EntryContextMenuItems} from '../EntryContextMenu/helpers'; +import ExpandablePanel from '../ExpandablePanel/ExpandablePanel'; +import Revisions from '../Revisions/Revisions'; +import type {GetRevisionMenuItems, GetRevisionRowExtendedProps} from '../Revisions/types'; +import RevisionsPanel from '../RevisionsPanel/RevisionsPanel'; + +import EntryPanel from './components/EntryPanel/EntryPanel'; + +import './ActionPanel.scss'; + +const b = block('action-panel'); +const i18n = I18n.keyset('component.action-panel.view'); + +type StateProps = ReturnType; + +type OwnProps = { + entryId?: string; + entry?: GetEntryResponse; + additionalEntryItems?: EntryContextMenuItems; + leftItems?: React.ReactNode | React.ReactNode[]; + centerItems?: React.ReactNode[]; + rightItems?: React.ReactNode | React.ReactNode[]; + className?: string; + enablePublish?: boolean; + setActualVersion?: () => void; + isEditing?: boolean; + deprecationWarning?: null | { + message: string; + onConfirm?: () => void; + }; + renderRevisionItemActions?: (item: GetRevisionsEntry, currentRevId: string) => React.ReactNode; + wrapperRef?: React.Ref | React.RefCallback; + style?: React.CSSProperties; + expandablePanelDescription?: string; + getRevisionRowExtendedProps?: GetRevisionRowExtendedProps; + filterEntryContextMenuItems?: FilterEntryContextMenuItems; + lastCrumbAdditionalContent?: React.ReactNode; + getRevisionContextMenuItems?: GetRevisionMenuItems; +}; + +type DispatchProps = ReturnType; + +type Props = Partial & OwnProps & DispatchProps & RouteComponentProps; + +type State = { + entry?: GetEntryResponse; + isEditing: boolean; +}; + +class ActionPanel extends React.Component { + static EntryPanel: typeof EntryPanel = EntryPanel; + + state: State = { + isEditing: false, + }; + + concurrentId: string | null = null; + + componentDidMount() { + const {entryId, actions} = this.props; + + if (entryId) { + this.concurrentId = `fetchEntryById-${entryId}`; + actions.fetchEntryById(entryId, this.concurrentId, (entry) => { + this.setState({ + entry, + }); + this.setEntryContent(); + }); + } else { + this.setEntryContent(); + } + + actions.cleanRevisions(); + actions.setRevisionsMode(RevisionsMode.Closed); + } + + componentDidUpdate(prevProps: Readonly) { + const {entry, actions, match} = this.props; + const entryId = entry?.entryId; + const prevEntryId = prevProps.entry?.entryId; + const hasEntryChanged = entryId && prevEntryId && entryId !== prevEntryId; + + const {extractEntryId} = registry.common.functions.getAll(); + + const oldPageEntryId = extractEntryId(prevProps.match.url); + const newPageEntryId = extractEntryId(match.url); + + const hasPageEntryChanged = oldPageEntryId !== newPageEntryId; + const hasPageEntryChangedWithId = hasPageEntryChanged && newPageEntryId; + const hasEntryChangedWithId = entryId && !prevEntryId; + const needCleanRevisions = hasEntryChanged || hasPageEntryChangedWithId; + + if (needCleanRevisions) { + actions.cleanRevisions(); + actions.setRevisionsMode(RevisionsMode.Closed); + } + + if (entryId !== null && (hasEntryChangedWithId || hasPageEntryChangedWithId)) { + this.concurrentId = `fetchEntryById-${entryId}`; + const fetchEntryId = hasPageEntryChangedWithId ? newPageEntryId! : entryId!; + actions.fetchEntryById(fetchEntryId, this.concurrentId, (entryItem) => { + this.concurrentId = null; + this.setState({ + entry: entryItem, + }); + this.setEntryContent(); + }); + } else if (hasEntryChanged) { + this.setEntryContent(); + } + } + + componentWillUnmount() { + if (this.concurrentId) { + getSdk().cancelRequest(this.concurrentId); + } + this.props.actions.cleanRevisions(); + this.props.actions.setRevisionsMode(RevisionsMode.Closed); + } + + render() { + const { + additionalEntryItems = [], + leftItems, + centerItems = [], + rightItems, + className: mix, + sidebarSize, + entryContent, + isRevisionsOpened, + setActualVersion, + isEditing, + deprecationWarning, + renderRevisionItemActions, + wrapperRef, + style: externalStyle, + expandablePanelDescription, + getRevisionRowExtendedProps, + filterEntryContextMenuItems, + lastCrumbAdditionalContent, + getRevisionContextMenuItems, + } = this.props; + + const style: React.CSSProperties = {left: sidebarSize, ...externalStyle}; + + const entry = this.getEntry(); + + const revisionsListDescription = isEnabledFeature(Feature.RevisionsListNoLimit) + ? undefined + : i18n('label_history-changes-date-limit'); + + const description = expandablePanelDescription ?? revisionsListDescription; + + return ( +

+ ); + } + + private getEntry() { + return this.props.entry || this.state.entry; + } + + private getEnablePublish() { + const getGloballyEntrySettings = registry.common.functions.get('getGloballyEntrySettings'); + const globallyEntrySettings = getGloballyEntrySettings(); + if (globallyEntrySettings?.isPublishDisabled) { + return false; + } + + return Boolean(this.props.enablePublish); + } + + private setEntryContent() { + const entry = this.getEntry(); + if (!entry?.entryId) { + return; + } + + this.props.actions.setEntry(entry); + } + + private handleOpenDraftRevision = () => { + this.props.actions.setEntryCurrentRevId(this.getEntry()?.savedId || ''); + }; + + private handleOpenCurrentRevision = () => { + this.props.actions.setEntryCurrentRevId(this.getEntry()?.publishedId || ''); + }; + + private handleExpandablePanelClose = () => { + const {setRevisionsListMode, setRevisionsMode, cleanRevisions} = this.props.actions; + setRevisionsListMode(RevisionsListMode.Expanded); + setRevisionsMode(RevisionsMode.Closed); + cleanRevisions(); + }; +} + +const mapStateToProps = (state: DatalensGlobalState) => { + return { + sidebarSize: selectAsideHeaderData(state).size || 0, + entryContent: selectEntryContent(state), + isRevisionsOpened: selectIsRevisionsOpened(state), + }; +}; + +const mapDispatchToProps = (dispatch: Dispatch) => { + return { + actions: bindActionCreators( + { + setEntry: setEntryContent, + setEntryCurrentRevId, + setRevisionsMode, + setRevisionsListMode, + cleanRevisions, + fetchEntryById, + }, + dispatch, + ), + }; +}; + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ActionPanel)); diff --git a/src/ui/components/ActionPanel/ActionPanelHelpers.ts b/src/ui/components/ActionPanel/ActionPanelHelpers.ts new file mode 100644 index 0000000..565eeff --- /dev/null +++ b/src/ui/components/ActionPanel/ActionPanelHelpers.ts @@ -0,0 +1,7 @@ +export default class ActionPanelHelpers { + static getNameByKey({key = ''}) { + const matchedValues = key.match(/\/([^/]*)$/); + + return matchedValues ? matchedValues[1] : key; + } +} diff --git a/src/ui/components/ActionPanel/components/ChartSaveControls/ChartSaveControl.scss b/src/ui/components/ActionPanel/components/ChartSaveControls/ChartSaveControl.scss new file mode 100644 index 0000000..80393f3 --- /dev/null +++ b/src/ui/components/ActionPanel/components/ChartSaveControls/ChartSaveControl.scss @@ -0,0 +1,35 @@ +.action-panel-chart-save-controls { + &__save-btn-wrapper { + position: relative; + display: inline-block; + } + + &__pseudosave-btn { + width: 100%; + height: 100%; + position: absolute; + top: 0; + right: 0; + display: none; + + &_active { + display: block; + } + } + + &__splitter { + display: inline-block; + vertical-align: middle; + width: 1px; + height: 22px; + margin-left: 4px; + + // stylelint-disable-next-line declaration-no-important + margin-right: 8px !important; + background-color: var(--g-color-line-generic); + } + + &_mobile { + display: flex; + } +} diff --git a/src/ui/components/ActionPanel/components/ChartSaveControls/ChartSaveControl.tsx b/src/ui/components/ActionPanel/components/ChartSaveControls/ChartSaveControl.tsx new file mode 100644 index 0000000..1dad01e --- /dev/null +++ b/src/ui/components/ActionPanel/components/ChartSaveControls/ChartSaveControl.tsx @@ -0,0 +1,146 @@ +import React from 'react'; + +import type {DropdownMenuItemMixed} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {i18n} from 'i18n'; +import {useSelector} from 'react-redux'; +import type {EntryUpdateMode} from 'shared'; +import {DL} from 'ui/constants/common'; +import {selectIsRenameWithoutReload} from 'ui/store/selectors/entryContent'; + +import NavigationPrompt from '../../../NavigationPrompt/NavigationPrompt'; + +import {SaveButton} from './components/SaveButton/SaveButton'; +import {SaveDropdown} from './components/SaveDropdown/SaveDropdown'; +import type {AdditionalButtonTemplate} from './types'; +import {useAdditionalItems} from './useAdditionalItems'; + +import './ChartSaveControl.scss'; + +type ChartSaveControlProps = { + className?: string; + + onClickButtonSave: (mode?: EntryUpdateMode) => void; + onOpenNoRightsDialog: () => void; + onSaveAsDraftClick: () => void; + onSaveAndPublishClick: () => void; + onSaveAsNewClick: () => void; + + additionalControls?: AdditionalButtonTemplate[]; + dropdownItems?: DropdownMenuItemMixed<() => void>[]; + needSplitMainAndAdditionalButtons?: boolean; + + canEdit: boolean; + isSaveButtonDisabled: boolean; + isDropdownDisabled: boolean; + isCurrentRevisionActual: boolean; + isNewChart: boolean; + + hideSaveButton?: boolean; + hideSaveDropdown?: boolean; +}; + +const b = block('action-panel-chart-save-controls'); + +export const ChartSaveControls: React.FC = ( + props: ChartSaveControlProps, +) => { + const { + className, + additionalControls, + needSplitMainAndAdditionalButtons, + canEdit, + isSaveButtonDisabled, + isCurrentRevisionActual, + dropdownItems, + isDropdownDisabled, + hideSaveButton, + hideSaveDropdown, + onSaveAndPublishClick, + onSaveAsDraftClick, + onSaveAsNewClick, + onClickButtonSave, + onOpenNoRightsDialog, + isNewChart, + } = props; + const additionalItems = useAdditionalItems({items: additionalControls}); + + const isRenameWithoutReload = useSelector(selectIsRenameWithoutReload); + + const mainDropdownItems = React.useMemo void>[]>(() => { + const saveAsDraftItem = { + action: () => onSaveAsDraftClick(), + text: i18n('component.chart-save-controls', 'button_save-as-draft'), + extraProps: { + 'data-qa': 'save-as-draft', + } as any, + hidden: isNewChart || !canEdit, + }; + + const saveAndPublishItem = { + action: () => onSaveAndPublishClick(), + text: i18n('component.chart-save-controls', 'button_save-as-published'), + hidden: isNewChart, + extraProps: { + 'data-qa': 'save-and-publish', + } as any, + }; + + const items: DropdownMenuItemMixed<() => void>[] = []; + + items.push(isCurrentRevisionActual ? saveAsDraftItem : saveAndPublishItem); + + return [ + { + action: () => onSaveAsNewClick(), + text: i18n('component.chart-save-controls', 'button_save-as-new'), + hidden: isNewChart, + qa: 'save-as-new-chart', + }, + ...items, + ]; + }, [ + isNewChart, + canEdit, + isCurrentRevisionActual, + onSaveAsDraftClick, + onSaveAndPublishClick, + onSaveAsNewClick, + ]); + + const allDropdownItems = React.useMemo( + () => [...mainDropdownItems, ...(dropdownItems || [])], + [mainDropdownItems, dropdownItems], + ); + + return ( +
+ {additionalItems} + {Boolean(needSplitMainAndAdditionalButtons) &&
} + {!hideSaveButton && ( + +
+
onOpenNoRightsDialog()} + /> + +
+ + + )} + {!hideSaveDropdown && ( + + )} +
+ ); +}; diff --git a/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveButton/SaveButton.scss b/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveButton/SaveButton.scss new file mode 100644 index 0000000..85b482c --- /dev/null +++ b/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveButton/SaveButton.scss @@ -0,0 +1,10 @@ +.action-panel-save-button { + &__save-btn { + min-width: 60px; + + svg { + height: 16px; + width: 16px; + } + } +} diff --git a/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveButton/SaveButton.tsx b/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveButton/SaveButton.tsx new file mode 100644 index 0000000..bf47058 --- /dev/null +++ b/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveButton/SaveButton.tsx @@ -0,0 +1,53 @@ +import React from 'react'; + +import {Button, Icon} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {i18n} from 'i18n'; +import {EntryUpdateMode, SaveChartControlsQa} from 'shared'; + +import iconLock from 'ui/assets/icons/lock.svg'; + +import './SaveButton.scss'; + +type SaveButtonProps = { + onClick: (entry?: EntryUpdateMode) => void; + canEdit: boolean; + disabled: boolean; + isSaveAsDraft: boolean; +}; + +const b = block('action-panel-save-button'); + +export const SaveButton: React.FC = (props: SaveButtonProps) => { + const {disabled, canEdit, isSaveAsDraft} = props; + + let buttonText; + + if (disabled) { + buttonText = 'button_saved'; + } else { + buttonText = isSaveAsDraft ? 'button_save-as-draft' : 'button_save'; + } + + return ( + + ); +}; diff --git a/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveDropdown/SaveDropdown.scss b/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveDropdown/SaveDropdown.scss new file mode 100644 index 0000000..9c7681b --- /dev/null +++ b/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveDropdown/SaveDropdown.scss @@ -0,0 +1,5 @@ +.action-panel-save-dropdown { + &__switcher { + margin-left: 4px; + } +} diff --git a/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveDropdown/SaveDropdown.tsx b/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveDropdown/SaveDropdown.tsx new file mode 100644 index 0000000..8b8c6e7 --- /dev/null +++ b/src/ui/components/ActionPanel/components/ChartSaveControls/components/SaveDropdown/SaveDropdown.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +import {ChevronDown} from '@gravity-ui/icons'; +import type {DropdownMenuItemMixed} from '@gravity-ui/uikit'; +import {Button, DropdownMenu, Icon} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {ChartSaveControlsQA} from 'shared'; + +import './SaveDropdown.scss'; + +type SaveDropdownProps = { + dropdownItems: DropdownMenuItemMixed<() => void>[]; + disabled: boolean; +}; + +const b = block('action-panel-save-dropdown'); + +export const SaveDropdown: React.FC = (props: SaveDropdownProps) => { + const {disabled, dropdownItems} = props; + + return ( + ( + + )} + popupProps={{placement: 'bottom-end'}} + /> + ); +}; diff --git a/src/ui/components/ActionPanel/components/ChartSaveControls/types.ts b/src/ui/components/ActionPanel/components/ChartSaveControls/types.ts new file mode 100644 index 0000000..823cd0c --- /dev/null +++ b/src/ui/components/ActionPanel/components/ChartSaveControls/types.ts @@ -0,0 +1,21 @@ +import type {ButtonView, IconData} from '@gravity-ui/uikit'; + +export type AdditionalButtonTemplate = { + key: string; + action: () => void; + text?: string; + textClassName?: string; + className?: string; + icon?: { + data: IconData; + size?: number; + className?: string; + }; + hidden?: boolean; + loading?: boolean; + view?: ButtonView; + disabled?: boolean; + qa?: string; + title?: string; + hotkey?: string; +}; diff --git a/src/ui/components/ActionPanel/components/ChartSaveControls/useAdditionalItems.tsx b/src/ui/components/ActionPanel/components/ChartSaveControls/useAdditionalItems.tsx new file mode 100644 index 0000000..8481702 --- /dev/null +++ b/src/ui/components/ActionPanel/components/ChartSaveControls/useAdditionalItems.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +import {ActionTooltip, Button, Icon} from '@gravity-ui/uikit'; + +import type {AdditionalButtonTemplate} from './types'; + +type UseAdditionalItemsArgs = { + items: AdditionalButtonTemplate[] | undefined; +}; + +const renderItemButton = (itemProps: AdditionalButtonTemplate) => { + return ( + + ); +}; + +export const useAdditionalItems = (args: UseAdditionalItemsArgs) => { + const {items} = args; + + if (!items) { + return []; + } + + return items + .map((itemProps) => { + if (itemProps.hidden) { + return null; + } + + if (itemProps.title) { + return ( + + {renderItemButton(itemProps)} + + ); + } else { + return renderItemButton(itemProps); + } + }) + .filter(Boolean); +}; diff --git a/src/ui/components/ActionPanel/components/EntryPanel/EntryPanel.scss b/src/ui/components/ActionPanel/components/EntryPanel/EntryPanel.scss new file mode 100644 index 0000000..c397482 --- /dev/null +++ b/src/ui/components/ActionPanel/components/EntryPanel/EntryPanel.scss @@ -0,0 +1,66 @@ +@import '~@gravity-ui/uikit/styles/mixins'; +@import '../../../../styles/mixins'; + +.dl-entry-panel { + $class: &; + height: inherit; + display: flex; + align-items: center; + flex-shrink: 0; + margin-right: auto; + + &__entry { + &-title { + max-width: 50%; + height: inherit; + display: flex; + align-items: center; + overflow: hidden; + flex-shrink: 1; + + &-text { + @include overflow-ellipsis(); + + font-weight: bold; + } + } + + &-actions { + display: flex; + align-items: center; + margin-bottom: 2px; + padding-right: 5px; + margin-left: var(--g-spacing-2); + + #{$class}__action-btn { + color: var(--g-color-text-secondary); + margin-right: 5px; + + &_active { + svg { + color: var(--g-color-base-misc-heavy-hover); + } + } + } + } + } + + &__project-title { + position: relative; + padding: 0 16px 0 8px; + + &::after { + content: ''; + position: absolute; + top: -2px; + right: 3px; + width: 1px; + height: 20px; + background-color: var(--g-color-line-generic); + } + } + + &__cloud-select-container { + margin-right: 8px; + } +} diff --git a/src/ui/components/ActionPanel/components/EntryPanel/EntryPanel.tsx b/src/ui/components/ActionPanel/components/EntryPanel/EntryPanel.tsx new file mode 100644 index 0000000..bf90d62 --- /dev/null +++ b/src/ui/components/ActionPanel/components/EntryPanel/EntryPanel.tsx @@ -0,0 +1,365 @@ +import React from 'react'; + +import {Ellipsis, Globe} from '@gravity-ui/icons'; +import {Button, Icon, Link} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {EntryDialogName, EntryDialogResolveStatus} from 'components/EntryDialogues'; +import {I18n} from 'i18n'; +import {connect} from 'react-redux'; +import type {RouteComponentProps} from 'react-router-dom'; +import {withRouter} from 'react-router-dom'; +import type {Dispatch} from 'redux'; +import {bindActionCreators} from 'redux'; +import {ActionPanelQA, EntryScope} from 'shared'; +import type {DatalensGlobalState, EntryDialogues} from 'ui'; +import type {FilterEntryContextMenuItems} from 'ui/components/EntryContextMenu'; +import {CounterName, GoalId, reachMetricaGoal} from 'ui/libs/metrica'; +import {registry} from 'ui/registry'; +import type {BreadcrumbsItem} from 'ui/registry/units/common/types/components/EntryBreadcrumbs'; +import { + addCollectionBreadcrumbs, + addWorkbookInfo, + resetWorkbookPermissions, +} from 'units/workbooks/store/actions'; +import {selectEntityBreadcrumbs, selectWorkbookName} from 'units/workbooks/store/selectors'; + +import type {GetEntryResponse} from '../../../../../shared/schema'; +import {DL} from '../../../../constants/common'; +import logger from '../../../../libs/logger'; +import {getSdk} from '../../../../libs/schematic-sdk'; +import type {EntryContextMenuProps} from '../../../EntryContextMenu/EntryContextMenu'; +import EntryContextMenu from '../../../EntryContextMenu/EntryContextMenu'; +import { + ICONS_ENTRY_MENU_DEFAULT_CLASSNAME, + ICONS_ENTRY_MENU_DEFAULT_SIZE, +} from '../../../EntryContextMenu/EntryContextMenuBase/EntryContextMenuBase'; +import type {EntryContextMenuItems} from '../../../EntryContextMenu/helpers'; +import type {DialogSwitchPublicProps} from '../../../EntryDialogues/DialogSwitchPublic'; + +import './EntryPanel.scss'; + +const i18n = I18n.keyset('component.action-panel.view'); + +const b = block('dl-entry-panel'); + +type StateProps = ReturnType; + +type DispatchProps = ReturnType; + +type OwnProps = { + additionalEntryItems: EntryContextMenuItems; + entry?: GetEntryResponse; + onCloseNavigation?: () => void; + enablePublish?: boolean; + filterEntryContextMenuItems?: FilterEntryContextMenuItems; + lastCrumbAdditionalContent?: React.ReactNode; +}; + +type Props = OwnProps & DispatchProps & StateProps & RouteComponentProps; + +type State = { + visibleEntryContextMenu?: boolean; + entry?: GetEntryResponse; + error?: Error; +}; + +class EntryPanel extends React.Component { + static defaultProps = { + additionalEntryItems: [], + enablePublish: false, + }; + + static getDerivedStateFromProps(props: Props, state: State): State | null { + const {entry: entryState} = state; + const {entry: entryProps} = props; + + if (entryState) { + if ( + entryProps && + (entryState.entryId !== entryProps.entryId || + entryState.key !== entryProps.key || + entryState.collectionId !== entryProps.collectionId) + ) { + return { + entry: { + ...entryProps, + }, + }; + } + + if (!(entryState as {fake?: boolean}).fake) { + return null; + } + } + + return { + entry: { + ...entryProps!, + }, + }; + } + + state: State = { + entry: undefined, + visibleEntryContextMenu: false, + }; + + private btnEntryContextMenuRef = React.createRef(); + private entryDialogsRef = React.createRef(); + private entryContextMenuRef = React.createRef(); + + componentDidMount() { + const workbookId = this.state.entry?.workbookId; + const collectionId = this.state.entry?.collectionId; + + if (workbookId) { + this.props.actions.addWorkbookInfo(workbookId, true); + } else if (collectionId) { + this.props.actions.addCollectionBreadcrumbs({collectionId}); + } + } + + componentDidUpdate(prevProps: Props) { + const workbookId = this.props.entry?.workbookId; + const collectionId = this.props.entry?.collectionId; + const prevWorkbookId = prevProps.entry?.workbookId; + const prevCollectionId = prevProps.entry?.collectionId; + + if (prevWorkbookId !== workbookId && workbookId) { + this.props.actions.addWorkbookInfo(workbookId, true); + } else if (prevCollectionId !== collectionId && collectionId) { + this.props.actions.addCollectionBreadcrumbs({collectionId}); + } + + if (prevWorkbookId && !workbookId) { + this.props.actions.resetWorkbookPermissions(); + } + } + + render() { + const {children, workbookName, entityBreadcrumbs, lastCrumbAdditionalContent} = this.props; + const {EntryBreadcrumbs} = registry.common.components.getAll(); + + return ( + + {this.renderEntryTitle()} + + {lastCrumbAdditionalContent} + {this.renderControls()} +
{children}
+
+ } + /> +
+ ); + } + + renderControls() { + if (DL.IS_MOBILE) { + return null; + } + + const {entry: {isFavorite} = {isFavorite: undefined}, entry} = this.state; + + const isFakeEntry = (entry as any)?.fake; + + const additionalItems = this.getEntryContextMenuItems(); + + const {ButtonFavorite} = registry.common.components.getAll(); + + return ( +
+ {!isFakeEntry && ( + + )} + {!isFakeEntry || additionalItems.length ? ( + + ) : null} + {this.btnEntryContextMenuRef.current && ( + + )} +
+ ); + } + + toggleFavorite = () => { + const entry = this.state.entry!; + const {entryId, isFavorite} = entry; + + try { + if (isFavorite) { + getSdk() + .sdk.us.deleteFavorite({entryId}) + .then(() => + this.setState({ + entry: { + ...entry, + isFavorite: false, + }, + }), + ); + return; + } + getSdk() + .sdk.us.addFavorite({entryId}) + .then(() => + this.setState({ + entry: { + ...entry, + isFavorite: true, + }, + }), + ); + } catch (error) { + logger.logError('EntryPanel: toggleFavorite failed', error); + this.setState({ + error, + }); + } + }; + + onChangePublicClick = async () => { + if (!this.state.entry || !this.entryDialogsRef.current) { + return; + } + + if (this.state.entry.scope === EntryScope.Dash) { + reachMetricaGoal(CounterName.Main, GoalId.DashboardPublicAccessClick); + } + + const result = await this.entryDialogsRef.current.open({ + dialog: EntryDialogName.SwitchPublic, + dialogProps: { + entry: this.state.entry as DialogSwitchPublicProps['entry'], + }, + }); + + if (result.status === EntryDialogResolveStatus.Success && result.data) { + this.setState({ + entry: { + ...this.state.entry!, + public: Boolean(result.data?.publish), + }, + }); + } + }; + + onCloseEntryContextMenu = () => this.setState({visibleEntryContextMenu: false}); + + toggleEntryContextMenu = () => + this.setState({visibleEntryContextMenu: !this.state.visibleEntryContextMenu}); + + private getEntryContextMenuItems(): EntryContextMenuItems { + const {enablePublish, additionalEntryItems} = this.props; + const entry = this.state.entry!; + const isAdmin = Boolean(entry.permissions?.admin); + + let disabled = false; + + if ((entry as any).fake) { + disabled = true; + } + + const items = [...additionalEntryItems]; + + if (isAdmin && enablePublish && !disabled) { + items.push({ + icon: ( + + ), + text: i18n('button_switch-public'), + action: this.onChangePublicClick, + id: 'public', + }); + } + + return items; + } + + private renderRootContent = (rootItem: BreadcrumbsItem) => { + if (rootItem.href) { + return ( + + {rootItem.text} + + ); + } + + return ( +
+ {rootItem.text} +
+ ); + }; + + private renderEntryTitle = () => { + const {ActionPanelEntrySelect} = registry.common.components.getAll(); + + return ; + }; +} + +const mapStateToProps = (state: DatalensGlobalState, ownProps: OwnProps) => { + const workbookId = ownProps.entry?.workbookId || ''; + + return { + workbookName: selectWorkbookName(state, workbookId), + entityBreadcrumbs: selectEntityBreadcrumbs(state), + }; +}; + +const mapDispatchToProps = (dispatch: Dispatch) => { + return { + actions: bindActionCreators( + { + addCollectionBreadcrumbs, + addWorkbookInfo, + resetWorkbookPermissions, + }, + dispatch, + ), + }; +}; + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(EntryPanel)); diff --git a/src/ui/components/ActionPanel/index.ts b/src/ui/components/ActionPanel/index.ts new file mode 100644 index 0000000..80c7b46 --- /dev/null +++ b/src/ui/components/ActionPanel/index.ts @@ -0,0 +1,2 @@ +export {default as ActionPanel} from './ActionPanel'; +export * from './utils'; diff --git a/src/ui/components/ActionPanel/utils.ts b/src/ui/components/ActionPanel/utils.ts new file mode 100644 index 0000000..339cfe1 --- /dev/null +++ b/src/ui/components/ActionPanel/utils.ts @@ -0,0 +1,7 @@ +import type {GetEntryResponse} from '../../../shared/schema/types'; + +export const getFakeEntry = ( + entryData?: Partial & {fakeName?: string}, +) => { + return {fake: true, ...(Boolean(entryData) && {...entryData})} as unknown as T; +}; diff --git a/src/ui/components/AdaptiveDialog/AdaptiveDialog.tsx b/src/ui/components/AdaptiveDialog/AdaptiveDialog.tsx new file mode 100644 index 0000000..7a9602d --- /dev/null +++ b/src/ui/components/AdaptiveDialog/AdaptiveDialog.tsx @@ -0,0 +1,69 @@ +import React from 'react'; + +import { + Dialog, + type DialogFooterProps, + type DialogHeaderProps, + type DialogProps, + Sheet, +} from '@gravity-ui/uikit'; +import {DL} from 'ui/constants/common'; + +type AdaptiveDialogProps = { + visible: boolean; + id?: string; + onClose: () => void; + sheetClassName?: string; + sheetContentClassName?: string; + dialogBodyClassName?: string; + title?: string; + renderSheetFooter?: () => React.ReactNode; + renderDialogFooter?: () => React.ReactNode; + qa?: string; + dialogProps?: Omit; + dialogHeaderProps?: DialogHeaderProps; + dialogFooterProps?: DialogFooterProps; +}; + +export const AdaptiveDialog: React.FC = ({ + children, + onClose, + renderSheetFooter, + renderDialogFooter, + title, + sheetClassName, + sheetContentClassName, + dialogBodyClassName, + dialogProps, + dialogHeaderProps, + dialogFooterProps, + visible, + qa, + id, +}) => { + const showDialogFooter = renderDialogFooter || dialogFooterProps; + + return DL.IS_MOBILE ? ( + + {children} + {renderSheetFooter?.()} + + ) : ( + + + {children} + {showDialogFooter && ( + {renderDialogFooter?.()} + )} + + ); +}; diff --git a/src/ui/components/AnimateBlock/AnimateBlock.scss b/src/ui/components/AnimateBlock/AnimateBlock.scss new file mode 100644 index 0000000..1f9d1a5 --- /dev/null +++ b/src/ui/components/AnimateBlock/AnimateBlock.scss @@ -0,0 +1,13 @@ +.dl-animate-block { + position: relative; + top: 6px; + opacity: 0; + + &_animate { + top: 0; + opacity: 1; + transition: + top 0.2s, + opacity 0.2s; + } +} diff --git a/src/ui/components/AnimateBlock/AnimateBlock.tsx b/src/ui/components/AnimateBlock/AnimateBlock.tsx new file mode 100644 index 0000000..286f15a --- /dev/null +++ b/src/ui/components/AnimateBlock/AnimateBlock.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import block from 'bem-cn-lite'; +import {Waypoint} from 'react-waypoint'; + +import './AnimateBlock.scss'; + +const b = block('dl-animate-block'); + +export interface AnimateBlockProps { + className?: string; + delay?: number; + offset?: number; + children?: React.ReactNode; +} + +export const AnimateBlock = ({className, delay = 0, offset = 100, children}: AnimateBlockProps) => { + const [isScheduledAnimation, setIsScheduledAnimation] = React.useState(false); + const [isPlayedAnimation, setIsPlayedAnimation] = React.useState(false); + + React.useEffect(() => { + let timer: NodeJS.Timeout; + + if (isScheduledAnimation) { + timer = setTimeout(() => { + setIsPlayedAnimation(true); + }, delay); + } + + return () => { + if (isScheduledAnimation && timer) { + clearTimeout(timer); + } + }; + }, [delay, isScheduledAnimation]); + + return ( +
+ { + setIsScheduledAnimation(true); + }} + /> + {children} +
+ ); +}; diff --git a/src/ui/components/AnimateBlock/index.ts b/src/ui/components/AnimateBlock/index.ts new file mode 100644 index 0000000..6307d07 --- /dev/null +++ b/src/ui/components/AnimateBlock/index.ts @@ -0,0 +1 @@ +export * from './AnimateBlock'; diff --git a/src/ui/components/AsideHeaderAdapter/AsideHeaderAdapter.scss b/src/ui/components/AsideHeaderAdapter/AsideHeaderAdapter.scss new file mode 100644 index 0000000..2f0d49e --- /dev/null +++ b/src/ui/components/AsideHeaderAdapter/AsideHeaderAdapter.scss @@ -0,0 +1,53 @@ +@import '../../styles/mixins.scss'; + +.dl-aside-header { + &__item-link { + @include link-reset; + & { + display: flex; + height: 40px; + width: 100%; + align-items: center; + } + } + + &__item-wrap { + display: flex; + width: 100%; + height: 100%; + align-items: center; + } + + &__logo { + @include link-reset; + } + + &__docs-link, + &__info-btn { + align-items: center; + box-sizing: border-box; + display: flex; + flex-grow: 1; + height: 100%; + padding: 8px 16px; + text-decoration: none; + color: var(--g-color-text-primary); + } + + &_hidden .gn-aside-header__aside { + display: none; + } + + &_hidden .gn-aside-header__pane-container { + height: 100%; + } + + &_hidden .gn-aside-header__content { + width: 100%; + } + + // TODO: Support css api in gravity-ui/navigation and use it + .gn-settings-menu__item-icon { + color: var(--gn-aside-header-item-icon-color); + } +} diff --git a/src/ui/components/AsideHeaderAdapter/AsideHeaderAdapter.tsx b/src/ui/components/AsideHeaderAdapter/AsideHeaderAdapter.tsx new file mode 100644 index 0000000..8a32ed2 --- /dev/null +++ b/src/ui/components/AsideHeaderAdapter/AsideHeaderAdapter.tsx @@ -0,0 +1,358 @@ +import React from 'react'; + +import {CircleQuestion, Gear, Sliders} from '@gravity-ui/icons'; +import type {AsideHeaderProps, MenuItem, TopAlertProps} from '@gravity-ui/navigation'; +import {AsideHeader, FooterItem} from '@gravity-ui/navigation'; +import type {IconData} from '@gravity-ui/uikit'; +import {List} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {I18n, i18n as baseI18n} from 'i18n'; +import {useDispatch, useSelector} from 'react-redux'; +import {Link, useLocation} from 'react-router-dom'; +import {DlNavigationQA, Feature} from 'shared'; +import {DL} from 'ui/constants'; +import {closeDialog, openDialog} from 'ui/store/actions/dialog'; +import { + selectAsideHeaderIsCompact, + selectAsideHeaderIsHidden, +} from 'ui/store/selectors/asideHeader'; +import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; + +import {setAsideHeaderData, updateAsideHeaderIsCompact} from '../../store/actions/asideHeader'; +import type {AsideHeaderData} from '../../store/typings/asideHeader'; +import {UserAvatar} from '../UserMenu/UserAvatar'; +import {UserMenu} from '../UserMenu/UserMenu'; + +import type {LogoTextProps} from './LogoText/LogoText'; +import {LogoText} from './LogoText/LogoText'; +import {Settings as SettingsPanel} from './Settings/Settings'; +import {DIALOG_RELEASE_VERSION} from './VersionDialog/VersionDialog'; +import {ASIDE_HEADER_LOGO_ICON_SIZE} from './constants'; + +import iconCollection from '../../assets/icons/mono-collection.svg'; +import defaultLogoIcon from '../../assets/icons/os-logo.svg'; + +import './AsideHeaderAdapter.scss'; + +const b = block('dl-aside-header'); +const i18n = I18n.keyset('component.aside-header.view'); + +const COLLECTIONS_PATH = '/collections'; +const SERVICE_SETTINGS_PATH = '/settings'; + +const FOOTER_ITEM_DEFAULT_SIZE = 18; +const PROMO_SITE_DOMAIN = 'https://datalens.ru/opensource'; +const PROMO_DOC_PATH = '/docs'; +const GITHUB_URL = 'https://github.com/datalens-tech/datalens'; + +export const DOCUMENTATION_LINK = + DL.DOCS_URL || `${PROMO_SITE_DOMAIN}${PROMO_DOC_PATH}/${DL.USER_LANG}/`; + +export const ITEMS_NAVIGATION_DEFAULT_SIZE = 18; + +export type AsideHeaderAdapterProps = { + renderContent?: AsideHeaderProps['renderContent']; + logoIcon?: IconData; + logoTextProps?: LogoTextProps & {ref?: React.RefObject}; + collapseButtonWrapper?: AsideHeaderProps['collapseButtonWrapper']; + customMenuItems?: MenuItem[]; + logoWrapperRef?: React.RefObject; + asideRef?: React.RefObject; +}; + +enum Panel { + Settings = 'settings', +} + +enum PopupName { + Main = 'main', + Account = 'account', +} + +export const getLinkWrapper = (node: React.ReactNode, path: string) => { + return ( + +
{node}
+ + ); +}; + +type DocsItem = { + text: React.ReactNode; + hint?: string; + url?: string; + itemWrapper?: (item: DocsItem) => React.ReactNode; +}; + +const renderDocsItem = (item: DocsItem) => { + const {text, url, hint, itemWrapper} = item; + const title = hint ?? (typeof text === 'string' ? text : undefined); + if (typeof itemWrapper === 'function') { + return itemWrapper(item); + } else if (url) { + return ( +
+ {text} + + ); + } else { + return text; + } +}; + +export const AsideHeaderAdapter = ({ + renderContent, + logoIcon, + logoTextProps, + collapseButtonWrapper, + customMenuItems, + logoWrapperRef, + asideRef, +}: AsideHeaderAdapterProps) => { + const dispatch = useDispatch(); + const {pathname} = useLocation(); + const isCompact = useSelector(selectAsideHeaderIsCompact); + const isHidden = useSelector(selectAsideHeaderIsHidden); + const [visiblePanel, setVisiblePanel] = React.useState(); + const [currentPopup, setCurrentPopup] = React.useState(null); + + const renderAsideHeaderContent = React.useCallback( + (asideHeaderData: AsideHeaderData) => { + // Cause it's dispatch moving it to next tick to prevent render race warnings + window.requestAnimationFrame(() => { + dispatch(setAsideHeaderData(asideHeaderData)); + }); + + return renderContent?.(asideHeaderData); + }, + [renderContent, dispatch], + ); + + const onChangeCompact = React.useCallback( + (compact: boolean) => { + dispatch(updateAsideHeaderIsCompact(compact)); + }, + [dispatch], + ); + + const handleClosePanel = React.useCallback(() => { + setVisiblePanel(undefined); + }, []); + + const isReadOnly = isEnabledFeature(Feature.ReadOnlyMode); + const topAlert: TopAlertProps | undefined = isReadOnly + ? { + message: baseI18n('common.read-only', 'toast_editing-warning'), + } + : undefined; + + const menuItems: MenuItem[] = React.useMemo( + () => [ + { + id: 'collections', + title: i18n('label_collections'), + icon: iconCollection, + iconSize: 16, + current: pathname.includes(COLLECTIONS_PATH), + itemWrapper: (params, makeItem) => { + return getLinkWrapper(makeItem(params), COLLECTIONS_PATH); + }, + }, + ...(customMenuItems || []), + { + id: 'settings', + title: i18n('switch_service-settings'), + icon: Sliders, + iconSize: ITEMS_NAVIGATION_DEFAULT_SIZE, + current: pathname.includes(SERVICE_SETTINGS_PATH), + itemWrapper: (params, makeItem) => { + return getLinkWrapper(makeItem(params), SERVICE_SETTINGS_PATH); + }, + }, + ], + [pathname, customMenuItems], + ); + + const panelItems = React.useMemo( + () => [ + { + id: Panel.Settings, + content: , + visible: visiblePanel === Panel.Settings, + }, + ], + [visiblePanel], + ); + + const getReliaseVersionWrapper = React.useCallback( + ({text}) => { + const handleShowReleaseVersion = () => { + setCurrentPopup(null); + dispatch( + openDialog({ + id: DIALOG_RELEASE_VERSION, + props: { + releaseVersion: DL.RELEASE_VERSION || '', + open: true, + onClose: () => { + dispatch(closeDialog()); + }, + }, + }), + ); + }; + + return ( +
+ {text} +
+ ); + }, + [dispatch], + ); + + const handleClosePopup = React.useCallback(() => { + setCurrentPopup(null); + }, []); + + const renderFooter = () => { + return ( + + + setVisiblePanel( + visiblePanel === Panel.Settings ? undefined : Panel.Settings, + ), + }} + /> + { + setVisiblePanel(undefined); + setCurrentPopup( + currentPopup === PopupName.Main ? null : PopupName.Main, + ); + }, + }} + enableTooltip={false} + popupVisible={currentPopup === PopupName.Main} + popupOffset={{mainAxis: 0, crossAxis: 8}} + onOpenChangePopup={(isOpen) => { + if (!isOpen) { + setCurrentPopup(null); + } + }} + renderPopupContent={() => { + return ( + + ); + }} + /> + {DL.AUTH_ENABLED && ( + + makeItem({...params, icon: }), + title: i18n('label_account'), + tooltipText: i18n('label_account'), + current: currentPopup === PopupName.Account, + onItemClick: () => { + setVisiblePanel(undefined); + setCurrentPopup( + currentPopup === PopupName.Account ? null : PopupName.Account, + ); + }, + }} + enableTooltip={false} + popupVisible={currentPopup === PopupName.Account} + popupOffset={{mainAxis: 0, crossAxis: 8}} + onOpenChangePopup={(isOpen) => { + if (!isOpen) { + handleClosePopup(); + } + }} + renderPopupContent={() => } + /> + )} + + ); + }; + + return ( + , + icon: logoIcon ?? defaultLogoIcon, + iconSize: ASIDE_HEADER_LOGO_ICON_SIZE, + iconClassName: b('logo-icon'), + className: b('logo'), + wrapper: (logoElement) => ( + + {logoElement} + + ), + }} + topAlert={topAlert} + menuItems={menuItems} + panelItems={panelItems} + headerDecoration={false} + onChangeCompact={onChangeCompact} + renderFooter={renderFooter} + renderContent={renderAsideHeaderContent} + onClosePanel={handleClosePanel} + className={b({ + hidden: isHidden, + })} + collapseButtonWrapper={collapseButtonWrapper} + ref={asideRef} + /> + ); +}; diff --git a/src/ui/components/AsideHeaderAdapter/LogoText/LogoText.scss b/src/ui/components/AsideHeaderAdapter/LogoText/LogoText.scss new file mode 100644 index 0000000..ac5c95a --- /dev/null +++ b/src/ui/components/AsideHeaderAdapter/LogoText/LogoText.scss @@ -0,0 +1,12 @@ +@import '../../../styles/mixins.scss'; + +.aside-header-logo-text { + &__title { + @include text-subheader-2; + } + + &__installation-info { + @include installation-info-font; + width: fit-content; + } +} diff --git a/src/ui/components/AsideHeaderAdapter/LogoText/LogoText.tsx b/src/ui/components/AsideHeaderAdapter/LogoText/LogoText.tsx new file mode 100644 index 0000000..f2162f0 --- /dev/null +++ b/src/ui/components/AsideHeaderAdapter/LogoText/LogoText.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import block from 'bem-cn-lite'; +import {FULL_PRODUCT_NAME} from 'ui/constants'; + +import './LogoText.scss'; + +const b = block('aside-header-logo-text'); + +export type LogoTextProps = { + installationInfo?: string; + productName?: string; + installationInfoClassName?: string; +}; + +export const LogoText = React.forwardRef( + ({installationInfo, productName, installationInfoClassName}, ref) => { + const showInstallation = installationInfo; + const defaultProductName = FULL_PRODUCT_NAME; + + return ( +
+
{productName || defaultProductName}
+ {showInstallation && ( +
+ {installationInfo} +
+ )} +
+ ); + }, +); + +LogoText.displayName = 'LogoText'; diff --git a/src/ui/components/AsideHeaderAdapter/Settings/Appearance/HighcontrastSettings.tsx b/src/ui/components/AsideHeaderAdapter/Settings/Appearance/HighcontrastSettings.tsx new file mode 100644 index 0000000..c4a81f6 --- /dev/null +++ b/src/ui/components/AsideHeaderAdapter/Settings/Appearance/HighcontrastSettings.tsx @@ -0,0 +1,26 @@ +import React from 'react'; + +import {SegmentedRadioGroup as RadioButton} from '@gravity-ui/uikit'; +import {I18n} from 'i18n'; + +import {HighcontrastValue} from '../types'; + +const i18n = I18n.keyset('component.aside-header-settings.view'); + +type HighcontrastSettingsProps = { + value: HighcontrastValue; + onUpdate: (updatedHc: string) => void; +}; + +export const HighcontrastSettings = ({value, onUpdate}: HighcontrastSettingsProps) => { + return ( + + + {i18n('value_theme-contrast-normal')} + + + {i18n('value_theme-contrast-enhanced')} + + + ); +}; diff --git a/src/ui/components/AsideHeaderAdapter/Settings/Appearance/ThemeSettings.tsx b/src/ui/components/AsideHeaderAdapter/Settings/Appearance/ThemeSettings.tsx new file mode 100644 index 0000000..37cb276 --- /dev/null +++ b/src/ui/components/AsideHeaderAdapter/Settings/Appearance/ThemeSettings.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import {SegmentedRadioGroup as RadioButton} from '@gravity-ui/uikit'; +import {I18n} from 'i18n'; +import {DARK_THEME, LIGHT_THEME, SYSTEM_THEME} from 'shared'; +const i18n = I18n.keyset('component.aside-header-settings.view'); + +type ThemeSettingsProps = { + value: string; + onUpdate: (updatedTheme: string) => void; +}; + +export const ThemeSettings = ({value, onUpdate}: ThemeSettingsProps) => { + return ( + + {i18n('value_theme-light')} + {i18n('value_theme-dark')} + + {i18n('value_theme-system')} + + + ); +}; diff --git a/src/ui/components/AsideHeaderAdapter/Settings/ItemField/ItemField.tsx b/src/ui/components/AsideHeaderAdapter/Settings/ItemField/ItemField.tsx new file mode 100644 index 0000000..243096d --- /dev/null +++ b/src/ui/components/AsideHeaderAdapter/Settings/ItemField/ItemField.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import type {ControlGroupOption} from '@gravity-ui/uikit'; +import {SegmentedRadioGroup as RadioButton, Select} from '@gravity-ui/uikit'; + +type ItemFieldProps = { + isMobile: boolean; + value: string; + onUpdate: (value: string) => void; + options: ControlGroupOption[]; +}; + +export const ItemField = ({isMobile, value, onUpdate, options}: ItemFieldProps) => { + const handleSelectOption = (option: string[]) => { + onUpdate(option[0]); + }; + + return ( + + {isMobile ? ( + +
+ ); + }, +); + +ButtonAttach.displayName = 'ButtonAttach'; diff --git a/src/ui/components/ChartModelingSettings/ChartModelingSettings.scss b/src/ui/components/ChartModelingSettings/ChartModelingSettings.scss new file mode 100644 index 0000000..868e175 --- /dev/null +++ b/src/ui/components/ChartModelingSettings/ChartModelingSettings.scss @@ -0,0 +1,77 @@ +.chart-modeling-settings { + &__container { + top: var(--dl-chart-modelling-settings-top, 40px); + width: 280px; + z-index: 10; + background: var(--g-color-base-float); + overflow: auto; + } + + &__section { + border-bottom: 1px solid var(--g-color-line-generic-solid); + padding: var(--g-spacing-4); + } + + &__header { + display: flex; + justify-content: space-between; + align-items: center; + } + + &__title { + margin-right: var(--g-spacing-2); + } + + &__section-header { + display: flex; + justify-content: space-between; + align-items: center; + } + + &__form-row { + padding: var(--g-spacing-5) 0 0; + display: flex; + flex-direction: column; + gap: var(--g-spacing-2); + + &_switch { + flex-direction: row; + justify-content: space-between; + align-items: center; + } + } + + &__shape-and-width-controls { + display: flex; + gap: var(--g-spacing-2); + } + + &__line-width-input { + width: 120px; + } + + &__shape-select { + width: 112px; + } + + &__shape-select-icon { + display: flex; + overflow: hidden; + } + + &__widget-name { + display: flex; + align-items: center; + gap: var(--g-spacing-1); + padding-top: var(--g-spacing-2); + } + + &__control-label { + cursor: pointer; + } + + &__label-with-hint { + display: flex; + gap: var(--g-spacing-1); + } +} diff --git a/src/ui/components/ChartModelingSettings/ChartModelingSettings.tsx b/src/ui/components/ChartModelingSettings/ChartModelingSettings.tsx new file mode 100644 index 0000000..eabc2ec --- /dev/null +++ b/src/ui/components/ChartModelingSettings/ChartModelingSettings.tsx @@ -0,0 +1,141 @@ +import React from 'react'; + +import {Xmark} from '@gravity-ui/icons'; +import {Drawer, DrawerItem} from '@gravity-ui/navigation'; +import {Alert, Button, Icon, Label, Switch, Text} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {useDispatch, useSelector} from 'react-redux'; +import {DEFAULT_SMOOTHING, DEFAULT_TREND_SETTINGS} from 'shared/constants/chart-modeling'; +import type {DatalensGlobalState} from 'ui/index'; +import {chartModelingActions} from 'ui/store/toolkit/chart-modeling/actions'; +import {getChartModelingState, getEditingWidgetId} from 'ui/store/toolkit/chart-modeling/selectors'; + +import {EntityIcon} from '../EntityIcon/EntityIcon'; + +import {SmothSettings} from './components/SmothSettings'; +import {TrendSettings} from './components/TrendSettings'; +import {i18n} from './i18n'; +import {getChartModelingWarning} from './utils'; + +import './ChartModelingSettings.scss'; + +const b = block('chart-modeling-settings'); + +export const ChartModelingSettings = () => { + const dispatch = useDispatch(); + const containerRef = React.useRef(null); + const widgetId = useSelector(getEditingWidgetId); + const isOpen = Boolean(widgetId); + const chartState = useSelector((state: DatalensGlobalState) => + getChartModelingState(state, widgetId), + ); + const widgetName = chartState?.widgetName; + const warning = React.useMemo( + () => getChartModelingWarning(chartState?.warnings)?.description, + [chartState?.warnings], + ); + + const handleClose = React.useCallback(() => { + dispatch(chartModelingActions.closeChartModelingDialog()); + }, [dispatch]); + + const shouldLinkSeries = Boolean( + (chartState?.smoothing?.settings?.linked ?? DEFAULT_SMOOTHING.linked) || + (chartState?.trends?.settings?.linked ?? DEFAULT_TREND_SETTINGS.linked), + ); + const handleLinkSeries = React.useCallback(() => { + if (!widgetId) { + return; + } + + dispatch( + chartModelingActions.updateChartSettings({ + id: widgetId, + settings: { + smoothing: { + ...chartState.smoothing, + settings: { + ...chartState.smoothing?.settings, + linked: !shouldLinkSeries, + }, + }, + trends: { + ...chartState.trends, + settings: { + ...chartState.trends?.settings, + linked: !shouldLinkSeries, + }, + }, + }, + }), + ); + }, [chartState, dispatch, shouldLinkSeries, widgetId]); + + React.useEffect(() => { + return () => { + dispatch(chartModelingActions.closeChartModelingDialog()); + }; + }, [dispatch]); + + return ( + + +
+
+
+ + {i18n('title_modeling')} + + +
+ +
+ {widgetName && ( +
+ + {widgetName} +
+ )} +
+ {Boolean(widgetId) && ( + + {warning && } +
+ +
+
+ +
+
+
+ + {i18n('label_additional-settings')} + +
+
+ + {i18n('label_link-series')} + + +
+
+
+ )} +
+
+ ); +}; diff --git a/src/ui/components/ChartModelingSettings/components/ShapeSelect/ShapeSelect.tsx b/src/ui/components/ChartModelingSettings/components/ShapeSelect/ShapeSelect.tsx new file mode 100644 index 0000000..fd0f0e4 --- /dev/null +++ b/src/ui/components/ChartModelingSettings/components/ShapeSelect/ShapeSelect.tsx @@ -0,0 +1,55 @@ +import React from 'react'; + +import {Select, type SelectOption} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import type {LineShapeType} from 'shared'; +import {SHAPES_PALETTE_ORDER} from 'shared'; +import {selectClientAvailableLineShapes} from 'ui/constants/common'; + +import {getShapedLineIcon} from '../../../../utils/line-shapes'; +import {i18n} from '../../i18n'; + +const b = block('chart-modeling-settings'); + +function getShapeSelectOptions() { + const availableLineShapes = selectClientAvailableLineShapes(); + const values = availableLineShapes.sort( + (shape1, shape2) => SHAPES_PALETTE_ORDER[shape1] - SHAPES_PALETTE_ORDER[shape2], + ); + + const items: SelectOption[] = values.map((v) => ({value: v})); + return items; +} + +function renderShapeSelectOption({value, width}: {value: string; width: number}) { + if (value === 'auto') { + return {i18n('label_auto')}; + } + + return ( +
+ {getShapedLineIcon({shape: value as LineShapeType, width, height: 2})} +
+ ); +} + +export const ShapeSelect = ({ + value, + onChange, +}: { + value: string; + onChange: (values: string[]) => void; +}) => { + return ( + + {SMOOTHING_SELECT_OPTION.map((item) => ( + + {item.label} + + ))} + +
+
+
+ {i18n('label_window-size')} + +
+ +
+
+ {i18n('label_shape-and-width')} +
+ + +
+
+ + )} + + ); +}; diff --git a/src/ui/components/ChartModelingSettings/components/TrendSettings.tsx b/src/ui/components/ChartModelingSettings/components/TrendSettings.tsx new file mode 100644 index 0000000..264b672 --- /dev/null +++ b/src/ui/components/ChartModelingSettings/components/TrendSettings.tsx @@ -0,0 +1,133 @@ +import React from 'react'; + +import {NumberInput, Select, Switch, Text} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {useDispatch} from 'react-redux'; +import type {ChartStateSettings, TrendLineSettings} from 'shared'; +import {DEFAULT_TREND_SETTINGS} from 'shared/constants/chart-modeling'; +import {MarkdownHelpPopover} from 'ui/components/MarkdownHelpPopover/MarkdownHelpPopover'; +import {chartModelingActions} from 'ui/store/toolkit/chart-modeling/actions'; + +import {MAX_LINE_WIDTH, TREND_SELECT_OPTION} from '../constants'; +import {i18n} from '../i18n'; + +import {ShapeSelect} from './ShapeSelect/ShapeSelect'; + +const b = block('chart-modeling-settings'); + +type Props = { + widgetId: string; + chartState: ChartStateSettings | undefined; +}; + +export const TrendSettings = (props: Props) => { + const {widgetId, chartState = {}} = props; + const settings = chartState.trends?.settings; + const dispatch = useDispatch(); + + const enabled = chartState.trends?.enabled ?? false; + const method = String(settings?.method ?? DEFAULT_TREND_SETTINGS.method); + const dashStyle = settings?.dashStyle ?? 'auto'; + const lineWidth = settings?.lineWidth ?? DEFAULT_TREND_SETTINGS.lineWidth; + + const handleEnableTrends = React.useCallback(() => { + dispatch( + chartModelingActions.updateChartSettings({ + id: widgetId, + settings: { + trends: {...chartState.trends, enabled: !enabled}, + }, + }), + ); + }, [chartState.trends, dispatch, enabled, widgetId]); + + const updateSettings = React.useCallback( + (updates: Partial) => { + dispatch( + chartModelingActions.updateChartSettings({ + id: widgetId, + settings: { + trends: { + ...chartState.trends, + settings: { + ...settings, + ...updates, + }, + }, + }, + }), + ); + }, + [chartState.trends, dispatch, settings, widgetId], + ); + + const handleUpdateMethod = React.useCallback( + (value: string[]) => { + updateSettings({method: value[0] as TrendLineSettings['method']}); + }, + [updateSettings], + ); + + const handleUpdateShape = React.useCallback( + (values: string[]) => { + const value = values[0] === 'auto' ? undefined : values[0]; + updateSettings({dashStyle: value}); + }, + [updateSettings], + ); + + const handleUpdateLineWidth = React.useCallback( + (value: number | null) => { + const lineWidthValue = + value === null ? undefined : Math.max(1, Math.min(MAX_LINE_WIDTH, value)); + updateSettings({lineWidth: lineWidthValue}); + }, + [updateSettings], + ); + + return ( + +
+ + {i18n('label_trend')} + + +
+ {enabled && ( + +
+
+ {i18n('label_method')} + +
+ +
+
+ {i18n('label_shape-and-width')} +
+ + +
+
+
+ )} +
+ ); +}; diff --git a/src/ui/components/ChartModelingSettings/constants.ts b/src/ui/components/ChartModelingSettings/constants.ts new file mode 100644 index 0000000..f1d451e --- /dev/null +++ b/src/ui/components/ChartModelingSettings/constants.ts @@ -0,0 +1,11 @@ +import {i18n} from './i18n'; + +export const SMOOTHING_SELECT_OPTION = [{value: 'sma', label: i18n('label_simple-moving-average')}]; + +export const TREND_SELECT_OPTION = [ + {value: 'linear', label: i18n('label_linear')}, + {value: 'quadratic', label: i18n('label_quadratic')}, + {value: 'cubic', label: i18n('label_cubic')}, +]; + +export const MAX_LINE_WIDTH = 12; diff --git a/src/ui/components/ChartModelingSettings/i18n.ts b/src/ui/components/ChartModelingSettings/i18n.ts new file mode 100644 index 0000000..72d94a5 --- /dev/null +++ b/src/ui/components/ChartModelingSettings/i18n.ts @@ -0,0 +1,3 @@ +import {I18n} from 'i18n'; + +export const i18n = I18n.keyset('component.chart-modeling-settings'); diff --git a/src/ui/components/ChartModelingSettings/utils.ts b/src/ui/components/ChartModelingSettings/utils.ts new file mode 100644 index 0000000..1444466 --- /dev/null +++ b/src/ui/components/ChartModelingSettings/utils.ts @@ -0,0 +1,15 @@ +import {i18n} from './i18n'; + +export function getChartModelingWarning(warnings?: string[]) { + switch (warnings?.[0]) { + case 'dataWithNull': { + return { + title: i18n('label_null-data-warning-short'), + description: i18n('label_null-data-warning'), + }; + } + default: { + return null; + } + } +} diff --git a/src/ui/components/Collapse/Collapse.scss b/src/ui/components/Collapse/Collapse.scss new file mode 100644 index 0000000..781695b --- /dev/null +++ b/src/ui/components/Collapse/Collapse.scss @@ -0,0 +1,75 @@ +@import '~@gravity-ui/uikit/styles/mixins'; + +.dl-collapse { + &__header { + display: inline-flex; + align-items: center; + } + + &__panel { + display: inline-flex; + align-items: center; + + cursor: pointer; + + &_no-data { + cursor: default; + } + } + + &__title { + margin: 0; + } + + &__title_secondary { + color: var(--g-color-text-secondary); + } + + &__title_size { + &_s { + @include text-body-1; + } + + &_m { + @include text-body-2; + } + + &_l { + @include text-body-3; + } + } + + &__arrow-wrapper { + &_position_left { + margin-right: 8px; + } + + &_position_right { + margin-left: 8px; + } + } + + &__arrow-wrapper_secondary { + color: var(--g-color-text-secondary); + .g-button__text { + color: var(--g-color-text-secondary); + } + } + + &__arrow-button { + .g-button__text { + margin: 0 4px; + } + } + + &__empty-state-title { + @include text-body-1; + font-weight: normal; + + opacity: 0.5; + } + + &_collapsed &__content { + display: none; + } +} diff --git a/src/ui/components/Collapse/Collapse.tsx b/src/ui/components/Collapse/Collapse.tsx new file mode 100644 index 0000000..2f77968 --- /dev/null +++ b/src/ui/components/Collapse/Collapse.tsx @@ -0,0 +1,183 @@ +import React from 'react'; + +import {ArrowToggle, Button} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import type {CollapseProps} from './types'; + +import './Collapse.scss'; + +const b = block('dl-collapse'); + +export function Collapse({ + className, + headerClassName, + title, + titleClassName, + children, + toolbar, + toolbarClassName, + contentClassName, + arrowPosition = 'right', + arrowView = 'icon', + arrowQa, + arrowClassName, + emptyState = 'No data', + titleSize = 'l', + contentMarginTop = 12, + defaultIsExpand = false, + isSecondary = false, + beforeExpandChange, + isExpand: isControlledExpand, + cacheContent, +}: CollapseProps) { + const [isExpand, setIsExpand] = React.useState(isControlledExpand || defaultIsExpand); + const [contentMounted, setContentMounted] = React.useState(false); + + React.useEffect(() => { + if (isControlledExpand !== undefined) { + beforeExpandChange?.(isControlledExpand); + setIsExpand(isControlledExpand); + } + }, [beforeExpandChange, isControlledExpand]); + + const toggleExpand = React.useCallback(() => { + const newExpandValue = !isExpand; + + beforeExpandChange?.(newExpandValue); + + setIsExpand(newExpandValue); + }, [setIsExpand, isExpand, beforeExpandChange]); + + const initContent = React.useCallback((element: HTMLDivElement | null) => { + if (element !== null) { + setContentMounted(true); + } + }, []); + + const shouldRenderContent = children && (isExpand || (cacheContent && contentMounted)); + + return ( +
+
+
+ {children && arrowPosition === 'left' && ( + + )} + {typeof title === 'string' ? ( +

+ {title} +

+ ) : ( + title + )} + {children && arrowPosition === 'right' && ( + + )} +
+ + {toolbar &&
{toolbar}
} +
+ + {!children &&

{emptyState}

} + + {shouldRenderContent && ( +
+ {children} +
+ )} +
+ ); +} + +interface CollapseTogglerProps { + isExpand: boolean; + isSecondary: boolean; + titleSize: 's' | 'm' | 'l'; + arrowView: 'icon' | 'button'; + arrowPosition: 'left' | 'right'; + arrowQa?: string; + arrowClassName?: string; +} + +const arrowSizes = { + s: 14, + m: 16, + l: 20, +}; + +function CollapseToggler({ + arrowView, + arrowPosition, + arrowQa, + arrowClassName, + isSecondary, + titleSize, + isExpand, +}: CollapseTogglerProps) { + const arrowDirection = isExpand ? 'top' : 'bottom'; + + return ( +
+ {arrowView === 'button' ? ( + + ) : ( + + )} +
+ ); +} diff --git a/src/ui/components/Collapse/types.ts b/src/ui/components/Collapse/types.ts new file mode 100644 index 0000000..d400d30 --- /dev/null +++ b/src/ui/components/Collapse/types.ts @@ -0,0 +1,22 @@ +export interface CollapseProps { + className?: string; + headerClassName?: string; + arrowPosition?: 'left' | 'right'; + children?: React.ReactNode; + toolbar?: React.ReactNode; + toolbarClassName?: string; + title: string | JSX.Element; + titleClassName?: string; + contentClassName?: string; + titleSize?: 's' | 'm' | 'l'; + defaultIsExpand?: boolean; + emptyState?: string | JSX.Element; + arrowView?: 'icon' | 'button'; + contentMarginTop?: string | number; + isSecondary?: boolean; + beforeExpandChange?: (isExpand: boolean) => void; + isExpand?: boolean; + cacheContent?: boolean; + arrowQa?: string; + arrowClassName?: string; +} diff --git a/src/ui/components/CollectionFilters/CollectionFilters.scss b/src/ui/components/CollectionFilters/CollectionFilters.scss new file mode 100644 index 0000000..82c672a --- /dev/null +++ b/src/ui/components/CollectionFilters/CollectionFilters.scss @@ -0,0 +1,53 @@ +$block: '.dl-collection-filters'; + +#{$block} { + display: flex; + align-items: center; + + &_compact-mode { + flex-direction: column; + align-items: flex-start; + justify-content: center; + + #{$block}__filters-block { + width: 100%; + margin-top: 8px; + } + + #{$block}__filter-by-type { + margin-left: 0; + } + + #{$block}__sort { + flex: 1; + } + } + + &__filters-block { + display: flex; + align-items: center; + } + + &__filter-string-container { + width: 100%; + display: flex; + } + + &__filter-string { + flex: 1; + } + + &__filter-by-type { + margin-left: 8px; + min-width: 150px; + } + + &__sort { + margin-left: 8px; + min-width: 220px; + } + + &__radio-button { + margin-left: 8px; + } +} diff --git a/src/ui/components/CollectionFilters/CollectionFilters.tsx b/src/ui/components/CollectionFilters/CollectionFilters.tsx new file mode 100644 index 0000000..b364429 --- /dev/null +++ b/src/ui/components/CollectionFilters/CollectionFilters.tsx @@ -0,0 +1,280 @@ +import React from 'react'; + +import {LayoutRows} from '@gravity-ui/icons'; +import type {SegmentedRadioGroupSize as RadioButtonSize, SelectOption} from '@gravity-ui/uikit'; +import {Icon, SegmentedRadioGroup as RadioButton, Select, TextInput} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {I18n} from 'i18n'; +import debounce from 'lodash/debounce'; +import {CollectionFiltersQa, Feature} from 'shared'; +import type {GetStructureItemsMode} from 'shared/schema/us/types/collections'; +import type {OrderBasicField, OrderDirection} from 'shared/schema/us/types/sort'; +import {OrderBySelect, SORT_TYPE} from 'ui/components/OrderBySelect'; +import type {OrderBy, OrderByOptions, SortType} from 'ui/components/OrderBySelect'; +import Tabs from 'ui/components/Tabs/Tabs'; +import {DL} from 'ui/constants/common'; +import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; +import {MOBILE_SIZE} from 'ui/utils/mobile'; + +import GridIcon from 'assets/icons/collections/grid.svg'; + +import './CollectionFilters.scss'; + +const i18n = I18n.keyset('component.collection-filters'); + +const b = block('dl-collection-filters'); + +export const collectionPageViewModeStore = 'collectionPageViewMode'; + +export enum CollectionPageViewMode { + Grid = 'grid', + Table = 'table', +} + +export const SORT_TYPE_VALUES: OrderByOptions = { + [SORT_TYPE.FIRST_NEW]: { + field: 'createdAt', + direction: 'desc', + content: i18n('label_sort-first-new'), + }, + [SORT_TYPE.FIRST_OLD]: { + field: 'createdAt', + direction: 'asc', + content: i18n('label_sort-first-old'), + }, + [SORT_TYPE.ALPHABET_ASC]: { + field: 'title', + direction: 'asc', + content: i18n('label_sort-first-alphabet-asc'), + }, + [SORT_TYPE.ALPHABET_DESC]: { + field: 'title', + direction: 'desc', + content: i18n('label_sort-first-alphabet-desc'), + }, +}; + +export type StructureItemsFilters = { + filterString?: string; + orderField: OrderBasicField; + orderDirection: OrderDirection; + mode: GetStructureItemsMode; + onlyMy: boolean; +}; + +type Props = { + className?: string; + filters: StructureItemsFilters; + compactMode?: boolean; + controlSize?: RadioButtonSize; + viewMode?: CollectionPageViewMode; + onChange: (value: StructureItemsFilters) => void; + changeViewMode?: (value: CollectionPageViewMode) => void; + searchRowExtendContent?: React.ReactNode; + canFilterOnlyEntries?: boolean; +}; + +const onlyMyOptions = [ + {value: 'false', content: i18n('label_filter-by-ownership-all')}, + {value: 'true', content: i18n('label_filter-by-ownership-only-my')}, +]; + +export const CollectionFilters = React.memo( + ({ + className, + filters, + compactMode = false, + controlSize = 'm', + viewMode, + changeViewMode, + onChange, + canFilterOnlyEntries = false, + searchRowExtendContent, + }) => { + const {filterString, onlyMy, mode, orderField, orderDirection} = filters; + + const [innerFilterString, setInnerFilterString] = React.useState( + filterString || '', + ); + + const selectOptions: SelectOption[] = React.useMemo(() => { + const options = [ + { + value: 'all', + content: i18n('label_filter-by-type-all'), + }, + { + value: 'onlyWorkbooks', + content: i18n('label_filter-by-type-only-workbooks'), + }, + { + value: 'onlyCollections', + content: i18n('label_filter-by-type-only-collections'), + }, + ]; + + if (canFilterOnlyEntries) { + options.push({ + value: 'onlyEntries', + content: i18n('label_filter-by-type-only-entries'), + }); + } + return options; + }, [canFilterOnlyEntries]); + + const handleChangeFilters = React.useCallback( + (updatedValues: Partial) => + onChange({...filters, ...updatedValues}), + [filters, onChange], + ); + + const handleChangeFilterString = React.useMemo( + () => + debounce( + (newValue: string | undefined) => handleChangeFilters({filterString: newValue}), + 800, + ), + [handleChangeFilters], + ); + + const handleUpdateFilterString = React.useCallback( + (val) => { + setInnerFilterString(val); + handleChangeFilterString(val); + }, + [handleChangeFilterString], + ); + + const handleChangeMode = React.useCallback( + (value) => { + const val = value[0]; + handleChangeFilters({ + mode: val, + }); + }, + [handleChangeFilters], + ); + + const handleChangeOnlyMy = React.useCallback( + (value) => { + handleChangeFilters({ + onlyMy: value === 'true', + }); + }, + [handleChangeFilters], + ); + + const handleChangeViewMode = React.useCallback( + (value) => { + changeViewMode?.(value); + }, + [changeViewMode], + ); + + const handleChangeSort = React.useCallback( + ({field, direction}: OrderBy) => { + handleChangeFilters({ + orderField: field, + orderDirection: direction, + }); + }, + [handleChangeFilters], + ); + + React.useEffect(() => { + if (!filterString) { + setInnerFilterString(''); + } + }, [filterString]); + + const orderBy = React.useMemo(() => { + return { + field: orderField, + direction: orderDirection, + }; + }, [orderField, orderDirection]); + + if (DL.IS_MOBILE) { + const tabItems = onlyMyOptions.map(({value, content}) => ({ + id: value, + title: content, + })); + + return ( + + {isEnabledFeature(Feature.HideMultitenant) ? null : ( + + )} + + ); + } + + return ( +
+
+ + {searchRowExtendContent} +
+ +
+