ydb-embedded-ui 3.5.0 → 4.0.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 +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
|
+
}
|