ydb-embedded-ui 1.13.1 → 1.14.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +42 -0
- package/dist/assets/icons/flask.svg +3 -0
- package/dist/components/InfoViewer/formatters/common.ts +15 -0
- package/dist/components/InfoViewer/formatters/index.ts +2 -0
- package/dist/components/InfoViewer/formatters/schema.ts +43 -0
- package/dist/components/InfoViewer/schemaInfo/CDCStreamInfo.tsx +44 -0
- package/dist/components/InfoViewer/schemaInfo/PersQueueGroupInfo.tsx +34 -0
- package/dist/components/{IndexInfoViewer/IndexInfoViewer.tsx → InfoViewer/schemaInfo/TableIndexInfo.tsx} +7 -18
- package/dist/components/InfoViewer/schemaInfo/index.ts +3 -0
- package/dist/components/InfoViewer/schemaOverview/CDCStreamOverview.tsx +44 -0
- package/dist/components/InfoViewer/schemaOverview/PersQueueGroupOverview.tsx +35 -0
- package/dist/components/InfoViewer/schemaOverview/index.ts +2 -0
- package/dist/components/QueryResultTable/Cell/Cell.tsx +33 -0
- package/dist/components/QueryResultTable/Cell/index.ts +1 -0
- package/dist/components/QueryResultTable/QueryResultTable.scss +11 -0
- package/dist/components/QueryResultTable/QueryResultTable.tsx +115 -0
- package/dist/components/QueryResultTable/i18n/en.json +3 -0
- package/dist/components/QueryResultTable/i18n/index.ts +11 -0
- package/dist/components/QueryResultTable/i18n/ru.json +3 -0
- package/dist/components/QueryResultTable/index.ts +1 -0
- package/dist/containers/App/App.scss +1 -0
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +39 -14
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +18 -7
- package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +4 -3
- package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +7 -7
- package/dist/containers/Tenant/Acl/Acl.js +7 -1
- package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +6 -2
- package/dist/containers/Tenant/Diagnostics/HotKeys/HotKeys.js +1 -1
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +8 -3
- package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.js +1 -1
- package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.js +1 -1
- package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +36 -10
- package/dist/containers/Tenant/Preview/Preview.js +15 -57
- package/dist/containers/Tenant/Preview/Preview.scss +4 -8
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +12 -41
- package/dist/containers/Tenant/QueryEditor/QueryEditor.scss +1 -5
- package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.scss +1 -2
- package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.scss +2 -2
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +9 -1
- package/dist/containers/Tenant/Tenant.scss +2 -50
- package/dist/containers/Tenant/Tenant.tsx +24 -22
- package/dist/containers/Tenant/utils/schema.ts +3 -0
- package/dist/containers/Tenant/utils/schemaActions.ts +1 -2
- package/dist/containers/Tenants/Tenants.js +12 -2
- package/dist/containers/UserSettings/UserSettings.tsx +26 -3
- package/dist/services/api.d.ts +19 -2
- package/dist/services/api.js +2 -2
- package/dist/setupTests.js +4 -0
- package/dist/store/reducers/executeQuery.js +4 -9
- package/dist/store/reducers/{preview.js → preview.ts} +22 -18
- package/dist/store/reducers/settings.js +3 -1
- package/dist/store/utils.ts +88 -0
- package/dist/types/api/query.ts +147 -0
- package/dist/types/api/schema.ts +235 -2
- package/dist/types/index.ts +33 -0
- package/dist/types/store/query.ts +9 -0
- package/dist/utils/{constants.js → constants.ts} +11 -6
- package/dist/utils/index.js +0 -24
- package/dist/utils/query.test.ts +189 -0
- package/dist/utils/query.ts +156 -0
- package/dist/utils/tests/providers.tsx +29 -0
- package/package.json +2 -2
- package/dist/store/utils.js +0 -51
@@ -1,3 +1,4 @@
|
|
1
|
+
import {useEffect} from 'react';
|
1
2
|
import {useDispatch} from 'react-redux';
|
2
3
|
|
3
4
|
import {NavigationTree} from 'ydb-ui-components';
|
@@ -13,7 +14,7 @@ import {getActions} from '../../utils/schemaActions';
|
|
13
14
|
interface SchemaTreeProps {
|
14
15
|
rootPath: string;
|
15
16
|
rootName: string;
|
16
|
-
rootType
|
17
|
+
rootType?: EPathType;
|
17
18
|
currentPath: string;
|
18
19
|
}
|
19
20
|
|
@@ -48,6 +49,13 @@ export function SchemaTree(props: SchemaTreeProps) {
|
|
48
49
|
dispatch(getSchemaAcl({path: activePath}));
|
49
50
|
};
|
50
51
|
|
52
|
+
useEffect(() => {
|
53
|
+
// if the cached path is not in the current tree, show root
|
54
|
+
if (!currentPath.startsWith(rootPath)) {
|
55
|
+
handleActivePathUpdate(rootPath);
|
56
|
+
}
|
57
|
+
}, []);
|
58
|
+
|
51
59
|
return (
|
52
60
|
<NavigationTree
|
53
61
|
rootState={{
|
@@ -11,55 +11,7 @@
|
|
11
11
|
overflow: initial;
|
12
12
|
}
|
13
13
|
|
14
|
-
&
|
15
|
-
|
16
|
-
flex: 1 1 auto;
|
17
|
-
justify-content: center;
|
18
|
-
align-items: center;
|
19
|
-
}
|
20
|
-
|
21
|
-
&__content {
|
22
|
-
overflow: auto;
|
23
|
-
@include flex-container();
|
24
|
-
}
|
25
|
-
|
26
|
-
&__storage {
|
27
|
-
overflow: auto;
|
28
|
-
|
29
|
-
height: 100%;
|
30
|
-
padding-left: 20px;
|
31
|
-
}
|
32
|
-
|
33
|
-
&__row {
|
34
|
-
display: flex;
|
35
|
-
align-items: flex-start;
|
36
|
-
|
37
|
-
&_schema {
|
38
|
-
height: 100%;
|
39
|
-
}
|
40
|
-
|
41
|
-
&_schema-tags {
|
42
|
-
align-items: center;
|
43
|
-
}
|
44
|
-
}
|
45
|
-
|
46
|
-
&__schema-wrapper {
|
47
|
-
overflow: auto;
|
48
|
-
|
49
|
-
min-width: 300px;
|
50
|
-
height: 100%;
|
51
|
-
padding-top: 15px;
|
52
|
-
padding-left: 8px;
|
53
|
-
}
|
54
|
-
|
55
|
-
&__schema-tabs-wrapper {
|
56
|
-
overflow: auto;
|
57
|
-
|
58
|
-
width: 100%;
|
59
|
-
height: 100%;
|
60
|
-
padding: 15px 0 0 15px;
|
61
|
-
|
62
|
-
border-left: 1px solid rgba(0, 0, 0, 0.07);
|
63
|
-
@include flex-container();
|
14
|
+
&__tab-content {
|
15
|
+
height: calc(100% - 56px); // general tabs height
|
64
16
|
}
|
65
17
|
}
|
@@ -134,28 +134,30 @@ function Tenant(props: TenantProps) {
|
|
134
134
|
) : (
|
135
135
|
<>
|
136
136
|
<ObjectGeneralTabs />
|
137
|
-
<
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
137
|
+
<div className={b('tab-content')}>
|
138
|
+
<SplitPane
|
139
|
+
defaultSizePaneKey={DEFAULT_SIZE_TENANT_KEY}
|
140
|
+
defaultSizes={[25, 75]}
|
141
|
+
triggerCollapse={summaryVisibilityState.triggerCollapse}
|
142
|
+
triggerExpand={summaryVisibilityState.triggerExpand}
|
143
|
+
minSize={[36, 200]}
|
144
|
+
onSplitStartDragAdditional={onSplitStartDragAdditional}
|
145
|
+
>
|
146
|
+
<ObjectSummary
|
147
|
+
type={currentPathType}
|
148
|
+
subType={currentPathSubType}
|
149
|
+
onCollapseSummary={onCollapseSummaryHandler}
|
150
|
+
onExpandSummary={onExpandSummaryHandler}
|
151
|
+
isCollapsed={summaryVisibilityState.collapsed}
|
152
|
+
additionalTenantInfo={props.additionalTenantInfo}
|
153
|
+
/>
|
154
|
+
<ObjectGeneral
|
155
|
+
type={currentPathType}
|
156
|
+
additionalTenantInfo={props.additionalTenantInfo}
|
157
|
+
additionalNodesInfo={props.additionalNodesInfo}
|
158
|
+
/>
|
159
|
+
</SplitPane>
|
160
|
+
</div>
|
159
161
|
</>
|
160
162
|
)}
|
161
163
|
</div>
|
@@ -29,6 +29,7 @@ const pathTypeToNodeType: Record<EPathType, NavigationTreeNodeType | undefined>
|
|
29
29
|
[EPathType.EPathTypeColumnTable]: 'column_table',
|
30
30
|
|
31
31
|
[EPathType.EPathTypeCdcStream]: 'topic',
|
32
|
+
[EPathType.EPathTypePersQueueGroup]: 'topic',
|
32
33
|
};
|
33
34
|
|
34
35
|
export const mapPathTypeToNavigationTreeType = (
|
@@ -51,6 +52,7 @@ const pathTypeToIsTable: Record<EPathType, boolean> = {
|
|
51
52
|
[EPathType.EPathTypeExtSubDomain]: false,
|
52
53
|
[EPathType.EPathTypeColumnStore]: false,
|
53
54
|
[EPathType.EPathTypeCdcStream]: false,
|
55
|
+
[EPathType.EPathTypePersQueueGroup]: false,
|
54
56
|
};
|
55
57
|
|
56
58
|
export const isTableType = (pathType?: EPathType) =>
|
@@ -82,6 +84,7 @@ const pathTypeToIsColumn: Record<EPathType, boolean> = {
|
|
82
84
|
[EPathType.EPathTypeTableIndex]: false,
|
83
85
|
[EPathType.EPathTypeExtSubDomain]: false,
|
84
86
|
[EPathType.EPathTypeCdcStream]: false,
|
87
|
+
[EPathType.EPathTypePersQueueGroup]: false,
|
85
88
|
};
|
86
89
|
|
87
90
|
export const isColumnEntityType = (type?: EPathType) =>
|
@@ -20,9 +20,8 @@ const alterTableTemplate = (path: string) => {
|
|
20
20
|
ADD COLUMN is_deleted Bool;`;
|
21
21
|
};
|
22
22
|
const selectQueryTemplate = (path: string) => {
|
23
|
-
return `SELECT
|
23
|
+
return `SELECT *
|
24
24
|
FROM \`${path}\`
|
25
|
-
ORDER BY \`id\`
|
26
25
|
LIMIT 10;`;
|
27
26
|
};
|
28
27
|
const upsertQueryTemplate = (path: string) => {
|
@@ -14,7 +14,7 @@ import ProblemFilter, {problemFilterType} from '../../components/ProblemFilter/P
|
|
14
14
|
import {AutoFetcher} from '../../utils/autofetcher';
|
15
15
|
|
16
16
|
import routes, {CLUSTER_PAGES, createHref} from '../../routes';
|
17
|
-
import {formatCPU, formatBytesToGigabyte} from '../../utils';
|
17
|
+
import {formatCPU, formatBytesToGigabyte, formatNumber} from '../../utils';
|
18
18
|
import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
|
19
19
|
import {withSearch} from '../../HOCS';
|
20
20
|
import {ALL, DEFAULT_TABLE_SETTINGS, TENANT_INITIAL_TAB_KEY} from '../../utils/constants';
|
@@ -257,13 +257,23 @@ class Tenants extends React.Component {
|
|
257
257
|
align: DataTable.RIGHT,
|
258
258
|
defaultOrder: DataTable.DESCENDING,
|
259
259
|
},
|
260
|
+
{
|
261
|
+
name: 'NodeIds',
|
262
|
+
header: 'Nodes',
|
263
|
+
width: 100,
|
264
|
+
accessor: ({NodeIds}) => NodeIds.length,
|
265
|
+
sortAccessor: ({NodeIds}) => NodeIds.length,
|
266
|
+
render: ({value}) => formatNumber(value) || '—',
|
267
|
+
align: DataTable.RIGHT,
|
268
|
+
defaultOrder: DataTable.DESCENDING,
|
269
|
+
},
|
260
270
|
{
|
261
271
|
name: 'StorageGroups',
|
262
272
|
header: 'Groups',
|
263
273
|
width: 100,
|
264
274
|
sortAccessor: ({StorageGroups}) =>
|
265
275
|
isNaN(Number(StorageGroups)) ? 0 : Number(StorageGroups),
|
266
|
-
render: ({value}) => value
|
276
|
+
render: ({value}) => formatNumber(value) || '—',
|
267
277
|
align: DataTable.RIGHT,
|
268
278
|
defaultOrder: DataTable.DESCENDING,
|
269
279
|
},
|
@@ -1,10 +1,11 @@
|
|
1
1
|
import {connect} from 'react-redux';
|
2
2
|
|
3
|
-
import {RadioButton} from '@yandex-cloud/uikit';
|
3
|
+
import {RadioButton, Switch} from '@yandex-cloud/uikit';
|
4
4
|
import {Settings} from '../../components/AsideNavigation/Settings';
|
5
5
|
import favoriteFilledIcon from '../../assets/icons/star.svg';
|
6
|
+
import flaskIcon from '../../assets/icons/flask.svg';
|
6
7
|
//@ts-ignore
|
7
|
-
import {THEME_KEY} from '../../utils/constants';
|
8
|
+
import {INVERTED_DISKS_KEY, THEME_KEY} from '../../utils/constants';
|
8
9
|
//@ts-ignore
|
9
10
|
import {setSettingValue} from '../../store/reducers/settings';
|
10
11
|
|
@@ -19,6 +20,10 @@ function UserSettings(props: any) {
|
|
19
20
|
props.setSettingValue(THEME_KEY, value);
|
20
21
|
};
|
21
22
|
|
23
|
+
const _onInvertedDisksChangeHandler = (value: boolean) => {
|
24
|
+
props.setSettingValue(INVERTED_DISKS_KEY, value);
|
25
|
+
};
|
26
|
+
|
22
27
|
return (
|
23
28
|
<Settings>
|
24
29
|
<Settings.Page
|
@@ -36,15 +41,33 @@ function UserSettings(props: any) {
|
|
36
41
|
</Settings.Item>
|
37
42
|
</Settings.Section>
|
38
43
|
</Settings.Page>
|
44
|
+
<Settings.Page
|
45
|
+
id="experiments"
|
46
|
+
title="Experiments"
|
47
|
+
icon={{data: flaskIcon}}
|
48
|
+
>
|
49
|
+
<Settings.Section title="Experiments">
|
50
|
+
<Settings.Item title="Inverted disks space indicators">
|
51
|
+
<Switch
|
52
|
+
checked={props.invertedDisks}
|
53
|
+
onUpdate={_onInvertedDisksChangeHandler}
|
54
|
+
/>
|
55
|
+
</Settings.Item>
|
56
|
+
</Settings.Section>
|
57
|
+
</Settings.Page>
|
39
58
|
</Settings>
|
40
59
|
);
|
41
60
|
}
|
42
61
|
|
43
62
|
const mapStateToProps = (state: any) => {
|
44
|
-
const {
|
63
|
+
const {
|
64
|
+
theme,
|
65
|
+
invertedDisks,
|
66
|
+
} = state.settings.userSettings;
|
45
67
|
|
46
68
|
return {
|
47
69
|
theme,
|
70
|
+
invertedDisks,
|
48
71
|
};
|
49
72
|
};
|
50
73
|
|
package/dist/services/api.d.ts
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
type AxiosOptions = {
|
2
|
+
concurrentId?: string;
|
3
|
+
};
|
4
|
+
|
1
5
|
interface Window {
|
2
6
|
api: {
|
3
7
|
getSchema: (
|
4
8
|
params: {path: string},
|
5
|
-
axiosOptions?:
|
9
|
+
axiosOptions?: AxiosOptions,
|
6
10
|
) => Promise<import('../types/api/schema').TEvDescribeSchemeResult>;
|
7
11
|
getStorageInfo: (
|
8
12
|
params: {
|
@@ -11,8 +15,21 @@ interface Window {
|
|
11
15
|
nodeId: string,
|
12
16
|
type: 'Groups' | 'Nodes',
|
13
17
|
},
|
14
|
-
axiosOptions?:
|
18
|
+
axiosOptions?: AxiosOptions,
|
15
19
|
) => Promise<import('../types/api/storage').TStorageInfo>;
|
20
|
+
sendQuery: <
|
21
|
+
Action extends import('../types/api/query').Actions,
|
22
|
+
Schema extends import('../types/api/query').Schemas = undefined
|
23
|
+
>(
|
24
|
+
params: {
|
25
|
+
query?: string,
|
26
|
+
database?: string,
|
27
|
+
action?: Action,
|
28
|
+
stats?: string,
|
29
|
+
schema?: Schema,
|
30
|
+
},
|
31
|
+
axiosOptions?: AxiosOptions,
|
32
|
+
) => Promise<import('../types/api/query').QueryAPIResponse<Action, Schema>>;
|
16
33
|
[method: string]: Function;
|
17
34
|
};
|
18
35
|
}
|
package/dist/services/api.js
CHANGED
@@ -146,9 +146,9 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
|
|
146
146
|
state: 0,
|
147
147
|
});
|
148
148
|
}
|
149
|
-
sendQuery({query, database, action, stats}, {concurrentId} = {}) {
|
149
|
+
sendQuery({query, database, action, stats, schema}, {concurrentId} = {}) {
|
150
150
|
return this.post(
|
151
|
-
this.getPath(
|
151
|
+
this.getPath(`/viewer/json/query${schema ? `?schema=${schema}` : ''}`),
|
152
152
|
{
|
153
153
|
query,
|
154
154
|
database,
|
package/dist/setupTests.js
CHANGED
@@ -11,3 +11,7 @@ import {i18n, Lang} from '../src/utils/i18n';
|
|
11
11
|
i18n.setLang(Lang.En);
|
12
12
|
configureYdbUiComponents({lang: Lang.En});
|
13
13
|
configureUiKit({lang: Lang.En});
|
14
|
+
|
15
|
+
// only to prevent warnings from history lib
|
16
|
+
// all api calls in tests should be mocked
|
17
|
+
window.custom_backend = '/';
|
@@ -2,6 +2,7 @@ import {createRequestActionTypes, createApiRequest} from '../utils';
|
|
2
2
|
import '../../services/api';
|
3
3
|
import {getValueFromLS, parseJson} from '../../utils/utils';
|
4
4
|
import {QUERIES_HISTORY_KEY, QUERY_INITIAL_RUN_ACTION_KEY} from '../../utils/constants';
|
5
|
+
import {parseQueryAPIExecuteResponse} from '../../utils/query';
|
5
6
|
import {readSavedSettingsValue} from './settings';
|
6
7
|
|
7
8
|
const MAXIMUM_QUERIES_IN_HISTORY = 20;
|
@@ -57,7 +58,7 @@ const executeQuery = (state = initialState, action) => {
|
|
57
58
|
case SEND_QUERY.SUCCESS: {
|
58
59
|
return {
|
59
60
|
...state,
|
60
|
-
data: action.data
|
61
|
+
data: action.data,
|
61
62
|
stats: action.data.stats,
|
62
63
|
loading: false,
|
63
64
|
error: undefined,
|
@@ -142,15 +143,9 @@ const executeQuery = (state = initialState, action) => {
|
|
142
143
|
|
143
144
|
export const sendQuery = ({query, database, action}) => {
|
144
145
|
return createApiRequest({
|
145
|
-
request: window.api.sendQuery({query, database, action, stats: 'profile'}),
|
146
|
+
request: window.api.sendQuery({schema: 'modern', query, database, action, stats: 'profile'}),
|
146
147
|
actions: SEND_QUERY,
|
147
|
-
dataHandler:
|
148
|
-
const resultData = result.result ?? result;
|
149
|
-
if (resultData && typeof resultData === 'string') {
|
150
|
-
throw 'Unexpected token in JSON.';
|
151
|
-
}
|
152
|
-
return result;
|
153
|
-
},
|
148
|
+
dataHandler: parseQueryAPIExecuteResponse,
|
154
149
|
});
|
155
150
|
};
|
156
151
|
|
@@ -1,15 +1,23 @@
|
|
1
|
-
import {createRequestActionTypes, createApiRequest} from '../utils';
|
2
1
|
import '../../services/api';
|
3
2
|
|
3
|
+
import type {ErrorRepsonse, ExecuteActions} from '../../types/api/query';
|
4
|
+
import type {IQueryResult} from '../../types/store/query';
|
5
|
+
import {parseQueryAPIExecuteResponse} from '../../utils/query';
|
6
|
+
|
7
|
+
import {createRequestActionTypes, createApiRequest, ApiRequestAction} from '../utils';
|
8
|
+
|
4
9
|
const SEND_QUERY = createRequestActionTypes('preview', 'SEND_QUERY');
|
5
|
-
const SET_QUERY_OPTIONS =
|
10
|
+
const SET_QUERY_OPTIONS = 'preview/SET_QUERY_OPTIONS';
|
6
11
|
|
7
12
|
const initialState = {
|
8
13
|
loading: false,
|
9
14
|
wasLoaded: false,
|
10
15
|
};
|
11
16
|
|
12
|
-
const preview = (
|
17
|
+
const preview = (
|
18
|
+
state = initialState,
|
19
|
+
action: ApiRequestAction<typeof SEND_QUERY, IQueryResult, ErrorRepsonse> | ReturnType<typeof setQueryOptions>,
|
20
|
+
) => {
|
13
21
|
switch (action.type) {
|
14
22
|
case SEND_QUERY.REQUEST: {
|
15
23
|
return {
|
@@ -45,29 +53,25 @@ const preview = (state = initialState, action) => {
|
|
45
53
|
}
|
46
54
|
};
|
47
55
|
|
48
|
-
|
56
|
+
interface SendQueryParams {
|
57
|
+
query?: string;
|
58
|
+
database?: string;
|
59
|
+
action?: ExecuteActions;
|
60
|
+
};
|
61
|
+
|
62
|
+
export const sendQuery = ({query, database, action}: SendQueryParams) => {
|
49
63
|
return createApiRequest({
|
50
|
-
request: window.api.sendQuery({query, database, action}),
|
64
|
+
request: window.api.sendQuery({schema: 'modern', query, database, action}),
|
51
65
|
actions: SEND_QUERY,
|
52
|
-
dataHandler:
|
53
|
-
if (!Array.isArray(data)) {
|
54
|
-
try {
|
55
|
-
return JSON.parse(data);
|
56
|
-
} catch (e) {
|
57
|
-
return [];
|
58
|
-
}
|
59
|
-
}
|
60
|
-
|
61
|
-
return data;
|
62
|
-
},
|
66
|
+
dataHandler: parseQueryAPIExecuteResponse,
|
63
67
|
});
|
64
68
|
};
|
65
69
|
|
66
|
-
export function setQueryOptions(options) {
|
70
|
+
export function setQueryOptions(options: any) {
|
67
71
|
return {
|
68
72
|
type: SET_QUERY_OPTIONS,
|
69
73
|
data: options,
|
70
|
-
};
|
74
|
+
} as const;
|
71
75
|
}
|
72
76
|
|
73
77
|
export default preview;
|
@@ -5,6 +5,7 @@ import {
|
|
5
5
|
THEME_KEY,
|
6
6
|
TENANT_INITIAL_TAB_KEY,
|
7
7
|
QUERY_INITIAL_RUN_ACTION_KEY,
|
8
|
+
INVERTED_DISKS_KEY,
|
8
9
|
} from '../../utils/constants';
|
9
10
|
import '../../services/api';
|
10
11
|
import {getValueFromLS} from '../../utils/utils';
|
@@ -28,7 +29,8 @@ export const initialState = {
|
|
28
29
|
userSettings: {
|
29
30
|
...defaultUserSettings,
|
30
31
|
...userSettings,
|
31
|
-
|
32
|
+
[THEME_KEY]: readSavedSettingsValue(THEME_KEY, 'light'),
|
33
|
+
[INVERTED_DISKS_KEY]: readSavedSettingsValue(INVERTED_DISKS_KEY) === 'true',
|
32
34
|
[SAVED_QUERIES_KEY]: readSavedSettingsValue(SAVED_QUERIES_KEY, '[]'),
|
33
35
|
[TENANT_INITIAL_TAB_KEY]: readSavedSettingsValue(TENANT_INITIAL_TAB_KEY),
|
34
36
|
[QUERY_INITIAL_RUN_ACTION_KEY]: readSavedSettingsValue(QUERY_INITIAL_RUN_ACTION_KEY),
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import type {Dispatch} from 'redux';
|
2
|
+
import {AxiosResponse} from 'axios';
|
3
|
+
|
4
|
+
import createToast from '../utils/createToast';
|
5
|
+
|
6
|
+
import {SET_UNAUTHENTICATED} from './reducers/authentication';
|
7
|
+
|
8
|
+
export const nop = (result: any) => result;
|
9
|
+
|
10
|
+
export function createRequestActionTypes<Prefix extends string, Type extends string>(prefix: Prefix, type: Type) {
|
11
|
+
return {
|
12
|
+
REQUEST: `${prefix}/${type}_REQUEST`,
|
13
|
+
SUCCESS: `${prefix}/${type}_SUCCESS`,
|
14
|
+
FAILURE: `${prefix}/${type}_FAILURE`,
|
15
|
+
} as const;
|
16
|
+
}
|
17
|
+
|
18
|
+
const isAxiosResponse = (response: any): response is AxiosResponse => response && 'status' in response;
|
19
|
+
|
20
|
+
type CreateApiRequestParams<Actions, Response, HandledResponse> = {
|
21
|
+
actions: Actions;
|
22
|
+
request: Promise<Response>;
|
23
|
+
dataHandler: (data: Response, getState?: () => any) => HandledResponse;
|
24
|
+
};
|
25
|
+
|
26
|
+
export function createApiRequest<
|
27
|
+
Actions extends ReturnType<typeof createRequestActionTypes>,
|
28
|
+
Response,
|
29
|
+
HandledResponse,
|
30
|
+
>({actions, request, dataHandler = nop}: CreateApiRequestParams<Actions, Response, HandledResponse>) {
|
31
|
+
const doRequest = async function (dispatch: Dispatch, getState: () => any) {
|
32
|
+
dispatch({
|
33
|
+
type: actions.REQUEST,
|
34
|
+
});
|
35
|
+
|
36
|
+
try {
|
37
|
+
const result = await request;
|
38
|
+
const data = dataHandler(result, getState);
|
39
|
+
|
40
|
+
dispatch({
|
41
|
+
type: actions.SUCCESS,
|
42
|
+
data,
|
43
|
+
});
|
44
|
+
|
45
|
+
return data;
|
46
|
+
} catch (error) {
|
47
|
+
if (isAxiosResponse(error) && error.status === 401) {
|
48
|
+
dispatch({
|
49
|
+
type: SET_UNAUTHENTICATED.SUCCESS,
|
50
|
+
});
|
51
|
+
} else if (isAxiosResponse(error) && error.status >= 500 && error.statusText) {
|
52
|
+
createToast({
|
53
|
+
name: 'Request failure',
|
54
|
+
title: 'Request failure',
|
55
|
+
type: 'error',
|
56
|
+
content: `${error.status} ${error.statusText}`,
|
57
|
+
});
|
58
|
+
}
|
59
|
+
|
60
|
+
dispatch({
|
61
|
+
type: actions.FAILURE,
|
62
|
+
error,
|
63
|
+
});
|
64
|
+
|
65
|
+
// TODO should probably throw the received error here, but this change requires a thorough revision of all api calls
|
66
|
+
return undefined;
|
67
|
+
}
|
68
|
+
};
|
69
|
+
|
70
|
+
return doRequest;
|
71
|
+
}
|
72
|
+
|
73
|
+
export type ApiRequestAction<
|
74
|
+
Actions extends ReturnType<typeof createRequestActionTypes>,
|
75
|
+
SuccessResponse = unknown,
|
76
|
+
ErrorResponse = unknown
|
77
|
+
> =
|
78
|
+
| {
|
79
|
+
type: Actions['REQUEST'],
|
80
|
+
}
|
81
|
+
| {
|
82
|
+
type: Actions['SUCCESS'],
|
83
|
+
data: SuccessResponse,
|
84
|
+
}
|
85
|
+
| {
|
86
|
+
type: Actions['FAILURE'],
|
87
|
+
error: ErrorResponse,
|
88
|
+
};
|