ydb-embedded-ui 4.12.0 → 4.13.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +13 -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/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/QueryEditor/QueryEditor.js +4 -6
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +8 -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 +75 -20
- package/dist/index.js +7 -3
- package/dist/styles/constants.scss +3 -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 +1 -0
- package/dist/utils/hooks/useQueryModes.ts +34 -0
- package/dist/utils/query.ts +5 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [4.13.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.12.0...v4.13.0) (2023-08-04)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* info and summary tabs for external objects ([#493](https://github.com/ydb-platform/ydb-embedded-ui/issues/493)) ([88d9041](https://github.com/ydb-platform/ydb-embedded-ui/commit/88d9041f080f13046aeaf55765609dbc13b87285))
|
9
|
+
|
10
|
+
|
11
|
+
### Bug Fixes
|
12
|
+
|
13
|
+
* **SchemaTree:** add actions to external objects ([#497](https://github.com/ydb-platform/ydb-embedded-ui/issues/497)) ([5029579](https://github.com/ydb-platform/ydb-embedded-ui/commit/5029579796dd5fb985005f39e9ef8daf142366d0))
|
14
|
+
* **SchemaTree:** set required query mode for tree actions ([#491](https://github.com/ydb-platform/ydb-embedded-ui/issues/491)) ([ccd1eda](https://github.com/ydb-platform/ydb-embedded-ui/commit/ccd1edac0d84357cd605c9d131c99890449d8bd8))
|
15
|
+
|
3
16
|
## [4.12.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v4.11.1...v4.12.0) (2023-08-02)
|
4
17
|
|
5
18
|
|
@@ -3,13 +3,15 @@ import {formatDateTime} from '../../../utils';
|
|
3
3
|
|
4
4
|
import {createInfoFormatter} from '../utils';
|
5
5
|
|
6
|
+
import i18n from '../i18n';
|
7
|
+
|
6
8
|
export const formatCommonItem = createInfoFormatter<TDirEntry>({
|
7
9
|
values: {
|
8
10
|
PathType: (value) => value?.substring('EPathType'.length),
|
9
11
|
CreateStep: formatDateTime,
|
10
12
|
},
|
11
13
|
labels: {
|
12
|
-
PathType: '
|
13
|
-
CreateStep: '
|
14
|
+
PathType: i18n('common.type'),
|
15
|
+
CreateStep: i18n('common.created'),
|
14
16
|
},
|
15
17
|
});
|
@@ -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);
|
@@ -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]:
|
128
|
-
[EPathType.EPathTypeExternalDataSource]:
|
130
|
+
[EPathType.EPathTypeExternalTable]: () => <ExternalTableInfo data={data} />,
|
131
|
+
[EPathType.EPathTypeExternalDataSource]: () => <ExternalDataSourceInfo data={data} />,
|
129
132
|
};
|
130
133
|
|
131
134
|
return (
|
@@ -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 =
|
@@ -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]);
|
@@ -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,
|
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"
|
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,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
|
-
|
67
|
+
additionalEffects: ActionsAdditionalEffects,
|
41
68
|
) => {
|
42
|
-
const
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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>,
|
124
|
+
(dispatch: Dispatch<any>, additionalEffects: ActionsAdditionalEffects) =>
|
83
125
|
(path: string, type: NavigationTreeNodeType) => {
|
84
|
-
const actions = bindActions(path, dispatch,
|
85
|
-
const copyItem = {text: '
|
86
|
-
const openPreview = {text: '
|
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: '
|
132
|
+
[{text: i18n('actions.createTable'), action: actions.createTable}],
|
92
133
|
];
|
93
134
|
const TABLE_SET: ActionsSet = [
|
94
135
|
[openPreview, copyItem],
|
95
136
|
[
|
96
|
-
{text: '
|
97
|
-
selectQuery,
|
98
|
-
{text: '
|
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
|
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:
|
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,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,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
|
+
};
|
package/dist/utils/query.ts
CHANGED
@@ -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.
|
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.
|
43
|
+
"ydb-ui-components": "^3.2.2"
|
44
44
|
},
|
45
45
|
"scripts": {
|
46
46
|
"start": "react-app-rewired start",
|