ydb-embedded-ui 4.15.0 → 4.16.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 (50) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/components/DiagnosticCard/DiagnosticCard.scss +5 -0
  3. package/dist/components/DiagnosticCard/DiagnosticCard.tsx +17 -0
  4. package/dist/components/EntityStatus/EntityStatus.js +2 -2
  5. package/dist/components/EntityStatus/EntityStatus.scss +15 -0
  6. package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +3 -11
  7. package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/Details.tsx +20 -11
  8. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +14 -1
  9. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +26 -37
  10. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/Preview.tsx +37 -25
  11. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.tsx +154 -0
  12. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/en.json +7 -0
  13. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/index.ts +11 -0
  14. package/dist/containers/Tenant/Diagnostics/TenantOverview/i18n/ru.json +7 -0
  15. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss +4 -1
  16. package/dist/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +42 -11
  17. package/dist/containers/Tenant/Query/QueryEditor/QueryEditor.js +1 -1
  18. package/dist/containers/Tenant/Query/QueryEditorControls/QueryEditorControls.tsx +11 -7
  19. package/dist/containers/Tenant/Query/i18n/en.json +3 -0
  20. package/dist/containers/Tenant/Query/i18n/ru.json +3 -0
  21. package/dist/containers/Tenant/utils/queryTemplates.ts +30 -6
  22. package/dist/containers/Tenants/Tenants.tsx +3 -1
  23. package/dist/containers/UserSettings/Setting.tsx +9 -2
  24. package/dist/containers/UserSettings/i18n/en.json +5 -1
  25. package/dist/containers/UserSettings/i18n/ru.json +5 -1
  26. package/dist/containers/UserSettings/settings.ts +25 -0
  27. package/dist/services/api.ts +16 -16
  28. package/dist/store/reducers/executeQuery.ts +33 -7
  29. package/dist/store/reducers/explainQuery.ts +12 -4
  30. package/dist/store/reducers/healthcheckInfo.ts +27 -11
  31. package/dist/store/reducers/settings/settings.ts +4 -10
  32. package/dist/store/reducers/tenant/tenant.ts +19 -1
  33. package/dist/store/reducers/tenant/types.ts +3 -1
  34. package/dist/store/reducers/tenants/selectors.ts +1 -1
  35. package/dist/store/reducers/tenants/utils.ts +2 -2
  36. package/dist/types/api/tenant.ts +19 -20
  37. package/dist/types/store/executeQuery.ts +11 -1
  38. package/dist/types/store/query.ts +2 -1
  39. package/dist/utils/autofetcher.ts +7 -7
  40. package/dist/utils/constants.ts +2 -1
  41. package/dist/utils/hooks/i18n/en.json +1 -1
  42. package/dist/utils/hooks/i18n/ru.json +1 -1
  43. package/dist/utils/hooks/useQueryModes.ts +4 -2
  44. package/dist/utils/i18n/i18n.ts +10 -4
  45. package/dist/utils/query.ts +14 -0
  46. package/dist/utils/settings.ts +10 -0
  47. package/package.json +1 -1
  48. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/PreviewItem.tsx +0 -33
  49. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/index.ts +0 -1
  50. package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +0 -213
@@ -3,10 +3,13 @@ import block from 'bem-cn-lite';
3
3
 
4
4
  import DataTable, {Column} from '@gravity-ui/react-data-table';
5
5
 
6
+ import type {QueryInHistory} from '../../../../types/store/executeQuery';
6
7
  import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery';
7
8
  import {setQueryTab} from '../../../../store/reducers/tenant/tenant';
9
+ import {selectQueriesHistory} from '../../../../store/reducers/executeQuery';
8
10
  import {TENANT_QUERY_TABS_ID} from '../../../../store/reducers/tenant/constants';
9
- import {useTypedSelector} from '../../../../utils/hooks';
11
+ import {useQueryModes, useTypedSelector} from '../../../../utils/hooks';
12
+ import {QUERY_MODES, QUERY_MODES_TITLES, QUERY_SYNTAX} from '../../../../utils/query';
10
13
  import {MAX_QUERY_HEIGHT, QUERY_TABLE_SETTINGS} from '../../utils/constants';
11
14
 
12
15
  import i18n from '../i18n';
