ydb-embedded-ui 3.4.5 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/components/InfoViewer/formatters/table.ts +6 -0
  3. package/dist/components/TruncatedQuery/TruncatedQuery.js +1 -1
  4. package/dist/components/TruncatedQuery/TruncatedQuery.scss +7 -3
  5. package/dist/containers/Node/{NodePages.js → NodePages.ts} +1 -1
  6. package/dist/containers/Tablet/TabletControls/TabletControls.tsx +2 -2
  7. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +11 -43
  8. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx +19 -17
  9. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.ts +192 -37
  10. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +20 -14
  11. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +49 -12
  12. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +37 -18
  13. package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.tsx +3 -3
  14. package/dist/containers/Tenant/QueryEditor/QueryDuration/QueryDuration.scss +8 -0
  15. package/dist/containers/Tenant/QueryEditor/QueryDuration/QueryDuration.tsx +21 -0
  16. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +58 -82
  17. package/dist/containers/Tenant/QueryEditor/QueryEditor.scss +0 -33
  18. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/OldQueryEditorControls.tsx +83 -0
  19. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.scss +57 -0
  20. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/QueryEditorControls.tsx +84 -0
  21. package/dist/containers/Tenant/QueryEditor/QueryEditorControls/shared.ts +23 -0
  22. package/dist/containers/Tenant/QueryEditor/QueryExplain/QueryExplain.js +12 -23
  23. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.js +4 -6
  24. package/dist/containers/Tenant/QueryEditor/i18n/en.json +3 -0
  25. package/dist/containers/Tenant/QueryEditor/i18n/index.ts +11 -0
  26. package/dist/containers/Tenant/QueryEditor/i18n/ru.json +3 -0
  27. package/dist/containers/UserSettings/UserSettings.tsx +30 -1
  28. package/dist/routes.ts +1 -1
  29. package/dist/services/api.d.ts +4 -3
  30. package/dist/services/api.js +5 -5
  31. package/dist/store/reducers/{executeQuery.js → executeQuery.ts} +48 -43
  32. package/dist/store/reducers/executeTopQueries.ts +5 -1
  33. package/dist/store/reducers/{explainQuery.js → explainQuery.ts} +44 -59
  34. package/dist/store/reducers/{olapStats.js → olapStats.ts} +8 -18
  35. package/dist/store/reducers/settings.js +19 -4
  36. package/dist/store/reducers/storage.js +5 -7
  37. package/dist/types/api/error.ts +14 -0
  38. package/dist/types/api/query.ts +227 -115
  39. package/dist/types/api/schema.ts +523 -3
  40. package/dist/types/common.ts +1 -0
  41. package/dist/types/store/executeQuery.ts +38 -0
  42. package/dist/types/store/explainQuery.ts +38 -0
  43. package/dist/types/store/olapStats.ts +14 -0
  44. package/dist/types/store/query.ts +23 -3
  45. package/dist/utils/constants.ts +2 -1
  46. package/dist/utils/error.ts +25 -0
  47. package/dist/utils/index.js +0 -49
  48. package/dist/utils/prepareQueryExplain.ts +7 -24
  49. package/dist/utils/query.test.ts +148 -213
  50. package/dist/utils/query.ts +68 -90
  51. package/dist/utils/timeParsers/formatDuration.ts +30 -12
  52. package/dist/utils/timeParsers/i18n/en.json +9 -5
  53. package/dist/utils/timeParsers/i18n/ru.json +9 -5
  54. package/dist/utils/timeParsers/parsers.ts +9 -0
  55. package/dist/utils/utils.js +1 -2
  56. package/package.json +1 -1
