ydb-embedded-ui 4.11.1 → 4.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. package/CHANGELOG.md +26 -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/Search/Search.scss +3 -0
  7. package/dist/components/Search/Search.tsx +6 -1
  8. package/dist/components/Tablet/Tablet.scss +1 -1
  9. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +5 -2
  10. package/dist/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.scss +5 -0
  11. package/dist/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.tsx +81 -0
  12. package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.scss +5 -0
  13. package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.tsx +103 -0
  14. package/dist/containers/Tenant/Info/i18n/en.json +8 -0
  15. package/dist/containers/Tenant/Info/i18n/index.ts +11 -0
  16. package/dist/containers/Tenant/Info/i18n/ru.json +8 -0
  17. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.scss +4 -4
  18. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +10 -3
  19. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +4 -6
  20. package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.scss +13 -0
  21. package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx +40 -14
  22. package/dist/containers/Tenant/Query/i18n/en.json +6 -1
  23. package/dist/containers/Tenant/Query/i18n/ru.json +6 -1
  24. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +8 -2
  25. package/dist/containers/Tenant/i18n/en.json +12 -2
  26. package/dist/containers/Tenant/i18n/ru.json +11 -1
  27. package/dist/containers/Tenant/utils/schemaActions.ts +75 -20
  28. package/dist/index.js +7 -3
  29. package/dist/styles/constants.scss +3 -0
  30. package/dist/utils/constants.ts +14 -11
  31. package/dist/utils/hooks/i18n/en.json +3 -0
  32. package/dist/utils/hooks/i18n/index.ts +11 -0
  33. package/dist/utils/hooks/i18n/ru.json +3 -0
  34. package/dist/utils/hooks/index.ts +1 -0
  35. package/dist/utils/hooks/useQueryModes.ts +34 -0
  36. package/dist/utils/query.ts +5 -1
  37. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
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
+
16
+ ## [4.12.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.11.1...v4.12.0) (2023-08-02)
17
+
18
+
19
+ ### Features
20
+
21
+ * **Query:** add explanation to the query method selector ([#492](https://github.com/ydb-platform/ydb-embedded-ui/issues/492)) ([ce6407c](https://github.com/ydb-platform/ydb-embedded-ui/commit/ce6407c254e9498d5b3bce60298905ea72621766))
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * fix tablet size ([#490](https://github.com/ydb-platform/ydb-embedded-ui/issues/490)) ([5a9b9d9](https://github.com/ydb-platform/ydb-embedded-ui/commit/5a9b9d955a882b1191502f5bac8eff5cf8638a52))
27
+ * **Search:** add minimum width to Search ([#494](https://github.com/ydb-platform/ydb-embedded-ui/issues/494)) ([2add1dc](https://github.com/ydb-platform/ydb-embedded-ui/commit/2add1dcb3c8a76297ab35600e6d8a8772a411b1d))
28
+
3
29
  ## [4.11.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.11.0...v4.11.1) (2023-07-27)
4
30
 
5
31
 
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ .ydb-search {
2
+ min-width: 100px;
3
+ }
@@ -1,7 +1,12 @@
1
1
  import {useRef, useEffect, useState} from 'react';
2
+ import cn from 'bem-cn-lite';
2
3
 
3
4
  import {TextInput} from '@gravity-ui/uikit';
4
5
 
6
+ import './Search.scss';
7
+
8
+ const b = cn('ydb-search');
9
+
5
10
  interface SearchProps {
6
11
  onChange: (value: string) => void;
7
12
  value?: string;
@@ -44,7 +49,7 @@ export const Search = ({
44
49
  <TextInput
45
50
  hasClear
46
51
  autoFocus
47
- className={className}
52
+ className={b(null, className)}
48
53
  placeholder={placeholder}
49
54
  value={searchValue}
50
55
  onUpdate={onSearchValueChange}
@@ -2,7 +2,7 @@
2
2
  display: flex;
3
3
  justify-content: center;
4
4
 
5
- width: 18px;
5
+ width: 23px;
6
6
  height: 18px;
7
7
 
8
8
  font-size: 10px;
@@ -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]);
@@ -56,4 +56,17 @@
56
56
  }
57
57
  }
58
58
  }
59
+
60
+ &__item-with-popover {
61
+ display: flex;
62
+ align-items: center;
63
+
64
+ height: 24px;
65
+
66
+ line-height: initial;
67
+ }
68
+
69
+ &__popover {
70
+ max-width: 340px;
71
+ }
59
72
  }
@@ -6,6 +6,7 @@ import {useMemo} from 'react';
6
6
  import type {QueryAction, QueryMode} from '../../../../types/store/query';
7
7
  import {QUERY_MODES} from '../../../../utils/query';
8
8
  import {Icon} from '../../../../components/Icon';
9
+ import {LabelWithPopover} from '../../../../components/LabelWithPopover';
9
10
 
10
11
  import SaveQuery from '../SaveQuery/SaveQuery';
11
12
 
@@ -18,16 +19,34 @@ const queryModeSelectorPopupQa = 'query-mode-selector-popup';
18
19
 
19
20
  const b = block('ydb-query-editor-controls');
20
21
 
21
- const OldQueryModeSelectorTitles = {
22
- [QUERY_MODES.script]: 'YQL Script',
23
- [QUERY_MODES.scan]: 'Scan',
22
+ const OldQueryModeSelectorOptions = {
23
+ [QUERY_MODES.script]: {
24
+ title: 'YQL Script',
25
+ description: i18n('method-description.script'),
26
+ },
27
+ [QUERY_MODES.scan]: {
28
+ title: 'Scan',
29
+ description: i18n('method-description.scan'),
30
+ },
24
31
  } as const;
25
32
 
26
- const QueryModeSelectorTitles = {
27
- [QUERY_MODES.script]: 'YQL Script',
28
- [QUERY_MODES.scan]: 'Scan',
29
- [QUERY_MODES.data]: 'Data',
30
- [QUERY_MODES.query]: 'YQL - QueryService',
33
+ const QueryModeSelectorOptions = {
34
+ [QUERY_MODES.script]: {
35
+ title: 'YQL Script',
36
+ description: i18n('method-description.script'),
37
+ },
38
+ [QUERY_MODES.scan]: {
39
+ title: 'Scan',
40
+ description: i18n('method-description.scan'),
41
+ },
42
+ [QUERY_MODES.data]: {
43
+ title: 'Data',
44
+ description: i18n('method-description.data'),
45
+ },
46
+ [QUERY_MODES.query]: {
47
+ title: 'YQL - QueryService',
48
+ description: i18n('method-description.query'),
49
+ },
31
50
  } as const;
32
51
 
33
52
  interface QueryEditorControlsProps {
@@ -58,13 +77,20 @@ export const QueryEditorControls = ({
58
77
  enableAdditionalQueryModes,
59
78
  }: QueryEditorControlsProps) => {
60
79
  const querySelectorMenuItems = useMemo(() => {
61
- const titles = enableAdditionalQueryModes
62
- ? QueryModeSelectorTitles
63
- : OldQueryModeSelectorTitles;
80
+ const options = enableAdditionalQueryModes
81
+ ? QueryModeSelectorOptions
82
+ : OldQueryModeSelectorOptions;
64
83
 
65
- return Object.entries(titles).map(([mode, title]) => {
84
+ return Object.entries(options).map(([mode, {title, description}]) => {
66
85
  return {
67
- text: title,
86
+ text: (
87
+ <LabelWithPopover
88
+ className={b('item-with-popover')}
89
+ contentClassName={b('popover')}
90
+ text={title}
91
+ popoverContent={description}
92
+ />
93
+ ),
68
94
  action: () => {
69
95
  onUpdateQueryMode(mode as QueryMode);
70
96
  },
@@ -119,7 +145,7 @@ export const QueryEditorControls = ({
119
145
  <Button className={b('mode-selector__button')} qa={queryModeSelectorQa}>
120
146
  <span className={b('mode-selector__button-content')}>
121
147
  {`${i18n('controls.query-mode-selector_type')} ${
122
- QueryModeSelectorTitles[queryMode]
148
+ QueryModeSelectorOptions[queryMode].title
123
149
  }`}
124
150
  <Icon name="chevron-down" width={16} height={16} />
125
151
  </span>
@@ -15,5 +15,10 @@
15
15
 
16
16
  "preview.title": "Preview",
17
17
  "preview.not-available": "Preview is not available",
18
- "preview.close": "Close preview"
18
+ "preview.close": "Close preview",
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"
19
24
  }
@@ -15,5 +15,10 @@
15
15
 
16
16
  "preview.title": "Предпросмотр",
17
17
  "preview.not-available": "Предпросмотр недоступен",
18
- "preview.close": "Закрыть предпросмотр"
18
+ "preview.close": "Закрыть предпросмотр",
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"
19
24
  }
@@ -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
+ }
@@ -1,5 +1,7 @@
1
1
  import DataTable from '@gravity-ui/react-data-table';
2
2
 
3
+ import {EType} from '../types/api/tablet';
4
+
3
5
  const SECOND = 1000;
4
6
 
5
7
  export const AUTO_RELOAD_INTERVAL = 10 * SECOND;
@@ -40,17 +42,18 @@ export const TABLET_COLORS = {
40
42
  };
41
43
 
42
44
  export const TABLET_SYMBOLS = {
43
- OldTxProxy: 'P',
44
- TxProxy: 'P',
45
- BSController: 'BS',
46
- Dummy: 'DY',
47
- RTMRPartition: 'RP',
48
- PersQueueReadBalancer: 'PB',
49
- Cms: 'CM',
50
- BlockStorePartition: 'BP',
51
- BlockStoreVolume: 'BV',
52
- Console: 'CN',
53
- TenantSlotBroker: 'TB',
45
+ [EType.OldTxProxy]: 'P',
46
+ [EType.TxProxy]: 'P',
47
+ [EType.BSController]: 'BS',
48
+ [EType.Dummy]: 'DY',
49
+ [EType.RTMRPartition]: 'RP',
50
+ [EType.PersQueueReadBalancer]: 'PB',
51
+ [EType.Cms]: 'CM',
52
+ [EType.BlockStorePartition]: 'BP',
53
+ [EType.BlockStoreVolume]: 'BV',
54
+ [EType.Console]: 'CN',
55
+ [EType.TenantSlotBroker]: 'TB',
56
+ [EType.BlockStoreDiskRegistry]: 'BDR',
54
57
  };
55
58
 
56
59
  const isTabletType = (type: string): type is keyof typeof TABLET_SYMBOLS => type in TABLET_SYMBOLS;
@@ -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.11.1",
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",