@@ -22,24 +25,51 @@ interface QueriesHistoryProps {
22
25
  function QueriesHistory({changeUserInput}: QueriesHistoryProps) {
23
26
  const dispatch = useDispatch();
24
27
 
25
- const queriesHistory = useTypedSelector((state) => state.executeQuery.history.queries) ?? [];
28
+ const [queryMode, setQueryMode] = useQueryModes();
29
+
30
+ const queriesHistory = useTypedSelector(selectQueriesHistory);
26
31
  const reversedHistory = [...queriesHistory].reverse();
27
32
 
28
- const onQueryClick = (queryText: string) => {
29
- changeUserInput({input: queryText});
30
- dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
33
+ const onQueryClick = (query: QueryInHistory) => {
34
+ let isQueryModeSet = true;
35
+
36
+ if (query.syntax === QUERY_SYNTAX.pg && queryMode !== QUERY_MODES.pg) {
37
+ isQueryModeSet = setQueryMode(
38
+ QUERY_MODES.pg,
39
+ i18n('history.cannot-set-mode', {mode: QUERY_MODES_TITLES[QUERY_MODES.pg]}),
40
+ );
41
+ } else if (query.syntax !== QUERY_SYNTAX.pg && queryMode === QUERY_MODES.pg) {
42
+ // Set query mode for queries with yql syntax
43
+ isQueryModeSet = setQueryMode(QUERY_MODES.script);
44
+ }
45
+
46
+ if (isQueryModeSet) {
47
+ changeUserInput({input: query.queryText});
48
+ dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
49
+ }
31
50
  };
32
51
 
33
- const columns: Column<string>[] = [
52
+ const columns: Column<QueryInHistory>[] = [
34
53
  {
35
54
  name: 'queryText',
36
55
  header: 'Query Text',
37
- render: ({row: query}) => (
38
- <div className={b('query')}>
39
- <TruncatedQuery value={query} maxQueryHeight={MAX_QUERY_HEIGHT} />
40
- </div>
41
- ),
56
+ render: ({row}) => {
57
+ return (
58
+ <div className={b('query')}>
59
+ <TruncatedQuery value={row.queryText} maxQueryHeight={MAX_QUERY_HEIGHT} />
60
+ </div>
61
+ );
62
+ },
63
+ sortable: false,
64
+ },
65
+ {
66
+ name: 'syntax',
67
+ header: 'Syntax',
68
+ render: ({row}) => {
69
+ return row.syntax === QUERY_SYNTAX.pg ? 'PostgreSQL' : 'YQL';
70
+ },
42
71
  sortable: false,
72
+ width: 200,
43
73
  },
44
74
  ];
45
75
 
@@ -52,6 +82,7 @@ function QueriesHistory({changeUserInput}: QueriesHistoryProps) {
52
82
  settings={QUERY_TABLE_SETTINGS}
53
83
  emptyDataMessage={i18n('history.empty')}
54
84
  onRowClick={(row) => onQueryClick(row)}
85
+ rowClassName={() => b('table-row')}
55
86
  />
56
87
  </div>
57
88
  );
@@ -264,7 +264,7 @@ function QueryEditor(props) {
264
264
 
265
265
  const {queries, currentIndex} = history;
266
266
  if (input !== queries[currentIndex]) {
267
- saveQueryToHistory(input);
267
+ saveQueryToHistory(input, mode);
268
268
  }
269
269
  dispatchResultVisibilityState(PaneVisibilityActionTypes.triggerExpand);
270
270
  };
@@ -4,7 +4,7 @@ import {Button, ButtonView, DropdownMenu} from '@gravity-ui/uikit';
4
4
  import {useMemo} from 'react';
5
5
 
6
6
  import type {QueryAction, QueryMode} from '../../../../types/store/query';
7
- import {QUERY_MODES} from '../../../../utils/query';
7
+ import {QUERY_MODES, QUERY_MODES_TITLES} from '../../../../utils/query';
8
8
  import {Icon} from '../../../../components/Icon';
9
9
  import {LabelWithPopover} from '../../../../components/LabelWithPopover';
10
10
 
@@ -21,32 +21,36 @@ const b = block('ydb-query-editor-controls');
21
21
 
22
22
  const OldQueryModeSelectorOptions = {
23
23
  [QUERY_MODES.script]: {
24
- title: 'YQL Script',
24
+ title: QUERY_MODES_TITLES[QUERY_MODES.script],
25
25
  description: i18n('method-description.script'),
26
26
  },
27
27
  [QUERY_MODES.scan]: {
28
- title: 'Scan',
28
+ title: QUERY_MODES_TITLES[QUERY_MODES.scan],
29
29
  description: i18n('method-description.scan'),
30
30
  },
31
31
  } as const;
32
32
 
33
33
  const QueryModeSelectorOptions = {
34
34
  [QUERY_MODES.script]: {
35
- title: 'YQL Script',
35
+ title: QUERY_MODES_TITLES[QUERY_MODES.script],
36
36
  description: i18n('method-description.script'),
37
37
  },
38
38
  [QUERY_MODES.scan]: {
39
- title: 'Scan',
39
+ title: QUERY_MODES_TITLES[QUERY_MODES.scan],
40
40
  description: i18n('method-description.scan'),
41
41
  },
42
42
  [QUERY_MODES.data]: {
43
- title: 'Data',
43
+ title: QUERY_MODES_TITLES[QUERY_MODES.data],
44
44
  description: i18n('method-description.data'),
45
45
  },
46
46
  [QUERY_MODES.query]: {
47
- title: 'YQL - QueryService',
47
+ title: QUERY_MODES_TITLES[QUERY_MODES.query],
48
48
  description: i18n('method-description.query'),
49
49
  },
50
+ [QUERY_MODES.pg]: {
51
+ title: QUERY_MODES_TITLES[QUERY_MODES.pg],
52
+ description: i18n('method-description.pg'),
53
+ },
50
54
  } as const;
51
55
 
52
56
  interface QueryEditorControlsProps {
@@ -8,6 +8,8 @@
8
8
  "history.empty": "History is empty",
9
9
  "saved.empty": "There are no saved queries",
10
10
 
11
+ "history.cannot-set-mode": "This query is available only with '{{mode}}' query mode. You need to turn in additional query modes in settings to enable it",
12
+
11
13
  "delete-dialog.header": "Delete query",
12
14
  "delete-dialog.question": "Are you sure you want to delete query",
13
15
  "delete-dialog.delete": "Delete",
@@ -21,6 +23,7 @@
21
23
  "method-description.scan": "Read-only queries, potentially reading a lot of data.\nAPI call: table.ExecuteScan",
22
24
  "method-description.data": "DML queries for changing and fetching data in serialization mode.\nAPI call: table.executeDataQuery",
23
25
  "method-description.query": "Any query. An experimental API call supposed to replace all existing methods.\nAPI Call: query.ExecuteScript",
26
+ "method-description.pg": "Queries in postgresql syntax.\nAPI call: query.ExecuteScript",
24
27
 
25
28
  "query-duration.description": "Duration of server-side query execution"
26
29
  }
@@ -8,6 +8,8 @@
8
8
  "history.empty": "История пуста",
9
9
  "saved.empty": "Нет сохраненных запросов",
10
10
 
11
+ "history.cannot-set-mode": "Этот запрос доступен только в режиме '{{mode}}'. Вам необходимо включить дополнительные режимы выполнения запросов в настройках",
12
+
11
13
  "delete-dialog.header": "Удалить запрос",
12
14
  "delete-dialog.question": "Вы уверены что хотите удалить запрос",
13
15
  "delete-dialog.delete": "Удалить",
@@ -21,6 +23,7 @@
21
23
  "method-description.scan": "Только читающие запросы, потенциально читающие много данных.\nAPI call: table.ExecuteScan",
22
24
  "method-description.data": "DML-запросы для изменения и выборки данных в режиме изоляции Serializable.\nAPI call: table.executeDataQuery",
23
25
  "method-description.query": "Любые запросы. Экспериментальный перспективный метод, который в будущем заменит все остальные.\nAPI call: query.ExecuteScript",
26
+ "method-description.pg": "Запросы в синтаксисе postgresql.\nAPI call: query.ExecuteScript",
24
27
 
25
28
  "query-duration.description": "Время выполнения запроса на стороне сервера"
26
29
  }
@@ -1,10 +1,34 @@
1
1
  export const createTableTemplate = (path: string) => {
2
- return `CREATE TABLE \`${path}/my_table\`
3
- (
4
- \`id\` Uint64,
5
- \`name\` String,
6
- PRIMARY KEY (\`id\`)
7
- );`;
2
+ return `-- docs: https://ydb.tech/en/docs/yql/reference/syntax/create_table
3
+ CREATE TABLE \`${path}/ydb_row_table\` (
4
+ category_id Uint64 NOT NULL,
5
+ id Uint64,
6
+ expire_at Datetime,
7
+ updated_on Datetime,
8
+ name Text,
9
+ \`binary-payload\` Bytes,
10
+ attributes JsonDocument,
11
+ -- uncomment to add a secondary index
12
+ -- INDEX idx_row_table_id GLOBAL SYNC ON ( id ) COVER ( name, attributes ), -- Secondary indexes docs https://ydb.tech/en/docs/yql/reference/syntax/create_table#secondary_index
13
+ PRIMARY KEY (category_id, id)
14
+ )
15
+ WITH (
16
+ AUTO_PARTITIONING_BY_SIZE = ENABLED,
17
+ AUTO_PARTITIONING_PARTITION_SIZE_MB = 2048,
18
+ AUTO_PARTITIONING_BY_LOAD = ENABLED,
19
+ AUTO_PARTITIONING_MIN_PARTITIONS_COUNT = 4,
20
+ AUTO_PARTITIONING_MAX_PARTITIONS_COUNT = 1024,
21
+ -- uncomment to create a table with predefined partitions
22
+ -- UNIFORM_PARTITIONS = 4, -- The number of partitions for uniform initial table partitioning.
23
+ -- The primary key's first column must have type Uint64 or Uint32.
24
+ -- A created table is immediately divided into the specified number of partitions
25
+ -- uncomment to launch read only replicas in every AZ
26
+ -- READ_REPLICAS_SETTINGS = 'PER_AZ:1', -- Enable read replicas for stale read, launch one replica in every availability zone
27
+ -- uncomment to enable ttl
28
+ -- TTL = Interval("PT1H") ON expire_at, -- Enable background deletion of expired rows https://ydb.tech/en/docs/concepts/ttl
29
+ KEY_BLOOM_FILTER = ENABLED -- With a Bloom filter, you can more efficiently determine
30
+ -- if some keys are missing in a table when making multiple single queries by the primary key.
31
+ )`;
8
32
  };
9
33
  export const alterTableTemplate = (path: string) => {
10
34
  return `ALTER TABLE \`${path}\`
@@ -106,7 +106,9 @@ export const Tenants = ({additionalTenantsProps}: TenantsProps) => {
106
106
  backend,
107
107
  })}
108
108
  />
109
- {additionalTenantsProps?.getMonitoringLink?.(row.Name, row.Type)}
109
+ {row.Name &&
110
+ row.Type &&
111
+ additionalTenantsProps?.getMonitoringLink?.(row.Name, row.Type)}
110
112
  </div>
111
113
  );
112
114
  },
@@ -18,6 +18,7 @@ export interface SettingProps {
18
18
  helpPopoverContent?: ReactNode;
19
19
  options?: {value: string; content: string}[];
20
20
  defaultValue?: unknown;
21
+ onValueUpdate?: VoidFunction;
21
22
  }
22
23
 
23
24
  export const Setting = ({
@@ -27,9 +28,15 @@ export const Setting = ({
27
28
  helpPopoverContent,
28
29
  options,
29
30
  defaultValue,
31
+ onValueUpdate,
30
32
  }: SettingProps) => {
31
33
  const [settingValue, setValue] = useSetting(settingKey, defaultValue);
32
34
 
35
+ const onUpdate = (value: unknown) => {
36
+ setValue(value);
37
+ onValueUpdate?.();
38
+ };
39
+
33
40
  const renderTitleComponent = (value: ReactNode) => {
34
41
  if (helpPopoverContent) {
35
42
  return (
@@ -48,7 +55,7 @@ export const Setting = ({
48
55
  const getSettingsElement = (elementType: SettingsElementType) => {
49
56
  switch (elementType) {
50
57
  case 'switch': {
51
- return <Switch checked={Boolean(settingValue)} onUpdate={setValue} />;
58
+ return <Switch checked={Boolean(settingValue)} onUpdate={onUpdate} />;
52
59
  }
53
60
 
54
61
  case 'radio': {
@@ -57,7 +64,7 @@ export const Setting = ({
57
64
  }
58
65
 
59
66
  return (
60
- <RadioButton value={String(settingValue)} onUpdate={setValue}>
67
+ <RadioButton value={String(settingValue)} onUpdate={onUpdate}>
61
68
  {options.map(({value, content}) => {
62
69
  return (
63
70
  <RadioButton.Option value={value} key={value}>
@@ -10,6 +10,10 @@
10
10
  "settings.theme.option-light": "Light",
11
11
  "settings.theme.option-system": "System",
12
12
 
13
+ "settings.language.title": "Interface language",
14
+ "settings.language.option-russian": "Russian",
15
+ "settings.language.option-english": "English",
16
+
13
17
  "settings.invertedDisks.title": "Inverted disks space indicators",
14
18
 
15
19
  "settings.useNodesEndpoint.title": "Break the Nodes tab in Diagnostics",
@@ -19,5 +23,5 @@
19
23
  "settings.useBackendParamsForTables.popover": "Filter and sort Nodes and Storage tables with request params. May increase performance, but could causes additional fetches and longer loading time on older versions",
20
24
 
21
25
  "settings.enableAdditionalQueryModes.title": "Enable additional query modes",
22
- "settings.enableAdditionalQueryModes.popover": "Adds 'Data' and 'YQL - QueryService' modes. May not work on some versions"
26
+ "settings.enableAdditionalQueryModes.popover": "Adds 'Data', 'YQL - QueryService' and 'PostgreSQL' modes. May not work on some versions"
23
27
  }
@@ -10,6 +10,10 @@
10
10
  "settings.theme.option-light": "Светлая",
11
11
  "settings.theme.option-system": "Системная",
12
12
 
13
+ "settings.language.title": "Язык интерфейса",
14
+ "settings.language.option-russian": "Русский",
15
+ "settings.language.option-english": "English",
16
+
13
17
  "settings.invertedDisks.title": "Инвертированные индикаторы места на дисках",
14
18
 
15
19
  "settings.useNodesEndpoint.title": "Сломать вкладку Nodes в диагностике",
@@ -19,5 +23,5 @@
19
23
  "settings.useBackendParamsForTables.popover": "Добавляет фильтрацию и сортировку таблиц Nodes и Storage с использованием параметров запроса. Может улушить производительность, но на старых версиях может привести к дополнительным запросам и большему времени ожидания загрузки",
20
24
 
21
25
  "settings.enableAdditionalQueryModes.title": "Включить дополнительные режимы выполнения запросов",
22
- "settings.enableAdditionalQueryModes.popover": "Добавляет режимы 'Data' и 'YQL - QueryService'. Может работать некорректно на некоторых версиях"
26
+ "settings.enableAdditionalQueryModes.popover": "Добавляет режимы 'Data', 'YQL - QueryService' и 'PostgreSQL'. Может работать некорректно на некоторых версиях"
23
27
  }
@@ -6,10 +6,12 @@ import flaskIcon from '../../assets/icons/flask.svg';
6
6
  import {
7
7
  ENABLE_ADDITIONAL_QUERY_MODES,
8
8
  INVERTED_DISKS_KEY,
9
+ LANGUAGE_KEY,
9
10
  THEME_KEY,
10
11
  USE_BACKEND_PARAMS_FOR_TABLES_KEY,
11
12
  USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
12
13
  } from '../../utils/constants';
14
+ import {Lang, defaultLang} from '../../utils/i18n';
13
15
 
14
16
  import type {SettingProps} from './Setting';
15
17
  import i18n from './i18n';
@@ -50,6 +52,29 @@ export const themeSetting: SettingProps = {
50
52
  type: 'radio',
51
53
  options: themeOptions,
52
54
  };
55
+
56
+ const languageOptions = [
57
+ {
58
+ value: Lang.Ru,
59
+ content: i18n('settings.language.option-russian'),
60
+ },
61
+ {
62
+ value: Lang.En,
63
+ content: i18n('settings.language.option-english'),
64
+ },
65
+ ];
66
+
67
+ export const languageSetting: SettingProps = {
68
+ settingKey: LANGUAGE_KEY,
69
+ title: i18n('settings.language.title'),
70
+ type: 'radio',
71
+ options: languageOptions,
72
+ defaultValue: defaultLang,
73
+ onValueUpdate: () => {
74
+ window.location.reload();
75
+ },
76
+ };
77
+
53
78
  export const invertedDisksSetting: SettingProps = {
54
79
  settingKey: INVERTED_DISKS_KEY,
55
80
  title: i18n('settings.invertedDisks.title'),
@@ -28,6 +28,7 @@ import type {DescribeTopicResult} from '../types/api/topic';
28
28
  import type {TEvPDiskStateResponse} from '../types/api/pdisk';
29
29
  import type {TEvVDiskStateResponse} from '../types/api/vdisk';
30
30
  import type {TUserToken} from '../types/api/whoami';
31
+ import type {QuerySyntax} from '../types/store/query';
31
32
  import type {ComputeApiRequestParams, NodesApiRequestParams} from '../store/reducers/nodes/types';
32
33
  import type {StorageApiRequestParams} from '../store/reducers/storage/types';
33
34
 
@@ -75,12 +76,16 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
75
76
  cluster_name: clusterName,
76
77
  });
77
78
  }
78
- getTenantInfo({path}: {path: string}) {
79
- return this.get<TTenantInfo>(this.getPath('/viewer/json/tenantinfo'), {
80
- path,
81
- tablets: true,
82
- storage: true,
83
- });
79
+ getTenantInfo({path}: {path: string}, {concurrentId}: AxiosOptions = {}) {
80
+ return this.get<TTenantInfo>(
81
+ this.getPath('/viewer/json/tenantinfo'),
82
+ {
83
+ path,
84
+ tablets: true,
85
+ storage: true,
86
+ },
87
+ {concurrentId: concurrentId || `getTenantInfo|${path}`},
88
+ );
84
89
  }
85
90
  getNodes(
86
91
  {
@@ -281,17 +286,15 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
281
286
  }
282
287
  sendQuery<Action extends Actions, Schema extends Schemas = undefined>(
283
288
  {
284
- query,
285
- database,
286
- action,
287
- stats,
288
289
  schema,
290
+ ...params
289
291
  }: {
290
292
  query?: string;
291
293
  database?: string;
292
294
  action?: Action;
293
295
  stats?: string;
294
296
  schema?: Schema;
297
+ syntax?: QuerySyntax;
295
298
  },
296
299
  {concurrentId}: AxiosOptions = {},
297
300
  ) {
@@ -303,12 +306,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
303
306
  this.getPath(
304
307
  `/viewer/json/query?timeout=${backendTimeout}${schema ? `&schema=${schema}` : ''}`,
305
308
  ),
306
- {
307
- query,
308
- database,
309
- action,
310
- stats,
311
- },
309
+ params,
312
310
  {},
313
311
  {
314
312
  concurrentId,
@@ -320,6 +318,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
320
318
  query: string,
321
319
  database: string,
322
320
  action: Action,
321
+ syntax?: QuerySyntax,
323
322
  ) {
324
323
  return this.post<ExplainResponse<Action>>(
325
324
  this.getPath('/viewer/json/query'),
@@ -327,6 +326,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
327
326
  query,
328
327
  database,
329
328
  action: action || 'explain',
329
+ syntax,
330
330
  timeout: 600000,
331
331
  },
332
332
  {},
@@ -4,12 +4,14 @@ import type {ExecuteActions} from '../../types/api/query';
4
4
  import type {
5
5
  ExecuteQueryAction,
6
6
  ExecuteQueryState,
7
+ ExecuteQueryStateSlice,
7
8
  MonacoHotKeyAction,
9
+ QueryInHistory,
8
10
  } from '../../types/store/executeQuery';
9
- import type {QueryRequestParams, QueryMode} from '../../types/store/query';
11
+ import type {QueryRequestParams, QueryMode, QuerySyntax} from '../../types/store/query';
10
12
  import {getValueFromLS, parseJson} from '../../utils/utils';
11
13
  import {QUERIES_HISTORY_KEY} from '../../utils/constants';
12
- import {parseQueryAPIExecuteResponse} from '../../utils/query';
14
+ import {QUERY_MODES, QUERY_SYNTAX, parseQueryAPIExecuteResponse} from '../../utils/query';
13
15
  import {parseQueryError} from '../../utils/error';
14
16
  import '../../services/api';
15
17
 
@@ -87,8 +89,12 @@ const executeQuery: Reducer<ExecuteQueryState, ExecuteQueryAction> = (
87
89
  }
88
90
 
89
91
  case SAVE_QUERY_TO_HISTORY: {
90
- const query = action.data;
91
- const newQueries = [...state.history.queries, query].slice(
92
+ const queryText = action.data.queryText;
93
+
94
+ // Do not save explicit yql syntax value for easier further support (use yql by default)
95
+ const syntax = action.data.mode === QUERY_MODES.pg ? QUERY_SYNTAX.pg : undefined;
96
+
97
+ const newQueries = [...state.history.queries, {queryText, syntax}].slice(
92
98
  state.history.queries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0,
93
99
  );
94
100
  window.localStorage.setItem(QUERIES_HISTORY_KEY, JSON.stringify(newQueries));
@@ -151,7 +157,15 @@ interface SendQueryParams extends QueryRequestParams {
151
157
  }
152
158
 
153
159
  export const sendExecuteQuery = ({query, database, mode}: SendQueryParams) => {
154
- const action: ExecuteActions = mode ? `execute-${mode}` : 'execute';
160
+ let action: ExecuteActions = 'execute';
161
+ let syntax: QuerySyntax = QUERY_SYNTAX.yql;
162
+
163
+ if (mode === 'pg') {
164
+ action = 'execute-query';
165
+ syntax = QUERY_SYNTAX.pg;
166
+ } else if (mode) {
167
+ action = `execute-${mode}`;
168
+ }
155
169
 
156
170
  return createApiRequest({
157
171
  request: window.api.sendQuery({
@@ -159,6 +173,7 @@ export const sendExecuteQuery = ({query, database, mode}: SendQueryParams) => {
159
173
  query,
160
174
  database,
161
175
  action,
176
+ syntax,
162
177
  stats: 'profile',
163
178
  }),
164
179
  actions: SEND_QUERY,
@@ -166,10 +181,10 @@ export const sendExecuteQuery = ({query, database, mode}: SendQueryParams) => {
166
181
  });
167
182
  };
168
183
 
169
- export const saveQueryToHistory = (query: string) => {
184
+ export const saveQueryToHistory = (queryText: string, mode: QueryMode) => {
170
185
  return {
171
186
  type: SAVE_QUERY_TO_HISTORY,
172
- data: query,
187
+ data: {queryText, mode},
173
188
  } as const;
174
189
  };
175
190
 
@@ -206,4 +221,15 @@ export const setTenantPath = (value: string) => {
206
221
  } as const;
207
222
  };
208
223
 
224
+ export const selectQueriesHistory = (state: ExecuteQueryStateSlice): QueryInHistory[] => {
225
+ return state.executeQuery.history.queries.map((rawQuery) => {
226
+ if (typeof rawQuery === 'string') {
227
+ return {
228
+ queryText: rawQuery,
229
+ };
230
+ }
231
+ return rawQuery;
232
+ });
233
+ };
234
+
209
235
  export default executeQuery;
@@ -9,10 +9,10 @@ import type {
9
9
  ExplainQueryState,
10
10
  PreparedExplainResponse,
11
11
  } from '../../types/store/explainQuery';
12
- import type {QueryRequestParams, QueryMode} from '../../types/store/query';
12
+ import type {QueryRequestParams, QueryMode, QuerySyntax} from '../../types/store/query';
13
13
 
14
14
  import {preparePlan} from '../../utils/prepareQueryExplain';
15
- import {parseQueryAPIExplainResponse, parseQueryExplainPlan} from '../../utils/query';
15
+ import {QUERY_SYNTAX, parseQueryAPIExplainResponse, parseQueryExplainPlan} from '../../utils/query';
16
16
  import {parseQueryError} from '../../utils/error';
17
17
 
18
18
  import {createRequestActionTypes, createApiRequest} from '../utils';
@@ -103,10 +103,18 @@ interface ExplainQueryParams extends QueryRequestParams {
103
103
  }
104
104
 
105
105
  export const getExplainQuery = ({query, database, mode}: ExplainQueryParams) => {
106
- const action: ExplainActions = mode ? `explain-${mode}` : 'explain';
106
+ let action: ExplainActions = 'explain';
107
+ let syntax: QuerySyntax = QUERY_SYNTAX.yql;
108
+
109
+ if (mode === 'pg') {
110
+ action = 'explain-query';
111
+ syntax = QUERY_SYNTAX.pg;
112
+ } else if (mode) {
113
+ action = `explain-${mode}`;
114
+ }
107
115
 
108
116
  return createApiRequest({
109
- request: window.api.getExplainQuery(query, database, action),
117
+ request: window.api.getExplainQuery(query, database, action, syntax),
110
118
  actions: GET_EXPLAIN_QUERY,
111
119
  dataHandler: (response): PreparedExplainResponse => {
112
120
  const {plan: rawPlan, ast} = parseQueryAPIExplainResponse(response);
@@ -1,17 +1,16 @@
1
1
  import _flow from 'lodash/fp/flow';
2
2
  import _sortBy from 'lodash/fp/sortBy';
3
3
  import _uniqBy from 'lodash/fp/uniqBy';
4
- import _omit from 'lodash/omit';
5
4
  import {createSelector, Selector} from 'reselect';
6
5
  import {Reducer} from 'redux';
7
6
 
8
- import {
9
- IHealthcheckInfoState,
7
+ import type {
8
+ IHealthCheckInfoAction,
10
9
  IHealthcheckInfoRootStateSlice,
10
+ IHealthcheckInfoState,
11
11
  IIssuesTree,
12
- IHealthCheckInfoAction,
13
12
  } from '../../types/store/healthcheck';
14
- import {IssueLog, StatusFlag} from '../../types/api/healthcheck';
13
+ import type {IssueLog, StatusFlag} from '../../types/api/healthcheck';
15
14
 
16
15
  import '../../services/api';
17
16
  import {createRequestActionTypes, createApiRequest} from '../utils';
@@ -49,6 +48,8 @@ const healthcheckInfo: Reducer<IHealthcheckInfoState, IHealthCheckInfoAction> =
49
48
  ...state,
50
49
  error: action.error,
51
50
  loading: false,
51
+ wasLoaded: true,
52
+ data: undefined,
52
53
  };
53
54
  }
54
55
 
@@ -110,6 +111,24 @@ const getInvertedConsequencesTree = ({
110
111
  : [];
111
112
  };
112
113
 
114
+ const getIssuesStatistics = (data: IssueLog[]): [StatusFlag, number][] => {
115
+ const issuesMap = {} as Record<StatusFlag, number>;
116
+
117
+ for (const issue of data) {
118
+ if (!issuesMap[issue.status]) {
119
+ issuesMap[issue.status] = 0;
120
+ }
121
+ issuesMap[issue.status]++;
122
+ }
123
+
124
+ return (Object.entries(issuesMap) as [StatusFlag, number][]).sort(([aStatus], [bStatus]) => {
125
+ const bPriority = mapStatusToPriority[bStatus] || 0;
126
+ const aPriority = mapStatusToPriority[aStatus] || 0;
127
+
128
+ return aPriority - bPriority;
129
+ });
130
+ };
131
+
113
132
  const getIssuesLog = (state: IHealthcheckInfoRootStateSlice) =>
114
133
  state.healthcheckInfo.data?.issue_log;
115
134
 
@@ -121,13 +140,10 @@ export const selectIssuesTrees: Selector<IHealthcheckInfoRootStateSlice, IIssues
121
140
  return getInvertedConsequencesTree({data, roots});
122
141
  });
123
142
 
124
- export const selectIssuesTreeById: Selector<
143
+ export const selectIssuesStatistics: Selector<
125
144
  IHealthcheckInfoRootStateSlice,
126
- IIssuesTree | undefined,
127
- [string | undefined]
128
- > = createSelector([selectIssuesTrees, (_, id: string | undefined) => id], (issuesTrees = [], id) =>
129
- issuesTrees.find((issuesTree) => issuesTree.id === id),
130
- );
145
+ [StatusFlag, number][]
146
+ > = createSelector(getIssuesLog, (issues = []) => getIssuesStatistics(issues));
131
147
 
132
148
  export function getHealthcheckInfo(database: string) {
133
149
  return createApiRequest({