ydb-embedded-ui 4.12.0 → 4.14.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.
- package/CHANGELOG.md +29 -0
- package/dist/components/InfoViewer/formatters/common.ts +4 -2
- package/dist/components/InfoViewer/i18n/en.json +4 -0
- package/dist/components/InfoViewer/i18n/index.ts +11 -0
- package/dist/components/InfoViewer/i18n/ru.json +4 -0
- package/dist/components/Tablet/Tablet.scss +1 -16
- package/dist/components/Tablet/Tablet.tsx +5 -5
- package/dist/components/TabletIcon/TabletIcon.scss +17 -0
- package/dist/components/TabletIcon/TabletIcon.tsx +18 -0
- package/dist/containers/Header/Header.scss +2 -0
- package/dist/containers/Header/Header.tsx +2 -7
- package/dist/containers/Header/{breadcrumbs.ts → breadcrumbs.tsx} +19 -8
- package/dist/containers/Nodes/Nodes.tsx +53 -16
- package/dist/containers/Nodes/getNodesColumns.tsx +31 -13
- package/dist/containers/Tablet/Tablet.tsx +9 -3
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +5 -2
- package/dist/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.scss +5 -0
- package/dist/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.tsx +81 -0
- package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.scss +5 -0
- package/dist/containers/Tenant/Info/ExternalTable/ExternalTable.tsx +103 -0
- package/dist/containers/Tenant/Info/i18n/en.json +8 -0
- package/dist/containers/Tenant/Info/i18n/index.ts +11 -0
- package/dist/containers/Tenant/Info/i18n/ru.json +8 -0
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.scss +4 -4
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +10 -3
- package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.scss +8 -0
- package/dist/containers/Tenant/Query/QueryDuration/QueryDuration.tsx +13 -1
- package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +4 -6
- package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.scss +3 -1
- package/dist/containers/Tenant/Query/i18n/en.json +6 -4
- package/dist/containers/Tenant/Query/i18n/ru.json +6 -4
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +12 -2
- package/dist/containers/Tenant/i18n/en.json +12 -2
- package/dist/containers/Tenant/i18n/ru.json +11 -1
- package/dist/containers/Tenant/utils/schemaActions.ts +76 -28
- package/dist/containers/Tenant/utils/schemaControls.tsx +69 -0
- package/dist/containers/UserSettings/i18n/en.json +3 -0
- package/dist/containers/UserSettings/i18n/ru.json +3 -0
- package/dist/containers/UserSettings/settings.ts +12 -1
- package/dist/index.js +7 -3
- package/dist/services/api.ts +24 -12
- package/dist/store/reducers/header/types.ts +2 -0
- package/dist/store/reducers/nodes/nodes.ts +23 -6
- package/dist/store/reducers/nodes/selectors.ts +2 -2
- package/dist/store/reducers/nodes/types.ts +15 -5
- package/dist/store/reducers/settings/settings.ts +5 -0
- package/dist/styles/constants.scss +3 -0
- package/dist/types/api/compute.ts +0 -12
- package/dist/types/api/nodes.ts +0 -12
- package/dist/utils/constants.ts +3 -0
- package/dist/utils/filters.ts +23 -0
- package/dist/utils/hooks/i18n/en.json +3 -0
- package/dist/utils/hooks/i18n/index.ts +11 -0
- package/dist/utils/hooks/i18n/ru.json +3 -0
- package/dist/utils/hooks/index.ts +4 -0
- package/dist/utils/hooks/useNodesRequestParams.ts +46 -0
- package/dist/utils/hooks/useQueryModes.ts +34 -0
- package/dist/utils/hooks/useTableSort.ts +37 -0
- package/dist/utils/nodes.ts +25 -0
- package/dist/utils/query.ts +5 -1
- 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,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]:
|
201
|
-
|
203
|
+
[EPathType.EPathTypeExternalTable]: () => (
|
204
|
+
<ExternalTableSummary data={currentObjectData} />
|
205
|
+
),
|
206
|
+
[EPathType.EPathTypeExternalDataSource]: () => (
|
207
|
+
<ExternalDataSourceSummary data={currentObjectData} />
|
208
|
+
),
|
202
209
|
};
|
203
210
|
|
204
211
|
let component =
|
@@ -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
|
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] =
|
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
|
-
|
100
|
-
if (!enableAdditionalQueryModes && isNewQueryMode) {
|
98
|
+
if (isNewQueryMode(queryMode) && !enableAdditionalQueryModes) {
|
101
99
|
setQueryMode(QUERY_MODES.script);
|
102
100
|
}
|
103
101
|
}, [enableAdditionalQueryModes, queryMode, setQueryMode]);
|
@@ -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
|
21
|
-
"method-description.scan": "Read-only queries, potentially reading a lot of data
|
22
|
-
"method-description.data": "DML queries for changing and fetching data in serialization mode
|
23
|
-
"method-description.query": "Any
|
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
|
21
|
-
"method-description.scan": "Только читающие запросы, потенциально читающие много
|
22
|
-
"method-description.data": "DML-запросы для изменения и выборки данных в режиме изоляции Serializable
|
23
|
-
"method-description.query": "Любые запросы. Экспериментальный перспективный метод, который в будущем заменит все
|
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,
|
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"
|
10
|
-
"actions.notCopied"
|
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
|
-
|
66
|
+
additionalEffects: ActionsAdditionalEffects,
|
41
67
|
) => {
|
42
|
-
const
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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>,
|
117
|
+
(dispatch: Dispatch<any>, additionalEffects: ActionsAdditionalEffects) =>
|
83
118
|
(path: string, type: NavigationTreeNodeType) => {
|
84
|
-
const actions = bindActions(path, dispatch,
|
85
|
-
const copyItem = {text: '
|
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: '
|
124
|
+
[{text: i18n('actions.createTable'), action: actions.createTable}],
|
92
125
|
];
|
93
126
|
const TABLE_SET: ActionsSet = [
|
94
|
-
[
|
127
|
+
[copyItem],
|
95
128
|
[
|
96
|
-
{text: '
|
97
|
-
selectQuery,
|
98
|
-
{text: '
|
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 = [
|
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:
|
169
|
+
external_data_source: EXTERNAL_DATA_SOURCE_SET,
|
122
170
|
};
|
123
171
|
|
124
172
|
return nodeTypeToActions[type];
|