ydb-embedded-ui 4.12.0 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. package/CHANGELOG.md +29 -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/components/Tablet/Tablet.scss +1 -16
  7. package/dist/components/Tablet/Tablet.tsx +5 -5
  8. package/dist/components/TabletIcon/TabletIcon.scss +17 -0
  9. package/dist/components/TabletIcon/TabletIcon.tsx +18 -0
  10. package/dist/containers/Header/Header.scss +2 -0
  11. package/dist/containers/Header/Header.tsx +2 -7
  12. package/dist/containers/Header/{breadcrumbs.ts → breadcrumbs.tsx} +19 -8
  13. package/dist/containers/Nodes/Nodes.tsx +53 -16
  14. package/dist/containers/Nodes/getNodesColumns.tsx +31 -13
  15. package/dist/containers/Tablet/Tablet.tsx +9 -3
  16. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +5 -2
  17. package/dist/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.scss +5 -0
  18. package/dist/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.tsx +81 -0
  19. package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.scss +5 -0
  20. package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.tsx +103 -0
  21. package/dist/containers/Tenant/Info/i18n/en.json +8 -0
  22. package/dist/containers/Tenant/Info/i18n/index.ts +11 -0
  23. package/dist/containers/Tenant/Info/i18n/ru.json +8 -0
  24. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.scss +4 -4
  25. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +10 -3
  26. package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.scss +8 -0
  27. package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.tsx +13 -1
  28. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +4 -6
  29. package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.scss +3 -1
  30. package/dist/containers/Tenant/Query/i18n/en.json +6 -4
  31. package/dist/containers/Tenant/Query/i18n/ru.json +6 -4
  32. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +12 -2
  33. package/dist/containers/Tenant/i18n/en.json +12 -2
  34. package/dist/containers/Tenant/i18n/ru.json +11 -1
  35. package/dist/containers/Tenant/utils/schemaActions.ts +76 -28
  36. package/dist/containers/Tenant/utils/schemaControls.tsx +69 -0
  37. package/dist/containers/UserSettings/i18n/en.json +3 -0
  38. package/dist/containers/UserSettings/i18n/ru.json +3 -0
  39. package/dist/containers/UserSettings/settings.ts +12 -1
  40. package/dist/index.js +7 -3
  41. package/dist/services/api.ts +24 -12
  42. package/dist/store/reducers/header/types.ts +2 -0
  43. package/dist/store/reducers/nodes/nodes.ts +23 -6
  44. package/dist/store/reducers/nodes/selectors.ts +2 -2
  45. package/dist/store/reducers/nodes/types.ts +15 -5
  46. package/dist/store/reducers/settings/settings.ts +5 -0
  47. package/dist/styles/constants.scss +3 -0
  48. package/dist/types/api/compute.ts +0 -12
  49. package/dist/types/api/nodes.ts +0 -12
  50. package/dist/utils/constants.ts +3 -0
  51. package/dist/utils/filters.ts +23 -0
  52. package/dist/utils/hooks/i18n/en.json +3 -0
  53. package/dist/utils/hooks/i18n/index.ts +11 -0
  54. package/dist/utils/hooks/i18n/ru.json +3 -0
  55. package/dist/utils/hooks/index.ts +4 -0
  56. package/dist/utils/hooks/useNodesRequestParams.ts +46 -0
  57. package/dist/utils/hooks/useQueryModes.ts +34 -0
  58. package/dist/utils/hooks/useTableSort.ts +37 -0
  59. package/dist/utils/nodes.ts +25 -0
  60. package/dist/utils/query.ts +5 -1
  61. package/package.json +2 -2
@@ -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 =
@@ -5,4 +5,12 @@
5
5
  margin-left: 10px;
6
6
 
7
7
  color: var(--yc-color-text-complementary);
8
+
9
+ &__item-with-popover {
10
+ white-space: nowrap;
11
+ }
12
+
13
+ &__popover {
14
+ max-width: 300px;
15
+ }
8
16
  }
