ydb-embedded-ui 3.4.5 → 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.
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
  }