@@ -0,0 +1,83 @@
1
+ import {Button, DropdownMenu} from '@gravity-ui/uikit';
2
+ import {useMemo} from 'react';
3
+
4
+ import {QueryModes} from '../../../../types/store/query';
5
+ import {Icon} from '../../../../components/Icon';
6
+
7
+ import SaveQuery from '../SaveQuery/SaveQuery';
8
+
9
+ import {b, QueryEditorControlsProps, QueryModeSelectorTitles} from './shared';
10
+
11
+ import './QueryEditorControls.scss';
12
+
13
+ export const OldQueryEditorControls = ({
14
+ onRunButtonClick,
15
+ runIsLoading,
16
+ onExplainButtonClick,
17
+ explainIsLoading,
18
+ onSaveQueryClick,
19
+ savedQueries,
20
+ disabled,
21
+ onUpdateQueryMode,
22
+ queryMode,
23
+ }: QueryEditorControlsProps) => {
24
+ const runModeSelectorMenuItems = useMemo(() => {
25
+ return Object.entries(QueryModeSelectorTitles).map(([mode, title]) => {
26
+ return {
27
+ text: `Run ${title}`,
28
+ action: () => {
29
+ onUpdateQueryMode(mode as QueryModes);
30
+ },
31
+ };
32
+ });
33
+ }, [onUpdateQueryMode]);
34
+
35
+ return (
36
+ <div className={b()}>
37
+ <div className={b('left')}>
38
+ <div className={b('run')}>
39
+ <Button
40
+ onClick={() => onRunButtonClick(queryMode)}
41
+ view="action"
42
+ pin="round-brick"
43
+ disabled={disabled}
44
+ loading={runIsLoading}
45
+ >
46
+ <Icon name="startPlay" viewBox="0 0 16 16" width={16} height={16} />
47
+ {`Run ${QueryModeSelectorTitles[queryMode]}`}
48
+ </Button>
49
+ <DropdownMenu
50
+ items={runModeSelectorMenuItems}
51
+ popupClassName={b('select-query-action-popup')}
52
+ switcher={
53
+ <Button
54
+ view="action"
55
+ pin="brick-round"
56
+ disabled={disabled}
57
+ loading={runIsLoading}
58
+ className={b('select-query-action')}
59
+ >
60
+ <Icon name="chevron-down" width={16} height={16} />
61
+ </Button>
62
+ }
63
+ />
64
+ </div>
65
+ <Button
66
+ onClick={() => {
67
+ // Without defined query mode it sends 'explain' action
68
+ onExplainButtonClick();
69
+ }}
70
+ disabled={disabled}
71
+ loading={explainIsLoading}
72
+ >
73
+ Explain
74
+ </Button>
75
+ </div>
76
+ <SaveQuery
77
+ savedQueries={savedQueries}
78
+ onSaveQuery={onSaveQueryClick}
79
+ saveButtonDisabled={disabled}
80
+ />
81
+ </div>
82
+ );
83
+ };
@@ -0,0 +1,57 @@
1
+ .ydb-query-editor-controls {
2
+ display: flex;
3
+ flex: 0 0 40px;
4
+ justify-content: space-between;
5
+ align-items: flex-end;
6
+
7
+ min-height: 40px;
8
+ padding: 5px 20px;
9
+
10
+ border-top: 1px solid var(--yc-color-line-generic);
11
+ border-bottom: 1px solid var(--yc-color-line-generic);
12
+ background-color: var(--yc-color-base-background);
13
+ gap: 24px;
14
+
15
+ &__left {
16
+ display: flex;
17
+ gap: 12px;
18
+ }
19
+
20
+ &__run {
21
+ display: flex;
22
+ align-items: center;
23
+ .yc-select__option-text {
24
+ display: none;
25
+ }
26
+
27
+ .yc-button__text {
28
+ display: flex;
29
+ justify-content: center;
30
+ align-items: center;
31
+ gap: 8px;
32
+ }
33
+ }
34
+
35
+ &__select-query-action {
36
+ margin-left: 2px;
37
+ }
38
+
39
+ &__mode-selector {
40
+ &__popup {
41
+ width: 120px;
42
+ }
43
+
44
+ &__button {
45
+ width: 120px;
46
+ margin-left: 2px;
47
+ }
48
+
49
+ &__button-content {
50
+ display: flex;
51
+ justify-content: space-between;
52
+ align-items: center;
53
+
54
+ width: 100px;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,84 @@
1
+ import {Button, DropdownMenu} from '@gravity-ui/uikit';
2
+ import {useMemo} from 'react';
3
+
4
+ import {QueryModes} from '../../../../types/store/query';
5
+ import {Icon} from '../../../../components/Icon';
6
+
7
+ import SaveQuery from '../SaveQuery/SaveQuery';
8
+
9
+ import i18n from '../i18n';
10
+
11
+ import {b, QueryEditorControlsProps, QueryModeSelectorTitles} from './shared';
12
+
13
+ import './QueryEditorControls.scss';
14
+
15
+ export const QueryEditorControls = ({
16
+ onRunButtonClick,
17
+ runIsLoading,
18
+ onExplainButtonClick,
19
+ explainIsLoading,
20
+ onSaveQueryClick,
21
+ savedQueries,
22
+ disabled,
23
+ onUpdateQueryMode,
24
+ queryMode,
25
+ }: QueryEditorControlsProps) => {
26
+ const querySelectorMenuItems = useMemo(() => {
27
+ return Object.entries(QueryModeSelectorTitles).map(([mode, title]) => {
28
+ return {
29
+ text: title,
30
+ action: () => {
31
+ onUpdateQueryMode(mode as QueryModes);
32
+ },
33
+ };
34
+ });
35
+ }, [onUpdateQueryMode]);
36
+
37
+ return (
38
+ <div className={b()}>
39
+ <div className={b('left')}>
40
+ <div className={b('run')}>
41
+ <Button
42
+ onClick={() => {
43
+ onRunButtonClick(queryMode);
44
+ }}
45
+ view="action"
46
+ disabled={disabled}
47
+ loading={runIsLoading}
48
+ >
49
+ <Icon name="startPlay" viewBox="0 0 16 16" width={16} height={16} />
50
+ {'Run'}
51
+ </Button>
52
+ </div>
53
+ <Button
54
+ onClick={() => {
55
+ onExplainButtonClick(queryMode);
56
+ }}
57
+ disabled={disabled}
58
+ loading={explainIsLoading}
59
+ >
60
+ Explain
61
+ </Button>
62
+ <DropdownMenu
63
+ items={querySelectorMenuItems}
64
+ popupClassName={b('mode-selector__popup')}
65
+ switcher={
66
+ <Button className={b('mode-selector__button')}>
67
+ <span className={b('mode-selector__button-content')}>
68
+ {`${i18n('controls.query-mode-selector_type')} ${
69
+ QueryModeSelectorTitles[queryMode]
70
+ }`}
71
+ <Icon name="chevron-down" width={16} height={16} />
72
+ </span>
73
+ </Button>
74
+ }
75
+ />
76
+ </div>
77
+ <SaveQuery
78
+ savedQueries={savedQueries}
79
+ onSaveQuery={onSaveQueryClick}
80
+ saveButtonDisabled={disabled}
81
+ />
82
+ </div>
83
+ );
84
+ };
@@ -0,0 +1,23 @@
1
+ import block from 'bem-cn-lite';
2
+
3
+ import {QueryModes} from '../../../../types/store/query';
4
+
5
+ export const b = block('ydb-query-editor-controls');
6
+
7
+ export const QueryModeSelectorTitles = {
8
+ [QueryModes.script]: 'Script',
9
+ [QueryModes.scan]: 'Scan',
10
+ } as const;
11
+
12
+ export interface QueryEditorControlsProps {
13
+ onRunButtonClick: (mode?: QueryModes) => void;
14
+ runIsLoading: boolean;
15
+ onExplainButtonClick: (mode?: QueryModes) => void;
16
+ explainIsLoading: boolean;
17
+ onSaveQueryClick: (queryName: string) => void;
18
+ savedQueries: unknown;
19
+ disabled: boolean;
20
+ onUpdateQueryMode: (mode: QueryModes) => void;
21
+ queryMode: QueryModes;
22
+ enableQueryModesForExplain: boolean;
23
+ }
@@ -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, version, theme} = props;
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
- if (version === explainVersions.v2) {
68
- paranoid.current = getTopology('graphRoot', data, opts, shapes);
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 <div className={b('text-message')}>There is no explanation for the request</div>;
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
- links && nodes && nodes.length ? (
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,3 @@
1
+ {
2
+ "controls.query-mode-selector_type": "Type:"
3
+ }
@@ -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);
@@ -0,0 +1,3 @@
1
+ {
2
+ "controls.query-mode-selector_type": "Тип:"
3
+ }
@@ -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} = state.settings.userSettings;
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/routes.ts CHANGED
@@ -28,7 +28,7 @@ export const CLUSTER_PAGES = {
28
28
 
29
29
  export function createHref(
30
30
  route: string,
31
- params?: object,
31
+ params?: Record<string, string | number>,
32
32
  query: Record<string | number, string | number | string[] | number[] | undefined> = {},
33
33
  ) {
34
34
  let extendedQuery = query;
@@ -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
- ) => Promise<import('../types/api/query').QueryAPIExplainResponse<'explain'>>;
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').QueryAPIExplainResponse<'explain-ast'>>;
49
+ ) => Promise<import('../types/api/query').ExplainResponse<'explain-ast'>>;
49
50
  getHealthcheckInfo: (
50
51
  database: string,
51
52
  ) => Promise<import('../types/api/healthcheck').HealthCheckAPIResponse>;
@@ -96,9 +96,9 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
96
96
  enums: true,
97
97
  backup: false,
98
98
  private: true,
99
- partition_config: false,
100
- partition_stats: false,
101
- partitioning_info: false,
99
+ partition_config: true,
100
+ partition_stats: true,
101
+ partitioning_info: true,
102
102
  subs: 1,
103
103
  },
104
104
  {concurrentId: concurrentId || `getSchema|${path}`},
@@ -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: 'explain',
214
+ action,
215
215
  timeout: 600000,
216
216
  });
217
217
  }