ydb-embedded-ui 4.15.1 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. package/CHANGELOG.md +14 -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/Tenants/Tenants.tsx +3 -1
  22. package/dist/containers/UserSettings/Setting.tsx +9 -2
  23. package/dist/containers/UserSettings/i18n/en.json +5 -1
  24. package/dist/containers/UserSettings/i18n/ru.json +5 -1
  25. package/dist/containers/UserSettings/settings.ts +25 -0
  26. package/dist/services/api.ts +16 -16
  27. package/dist/store/reducers/executeQuery.ts +33 -7
  28. package/dist/store/reducers/explainQuery.ts +12 -4
  29. package/dist/store/reducers/healthcheckInfo.ts +27 -11
  30. package/dist/store/reducers/settings/settings.ts +4 -10
  31. package/dist/store/reducers/tenant/tenant.ts +19 -1
  32. package/dist/store/reducers/tenant/types.ts +3 -1
  33. package/dist/store/reducers/tenants/selectors.ts +1 -1
  34. package/dist/store/reducers/tenants/utils.ts +2 -2
  35. package/dist/types/api/tenant.ts +19 -20
  36. package/dist/types/store/executeQuery.ts +11 -1
  37. package/dist/types/store/query.ts +2 -1
  38. package/dist/utils/autofetcher.ts +7 -7
  39. package/dist/utils/constants.ts +2 -1
  40. package/dist/utils/hooks/i18n/en.json +1 -1
  41. package/dist/utils/hooks/i18n/ru.json +1 -1
  42. package/dist/utils/hooks/useQueryModes.ts +4 -2
  43. package/dist/utils/i18n/i18n.ts +10 -4
  44. package/dist/utils/query.ts +14 -0
  45. package/dist/utils/settings.ts +10 -0
  46. package/package.json +1 -1
  47. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/PreviewItem.tsx +0 -33
  48. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/index.ts +0 -1
  49. 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
  }
@@ -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({
@@ -14,10 +14,12 @@ import {
14
14
  CLUSTER_INFO_HIDDEN_KEY,
15
15
  LAST_USED_QUERY_ACTION_KEY,
16
16
  USE_BACKEND_PARAMS_FOR_TABLES_KEY,
17
+ LANGUAGE_KEY,
17
18
  } from '../../../utils/constants';
18
19
  import '../../../services/api';
19
- import {getValueFromLS, parseJson} from '../../../utils/utils';
20
+ import {parseJson} from '../../../utils/utils';
20
21
  import {QUERY_ACTIONS, QUERY_MODES} from '../../../utils/query';
22
+ import {readSavedSettingsValue, systemSettings, userSettings} from '../../../utils/settings';
21
23
 
22
24
  import {TENANT_PAGES_IDS} from '../tenant/constants';
23
25
 
@@ -38,20 +40,12 @@ export const ProblemFilterValues = {
38
40
  PROBLEMS: 'With problems',
39
41
  } as const;
40
42
 
41
- const userSettings = window.userSettings || {};
42
- const systemSettings = window.systemSettings || {};
43
-
44
- export function readSavedSettingsValue(key: string, defaultValue?: string) {
45
- const savedValue = window.web_version ? userSettings[key] : getValueFromLS(key);
46
-
47
- return savedValue ?? defaultValue;
48
- }
49
-
50
43
  export const initialState = {
51
44
  problemFilter: ProblemFilterValues.ALL,
52
45
  userSettings: {
53
46
  ...userSettings,
54
47
  [THEME_KEY]: readSavedSettingsValue(THEME_KEY, 'system'),
48
+ [LANGUAGE_KEY]: readSavedSettingsValue(LANGUAGE_KEY),
55
49
  [INVERTED_DISKS_KEY]: readSavedSettingsValue(INVERTED_DISKS_KEY, 'false'),
56
50
  [USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY]: readSavedSettingsValue(
57
51
  USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,