ydb-embedded-ui 4.5.2 → 4.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/assets/icons/versions.svg +3 -0
  3. package/dist/components/NodeHostWrapper/NodeHostWrapper.tsx +7 -2
  4. package/dist/components/Tablet/Tablet.tsx +17 -3
  5. package/dist/components/TabletsStatistic/TabletsStatistic.tsx +23 -16
  6. package/dist/containers/App/Content.js +8 -4
  7. package/dist/containers/AsideNavigation/AsideNavigation.tsx +4 -50
  8. package/dist/containers/Cluster/Cluster.scss +7 -48
  9. package/dist/containers/Cluster/Cluster.tsx +129 -20
  10. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.scss +34 -17
  11. package/dist/containers/Cluster/ClusterInfo/ClusterInfo.tsx +58 -92
  12. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.scss +48 -0
  13. package/dist/containers/Cluster/ClusterInfoSkeleton/ClusterInfoSkeleton.tsx +34 -0
  14. package/dist/containers/Cluster/utils.tsx +45 -0
  15. package/dist/containers/Header/Header.scss +4 -19
  16. package/dist/containers/Header/Header.tsx +72 -46
  17. package/dist/containers/Header/breadcrumbs.ts +146 -0
  18. package/dist/containers/Node/Node.tsx +25 -29
  19. package/dist/containers/Node/NodePages.ts +10 -6
  20. package/dist/containers/Nodes/Nodes.tsx +0 -16
  21. package/dist/containers/Nodes/getNodesColumns.tsx +1 -1
  22. package/dist/containers/Storage/Storage.js +1 -11
  23. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +11 -3
  24. package/dist/containers/Tablet/Tablet.tsx +40 -4
  25. package/dist/containers/Tablet/TabletInfo/TabletInfo.tsx +2 -2
  26. package/dist/containers/TabletsFilters/TabletsFilters.js +15 -2
  27. package/dist/containers/Tenant/Diagnostics/Consumers/columns/columns.tsx +1 -1
  28. package/dist/containers/Tenant/Diagnostics/Describe/Describe.tsx +1 -1
  29. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +7 -0
  30. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -4
  31. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +5 -3
  32. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +1 -1
  33. package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +4 -6
  34. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +56 -53
  35. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +2 -1
  36. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +11 -13
  37. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +1 -1
  38. package/dist/containers/Tenant/ObjectGeneral/ObjectGeneral.tsx +2 -2
  39. package/dist/containers/Tenant/ObjectSummary/ObjectSummary.tsx +7 -3
  40. package/dist/containers/Tenant/Preview/Preview.js +1 -1
  41. package/dist/containers/Tenant/{QueryEditor/QueryResult/QueryResult.js → Query/ExecuteResult/ExecuteResult.js} +3 -5
  42. package/dist/containers/Tenant/{QueryEditor/QueryResult/QueryResult.scss → Query/ExecuteResult/ExecuteResult.scss} +1 -1
  43. package/dist/containers/Tenant/{QueryEditor/QueryExplain/QueryExplain.js → Query/ExplainResult/ExplainResult.js} +3 -5
  44. package/dist/containers/Tenant/{QueryEditor/QueryExplain/QueryExplain.scss → Query/ExplainResult/ExplainResult.scss} +1 -1
  45. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss +20 -0
  46. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +60 -0
  47. package/dist/containers/Tenant/Query/Query.scss +16 -0
  48. package/dist/containers/Tenant/Query/Query.tsx +73 -0
  49. package/dist/containers/Tenant/{QueryEditor → Query/QueryEditor}/QueryEditor.js +43 -100
  50. package/dist/containers/Tenant/{QueryEditor → Query/QueryEditor}/QueryEditor.scss +7 -23
  51. package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/OldQueryEditorControls.tsx +10 -3
  52. package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/QueryEditorControls.scss +1 -4
  53. package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/QueryEditorControls.tsx +8 -1
  54. package/dist/containers/Tenant/{QueryEditor → Query}/QueryEditorControls/shared.ts +1 -6
  55. package/dist/containers/Tenant/Query/QueryTabs/QueryTabs.tsx +59 -0
  56. package/dist/containers/Tenant/{QueryEditor → Query}/SaveQuery/SaveQuery.js +5 -5
  57. package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.scss +55 -0
  58. package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.tsx +150 -0
  59. package/dist/containers/Tenant/Query/i18n/en.json +12 -0
  60. package/dist/containers/Tenant/Query/i18n/ru.json +12 -0
  61. package/dist/containers/Tenant/Query/utils/getPreparedResult.ts +30 -0
  62. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +1 -1
  63. package/dist/containers/Tenant/Tenant.tsx +4 -25
  64. package/dist/containers/Tenant/TenantPages.tsx +8 -2
  65. package/dist/containers/Tenant/utils/constants.ts +10 -0
  66. package/dist/containers/Tenant/utils/schemaActions.ts +8 -3
  67. package/dist/containers/Tenants/Tenants.js +39 -37
  68. package/dist/containers/Tenants/Tenants.scss +2 -4
  69. package/dist/containers/UserSettings/i18n/en.json +2 -2
  70. package/dist/containers/UserSettings/i18n/ru.json +2 -2
  71. package/dist/containers/UserSettings/settings.ts +4 -4
  72. package/dist/containers/Versions/Versions.scss +0 -4
  73. package/dist/containers/Versions/Versions.tsx +74 -66
  74. package/dist/routes.ts +8 -6
  75. package/dist/services/api.ts +15 -7
  76. package/dist/store/reducers/clusterNodes/clusterNodes.tsx +4 -0
  77. package/dist/store/reducers/executeQuery.ts +1 -1
  78. package/dist/store/reducers/header/header.ts +31 -0
  79. package/dist/store/reducers/header/types.ts +54 -0
  80. package/dist/store/reducers/index.ts +4 -2
  81. package/dist/store/reducers/node/types.ts +2 -0
  82. package/dist/store/reducers/overview/overview.ts +109 -0
  83. package/dist/store/reducers/overview/types.ts +24 -0
  84. package/dist/store/reducers/{schema.ts → schema/schema.ts} +24 -50
  85. package/dist/{types/store/schema.ts → store/reducers/schema/types.ts} +16 -15
  86. package/dist/store/reducers/settings/settings.ts +5 -3
  87. package/dist/store/reducers/tablet.ts +18 -1
  88. package/dist/store/reducers/tenant/constants.ts +6 -0
  89. package/dist/store/reducers/tenant/tenant.ts +21 -2
  90. package/dist/store/reducers/tenant/types.ts +9 -2
  91. package/dist/store/reducers/topic.ts +1 -1
  92. package/dist/store/state-url-mapping.js +4 -1
  93. package/dist/types/api/query.ts +78 -44
  94. package/dist/types/store/explainQuery.ts +2 -2
  95. package/dist/types/store/query.ts +9 -2
  96. package/dist/types/store/tablet.ts +7 -4
  97. package/dist/utils/constants.ts +5 -1
  98. package/dist/utils/nodes.ts +1 -1
  99. package/dist/utils/query.ts +3 -3
  100. package/package.json +2 -1
  101. package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.scss +0 -85
  102. package/dist/containers/Tenant/QueryEditor/QueriesHistory/QueriesHistory.tsx +0 -95
  103. package/dist/containers/Tenant/QueryEditor/SavedQueries/SavedQueries.js +0 -161
  104. package/dist/containers/Tenant/QueryEditor/SavedQueries/SavedQueries.scss +0 -93
  105. package/dist/containers/Tenant/QueryEditor/i18n/en.json +0 -3
  106. package/dist/containers/Tenant/QueryEditor/i18n/ru.json +0 -3
  107. package/dist/store/reducers/header.ts +0 -26
  108. /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/Issues.scss +0 -0
  109. /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/Issues.tsx +0 -0
  110. /package/dist/containers/Tenant/{QueryEditor → Query}/Issues/models.ts +0 -0
  111. /package/dist/containers/Tenant/{QueryEditor → Query}/QueryDuration/QueryDuration.scss +0 -0
  112. /package/dist/containers/Tenant/{QueryEditor → Query}/QueryDuration/QueryDuration.tsx +0 -0
  113. /package/dist/containers/Tenant/{QueryEditor → Query}/SaveQuery/SaveQuery.scss +0 -0
  114. /package/dist/containers/Tenant/{QueryEditor → Query}/i18n/index.ts +0 -0
