ydb-embedded-ui 4.12.0 → 4.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/components/InfoViewer/formatters/common.ts +4 -2
  3. package/dist/components/InfoViewer/i18n/en.json +4 -0
  4. package/dist/components/InfoViewer/i18n/index.ts +11 -0
  5. package/dist/components/InfoViewer/i18n/ru.json +4 -0
  6. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +5 -2
  7. package/dist/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.scss +5 -0
  8. package/dist/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.tsx +81 -0
  9. package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.scss +5 -0
  10. package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.tsx +103 -0
  11. package/dist/containers/Tenant/Info/i18n/en.json +8 -0
  12. package/dist/containers/Tenant/Info/i18n/index.ts +11 -0
  13. package/dist/containers/Tenant/Info/i18n/ru.json +8 -0
  14. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.scss +4 -4
  15. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +10 -3
  16. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +4 -6
  17. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +8 -2
  18. package/dist/containers/Tenant/i18n/en.json +12 -2
  19. package/dist/containers/Tenant/i18n/ru.json +11 -1
  20. package/dist/containers/Tenant/utils/schemaActions.ts +75 -20
  21. package/dist/index.js +7 -3
  22. package/dist/styles/constants.scss +3 -0
  23. package/dist/utils/hooks/i18n/en.json +3 -0
  24. package/dist/utils/hooks/i18n/index.ts +11 -0
  25. package/dist/utils/hooks/i18n/ru.json +3 -0
  26. package/dist/utils/hooks/index.ts +1 -0
  27. package/dist/utils/hooks/useQueryModes.ts +34 -0
  28. package/dist/utils/query.ts +5 -1
  29. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.13.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.12.0...v4.13.0) (2023-08-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * info and summary tabs for external objects ([#493](https://github.com/ydb-platform/ydb-embedded-ui/issues/493)) ([88d9041](https://github.com/ydb-platform/ydb-embedded-ui/commit/88d9041f080f13046aeaf55765609dbc13b87285))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **SchemaTree:** add actions to external objects ([#497](https://github.com/ydb-platform/ydb-embedded-ui/issues/497)) ([5029579](https://github.com/ydb-platform/ydb-embedded-ui/commit/5029579796dd5fb985005f39e9ef8daf142366d0))
14
+ * **SchemaTree:** set required query mode for tree actions ([#491](https://github.com/ydb-platform/ydb-embedded-ui/issues/491)) ([ccd1eda](https://github.com/ydb-platform/ydb-embedded-ui/commit/ccd1edac0d84357cd605c9d131c99890449d8bd8))
15
+
3
16
  ## [4.12.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.11.1...v4.12.0) (2023-08-02)
4
17
 
5
18
 
@@ -3,13 +3,15 @@ import {formatDateTime} from '../../../utils';
3
3
 
4
4
  import {createInfoFormatter} from '../utils';
5
5
 
6
+ import i18n from '../i18n';
7
+
6
8
  export const formatCommonItem = createInfoFormatter<TDirEntry>({
7
9
  values: {
8
10
  PathType: (value) => value?.substring('EPathType'.length),
9
11
  CreateStep: formatDateTime,
10
12
  },
11
13
  labels: {
12
- PathType: 'Type',
13
- CreateStep: 'Created',
14
+ PathType: i18n('common.type'),
15
+ CreateStep: i18n('common.created'),
14
16
  },
15
17
  });
@@ -0,0 +1,4 @@
1
+ {
2
+ "common.created": "Created",
3
+ "common.type": "Type"
4
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-components-info-viewer';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,4 @@
1
+ {
2
+ "common.created": "Создано",
3
+ "common.type": "Тип"
4
+ }
@@ -27,6 +27,9 @@ import {
27
27
  isPathTypeWithTopic,
28
28
  } from '../../utils/schema';
29
29
 
30
+ import {ExternalTableInfo} from '../../Info/ExternalTable/ExternalTable';
31
+ import {ExternalDataSourceInfo} from '../../Info/ExternalDataSource/ExternalDataSource';
32
+
30
33
  import {TopicInfo} from './TopicInfo';
31
34
  import {ChangefeedInfo} from './ChangefeedInfo';
32
35
  import {TableInfo} from './TableInfo';
@@ -124,8 +127,8 @@ function Overview({type, tenantName}: OverviewProps) {
124
127
  <ChangefeedInfo data={data} topic={additionalData?.[0]} />
125
128
  ),
126
129
  [EPathType.EPathTypePersQueueGroup]: () => <TopicInfo data={data} />,
127
- [EPathType.EPathTypeExternalTable]: undefined,
128
- [EPathType.EPathTypeExternalDataSource]: undefined,
130
+ [EPathType.EPathTypeExternalTable]: () => <ExternalTableInfo data={data} />,
131
+ [EPathType.EPathTypeExternalDataSource]: () => <ExternalDataSourceInfo data={data} />,
129
132
  };
130
133
 
131
134
  return (
@@ -0,0 +1,5 @@
1
+ .ydb-external-data-source-info {
2
+ &__location {
3
+ max-width: var(--tenant-object-info-max-value-width);
4
+ }
5
+ }
@@ -0,0 +1,81 @@
1
+ import block from 'bem-cn-lite';
2
+
3
+ import type {TEvDescribeSchemeResult} from '../../../../types/api/schema';
4
+ import {useTypedSelector} from '../../../../utils/hooks';
5
+
6
+ import {InfoViewer, InfoViewerItem} from '../../../../components/InfoViewer';
7
+ import {formatCommonItem} from '../../../../components/InfoViewer/formatters';
8
+ import EntityStatus from '../../../../components/EntityStatus/EntityStatus';
9
+ import {ResponseError} from '../../../../components/Errors/ResponseError';
10
+
11
+ import {getEntityName} from '../../utils';
12
+
13
+ import i18n from '../i18n';
14
+ import './ExternalDataSource.scss';
15
+
16
+ const b = block('ydb-external-data-source-info');
17
+
18
+ const prepareExternalDataSourceSummary = (data: TEvDescribeSchemeResult): InfoViewerItem[] => {
19
+ return [
20
+ {
21
+ label: i18n('external-objects.source-type'),
22
+ value: data.PathDescription?.ExternalDataSourceDescription?.SourceType,
23
+ },
24
+ formatCommonItem('CreateStep', data.PathDescription?.Self?.CreateStep),
25
+ ];
26
+ };
27
+
28
+ const prepareExternalDataSourceInfo = (data: TEvDescribeSchemeResult): InfoViewerItem[] => {
29
+ const {Location, Auth} = data.PathDescription?.ExternalDataSourceDescription || {};
30
+
31
+ return [
32
+ ...prepareExternalDataSourceSummary(data),
33
+ {
34
+ label: i18n('external-objects.location'),
35
+ value: (
36
+ <EntityStatus
37
+ name={Location}
38
+ showStatus={false}
39
+ hasClipboardButton
40
+ clipboardButtonAlwaysVisible
41
+ className={b('location')}
42
+ />
43
+ ),
44
+ },
45
+ {
46
+ label: i18n('external-objects.auth-method'),
47
+ value: Auth?.ServiceAccount
48
+ ? i18n('external-objects.auth-method.service-account')
49
+ : i18n('external-objects.auth-method.none'),
50
+ },
51
+ ];
52
+ };
53
+
54
+ interface ExternalDataSourceProps {
55
+ data?: TEvDescribeSchemeResult;
56
+ prepareData: (data: TEvDescribeSchemeResult) => InfoViewerItem[];
57
+ }
58
+
59
+ const ExternalDataSource = ({data, prepareData}: ExternalDataSourceProps) => {
60
+ const entityName = getEntityName(data?.PathDescription);
61
+
62
+ const {error: schemaError} = useTypedSelector((state) => state.schema);
63
+
64
+ if (schemaError) {
65
+ return <ResponseError error={schemaError} />;
66
+ }
67
+
68
+ if (!data) {
69
+ return <div className="error">No {entityName} data</div>;
70
+ }
71
+
72
+ return <InfoViewer title={entityName} info={prepareData(data)} />;
73
+ };
74
+
75
+ export const ExternalDataSourceInfo = ({data}: {data?: TEvDescribeSchemeResult}) => {
76
+ return <ExternalDataSource data={data} prepareData={prepareExternalDataSourceInfo} />;
77
+ };
78
+
79
+ export const ExternalDataSourceSummary = ({data}: {data?: TEvDescribeSchemeResult}) => {
80
+ return <ExternalDataSource data={data} prepareData={prepareExternalDataSourceSummary} />;
81
+ };
@@ -0,0 +1,5 @@
1
+ .ydb-external-table-info {
2
+ &__location {
3
+ max-width: var(--tenant-object-info-max-value-width);
4
+ }
5
+ }
@@ -0,0 +1,103 @@
1
+ import {useLocation} from 'react-router';
2
+ import block from 'bem-cn-lite';
3
+
4
+ import type {TEvDescribeSchemeResult} from '../../../../types/api/schema';
5
+ import {useTypedSelector} from '../../../../utils/hooks';
6
+ import {createHref, parseQuery} from '../../../../routes';
7
+ import {formatCommonItem} from '../../../../components/InfoViewer/formatters';
8
+ import {InfoViewer, InfoViewerItem} from '../../../../components/InfoViewer';
9
+ import {ExternalLinkWithIcon} from '../../../../components/ExternalLinkWithIcon/ExternalLinkWithIcon';
10
+ import EntityStatus from '../../../../components/EntityStatus/EntityStatus';
11
+ import {ResponseError} from '../../../../components/Errors/ResponseError';
12
+
13
+ import {getEntityName} from '../../utils';
14
+
15
+ import i18n from '../i18n';
16
+ import './ExternalTable.scss';
17
+
18
+ const b = block('ydb-external-table-info');
19
+
20
+ const prepareExternalTableSummary = (
21
+ data: TEvDescribeSchemeResult,
22
+ pathToDataSource: string,
23
+ ): InfoViewerItem[] => {
24
+ const {CreateStep} = data.PathDescription?.Self || {};
25
+ const {SourceType, DataSourcePath} = data.PathDescription?.ExternalTableDescription || {};
26
+
27
+ const dataSourceName = DataSourcePath?.split('/').pop();
28
+
29
+ return [
30
+ {label: i18n('external-objects.source-type'), value: SourceType},
31
+ formatCommonItem('CreateStep', CreateStep),
32
+ {
33
+ label: i18n('external-objects.data-source'),
34
+ value: DataSourcePath && (
35
+ <span title={DataSourcePath}>
36
+ <ExternalLinkWithIcon title={dataSourceName || ''} url={pathToDataSource} />
37
+ </span>
38
+ ),
39
+ },
40
+ ];
41
+ };
42
+
43
+ const prepareExternalTableInfo = (
44
+ data: TEvDescribeSchemeResult,
45
+ pathToDataSource: string,
46
+ ): InfoViewerItem[] => {
47
+ const location = data.PathDescription?.ExternalTableDescription?.Location;
48
+
49
+ return [
50
+ ...prepareExternalTableSummary(data, pathToDataSource),
51
+ {
52
+ label: i18n('external-objects.location'),
53
+ value: (
54
+ <EntityStatus
55
+ name={location}
56
+ showStatus={false}
57
+ hasClipboardButton
58
+ clipboardButtonAlwaysVisible
59
+ className={b('location')}
60
+ />
61
+ ),
62
+ },
63
+ ];
64
+ };
65
+
66
+ interface ExternalTableProps {
67
+ data?: TEvDescribeSchemeResult;
68
+ prepareData: (data: TEvDescribeSchemeResult, pathToDataSource: string) => InfoViewerItem[];
69
+ }
70
+
71
+ const ExternalTable = ({data, prepareData}: ExternalTableProps) => {
72
+ const location = useLocation();
73
+ const query = parseQuery(location);
74
+
75
+ // embedded version could be located in some folder (e.g. host/some_folder/app_router_path)
76
+ // window.location has the full pathname, while location from router ignores path to project
77
+ const pathToDataSource = createHref(window.location.pathname, undefined, {
78
+ ...query,
79
+ schema: data?.PathDescription?.ExternalTableDescription?.DataSourcePath,
80
+ });
81
+
82
+ const entityName = getEntityName(data?.PathDescription);
83
+
84
+ const {error: schemaError} = useTypedSelector((state) => state.schema);
85
+
86
+ if (schemaError) {
87
+ return <ResponseError error={schemaError} />;
88
+ }
89
+
90
+ if (!data) {
91
+ return <div className="error">No {entityName} data</div>;
92
+ }
93
+
94
+ return <InfoViewer title={entityName} info={prepareData(data, pathToDataSource)} />;
95
+ };
96
+
97
+ export const ExternalTableInfo = ({data}: {data?: TEvDescribeSchemeResult}) => {
98
+ return <ExternalTable data={data} prepareData={prepareExternalTableInfo} />;
99
+ };
100
+
101
+ export const ExternalTableSummary = ({data}: {data?: TEvDescribeSchemeResult}) => {
102
+ return <ExternalTable data={data} prepareData={prepareExternalTableSummary} />;
103
+ };
@@ -0,0 +1,8 @@
1
+ {
2
+ "external-objects.source-type": "Source Type",
3
+ "external-objects.data-source": "Data Source",
4
+ "external-objects.location": "Location",
5
+ "external-objects.auth-method": "Auth Method",
6
+ "external-objects.auth-method.none": "None",
7
+ "external-objects.auth-method.service-account": "Service Account"
8
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-tenant-objects-info';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,8 @@
1
+ {
2
+ "external-objects.source-type": "Тип источника",
3
+ "external-objects.data-source": "Источник",
4
+ "external-objects.location": "Расположение",
5
+ "external-objects.auth-method": "Авторизация",
6
+ "external-objects.auth-method.none": "Нет",
7
+ "external-objects.auth-method.service-account": "Сервисный аккаунт"
8
+ }
@@ -86,6 +86,10 @@
86
86
  &__info-controls {
87
87
  display: flex;
88
88
  gap: 4px;
89
+
90
+ .yc-button__text {
91
+ margin: 0 6px;
92
+ }
89
93
  }
90
94
 
91
95
  &__info-action-button {
@@ -148,8 +152,4 @@
148
152
  background-color: transparent;
149
153
  }
150
154
  }
151
-
152
- .yc-button__text {
153
- margin: 0 6px;
154
- }
155
155
  }
@@ -41,6 +41,8 @@ import {
41
41
  import {SchemaTree} from '../Schema/SchemaTree/SchemaTree';
42
42
  import {SchemaViewer} from '../Schema/SchemaViewer/SchemaViewer';
43
43
  import {Acl} from '../Acl/Acl';
44
+ import {ExternalTableSummary} from '../Info/ExternalTable/ExternalTable';
45
+ import {ExternalDataSourceSummary} from '../Info/ExternalDataSource/ExternalDataSource';
44
46
 
45
47
  import {TenantTabsGroups, TENANT_INFO_TABS, TENANT_SCHEMA_TAB} from '../TenantPages';
46
48
  import {
@@ -50,9 +52,10 @@ import {
50
52
  } from '../utils/paneVisibilityToggleHelpers';
51
53
  import {isColumnEntityType, isExternalTable, isIndexTable, isTableType} from '../utils/schema';
52
54
 
53
- import './ObjectSummary.scss';
54
55
  import i18n from '../i18n';
55
56
 
57
+ import './ObjectSummary.scss';
58
+
56
59
  const b = cn('object-summary');
57
60
 
58
61
  const getInitialIsSummaryCollapsed = () => {
@@ -197,8 +200,12 @@ export function ObjectSummary({
197
200
  [EPathType.EPathTypePersQueueGroup]: () => (
198
201
  <PersQueueGroupOverview data={currentObjectData} />
199
202
  ),
200
- [EPathType.EPathTypeExternalTable]: undefined,
201
- [EPathType.EPathTypeExternalDataSource]: undefined,
203
+ [EPathType.EPathTypeExternalTable]: () => (
204
+ <ExternalTableSummary data={currentObjectData} />
205
+ ),
206
+ [EPathType.EPathTypeExternalDataSource]: () => (
207
+ <ExternalDataSourceSummary data={currentObjectData} />
208
+ ),
202
209
  };
203
210
 
204
211
  let component =
@@ -24,12 +24,11 @@ import {
24
24
  DEFAULT_IS_QUERY_RESULT_COLLAPSED,
25
25
  DEFAULT_SIZE_RESULT_PANE_KEY,
26
26
  SAVED_QUERIES_KEY,
27
- QUERY_INITIAL_MODE_KEY,
28
27
  ENABLE_ADDITIONAL_QUERY_MODES,
29
28
  LAST_USED_QUERY_ACTION_KEY,
30
29
  } from '../../../../utils/constants';
31
- import {useSetting} from '../../../../utils/hooks';
32
- import {QUERY_ACTIONS, QUERY_MODES} from '../../../../utils/query';
30
+ import {useSetting, useQueryModes} from '../../../../utils/hooks';
31
+ import {QUERY_ACTIONS, QUERY_MODES, isNewQueryMode} from '../../../../utils/query';
33
32
 
34
33
  import {
35
34
  PaneVisibilityActionTypes,
@@ -91,13 +90,12 @@ function QueryEditor(props) {
91
90
  const [resultType, setResultType] = useState(RESULT_TYPES.EXECUTE);
92
91
 
93
92
  const [isResultLoaded, setIsResultLoaded] = useState(false);
94
- const [queryMode, setQueryMode] = useSetting(QUERY_INITIAL_MODE_KEY);
93
+ const [queryMode, setQueryMode] = useQueryModes();
95
94
  const [enableAdditionalQueryModes] = useSetting(ENABLE_ADDITIONAL_QUERY_MODES);
96
95
  const [lastUsedQueryAction, setLastUsedQueryAction] = useSetting(LAST_USED_QUERY_ACTION_KEY);
97
96
 
98
97
  useEffect(() => {
99
- const isNewQueryMode = queryMode !== QUERY_MODES.script && queryMode !== QUERY_MODES.scan;
100
- if (!enableAdditionalQueryModes && isNewQueryMode) {
98
+ if (isNewQueryMode(queryMode) && !enableAdditionalQueryModes) {
101
99
  setQueryMode(QUERY_MODES.script);
102
100
  }
103
101
  }, [enableAdditionalQueryModes, queryMode, setQueryMode]);
@@ -3,8 +3,9 @@ import {useDispatch} from 'react-redux';
3
3
 
4
4
  import {NavigationTree} from 'ydb-ui-components';
5
5
 
6
- import {setCurrentSchemaPath, preloadSchemas} from '../../../../store/reducers/schema/schema';
7
6
  import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
7
+ import {setCurrentSchemaPath, preloadSchemas} from '../../../../store/reducers/schema/schema';
8
+ import {useQueryModes} from '../../../../utils/hooks';
8
9
 
9
10
  import {isChildlessPathType, mapPathTypeToNavigationTreeType} from '../../utils/schema';
10
11
  import {getActions} from '../../utils/schemaActions';
@@ -21,6 +22,8 @@ export function SchemaTree(props: SchemaTreeProps) {
21
22
 
22
23
  const dispatch = useDispatch();
23
24
 
25
+ const [_, setQueryMode] = useQueryModes();
26
+
24
27
  const fetchPath = (path: string) =>
25
28
  window.api
26
29
  .getSchema({path}, {concurrentId: `NavigationTree.getSchema|${path}`})
@@ -71,7 +74,10 @@ export function SchemaTree(props: SchemaTreeProps) {
71
74
  collapsed: false,
72
75
  }}
73
76
  fetchPath={fetchPath}
74
- getActions={getActions(dispatch, handleActivePathUpdate)}
77
+ getActions={getActions(dispatch, {
78
+ setActivePath: handleActivePathUpdate,
79
+ setQueryMode,
80
+ })}
75
81
  activePath={currentPath}
76
82
  onActivePathUpdate={handleActivePathUpdate}
77
83
  cache={false}
@@ -6,6 +6,16 @@
6
6
  "summary.showPreview": "Show preview",
7
7
  "summary.copySchemaPath": "Copy schema path",
8
8
 
9
- "actions.copied" : "The path is copied to the clipboard",
10
- "actions.notCopied" : "Couldn’t copy the path"
9
+ "actions.copied": "The path is copied to the clipboard",
10
+ "actions.notCopied": "Couldn’t copy the path",
11
+ "actions.externalTableSelectUnavailable": "Select query for external tables available only with 'YQL - QueryService' query mode. You need to turn in additional query modes in settings to enable it",
12
+
13
+ "actions.copyPath": "Copy path",
14
+ "actions.openPreview": "Open preview",
15
+ "actions.createTable": "Create table...",
16
+ "actions.createExternalTable": "Create external table...",
17
+ "actions.dropTable": "Drop table...",
18
+ "actions.alterTable": "Alter table...",
19
+ "actions.selectQuery": "Select query...",
20
+ "actions.upsertQuery": "Upsert query..."
11
21
  }
@@ -7,5 +7,15 @@
7
7
  "summary.copySchemaPath": "Скопировать путь",
8
8
 
9
9
  "actions.copied": "Путь успешно скопирован",
10
- "actions.notCopied": "Не получилось скопировать путь"
10
+ "actions.notCopied": "Не получилось скопировать путь",
11
+ "actions.externalTableSelectUnavailable": "Select запрос для внешних таблиц доступен только в режиме 'YQL - QueryService'. Вам необходимо включить дополнительные режимы выполнения запросов в настройках",
12
+
13
+ "actions.copyPath": "Скопировать путь",
14
+ "actions.openPreview": "Открыть превью",
15
+ "actions.createTable": "Создать таблицу...",
16
+ "actions.createExternalTable": "Создать внешнюю таблицу...",
17
+ "actions.dropTable": "Удалить таблицу...",
18
+ "actions.alterTable": "Изменить таблицу...",
19
+ "actions.selectQuery": "Select запрос...",
20
+ "actions.upsertQuery": "Upsert запрос..."
11
21
  }
@@ -3,6 +3,8 @@ import copy from 'copy-to-clipboard';
3
3
 
4
4
  import type {NavigationTreeNodeType, NavigationTreeProps} from 'ydb-ui-components';
5
5
 
6
+ import type {QueryMode} from '../../../types/store/query';
7
+ import type {SetQueryModeIfAvailable} from '../../../utils/hooks';
6
8
  import {changeUserInput} from '../../../store/reducers/executeQuery';
7
9
  import {setShowPreview} from '../../../store/reducers/schema/schema';
8
10
  import {setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
@@ -34,23 +36,63 @@ const upsertQueryTemplate = (path: string) => {
34
36
  VALUES ( );`;
35
37
  };
36
38
 
39
+ const dropExternalTableTemplate = (path: string) => {
40
+ return `DROP EXTERNAL TABLE \`${path}\`;`;
41
+ };
42
+
43
+ const createExternalTableTemplate = (path: string) => {
44
+ // Remove data source name from path
45
+ // to create table in the same folder with data source
46
+ const targetPath = path.split('/').slice(0, -1).join('/');
47
+
48
+ return `CREATE EXTERNAL TABLE \`${targetPath}/my_external_table\` (
49
+ column1 Int,
50
+ column2 Int
51
+ ) WITH (
52
+ DATA_SOURCE="${path}",
53
+ LOCATION="",
54
+ FORMAT="json_as_string",
55
+ \`file_pattern\`=""
56
+ );`;
57
+ };
58
+
59
+ interface ActionsAdditionalEffects {
60
+ setQueryMode: SetQueryModeIfAvailable;
61
+ setActivePath: (path: string) => void;
62
+ }
63
+
37
64
  const bindActions = (
38
65
  path: string,
39
66
  dispatch: Dispatch<any>,
40
- setActivePath: (path: string) => void,
67
+ additionalEffects: ActionsAdditionalEffects,
41
68
  ) => {
42
- const inputQuery = (tmpl: (path: string) => string) => () => {
43
- dispatch(changeUserInput({input: tmpl(path)}));
44
- dispatch(setTenantPage(TENANT_PAGES_IDS.query));
45
- dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
46
- setActivePath(path);
47
- };
69
+ const {setActivePath, setQueryMode} = additionalEffects;
70
+
71
+ const inputQuery =
72
+ (tmpl: (path: string) => string, mode?: QueryMode, setQueryModeErrorMessage?: string) =>
73
+ () => {
74
+ const isNewQueryModeSet = mode && setQueryMode(mode, setQueryModeErrorMessage);
75
+
76
+ if (!mode || isNewQueryModeSet) {
77
+ dispatch(changeUserInput({input: tmpl(path)}));
78
+ dispatch(setTenantPage(TENANT_PAGES_IDS.query));
79
+ dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
80
+ setActivePath(path);
81
+ }
82
+ };
48
83
 
49
84
  return {
50
- createTable: inputQuery(createTableTemplate),
51
- alterTable: inputQuery(alterTableTemplate),
85
+ createTable: inputQuery(createTableTemplate, 'script'),
86
+ alterTable: inputQuery(alterTableTemplate, 'script'),
52
87
  selectQuery: inputQuery(selectQueryTemplate),
53
88
  upsertQuery: inputQuery(upsertQueryTemplate),
89
+ createExternalTable: inputQuery(createExternalTableTemplate, 'script'),
90
+ dropExternalTable: inputQuery(dropExternalTableTemplate, 'script'),
91
+ selectQueryFromExternalTable: inputQuery(
92
+ selectQueryTemplate,
93
+ 'query',
94
+ i18n('actions.externalTableSelectUnavailable'),
95
+ ),
54
96
  copyPath: () => {
55
97
  try {
56
98
  copy(path);
@@ -79,27 +121,40 @@ const bindActions = (
79
121
  type ActionsSet = ReturnType<Required<NavigationTreeProps>['getActions']>;
80
122
 
81
123
  export const getActions =
82
- (dispatch: Dispatch<any>, setActivePath: (path: string) => void) =>
124
+ (dispatch: Dispatch<any>, additionalEffects: ActionsAdditionalEffects) =>
83
125
  (path: string, type: NavigationTreeNodeType) => {
84
- const actions = bindActions(path, dispatch, setActivePath);
85
- const copyItem = {text: 'Copy path', action: actions.copyPath};
86
- const openPreview = {text: 'Open preview', action: actions.openPreview};
87
- const selectQuery = {text: 'Select query...', action: actions.selectQuery};
126
+ const actions = bindActions(path, dispatch, additionalEffects);
127
+ const copyItem = {text: i18n('actions.copyPath'), action: actions.copyPath};
128
+ const openPreview = {text: i18n('actions.openPreview'), action: actions.openPreview};
88
129
 
89
130
  const DIR_SET: ActionsSet = [
90
131
  [copyItem],
91
- [{text: 'Create table...', action: actions.createTable}],
132
+ [{text: i18n('actions.createTable'), action: actions.createTable}],
92
133
  ];
93
134
  const TABLE_SET: ActionsSet = [
94
135
  [openPreview, copyItem],
95
136
  [
96
- {text: 'Alter table...', action: actions.alterTable},
97
- selectQuery,
98
- {text: 'Upsert query...', action: actions.upsertQuery},
137
+ {text: i18n('actions.alterTable'), action: actions.alterTable},
138
+ {text: i18n('actions.selectQuery'), action: actions.selectQuery},
139
+ {text: i18n('actions.upsertQuery'), action: actions.upsertQuery},
140
+ ],
141
+ ];
142
+
143
+ const EXTERNAL_TABLE_SET = [
144
+ [openPreview, copyItem],
145
+ [
146
+ {
147
+ text: i18n('actions.selectQuery'),
148
+ action: actions.selectQueryFromExternalTable,
149
+ },
99
150
  ],
151
+ [{text: i18n('actions.dropTable'), action: actions.dropExternalTable}],
100
152
  ];
101
153
 
102
- const EXTERNAL_TABLE_SET = [[openPreview, copyItem], [selectQuery]];
154
+ const EXTERNAL_DATA_SOURCE_SET = [
155
+ [copyItem],
156
+ [{text: i18n('actions.createExternalTable'), action: actions.createExternalTable}],
157
+ ];
103
158
 
104
159
  const JUST_COPY: ActionsSet = [copyItem];
105
160
 
@@ -118,7 +173,7 @@ export const getActions =
118
173
  index: JUST_COPY,
119
174
 
120
175
  external_table: EXTERNAL_TABLE_SET,
121
- external_data_source: JUST_COPY,
176
+ external_data_source: EXTERNAL_DATA_SOURCE_SET,
122
177
  };
123
178
 
124
179
  return nodeTypeToActions[type];
package/dist/index.js CHANGED
@@ -1,13 +1,17 @@
1
1
  import React from 'react';
2
2
  import ReactDOM from 'react-dom';
3
- import './index.css';
4
- import App from './containers/App/App';
5
3
  import {Provider} from 'react-redux';
4
+
5
+ import '@gravity-ui/uikit/styles/styles.scss';
6
+
7
+ import App from './containers/App/App';
6
8
  import configureStore from './store';
7
9
  import reportWebVitals from './reportWebVitals';
8
- import '@gravity-ui/uikit/styles/styles.scss';
9
10
  import HistoryContext from './contexts/HistoryContext';
10
11
 
12
+ import './styles/constants.scss';
13
+ import './index.css';
14
+
11
15
  const {store, history} = configureStore();
12
16
  window.store = store;
13
17
 
@@ -0,0 +1,3 @@
1
+ :root {
2
+ --tenant-object-info-max-value-width: 300px;
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "useQueryModes.queryModeCannotBeSet": "Query mode \"{{mode}}\" cannot be set. You need to turn in additional query modes in settings to enable it"
3
+ }
@@ -0,0 +1,11 @@
1
+ import {i18n, Lang} from '../../../utils/i18n';
2
+
3
+ import en from './en.json';
4
+ import ru from './ru.json';
5
+
6
+ const COMPONENT = 'ydb-hooks';
7
+
8
+ i18n.registerKeyset(Lang.En, COMPONENT, en);
9
+ i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10
+
11
+ export default i18n.keyset(COMPONENT);
@@ -0,0 +1,3 @@
1
+ {
2
+ "useQueryModes.queryModeCannotBeSet": "Режим выполнения запроса \"{{mode}}\" недоступен. Вам необходимо включить дополнительные режимы выполнения запросов в настройках"
3
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './useAutofetcher';
2
2
  export * from './useTypedSelector';
3
3
  export * from './useSetting';
4
+ export * from './useQueryModes';
@@ -0,0 +1,34 @@
1
+ import type {QueryMode} from '../../types/store/query';
2
+ import {ENABLE_ADDITIONAL_QUERY_MODES, QUERY_INITIAL_MODE_KEY} from '../constants';
3
+ import {isNewQueryMode} from '../query';
4
+ import createToast from '../createToast';
5
+ import {useSetting} from './useSetting';
6
+ import i18n from './i18n';
7
+
8
+ export type SetQueryModeIfAvailable = (
9
+ value: QueryMode,
10
+ errorMessage?: string | undefined,
11
+ ) => boolean;
12
+
13
+ export const useQueryModes = (): [QueryMode, SetQueryModeIfAvailable] => {
14
+ const [queryMode, setQueryMode] = useSetting<QueryMode>(QUERY_INITIAL_MODE_KEY);
15
+ const [enableAdditionalQueryModes] = useSetting<boolean>(ENABLE_ADDITIONAL_QUERY_MODES);
16
+
17
+ const setQueryModeIfAvailable: SetQueryModeIfAvailable = (value, errorMessage) => {
18
+ if (isNewQueryMode(value) && !enableAdditionalQueryModes) {
19
+ createToast({
20
+ name: 'QueryModeCannotBeSet',
21
+ title: errorMessage ?? i18n('useQueryModes.queryModeCannotBeSet', {mode: value}),
22
+ type: 'error',
23
+ });
24
+
25
+ return false;
26
+ } else {
27
+ setQueryMode(value);
28
+
29
+ return true;
30
+ }
31
+ };
32
+
33
+ return [queryMode, setQueryModeIfAvailable];
34
+ };
@@ -7,7 +7,7 @@ import type {
7
7
  QueryPlan,
8
8
  ScriptPlan,
9
9
  } from '../types/api/query';
10
- import type {IQueryResult, QueryErrorResponse} from '../types/store/query';
10
+ import type {IQueryResult, QueryErrorResponse, QueryMode} from '../types/store/query';
11
11
 
12
12
  export const QUERY_ACTIONS = {
13
13
  execute: 'execute',
@@ -21,6 +21,10 @@ export const QUERY_MODES = {
21
21
  query: 'query',
22
22
  } as const;
23
23
 
24
+ export const isNewQueryMode = (value: QueryMode) => {
25
+ return value !== QUERY_MODES.script && value !== QUERY_MODES.scan;
26
+ };
27
+
24
28
  // eslint-disable-next-line complexity
25
29
  export const getColumnType = (type: string) => {
26
30
  switch (type.replace(/\?$/, '')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "4.12.0",
3
+ "version": "4.13.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -40,7 +40,7 @@
40
40
  "reselect": "4.1.6",
41
41
  "sass": "1.32.8",
42
42
  "web-vitals": "1.1.2",
43
- "ydb-ui-components": "^3.2.1"
43
+ "ydb-ui-components": "^3.2.2"
44
44
  },
45
45
  "scripts": {
46
46
  "start": "react-app-rewired start",