@@ -1,6 +1,9 @@
1
1
  import block from 'bem-cn-lite';
2
2
 
3
3
  import {formatDurationToShortTimeFormat, parseUsToMs} from '../../../../utils/timeParsers';
4
+ import {LabelWithPopover} from '../../../../components/LabelWithPopover';
5
+
6
+ import i18n from '../i18n';
4
7
 
5
8
  import './QueryDuration.scss';
6
9
 
@@ -17,5 +20,14 @@ export const QueryDuration = ({duration}: QueryDurationProps) => {
17
20
 
18
21
  const parsedDuration = formatDurationToShortTimeFormat(parseUsToMs(duration), 1);
19
22
 
20
- return <span className={b()}>{parsedDuration}</span>;
23
+ return (
24
+ <span className={b()}>
25
+ <LabelWithPopover
26
+ className={b('item-with-popover')}
27
+ contentClassName={b('popover')}
28
+ text={parsedDuration}
29
+ popoverContent={i18n('query-duration.description')}
30
+ />
31
+ </span>
32
+ );
21
33
  };
@@ -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]);
@@ -67,6 +67,8 @@
67
67
  }
68
68
 
69
69
  &__popover {
70
- max-width: 340px;
70
+ max-width: 420px;
71
+
72
+ white-space: pre-wrap;
71
73
  }
72
74
  }
@@ -17,8 +17,10 @@
17
17
  "preview.not-available": "Preview is not available",
18
18
  "preview.close": "Close preview",
19
19
 
20
- "method-description.script": "For YQL-scripts combining DDL and DML. API call: schema.scripting",
21
- "method-description.scan": "Read-only queries, potentially reading a lot of data. API call: table.ExecuteScan",
22
- "method-description.data": "DML queries for changing and fetching data in serialization mode. API call: table.executeDataQuery",
23
- "method-description.query": "Any queries. An experimental API call supposed to replace all existing methods. API Call: query.ExecuteScript"
20
+ "method-description.script": "For YQL-scripts combining DDL and DML.\nAPI call: schema.scripting",
21
+ "method-description.scan": "Read-only queries, potentially reading a lot of data.\nAPI call: table.ExecuteScan",
22
+ "method-description.data": "DML queries for changing and fetching data in serialization mode.\nAPI call: table.executeDataQuery",
23
+ "method-description.query": "Any query. An experimental API call supposed to replace all existing methods.\nAPI Call: query.ExecuteScript",
24
+
25
+ "query-duration.description": "Duration of server-side query execution"
24
26
  }
@@ -17,8 +17,10 @@
17
17
  "preview.not-available": "Предпросмотр недоступен",
18
18
  "preview.close": "Закрыть предпросмотр",
19
19
 
20
- "method-description.script": "Для скриптов, совмещающих DDL и DML-конструкции. API call: schema.scripting",
21
- "method-description.scan": "Только читающие запросы, потенциально читающие много данных. API call: table.ExecuteScan",
22
- "method-description.data": "DML-запросы для изменения и выборки данных в режиме изоляции Serializable. API call: table.executeDataQuery",
23
- "method-description.query": "Любые запросы. Экспериментальный перспективный метод, который в будущем заменит все остальные. API call: query.ExecuteScript"
20
+ "method-description.script": "Для скриптов, совмещающих DDL и DML-конструкции.\nAPI call: schema.scripting",
21
+ "method-description.scan": "Только читающие запросы, потенциально читающие много данных.\nAPI call: table.ExecuteScan",
22
+ "method-description.data": "DML-запросы для изменения и выборки данных в режиме изоляции Serializable.\nAPI call: table.executeDataQuery",
23
+ "method-description.query": "Любые запросы. Экспериментальный перспективный метод, который в будущем заменит все остальные.\nAPI call: query.ExecuteScript",
24
+
25
+ "query-duration.description": "Время выполнения запроса на стороне сервера"
24
26
  }