@@ -5,42 +5,44 @@ import cn from 'bem-cn-lite';
5
5
  import _ from 'lodash';
6
6
  import MonacoEditor from 'react-monaco-editor';
7
7
 
8
- import SplitPane from '../../../components/SplitPane';
9
- import {QueryResultTable} from '../../../components/QueryResultTable';
10
-
11
- import SavedQueries from './SavedQueries/SavedQueries';
12
- import QueryResult from './QueryResult/QueryResult';
13
- import QueryExplain from './QueryExplain/QueryExplain';
14
- import {QueryEditorControls} from './QueryEditorControls/QueryEditorControls';
15
- import {OldQueryEditorControls} from './QueryEditorControls/OldQueryEditorControls';
8
+ import SplitPane from '../../../../components/SplitPane';
9
+ import {QueryResultTable} from '../../../../components/QueryResultTable';
16
10
 
17
11
  import {
18
12
  sendExecuteQuery,
19
- changeUserInput,
20
13
  saveQueryToHistory,
21
14
  goToPreviousQuery,
22
15
  goToNextQuery,
23
16
  MONACO_HOT_KEY_ACTIONS,
24
17
  setMonacoHotKey,
25
- } from '../../../store/reducers/executeQuery';
26
- import {getExplainQuery, getExplainQueryAst} from '../../../store/reducers/explainQuery';
27
- import {getParsedSettingValue, setSettingValue} from '../../../store/reducers/settings/settings';
18
+ } from '../../../../store/reducers/executeQuery';
19
+ import {getExplainQuery, getExplainQueryAst} from '../../../../store/reducers/explainQuery';
20
+ import {getParsedSettingValue, setSettingValue} from '../../../../store/reducers/settings/settings';
21
+ import {setShowPreview} from '../../../../store/reducers/schema/schema';
28
22
  import {
29
23
  DEFAULT_IS_QUERY_RESULT_COLLAPSED,
30
24
  DEFAULT_SIZE_RESULT_PANE_KEY,
31
25
  SAVED_QUERIES_KEY,
32
26
  QUERY_INITIAL_MODE_KEY,
33
- ENABLE_QUERY_MODES_FOR_EXPLAIN,
34
- } from '../../../utils/constants';
27
+ ENABLE_ADDITIONAL_QUERY_MODES,
28
+ } from '../../../../utils/constants';
29
+ import {useSetting} from '../../../../utils/hooks';
30
+ import {QueryModes} from '../../../../types/store/query';
35
31
 
