ydb-embedded-ui 3.5.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +22 -0
- package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.tsx +3 -3
- package/dist/containers/Tenant/QueryEditor/QueryDuration/QueryDuration.scss +8 -0
- package/dist/containers/Tenant/QueryEditor/QueryDuration/QueryDuration.tsx +21 -0
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +58 -83
- package/dist/containers/Tenant/QueryEditor/QueryEditor.scss +0 -33
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/OldQueryEditorControls.tsx +83 -0
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.scss +57 -0
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.tsx +84 -0
- package/dist/containers/Tenant/QueryEditor/QueryEditorControls/shared.ts +23 -0
- package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +12 -23
- package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.js +4 -6
- package/dist/containers/Tenant/QueryEditor/i18n/en.json +3 -0
- package/dist/containers/Tenant/QueryEditor/i18n/index.ts +11 -0
- package/dist/containers/Tenant/QueryEditor/i18n/ru.json +3 -0
- package/dist/containers/UserSettings/UserSettings.tsx +30 -1
- package/dist/services/api.d.ts +4 -3
- package/dist/services/api.js +2 -2
- package/dist/store/reducers/executeQuery.ts +12 -37
- package/dist/store/reducers/{explainQuery.js → explainQuery.ts} +44 -59
- package/dist/store/reducers/settings.js +18 -3
- package/dist/types/api/error.ts +14 -0
- package/dist/types/api/query.ts +226 -117
- package/dist/types/store/executeQuery.ts +4 -8
- package/dist/types/store/explainQuery.ts +38 -0
- package/dist/types/store/query.ts +23 -3
- package/dist/utils/constants.ts +2 -1
- package/dist/utils/error.ts +25 -0
- package/dist/utils/index.js +0 -49
- package/dist/utils/prepareQueryExplain.ts +7 -24
- package/dist/utils/query.test.ts +153 -231
- package/dist/utils/query.ts +44 -78
- package/dist/utils/timeParsers/i18n/en.json +9 -9
- package/dist/utils/timeParsers/i18n/ru.json +9 -9
- package/dist/utils/timeParsers/parsers.ts +9 -0
- package/dist/utils/utils.js +1 -2
- package/package.json +1 -1
@@ -5,12 +5,7 @@ import MonacoEditor from 'react-monaco-editor';
|
|
5
5
|
import JSONTree from 'react-json-inspector';
|
6
6
|
import 'react-json-inspector/json-inspector.css';
|
7
7
|
|
8
|
-
import {
|
9
|
-
TextOverflow,
|
10
|
-
getYdbPlanNodeShape,
|
11
|
-
getCompactTopology,
|
12
|
-
getTopology,
|
13
|
-
} from '@gravity-ui/paranoid';
|
8
|
+
import {TextOverflow, getYdbPlanNodeShape, getTopology} from '@gravity-ui/paranoid';
|
14
9
|
import {Loader, RadioButton} from '@gravity-ui/uikit';
|
15
10
|
|
16
11
|
import Divider from '../../../../components/Divider/Divider';
|
@@ -55,7 +50,7 @@ const explainOptions = [
|
|
55
50
|
function GraphRoot(props) {
|
56
51
|
const paranoid = useRef();
|
57
52
|
|
58
|
-
const {data, opts, shapes,
|
53
|
+
const {data, opts, shapes, theme} = props;
|
59
54
|
|
60
55
|
const [componentTheme, updateComponentTheme] = useState(theme);
|
61
56
|
|
@@ -64,13 +59,8 @@ function GraphRoot(props) {
|
|
64
59
|
}, [theme]);
|
65
60
|
|
66
61
|
const render = () => {
|
67
|
-
|
68
|
-
|
69
|
-
paranoid.current.render();
|
70
|
-
} else if (version === explainVersions.v1) {
|
71
|
-
paranoid.current = getCompactTopology('graphRoot', data, opts);
|
72
|
-
paranoid.current.renderCompactTopology();
|
73
|
-
}
|
62
|
+
paranoid.current = getTopology('graphRoot', data, opts, shapes);
|
63
|
+
paranoid.current.render();
|
74
64
|
};
|
75
65
|
|
76
66
|
useEffect(() => {
|
@@ -112,12 +102,6 @@ function QueryExplain(props) {
|
|
112
102
|
};
|
113
103
|
}, []);
|
114
104
|
|
115
|
-
useEffect(() => {
|
116
|
-
if (!props.ast && activeOption === ExplainOptionIds.ast) {
|
117
|
-
props.astQuery();
|
118
|
-
}
|
119
|
-
}, [activeOption]);
|
120
|
-
|
121
105
|
const onSelectOption = (tabId) => {
|
122
106
|
setActiveOption(tabId);
|
123
107
|
};
|
@@ -131,7 +115,9 @@ function QueryExplain(props) {
|
|
131
115
|
};
|
132
116
|
|
133
117
|
const renderStub = () => {
|
134
|
-
return
|
118
|
+
return (
|
119
|
+
<div className={b('text-message')}>{`There is no ${activeOption} for the request`}</div>
|
120
|
+
);
|
135
121
|
};
|
136
122
|
|
137
123
|
const hasContent = () => {
|
@@ -189,8 +175,12 @@ function QueryExplain(props) {
|
|
189
175
|
const renderGraph = () => {
|
190
176
|
const {explain = {}, theme} = props;
|
191
177
|
const {links, nodes, version} = explain;
|
178
|
+
|
179
|
+
const isSupportedVersion = version === explainVersions.v2;
|
180
|
+
const isEnoughDataForGraph = links && nodes && nodes.length;
|
181
|
+
|
192
182
|
const content =
|
193
|
-
|
183
|
+
isSupportedVersion && isEnoughDataForGraph ? (
|
194
184
|
<div
|
195
185
|
className={b('explain-canvas-container', {
|
196
186
|
hidden: activeOption !== ExplainOptionIds.schema,
|
@@ -198,7 +188,6 @@ function QueryExplain(props) {
|
|
198
188
|
>
|
199
189
|
<GraphRoot
|
200
190
|
theme={theme}
|
201
|
-
version={version}
|
202
191
|
data={{links, nodes}}
|
203
192
|
opts={{
|
204
193
|
renderNodeTitle: renderExplainNode,
|
@@ -18,6 +18,7 @@ import {prepareQueryError} from '../../../../utils/query';
|
|
18
18
|
import {PaneVisibilityToggleButtons} from '../../utils/paneVisibilityToggleHelpers';
|
19
19
|
|
20
20
|
import ResultIssues from '../Issues/Issues';
|
21
|
+
import {QueryDuration} from '../QueryDuration/QueryDuration';
|
21
22
|
|
22
23
|
import './QueryResult.scss';
|
23
24
|
|
@@ -116,15 +117,11 @@ function QueryResult(props) {
|
|
116
117
|
</Fullscreen>
|
117
118
|
)}
|
118
119
|
</React.Fragment>
|
119
|
-
)
|
120
|
+
);
|
120
121
|
}
|
121
122
|
|
122
123
|
if (error) {
|
123
|
-
return (
|
124
|
-
<div className={b('error')}>
|
125
|
-
{prepareQueryError(error)}
|
126
|
-
</div>
|
127
|
-
);
|
124
|
+
return <div className={b('error')}>{prepareQueryError(error)}</div>;
|
128
125
|
}
|
129
126
|
};
|
130
127
|
|
@@ -136,6 +133,7 @@ function QueryResult(props) {
|
|
136
133
|
|
137
134
|
{props.stats && !props.error && (
|
138
135
|
<React.Fragment>
|
136
|
+
<QueryDuration duration={props.stats?.DurationUs} />
|
139
137
|
<Divider />
|
140
138
|
<RadioButton
|
141
139
|
options={resultOptions}
|
@@ -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-query-editor';
|
7
|
+
|
8
|
+
i18n.registerKeyset(Lang.En, COMPONENT, en);
|
9
|
+
i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
|
10
|
+
|
11
|
+
export default i18n.keyset(COMPONENT);
|
@@ -9,6 +9,7 @@ import favoriteFilledIcon from '../../assets/icons/star.svg';
|
|
9
9
|
import flaskIcon from '../../assets/icons/flask.svg';
|
10
10
|
|
11
11
|
import {
|
12
|
+
ENABLE_QUERY_MODES_FOR_EXPLAIN,
|
12
13
|
INVERTED_DISKS_KEY,
|
13
14
|
THEME_KEY,
|
14
15
|
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
|
@@ -39,6 +40,10 @@ function UserSettings(props: any) {
|
|
39
40
|
props.setSettingValue(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY, String(value));
|
40
41
|
};
|
41
42
|
|
43
|
+
const _onExplainQueryModesChangeHandler = (value: boolean) => {
|
44
|
+
props.setSettingValue(ENABLE_QUERY_MODES_FOR_EXPLAIN, String(value));
|
45
|
+
};
|
46
|
+
|
42
47
|
const renderBreakNodesSettingsItem = (title: ReactNode) => {
|
43
48
|
return (
|
44
49
|
<div className={b('item-with-popup')}>
|
@@ -52,6 +57,19 @@ function UserSettings(props: any) {
|
|
52
57
|
);
|
53
58
|
};
|
54
59
|
|
60
|
+
const renderEnableExplainQueryModesItem = (title: ReactNode) => {
|
61
|
+
return (
|
62
|
+
<div className={b('item-with-popup')}>
|
63
|
+
{title}
|
64
|
+
<HelpPopover
|
65
|
+
content="Enable script | scan query mode selector for both run and explain. May not work on some versions"
|
66
|
+
contentClassName={b('popup')}
|
67
|
+
hasArrow={true}
|
68
|
+
/>
|
69
|
+
</div>
|
70
|
+
);
|
71
|
+
};
|
72
|
+
|
55
73
|
return (
|
56
74
|
<Settings>
|
57
75
|
<Settings.Page
|
@@ -86,6 +104,15 @@ function UserSettings(props: any) {
|
|
86
104
|
onUpdate={_onNodesEndpointChangeHandler}
|
87
105
|
/>
|
88
106
|
</Settings.Item>
|
107
|
+
<Settings.Item
|
108
|
+
title="Enable query modes for explain"
|
109
|
+
renderTitleComponent={renderEnableExplainQueryModesItem}
|
110
|
+
>
|
111
|
+
<Switch
|
112
|
+
checked={props.enableQueryModesForExplain}
|
113
|
+
onUpdate={_onExplainQueryModesChangeHandler}
|
114
|
+
/>
|
115
|
+
</Settings.Item>
|
89
116
|
</Settings.Section>
|
90
117
|
</Settings.Page>
|
91
118
|
</Settings>
|
@@ -93,12 +120,14 @@ function UserSettings(props: any) {
|
|
93
120
|
}
|
94
121
|
|
95
122
|
const mapStateToProps = (state: any) => {
|
96
|
-
const {theme, invertedDisks, useNodesEndpointInDiagnostics} =
|
123
|
+
const {theme, invertedDisks, useNodesEndpointInDiagnostics, enableQueryModesForExplain} =
|
124
|
+
state.settings.userSettings;
|
97
125
|
|
98
126
|
return {
|
99
127
|
theme,
|
100
128
|
invertedDisks: JSON.parse(invertedDisks),
|
101
129
|
useNodesEndpointInDiagnostics: JSON.parse(useNodesEndpointInDiagnostics),
|
130
|
+
enableQueryModesForExplain: JSON.parse(enableQueryModesForExplain),
|
102
131
|
};
|
103
132
|
};
|
104
133
|
|
package/dist/services/api.d.ts
CHANGED
@@ -38,14 +38,15 @@ interface Window {
|
|
38
38
|
},
|
39
39
|
axiosOptions?: AxiosOptions,
|
40
40
|
) => Promise<import('../types/api/query').QueryAPIResponse<Action, Schema>>;
|
41
|
-
getExplainQuery: (
|
41
|
+
getExplainQuery: <Action extends import('../types/api/query').ExplainActions = 'explain'>(
|
42
42
|
query: string,
|
43
43
|
database: string,
|
44
|
-
|
44
|
+
action?: Action,
|
45
|
+
) => Promise<import('../types/api/query').ExplainResponse<Action>>;
|
45
46
|
getExplainQueryAst: (
|
46
47
|
query: string,
|
47
48
|
database: string,
|
48
|
-
) => Promise<import('../types/api/query').
|
49
|
+
) => Promise<import('../types/api/query').ExplainResponse<'explain-ast'>>;
|
49
50
|
getHealthcheckInfo: (
|
50
51
|
database: string,
|
51
52
|
) => Promise<import('../types/api/healthcheck').HealthCheckAPIResponse>;
|
package/dist/services/api.js
CHANGED
@@ -207,11 +207,11 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
|
|
207
207
|
},
|
208
208
|
);
|
209
209
|
}
|
210
|
-
getExplainQuery(query, database) {
|
210
|
+
getExplainQuery(query, database, action = 'explain') {
|
211
211
|
return this.post(this.getPath('/viewer/json/query'), {
|
212
212
|
query,
|
213
213
|
database,
|
214
|
-
action
|
214
|
+
action,
|
215
215
|
timeout: 600000,
|
216
216
|
});
|
217
217
|
}
|
@@ -1,21 +1,20 @@
|
|
1
1
|
import type {Reducer} from 'redux';
|
2
2
|
|
3
|
-
import type {
|
3
|
+
import type {ExecuteActions} from '../../types/api/query';
|
4
4
|
import type {
|
5
5
|
ExecuteQueryAction,
|
6
6
|
ExecuteQueryState,
|
7
7
|
MonacoHotKeyAction,
|
8
|
-
RunAction,
|
9
8
|
} from '../../types/store/executeQuery';
|
9
|
+
import type {QueryRequestParams, QueryModes} from '../../types/store/query';
|
10
10
|
import {getValueFromLS, parseJson} from '../../utils/utils';
|
11
|
-
import {QUERIES_HISTORY_KEY
|
11
|
+
import {QUERIES_HISTORY_KEY} from '../../utils/constants';
|
12
12
|
import {parseQueryAPIExecuteResponse} from '../../utils/query';
|
13
|
+
import {parseQueryError} from '../../utils/error';
|
13
14
|
import '../../services/api';
|
14
15
|
|
15
16
|
import {createRequestActionTypes, createApiRequest} from '../utils';
|
16
17
|
|
17
|
-
import {readSavedSettingsValue} from './settings';
|
18
|
-
|
19
18
|
const MAXIMUM_QUERIES_IN_HISTORY = 20;
|
20
19
|
|
21
20
|
export const SEND_QUERY = createRequestActionTypes('query', 'SEND_QUERY');
|
@@ -24,18 +23,12 @@ const CHANGE_USER_INPUT = 'query/CHANGE_USER_INPUT';
|
|
24
23
|
const SAVE_QUERY_TO_HISTORY = 'query/SAVE_QUERY_TO_HISTORY';
|
25
24
|
const GO_TO_PREVIOUS_QUERY = 'query/GO_TO_PREVIOUS_QUERY';
|
26
25
|
const GO_TO_NEXT_QUERY = 'query/GO_TO_NEXT_QUERY';
|
27
|
-
const SELECT_RUN_ACTION = 'query/SELECT_RUN_ACTION';
|
28
26
|
const MONACO_HOT_KEY = 'query/MONACO_HOT_KEY';
|
29
27
|
|
30
28
|
const queriesHistoryInitial: string[] = parseJson(getValueFromLS(QUERIES_HISTORY_KEY, '[]'));
|
31
29
|
|
32
30
|
const sliceLimit = queriesHistoryInitial.length - MAXIMUM_QUERIES_IN_HISTORY;
|
33
31
|
|
34
|
-
export const RUN_ACTIONS_VALUES = {
|
35
|
-
script: 'execute-script',
|
36
|
-
scan: 'execute-scan',
|
37
|
-
} as const;
|
38
|
-
|
39
32
|
export const MONACO_HOT_KEY_ACTIONS = {
|
40
33
|
sendQuery: 'sendQuery',
|
41
34
|
goPrev: 'goPrev',
|
@@ -53,7 +46,6 @@ const initialState = {
|
|
53
46
|
? MAXIMUM_QUERIES_IN_HISTORY - 1
|
54
47
|
: queriesHistoryInitial.length - 1,
|
55
48
|
},
|
56
|
-
runAction: readSavedSettingsValue(QUERY_INITIAL_RUN_ACTION_KEY, RUN_ACTIONS_VALUES.script),
|
57
49
|
monacoHotKey: null,
|
58
50
|
};
|
59
51
|
|
@@ -79,22 +71,14 @@ const executeQuery: Reducer<ExecuteQueryState, ExecuteQueryAction> = (
|
|
79
71
|
error: undefined,
|
80
72
|
};
|
81
73
|
}
|
82
|
-
// 401 Unauthorized error is handled by GenericAPI
|
83
74
|
case SEND_QUERY.FAILURE: {
|
84
75
|
return {
|
85
76
|
...state,
|
86
|
-
error: action.error
|
77
|
+
error: parseQueryError(action.error),
|
87
78
|
loading: false,
|
88
79
|
};
|
89
80
|
}
|
90
81
|
|
91
|
-
case SELECT_RUN_ACTION: {
|
92
|
-
return {
|
93
|
-
...state,
|
94
|
-
runAction: action.data,
|
95
|
-
};
|
96
|
-
}
|
97
|
-
|
98
82
|
case CHANGE_USER_INPUT: {
|
99
83
|
return {
|
100
84
|
...state,
|
@@ -156,15 +140,13 @@ const executeQuery: Reducer<ExecuteQueryState, ExecuteQueryAction> = (
|
|
156
140
|
}
|
157
141
|
};
|
158
142
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
}: {
|
164
|
-
|
165
|
-
|
166
|
-
action: Actions;
|
167
|
-
}) => {
|
143
|
+
interface SendQueryParams extends QueryRequestParams {
|
144
|
+
mode?: QueryModes;
|
145
|
+
}
|
146
|
+
|
147
|
+
export const sendExecuteQuery = ({query, database, mode}: SendQueryParams) => {
|
148
|
+
const action: ExecuteActions = mode ? `execute-${mode}` : 'execute';
|
149
|
+
|
168
150
|
return createApiRequest({
|
169
151
|
request: window.api.sendQuery({
|
170
152
|
schema: 'modern',
|
@@ -185,13 +167,6 @@ export const saveQueryToHistory = (query: string) => {
|
|
185
167
|
} as const;
|
186
168
|
};
|
187
169
|
|
188
|
-
export const selectRunAction = (value: RunAction) => {
|
189
|
-
return {
|
190
|
-
type: SELECT_RUN_ACTION,
|
191
|
-
data: value,
|
192
|
-
} as const;
|
193
|
-
};
|
194
|
-
|
195
170
|
export const goToPreviousQuery = () => {
|
196
171
|
return {
|
197
172
|
type: GO_TO_PREVIOUS_QUERY,
|
@@ -1,21 +1,33 @@
|
|
1
|
+
import type {Reducer} from 'redux';
|
2
|
+
import type {ExplainPlanNodeData, GraphNode, Link} from '@gravity-ui/paranoid';
|
1
3
|
import _ from 'lodash';
|
2
4
|
|
3
5
|
import '../../services/api';
|
6
|
+
import type {ExplainActions} from '../../types/api/query';
|
7
|
+
import type {
|
8
|
+
ExplainQueryAction,
|
9
|
+
ExplainQueryState,
|
10
|
+
PreparedExplainResponse,
|
11
|
+
} from '../../types/store/explainQuery';
|
12
|
+
import type {QueryRequestParams, QueryModes} from '../../types/store/query';
|
4
13
|
|
5
|
-
import {getExplainNodeId, getMetaForExplainNode} from '../../utils';
|
6
14
|
import {preparePlan} from '../../utils/prepareQueryExplain';
|
7
|
-
import {parseQueryAPIExplainResponse} from '../../utils/query';
|
15
|
+
import {parseQueryAPIExplainResponse, parseQueryExplainPlan} from '../../utils/query';
|
16
|
+
import {parseQueryError} from '../../utils/error';
|
8
17
|
|
9
18
|
import {createRequestActionTypes, createApiRequest} from '../utils';
|
10
19
|
|
11
|
-
const GET_EXPLAIN_QUERY = createRequestActionTypes('query', 'GET_EXPLAIN_QUERY');
|
12
|
-
const GET_EXPLAIN_QUERY_AST = createRequestActionTypes('query', 'GET_EXPLAIN_QUERY_AST');
|
20
|
+
export const GET_EXPLAIN_QUERY = createRequestActionTypes('query', 'GET_EXPLAIN_QUERY');
|
21
|
+
export const GET_EXPLAIN_QUERY_AST = createRequestActionTypes('query', 'GET_EXPLAIN_QUERY_AST');
|
13
22
|
|
14
23
|
const initialState = {
|
15
24
|
loading: false,
|
16
25
|
};
|
17
26
|
|
18
|
-
const explainQuery
|
27
|
+
const explainQuery: Reducer<ExplainQueryState, ExplainQueryAction> = (
|
28
|
+
state = initialState,
|
29
|
+
action,
|
30
|
+
) => {
|
19
31
|
switch (action.type) {
|
20
32
|
case GET_EXPLAIN_QUERY.REQUEST: {
|
21
33
|
return {
|
@@ -36,11 +48,10 @@ const explainQuery = (state = initialState, action) => {
|
|
36
48
|
error: undefined,
|
37
49
|
};
|
38
50
|
}
|
39
|
-
// 401 Unauthorized error is handled by GenericAPI
|
40
51
|
case GET_EXPLAIN_QUERY.FAILURE: {
|
41
52
|
return {
|
42
53
|
...state,
|
43
|
-
error: action.error
|
54
|
+
error: parseQueryError(action.error),
|
44
55
|
loading: false,
|
45
56
|
};
|
46
57
|
}
|
@@ -63,7 +74,7 @@ const explainQuery = (state = initialState, action) => {
|
|
63
74
|
case GET_EXPLAIN_QUERY_AST.FAILURE: {
|
64
75
|
return {
|
65
76
|
...state,
|
66
|
-
errorAst: action.error
|
77
|
+
errorAst: parseQueryError(action.error),
|
67
78
|
loadingAst: false,
|
68
79
|
};
|
69
80
|
}
|
@@ -73,7 +84,7 @@ const explainQuery = (state = initialState, action) => {
|
|
73
84
|
}
|
74
85
|
};
|
75
86
|
|
76
|
-
export const getExplainQueryAst = ({query, database}) => {
|
87
|
+
export const getExplainQueryAst = ({query, database}: QueryRequestParams) => {
|
77
88
|
return createApiRequest({
|
78
89
|
request: window.api.getExplainQueryAst(query, database),
|
79
90
|
actions: GET_EXPLAIN_QUERY_AST,
|
@@ -82,74 +93,48 @@ export const getExplainQueryAst = ({query, database}) => {
|
|
82
93
|
};
|
83
94
|
|
84
95
|
export const explainVersions = {
|
85
|
-
v1: '0.1',
|
86
96
|
v2: '0.2',
|
87
97
|
};
|
88
98
|
|
89
99
|
const supportedExplainQueryVersions = Object.values(explainVersions);
|
90
100
|
|
91
|
-
|
101
|
+
interface ExplainQueryParams extends QueryRequestParams {
|
102
|
+
mode?: QueryModes;
|
103
|
+
}
|
104
|
+
|
105
|
+
export const getExplainQuery = ({query, database, mode}: ExplainQueryParams) => {
|
106
|
+
const action: ExplainActions = mode ? `explain-${mode}` : 'explain';
|
107
|
+
|
92
108
|
return createApiRequest({
|
93
|
-
request: window.api.getExplainQuery(query, database),
|
109
|
+
request: window.api.getExplainQuery(query, database, action),
|
94
110
|
actions: GET_EXPLAIN_QUERY,
|
95
|
-
dataHandler: (response) => {
|
96
|
-
const {plan:
|
111
|
+
dataHandler: (response): PreparedExplainResponse => {
|
112
|
+
const {plan: rawPlan, ast} = parseQueryAPIExplainResponse(response);
|
97
113
|
|
98
|
-
if (!
|
114
|
+
if (!rawPlan) {
|
99
115
|
return {ast};
|
100
116
|
}
|
101
117
|
|
102
|
-
|
103
|
-
let nodes = [];
|
104
|
-
const {tables, meta, Plan} = result;
|
118
|
+
const {tables, meta, Plan} = parseQueryExplainPlan(rawPlan);
|
105
119
|
|
106
120
|
if (supportedExplainQueryVersions.indexOf(meta.version) === -1) {
|
121
|
+
// Do not prepare plan for not supported versions
|
107
122
|
return {
|
108
|
-
|
109
|
-
|
123
|
+
plan: {
|
124
|
+
pristine: rawPlan,
|
125
|
+
version: meta.version,
|
126
|
+
},
|
127
|
+
ast,
|
110
128
|
};
|
111
129
|
}
|
112
|
-
|
130
|
+
|
131
|
+
let links: Link[] = [];
|
132
|
+
let nodes: GraphNode<ExplainPlanNodeData>[] = [];
|
133
|
+
|
134
|
+
if (Plan) {
|
113
135
|
const preparedPlan = preparePlan(Plan);
|
114
136
|
links = preparedPlan.links;
|
115
137
|
nodes = preparedPlan.nodes;
|
116
|
-
} else {
|
117
|
-
_.forEach(tables, (table) => {
|
118
|
-
nodes.push({
|
119
|
-
name: table.name,
|
120
|
-
});
|
121
|
-
|
122
|
-
const tableTypes = {};
|
123
|
-
|
124
|
-
const {reads = [], writes = []} = table;
|
125
|
-
let prevNodeId = table.name;
|
126
|
-
|
127
|
-
_.forEach([...reads, ...writes], (node) => {
|
128
|
-
if (tableTypes[node.type]) {
|
129
|
-
tableTypes[node.type] = tableTypes[node.type] + 1;
|
130
|
-
} else {
|
131
|
-
tableTypes[node.type] = 1;
|
132
|
-
}
|
133
|
-
|
134
|
-
const nodeId = getExplainNodeId(
|
135
|
-
table.name,
|
136
|
-
node.type,
|
137
|
-
tableTypes[node.type],
|
138
|
-
);
|
139
|
-
|
140
|
-
links.push({
|
141
|
-
from: prevNodeId,
|
142
|
-
to: nodeId,
|
143
|
-
});
|
144
|
-
nodes.push({
|
145
|
-
name: nodeId,
|
146
|
-
meta: getMetaForExplainNode(node),
|
147
|
-
id: nodeId,
|
148
|
-
});
|
149
|
-
|
150
|
-
prevNodeId = nodeId;
|
151
|
-
});
|
152
|
-
});
|
153
138
|
}
|
154
139
|
|
155
140
|
return {
|
@@ -158,7 +143,7 @@ export const getExplainQuery = ({query, database}) => {
|
|
158
143
|
nodes,
|
159
144
|
tables,
|
160
145
|
version: meta.version,
|
161
|
-
pristine:
|
146
|
+
pristine: rawPlan,
|
162
147
|
},
|
163
148
|
ast,
|
164
149
|
};
|
@@ -3,14 +3,16 @@ import {
|
|
3
3
|
SAVED_QUERIES_KEY,
|
4
4
|
THEME_KEY,
|
5
5
|
TENANT_INITIAL_TAB_KEY,
|
6
|
-
QUERY_INITIAL_RUN_ACTION_KEY,
|
7
6
|
INVERTED_DISKS_KEY,
|
8
7
|
ASIDE_HEADER_COMPACT_KEY,
|
9
8
|
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
|
10
9
|
PARTITIONS_SELECTED_COLUMNS_KEY,
|
10
|
+
QUERY_INITIAL_MODE_KEY,
|
11
|
+
ENABLE_QUERY_MODES_FOR_EXPLAIN,
|
11
12
|
} from '../../utils/constants';
|
12
13
|
import '../../services/api';
|
13
|
-
import {getValueFromLS} from '../../utils/utils';
|
14
|
+
import {getValueFromLS, parseJson} from '../../utils/utils';
|
15
|
+
import {QueryModes} from '../../types/store/query';
|
14
16
|
|
15
17
|
const CHANGE_PROBLEM_FILTER = 'settings/CHANGE_PROBLEM_FILTER';
|
16
18
|
const SET_SETTING_VALUE = 'settings/SET_VALUE';
|
@@ -44,9 +46,13 @@ export const initialState = {
|
|
44
46
|
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
|
45
47
|
'false',
|
46
48
|
),
|
49
|
+
[ENABLE_QUERY_MODES_FOR_EXPLAIN]: readSavedSettingsValue(
|
50
|
+
ENABLE_QUERY_MODES_FOR_EXPLAIN,
|
51
|
+
'false',
|
52
|
+
),
|
47
53
|
[SAVED_QUERIES_KEY]: readSavedSettingsValue(SAVED_QUERIES_KEY, '[]'),
|
48
54
|
[TENANT_INITIAL_TAB_KEY]: readSavedSettingsValue(TENANT_INITIAL_TAB_KEY),
|
49
|
-
[
|
55
|
+
[QUERY_INITIAL_MODE_KEY]: readSavedSettingsValue(QUERY_INITIAL_MODE_KEY, QueryModes.script),
|
50
56
|
[ASIDE_HEADER_COMPACT_KEY]: readSavedSettingsValue(
|
51
57
|
ASIDE_HEADER_COMPACT_KEY,
|
52
58
|
legacyAsideNavCompactState || 'true',
|
@@ -97,6 +103,15 @@ export const getSettingValue = (state, name) => {
|
|
97
103
|
return state.settings.userSettings[name];
|
98
104
|
};
|
99
105
|
|
106
|
+
/**
|
107
|
+
* Returns parsed settings value.
|
108
|
+
* If value cannot be parsed, returns initially stored string
|
109
|
+
*/
|
110
|
+
export const getParsedSettingValue = (state, name) => {
|
111
|
+
const value = state.settings.userSettings[name];
|
112
|
+
return parseJson(value);
|
113
|
+
};
|
114
|
+
|
100
115
|
export const changeFilter = (filter) => {
|
101
116
|
return {
|
102
117
|
type: CHANGE_PROBLEM_FILTER,
|
package/dist/types/api/error.ts
CHANGED
@@ -4,3 +4,17 @@ export interface IResponseError {
|
|
4
4
|
statusText?: string;
|
5
5
|
isCancelled?: boolean;
|
6
6
|
}
|
7
|
+
|
8
|
+
// Error on offline backend or requests blocked by CORS
|
9
|
+
export interface NetworkError {
|
10
|
+
code?: unknown;
|
11
|
+
columnNumber?: unknown;
|
12
|
+
config?: Record<string, unknown>;
|
13
|
+
description?: unknown;
|
14
|
+
fileName?: unknown;
|
15
|
+
lineNumber?: unknown;
|
16
|
+
message?: 'Network Error';
|
17
|
+
name?: string;
|
18
|
+
number?: unknown;
|
19
|
+
stack?: string;
|
20
|
+
}
|