@@ -3,11 +3,13 @@ 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';
12
+ import {getControls} from '../../utils/schemaControls';
11
13
 
12
14
  interface SchemaTreeProps {
13
15
  rootPath: string;
@@ -21,6 +23,8 @@ export function SchemaTree(props: SchemaTreeProps) {
21
23
 
22
24
  const dispatch = useDispatch();
23
25
 
26
+ const [_, setQueryMode] = useQueryModes();
27
+
24
28
  const fetchPath = (path: string) =>
25
29
  window.api
26
30
  .getSchema({path}, {concurrentId: `NavigationTree.getSchema|${path}`})
@@ -71,7 +75,13 @@ export function SchemaTree(props: SchemaTreeProps) {
71
75
  collapsed: false,
72
76
  }}
73
77
  fetchPath={fetchPath}
74
- getActions={getActions(dispatch, handleActivePathUpdate)}
78
+ getActions={getActions(dispatch, {
79
+ setActivePath: handleActivePathUpdate,
80
+ setQueryMode,
81
+ })}
82
+ renderAdditionalNodeElements={getControls(dispatch, {
83
+ setActivePath: handleActivePathUpdate,
84
+ })}
75
85
  activePath={currentPath}
76
86
  onActivePathUpdate={handleActivePathUpdate}
77
87
  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,8 +3,9 @@ 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
- import {setShowPreview} from '../../../store/reducers/schema/schema';
8
9
  import {setQueryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
9
10
  import {TENANT_QUERY_TABS_ID, TENANT_PAGES_IDS} from '../../../store/reducers/tenant/constants';
10
11
  import createToast from '../../../utils/createToast';
@@ -34,23 +35,63 @@ const upsertQueryTemplate = (path: string) => {
34
35
  VALUES ( );`;
35
36
  };
36
37
 
38
+ const dropExternalTableTemplate = (path: string) => {
39
+ return `DROP EXTERNAL TABLE \`${path}\`;`;
40
+ };
41
+
42
+ const createExternalTableTemplate = (path: string) => {
43
+ // Remove data source name from path
44
+ // to create table in the same folder with data source
45
+ const targetPath = path.split('/').slice(0, -1).join('/');
46
+
47
+ return `CREATE EXTERNAL TABLE \`${targetPath}/my_external_table\` (
48
+ column1 Int,
49
+ column2 Int
50
+ ) WITH (
51
+ DATA_SOURCE="${path}",
52
+ LOCATION="",
53
+ FORMAT="json_as_string",
54
+ \`file_pattern\`=""
55
+ );`;
56
+ };
57
+
58
+ interface ActionsAdditionalEffects {
59
+ setQueryMode: SetQueryModeIfAvailable;
60
+ setActivePath: (path: string) => void;
61
+ }
62
+
37
63
  const bindActions = (
38
64
  path: string,
39
65
  dispatch: Dispatch<any>,
40
- setActivePath: (path: string) => void,
66
+ additionalEffects: ActionsAdditionalEffects,
41
67
  ) => {
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
- };
68
+ const {setActivePath, setQueryMode} = additionalEffects;
69
+
70
+ const inputQuery =
71
+ (tmpl: (path: string) => string, mode?: QueryMode, setQueryModeErrorMessage?: string) =>
72
+ () => {
73
+ const isNewQueryModeSet = mode && setQueryMode(mode, setQueryModeErrorMessage);
74
+
75
+ if (!mode || isNewQueryModeSet) {
76
+ dispatch(changeUserInput({input: tmpl(path)}));
77
+ dispatch(setTenantPage(TENANT_PAGES_IDS.query));
78
+ dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
79
+ setActivePath(path);
80
+ }
81
+ };
48
82
 
49
83
  return {
50
- createTable: inputQuery(createTableTemplate),
51
- alterTable: inputQuery(alterTableTemplate),
84
+ createTable: inputQuery(createTableTemplate, 'script'),
85
+ alterTable: inputQuery(alterTableTemplate, 'script'),
52
86
  selectQuery: inputQuery(selectQueryTemplate),
53
87
  upsertQuery: inputQuery(upsertQueryTemplate),
88
+ createExternalTable: inputQuery(createExternalTableTemplate, 'script'),
89
+ dropExternalTable: inputQuery(dropExternalTableTemplate, 'script'),
90
+ selectQueryFromExternalTable: inputQuery(
91
+ selectQueryTemplate,
92
+ 'query',
93
+ i18n('actions.externalTableSelectUnavailable'),
94
+ ),
54
95
  copyPath: () => {
55
96
  try {
56
97
  copy(path);
@@ -67,39 +108,45 @@ const bindActions = (
67
108
  });
68
109
  }
69
110
  },
70
- openPreview: () => {
71
- dispatch(setShowPreview(true));
72
- dispatch(setTenantPage(TENANT_PAGES_IDS.query));
73
- dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
74
- setActivePath(path);
75
- },
76
111
  };
77
112
  };