36
- import './QueryEditor.scss';
37
- import QueriesHistory from './QueriesHistory/QueriesHistory';
38
32
  import {
39
33
  PaneVisibilityActionTypes,
40
34
  paneVisibilityToggleReducerCreator,
41
- } from '../utils/paneVisibilityToggleHelpers';
42
- import Preview from '../Preview/Preview';
43
- import {setShowPreview} from '../../../store/reducers/schema';
35
+ } from '../../utils/paneVisibilityToggleHelpers';
36
+ import Preview from '../../Preview/Preview';
37
+
38
+ import {ExecuteResult} from '../ExecuteResult/ExecuteResult';
39
+ import {ExplainResult} from '../ExplainResult/ExplainResult';
40
+ import {QueryEditorControls} from '../QueryEditorControls/QueryEditorControls';
41
+ import {OldQueryEditorControls} from '../QueryEditorControls/OldQueryEditorControls';
42
+
43
+ import {getPreparedResult} from '../utils/getPreparedResult';
44
+
45
+ import './QueryEditor.scss';
44
46
 
45
47
  const TABLE_SETTINGS = {
46
48
  sortable: false,
@@ -64,6 +66,7 @@ const b = cn('query-editor');
64
66
 
65
67
  const propTypes = {
66
68
  sendExecuteQuery: PropTypes.func,
69
+ changeUserInput: PropTypes.func,
67
70
  path: PropTypes.string,
68
71
  response: PropTypes.oneOfType([PropTypes.bool, PropTypes.array]),
69
72
  executeQuery: PropTypes.object,
@@ -83,7 +86,15 @@ function QueryEditor(props) {
83
86
  const [resultType, setResultType] = useState(RESULT_TYPES.EXECUTE);
84
87
 
85
88
  const [isResultLoaded, setIsResultLoaded] = useState(false);
86
- const [queryMode, setQueryMode] = useState(props.initialQueryMode);
89
+ const [queryMode, setQueryMode] = useSetting(QUERY_INITIAL_MODE_KEY);
90
+ const [enableAdditionalQueryModes] = useSetting(ENABLE_ADDITIONAL_QUERY_MODES);
91
+
92
+ useEffect(() => {
93
+ const isNewQueryMode = queryMode !== QueryModes.script && queryMode !== QueryModes.scan;
94
+ if (!enableAdditionalQueryModes && isNewQueryMode) {
95
+ setQueryMode(QueryModes.script);
96
+ }
97
+ }, [enableAdditionalQueryModes, queryMode, setQueryMode]);
87
98
 
88
99
  const [resultVisibilityState, dispatchResultVisibilityState] = useReducer(
89
100
  paneVisibilityToggleReducerCreator(DEFAULT_IS_QUERY_RESULT_COLLAPSED),
@@ -313,11 +324,11 @@ function QueryEditor(props) {
313
324
  />
314
325
  );
315
326
  }
316
- const textResults = getPreparedResult();
327
+ const textResults = getPreparedResult(data);
317
328
  const disabled = !textResults.length || resultType !== RESULT_TYPES.EXECUTE;
318
329
 
319
330
  return data || error ? (
320
- <QueryResult
331
+ <ExecuteResult
321
332
  result={content}
322
333
  stats={stats}
323
334
  error={error}
@@ -337,7 +348,7 @@ function QueryEditor(props) {
337
348
  } = props;
338
349
 
339
350
  return (
340
- <QueryExplain
351
+ <ExplainResult
341
352
  error={error}
342
353
  explain={data}
343
354
  astQuery={handleAstQuery}
@@ -425,46 +436,6 @@ function QueryEditor(props) {
425
436
  return queries.length - 1 === currentIndex;
426
437
  };
427
438
 
428
- const renderHistoryNavigation = () => {
429
- const {changeUserInput} = props;
430
- return (
431
- <div className={b('history-controls')}>
432
- <QueriesHistory changeUserInput={changeUserInput} />
433
- </div>
434
- );
435
- };
436
-
437
- const getPreparedResult = () => {
438
- const {
439
- executeQuery: {data},
440
- } = props;
441
- const columnDivider = '\t';
442
- const rowDivider = '\n';
443
-
444
- if (!data?.result?.length) {
445
- return '';
446
- }
447
-
448
- const columnHeaders = Object.keys(data.result[0]);
449
- const rows = [columnHeaders].concat(data.result);
450
-
451
- return rows
452
- .map((item) => {
453
- const row = [];
454
-
455
- for (const field in item) {
456
- if (typeof item[field] === 'object' || Array.isArray(item[field])) {
457
- row.push(JSON.stringify(item[field]));
458
- } else {
459
- row.push(item[field]);
460
- }
461
- }
462
-
463
- return row.join(columnDivider);
464
- })
465
- .join(rowDivider);
466
- };
467
-
468
439
  const onChangeWindow = _.throttle(() => {
469
440
  updateEditor();
470
441
  }, 100);
@@ -502,23 +473,10 @@ function QueryEditor(props) {
502
473
  setSettingValue(SAVED_QUERIES_KEY, JSON.stringify(newSavedQueries));
503
474
  };
504
475
 
505
- const onDeleteQueryHandler = (queryName) => {
506
- const {savedQueries = [], setSettingValue} = props;
507
- const newSavedQueries = savedQueries.filter(
508
- (el) => el.name.toLowerCase() !== queryName.toLowerCase(),
509
- );
510
- setSettingValue(SAVED_QUERIES_KEY, JSON.stringify(newSavedQueries));
511
- };
512
-
513
- const onUpdateQueryMode = (mode) => {
514
- setQueryMode(mode);
515
- props.setSettingValue(QUERY_INITIAL_MODE_KEY, mode);
516
- };
517
-
518
476
  const renderControls = () => {
519
- const {executeQuery, explainQuery, savedQueries, enableQueryModesForExplain} = props;
477
+ const {executeQuery, explainQuery, savedQueries} = props;
520
478
 
521
- if (enableQueryModesForExplain) {
479
+ if (enableAdditionalQueryModes) {
522
480
  return (
523
481
  <QueryEditorControls
524
482
  onRunButtonClick={handleSendExecuteClick}
@@ -528,7 +486,7 @@ function QueryEditor(props) {
528
486
  onSaveQueryClick={onSaveQueryHandler}
529
487
  savedQueries={savedQueries}
530
488
  disabled={!executeQuery.input}
531
- onUpdateQueryMode={onUpdateQueryMode}
489
+ onUpdateQueryMode={setQueryMode}
532
490
  queryMode={queryMode}
533
491
  />
534
492
  );
@@ -543,27 +501,12 @@ function QueryEditor(props) {
543
501
  onSaveQueryClick={onSaveQueryHandler}
544
502
  savedQueries={savedQueries}
545
503
  disabled={!executeQuery.input}
546
- onUpdateQueryMode={onUpdateQueryMode}
504
+ onUpdateQueryMode={setQueryMode}
547
505
  queryMode={queryMode}
548
506
  />
549
507
  );
550
508
  };
551
509
 
552
- const renderUpperControls = () => {
553
- const {savedQueries, changeUserInput} = props;
554
-
555
- return (
556
- <div className={b('upper-controls')}>
557
- {renderHistoryNavigation()}
558
- <SavedQueries
559
- savedQueries={savedQueries}
560
- changeUserInput={changeUserInput}
561
- onDeleteQuery={onDeleteQueryHandler}
562
- />
563
- </div>
564
- );
565
- };
566
-
567
510
  const {executeQuery, theme} = props;
568
511
  const result = renderResult();
569
512
 
@@ -578,7 +521,11 @@ function QueryEditor(props) {
578
521
  collapsedSizes={[100, 0]}
579
522
  onSplitStartDragAdditional={onSplitStartDragAdditional}
580
523
  >
581
- <div className={b('pane-wrapper')}>
524
+ <div
525
+ className={b('pane-wrapper', {
526
+ top: true,
527
+ })}
528
+ >
582
529
  <div className={b('monaco-wrapper')}>
583
530
  <div className={b('monaco')}>
584
531
  <MonacoEditor
@@ -592,7 +539,6 @@ function QueryEditor(props) {
592
539
  </div>
593
540
  </div>
594
541
  {renderControls()}
595
- {renderUpperControls()}
596
542
  </div>
597
543
  <div className={b('pane-wrapper')}>
598
544
  {props.showPreview ? renderPreview() : result}
@@ -607,8 +553,6 @@ const mapStateToProps = (state) => {
607
553
  executeQuery: state.executeQuery,
608
554
  explainQuery: state.explainQuery,
609
555
  savedQueries: getParsedSettingValue(state, SAVED_QUERIES_KEY),
610
- initialQueryMode: getParsedSettingValue(state, QUERY_INITIAL_MODE_KEY),
611
- enableQueryModesForExplain: getParsedSettingValue(state, ENABLE_QUERY_MODES_FOR_EXPLAIN),
612
556
  showPreview: state.schema.showPreview,
613
557
  currentSchema: state.schema.currentSchema,
614
558
  monacoHotKey: state.executeQuery?.monacoHotKey,
@@ -617,7 +561,6 @@ const mapStateToProps = (state) => {
617
561
 
618
562
  const mapDispatchToProps = {
619
563
  sendExecuteQuery,
620
- changeUserInput,
621
564
  saveQueryToHistory,
622
565
  goToPreviousQuery,
623
566
  goToNextQuery,
@@ -1,4 +1,4 @@
1
- @import '../../../styles/mixins.scss';
1
+ @import '../../../../styles/mixins.scss';
2
2
 
3
3
  .query-editor {
4
4
  position: relative;
@@ -24,7 +24,8 @@
24
24
 
25
25
  width: 100%;
26
26
  height: 100%;
27
- padding-top: 9px;
27
+
28
+ border: 1px solid var(--yc-color-line-generic);
28
29
  }
29
30
 
30
31
  &__monaco-wrapper {
@@ -40,28 +41,11 @@
40
41
  flex-direction: column;
41
42
 
42
43
  background-color: var(--yc-color-base-background);
43
- }
44
-
45
- &__upper-controls {
46
- position: absolute;
47
- top: -38px;
48
- right: 20px;
49
-
50
- display: flex;
51
- gap: 12px;
52
-
53
- justify-content: flex-end;
54
- align-items: center;
55
- }
56
-
57
- &__history-controls {
58
- display: flex;
59
- align-items: center;
60
- }
61
44
 
62
- &__history-label {
63
- margin-right: 8px;
45
+ &_top {
46
+ padding: 0 16px;
64
47
 
65
- color: var(--yc-color-text-secondary);
48
+ border-bottom: 1px solid var(--yc-color-line-generic);
49
+ }
66
50
  }
67
51
  }
@@ -6,10 +6,17 @@ import {Icon} from '../../../../components/Icon';
6
6
 
7
7
  import SaveQuery from '../SaveQuery/SaveQuery';
8
8
 
9
- import {b, QueryEditorControlsProps, QueryModeSelectorTitles} from './shared';
9
+ import {QueryEditorControlsProps, b} from './shared';
10
10
 
11
11
  import './QueryEditorControls.scss';
12
12
 
13
+ type OldQueryModes = QueryModes.script | QueryModes.scan;
14
+
15
+ export const QueryModeSelectorTitles = {
16
+ [QueryModes.script]: 'Script',
17
+ [QueryModes.scan]: 'Scan',
18
+ } as const;
19
+
13
20
  export const OldQueryEditorControls = ({
14
21
  onRunButtonClick,
15
22
  runIsLoading,
@@ -26,7 +33,7 @@ export const OldQueryEditorControls = ({
26
33
  return {
27
34
  text: `Run ${title}`,
28
35
  action: () => {
29
- onUpdateQueryMode(mode as QueryModes);
36
+ onUpdateQueryMode(mode as OldQueryModes);
30
37
  },
31
38
  };
32
39
  });
@@ -44,7 +51,7 @@ export const OldQueryEditorControls = ({
44
51
  loading={runIsLoading}
45
52
  >
46
53
  <Icon name="startPlay" viewBox="0 0 16 16" width={16} height={16} />
47
- {`Run ${QueryModeSelectorTitles[queryMode]}`}
54
+ {`Run ${QueryModeSelectorTitles[queryMode as OldQueryModes]}`}
48
55
  </Button>
49
56
  <DropdownMenu
50
57
  items={runModeSelectorMenuItems}
@@ -5,11 +5,8 @@
5
5
  align-items: flex-end;
6
6
 
7
7
  min-height: 40px;
8
- padding: 5px 20px;
8
+ padding: 5px 0px;
9
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
10
  gap: 24px;
14
11
 
15
12
  &__left {
@@ -8,10 +8,17 @@ import SaveQuery from '../SaveQuery/SaveQuery';
8
8
 
9
9
  import i18n from '../i18n';
10
10
 
11
- import {b, QueryEditorControlsProps, QueryModeSelectorTitles} from './shared';
11
+ import {QueryEditorControlsProps, b} from './shared';
12
12
 
13
13
  import './QueryEditorControls.scss';
14
14
 
15
+ export const QueryModeSelectorTitles = {
16
+ [QueryModes.script]: 'Script',
17
+ [QueryModes.scan]: 'Scan',
18
+ [QueryModes.data]: 'Data',
19
+ [QueryModes.query]: 'Query',
20
+ } as const;
21
+
15
22
  export const QueryEditorControls = ({
16
23
  onRunButtonClick,
17
24
  runIsLoading,
@@ -1,14 +1,9 @@
1
1
  import block from 'bem-cn-lite';
2
2
 
3
- import {QueryModes} from '../../../../types/store/query';
3
+ import type {QueryModes} from '../../../../types/store/query';
4
4
 
5
5
  export const b = block('ydb-query-editor-controls');
6
6
 
7
- export const QueryModeSelectorTitles = {
8
- [QueryModes.script]: 'Script',
9
- [QueryModes.scan]: 'Scan',
10
- } as const;
11
-
12
7
  export interface QueryEditorControlsProps {
13
8
  onRunButtonClick: (mode?: QueryModes) => void;
14
9
  runIsLoading: boolean;
@@ -0,0 +1,59 @@
1
+ import {useLocation} from 'react-router';
2
+
3
+ import {Tabs} from '@gravity-ui/uikit';
4
+
5
+ import type {TenantQueryTab} from '../../../../store/reducers/tenant/types';
6
+ import {TENANT_QUERY_TABS_ID} from '../../../../store/reducers/tenant/constants';
7
+ import {InternalLink} from '../../../../components/InternalLink/InternalLink';
8
+ import {parseQuery} from '../../../../routes';
9
+
10
+ import {TenantTabsGroups, getTenantPath} from '../../TenantPages';
11
+
12
+ import i18n from '../i18n';
13
+
14
+ const newQuery = {
15
+ id: TENANT_QUERY_TABS_ID.newQuery,
16
+ title: i18n('tabs.newQuery'),
17
+ };
18
+ const history = {
19
+ id: TENANT_QUERY_TABS_ID.history,
20
+ title: i18n('tabs.history'),
21
+ };
22
+ const saved = {
23
+ id: TENANT_QUERY_TABS_ID.saved,
24
+ title: i18n('tabs.saved'),
25
+ };
26
+
27
+ const queryEditorTabs = [newQuery, history, saved];
28
+
29
+ interface QueryEditorTabsProps {
30
+ className?: string;
31
+ activeTab?: TenantQueryTab;
32
+ }
33
+
34
+ export const QueryTabs = ({className, activeTab}: QueryEditorTabsProps) => {
35
+ const location = useLocation();
36
+ const queryParams = parseQuery(location);
37
+
38
+ return (
39
+ <div className={className}>
40
+ <Tabs
41
+ size="l"
42
+ allowNotSelected={true}
43
+ activeTab={activeTab}
44
+ items={queryEditorTabs}
45
+ wrapTo={({id}, node) => {
46
+ const path = getTenantPath({
47
+ ...queryParams,
48
+ [TenantTabsGroups.queryTab]: id,
49
+ });
50
+ return (
51
+ <InternalLink to={path} key={id}>
52
+ {node}
53
+ </InternalLink>
54
+ );
55
+ }}
56
+ />
57
+ </div>
58
+ );
59
+ };
@@ -105,21 +105,21 @@ function SaveQuery({savedQueries, onSaveQuery, saveButtonDisabled}) {
105
105
  const renderSaveButton = (onClick) => {
106
106
  return (
107
107
  <Button onClick={onClick} disabled={saveButtonDisabled}>
108
- Save query
108
+ {queryNameToEdit ? 'Edit query' : 'Save query'}
109
109
  </Button>
110
110
  );
111
111
  };
112
112
 
113
113
  const renderSaveDropdownMenu = () => {
114
114
  const items = [
115
- {
116
- action: onSaveQueryClick,
117
- text: 'Save as new',
118
- },
119
115
  {
120
116
  action: onEditQueryClick,
121
117
  text: 'Edit existing',
122
118
  },
119
+ {
120
+ action: onSaveQueryClick,
121
+ text: 'Save as new',
122
+ },
123
123
  ];
124
124
  return (
125
125
  <DropdownMenu items={items} switcher={renderSaveButton()} popupPlacement={['top']} />
@@ -0,0 +1,55 @@
1
+ @import '../../../../styles/mixins.scss';
2
+
3
+ .ydb-saved-queries {
4
+ $block: &;
5
+
6
+ overflow: auto;
7
+
8
+ height: 100%;
9
+ padding: 0 16px;
10
+
11
+ @include flex-container();
12
+ @include table-styles;
13
+
14
+ &__row {
15
+ cursor: pointer;
16
+
17
+ :hover {
18
+ #{$block}__controls {
19
+ display: flex;
20
+ }
21
+ }
22
+ }
23
+
24
+ &__query-name {
25
+ overflow: hidden;
26
+
27
+ white-space: pre-wrap;
28
+ text-overflow: ellipsis;
29
+ }
30
+
31
+ &__query {
32
+ display: flex;
33
+ flex-direction: row;
34
+ justify-content: space-between;
35
+ align-items: center;
36
+ }
37
+
38
+ &__query-body {
39
+ overflow: hidden;
40
+ flex-grow: 1;
41
+
42
+ max-width: 100%;
43
+
44
+ white-space: pre;
45
+ text-overflow: ellipsis;
46
+ }
47
+
48
+ &__controls {
49
+ display: none;
50
+ }
51
+
52
+ &__dialog-query-name {
53
+ font-weight: 500;
54
+ }
55
+ }
@@ -0,0 +1,150 @@
1
+ import {MouseEvent, useState} from 'react';
2
+ import {useDispatch} from 'react-redux';
3
+ import block from 'bem-cn-lite';
4
+
5
+ import {Dialog, Button} from '@gravity-ui/uikit';
6
+ import DataTable, {Column} from '@gravity-ui/react-data-table';
7
+
8
+ import type {SavedQuery} from '../../../../types/store/query';
9
+ import {setQueryNameToEdit} from '../../../../store/reducers/saveQuery';
10
+ import {setQueryTab} from '../../../../store/reducers/tenant/tenant';
11
+ import {TENANT_QUERY_TABS_ID} from '../../../../store/reducers/tenant/constants';
12
+
13
+ import TruncatedQuery from '../../../../components/TruncatedQuery/TruncatedQuery';
14
+ import {IconWrapper} from '../../../../components/Icon';
15
+
16
+ import {MAX_QUERY_HEIGHT, QUERY_TABLE_SETTINGS} from '../../utils/constants';
17
+
18
+ import i18n from '../i18n';
19
+
20
+ import './SavedQueries.scss';
21
+
22
+ const b = block('ydb-saved-queries');
23
+
24
+ interface DeleteDialogProps {
25
+ visible: boolean;
26
+ queryName: string;
27
+ onCancelClick: VoidFunction;
28
+ onConfirmClick: VoidFunction;
29
+ }
30
+
31
+ const DeleteDialog = ({visible, queryName, onCancelClick, onConfirmClick}: DeleteDialogProps) => {
32
+ return (
33
+ <Dialog
34
+ open={visible}
35
+ hasCloseButton={false}
36
+ size="s"
37
+ onClose={onCancelClick}
38
+ onEnterKeyDown={onConfirmClick}
39
+ >
40
+ <Dialog.Header caption={i18n('delete-dialog.header')} />
41
+ <Dialog.Body className={b('dialog-body')}>
42
+ {i18n('delete-dialog.question')}
43
+ <span className={b('dialog-query-name')}>{` ${queryName}?`}</span>
44
+ </Dialog.Body>
45
+ <Dialog.Footer
46
+ textButtonApply={i18n('delete-dialog.delete')}
47
+ textButtonCancel={i18n('delete-dialog.cancel')}
48
+ onClickButtonCancel={onCancelClick}
49
+ onClickButtonApply={onConfirmClick}
50
+ />
51
+ </Dialog>
52
+ );
53
+ };
54
+
55
+ interface SavedQueriesProps {
56
+ savedQueries: SavedQuery[];
57
+ changeUserInput: (value: {input: string}) => void;
58
+ onDeleteQuery: (queryName: string) => void;
59
+ }
60
+
61
+ export const SavedQueries = ({savedQueries, changeUserInput, onDeleteQuery}: SavedQueriesProps) => {
62
+ const dispatch = useDispatch();
63
+
64
+ const [isDeleteDialogVisible, setIsDeleteDialogVisible] = useState(false);
65
+ const [queryNameToDelete, setQueryNameToDelete] = useState<string>('');
66
+
67
+ const closeDeleteDialog = () => {
68
+ setIsDeleteDialogVisible(false);
69
+ setQueryNameToDelete('');
70
+ };
71
+
72
+ const onCancelDeleteClick = () => {
73
+ closeDeleteDialog();
74
+ };
75
+
76
+ const onConfirmDeleteClick = () => {
77
+ closeDeleteDialog();
78
+ onDeleteQuery(queryNameToDelete);
79
+ setQueryNameToDelete('');
80
+ };
81
+
82
+ const onQueryClick = (queryText: string, queryName: string) => {
83
+ changeUserInput({input: queryText});
84
+ dispatch(setQueryNameToEdit(queryName));
85
+ dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
86
+ };
87
+
88
+ const onDeleteQueryClick = (queryName: string) => {
89
+ return (event: MouseEvent) => {
90
+ event.stopPropagation();
91
+ setIsDeleteDialogVisible(true);
92
+ setQueryNameToDelete(queryName);
93
+ };
94
+ };
95
+
96
+ const columns: Column<SavedQuery>[] = [
97
+ {
98
+ name: 'name',
99
+ header: 'Name',
100
+ render: ({row: query}) => <div className={b('query-name')}>{query.name}</div>,
101
+ width: 200,
102
+ },
103
+ {
104
+ name: 'body',
105
+ header: 'Query Text',
106
+ render: ({row: query}) => (
107
+ <div className={b('query')}>
108
+ <div className={b('query-body')}>
109
+ <TruncatedQuery value={query.body} maxQueryHeight={MAX_QUERY_HEIGHT} />
110
+ </div>
111
+ <span className={b('controls')}>
112
+ <Button view="flat-secondary">
113
+ <IconWrapper name="pencil" viewBox="0 0 24 24" />
114
+ </Button>
115
+ <Button view="flat-secondary" onClick={onDeleteQueryClick(query.name)}>
116
+ <IconWrapper name="trash" viewBox="0 0 24 24" />
117
+ </Button>
118
+ </span>
119
+ </div>
120
+ ),
121
+ sortable: false,
122
+ },
123
+ ];
124
+
125
+ return (
126
+ <>
127
+ <div className={b()}>
128
+ <DataTable
129
+ theme="yandex-cloud"
130
+ columns={columns}
131
+ data={savedQueries}
132
+ settings={QUERY_TABLE_SETTINGS}
133
+ emptyDataMessage={i18n('saved.empty')}
134
+ rowClassName={() => b('row')}
135
+ onRowClick={(row) => onQueryClick(row.body, row.name)}
136
+ initialSortOrder={{
137
+ columnId: 'name',
138
+ order: DataTable.ASCENDING,
139
+ }}
140
+ />
141
+ </div>
142
+ <DeleteDialog
143
+ visible={isDeleteDialogVisible}
144
+ queryName={queryNameToDelete}
145
+ onCancelClick={onCancelDeleteClick}
146
+ onConfirmClick={onConfirmDeleteClick}
147
+ />
148
+ </>
149
+ );
150
+ };