78
113
 
79
114
  type ActionsSet = ReturnType<Required<NavigationTreeProps>['getActions']>;
80
115
 
81
116
  export const getActions =
82
- (dispatch: Dispatch<any>, setActivePath: (path: string) => void) =>
117
+ (dispatch: Dispatch<any>, additionalEffects: ActionsAdditionalEffects) =>
83
118
  (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};
119
+ const actions = bindActions(path, dispatch, additionalEffects);
120
+ const copyItem = {text: i18n('actions.copyPath'), action: actions.copyPath};
88
121
 
89
122
  const DIR_SET: ActionsSet = [
90
123
  [copyItem],
91
- [{text: 'Create table...', action: actions.createTable}],
124
+ [{text: i18n('actions.createTable'), action: actions.createTable}],
92
125
  ];
93
126
  const TABLE_SET: ActionsSet = [
94
- [openPreview, copyItem],
127
+ [copyItem],
95
128
  [
96
- {text: 'Alter table...', action: actions.alterTable},
97
- selectQuery,
98
- {text: 'Upsert query...', action: actions.upsertQuery},
129
+ {text: i18n('actions.alterTable'), action: actions.alterTable},
130
+ {text: i18n('actions.selectQuery'), action: actions.selectQuery},
131
+ {text: i18n('actions.upsertQuery'), action: actions.upsertQuery},
99
132
  ],
100
133
  ];
101
134
 
102
- const EXTERNAL_TABLE_SET = [[openPreview, copyItem], [selectQuery]];
135
+ const EXTERNAL_TABLE_SET = [
136
+ [copyItem],
137
+ [
138
+ {
139
+ text: i18n('actions.selectQuery'),
140
+ action: actions.selectQueryFromExternalTable,
141
+ },
142
+ ],
143
+ [{text: i18n('actions.dropTable'), action: actions.dropExternalTable}],
144
+ ];
145
+
146
+ const EXTERNAL_DATA_SOURCE_SET = [
147
+ [copyItem],
148
+ [{text: i18n('actions.createExternalTable'), action: actions.createExternalTable}],
149
+ ];
103
150
 
104
151
  const JUST_COPY: ActionsSet = [copyItem];
105
152
 
@@ -114,11 +161,12 @@ export const getActions =
114
161
 
115
162
  index_table: JUST_COPY,
116
163
  topic: JUST_COPY,
164
+ stream: JUST_COPY,
117
165
 
118
166
  index: JUST_COPY,
119
167
 
120
168
  external_table: EXTERNAL_TABLE_SET,
121
- external_data_source: JUST_COPY,
169
+ external_data_source: EXTERNAL_DATA_SOURCE_SET,
122
170
  };
123
171
 
124
172
  return nodeTypeToActions[type];