ydb-embedded-ui 2.3.0 → 2.4.1

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 (53) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/components/Errors/403/AccessDenied.tsx +19 -0
  3. package/dist/components/Errors/403/index.ts +1 -0
  4. package/dist/components/Errors/i18n/en.json +4 -0
  5. package/dist/components/Errors/i18n/index.ts +11 -0
  6. package/dist/components/Errors/i18n/ru.json +4 -0
  7. package/dist/components/QueryResultTable/QueryResultTable.tsx +16 -21
  8. package/dist/{containers/Storage/StorageFilter/StorageFilter.tsx → components/Search/Search.tsx} +22 -22
  9. package/dist/components/Search/index.ts +1 -0
  10. package/dist/containers/Nodes/Nodes.js +5 -0
  11. package/dist/containers/Storage/Storage.js +11 -3
  12. package/dist/containers/TabletsFilters/TabletsFilters.js +5 -0
  13. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.scss +6 -0
  14. package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +82 -0
  15. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/en.json +6 -0
  16. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/index.ts +11 -0
  17. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +6 -0
  18. package/dist/containers/Tenant/Diagnostics/Consumers/index.ts +1 -0
  19. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -0
  20. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +15 -8
  21. package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/Details.tsx +55 -0
  22. package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/index.ts +1 -0
  23. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +5 -5
  24. package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +16 -6
  25. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/{IssueViewer.scss → IssueTree.scss} +3 -54
  26. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTree.tsx +87 -0
  27. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/IssueTreeItem.scss +50 -0
  28. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/IssueTreeItem.tsx +25 -0
  29. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/index.ts +1 -0
  30. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/Preview.tsx +13 -16
  31. package/dist/containers/Tenant/Diagnostics/Healthcheck/{IssuePreview/IssuePreview.tsx → Preview/PreviewItem/PreviewItem.tsx} +6 -8
  32. package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/index.ts +1 -0
  33. package/dist/containers/Tenant/Preview/Preview.scss +6 -0
  34. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +1 -9
  35. package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.scss +2 -2
  36. package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +11 -7
  37. package/dist/containers/Tenant/Tenant.tsx +2 -7
  38. package/dist/store/reducers/describe.ts +71 -0
  39. package/dist/store/reducers/healthcheckInfo.ts +123 -0
  40. package/dist/store/reducers/schema.js +10 -13
  41. package/dist/store/reducers/storage.js +6 -6
  42. package/dist/store/utils.ts +21 -13
  43. package/dist/types/api/consumers.ts +3 -0
  44. package/dist/types/api/healthcheck.ts +1 -1
  45. package/dist/types/store/healthcheck.ts +5 -1
  46. package/package.json +1 -1
  47. package/dist/containers/Storage/StorageFilter/index.ts +0 -1
  48. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuePreview/index.ts +0 -1
  49. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesList/IssuesList.tsx +0 -62
  50. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesList/index.ts +0 -1
  51. package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssuesViewer.js +0 -151
  52. package/dist/store/reducers/describe.js +0 -45
  53. package/dist/store/reducers/healthcheckInfo.js +0 -45
@@ -1,56 +1,5 @@
1
1
  @import '../../../../../styles/mixins.scss';
2
2
 
3
- .issue {
4
- display: flex;
5
- justify-content: space-between;
6
- align-items: center;
7
-
8
- height: 40px;
9
-
10
- cursor: pointer;
11
-
12
- &__field {
13
- display: flex;
14
- overflow: hidden;
15
-
16
- &_status {
17
- display: flex;
18
-
19
- white-space: nowrap;
20
- }
21
- &_additional {
22
- width: max-content;
23
-
24
- cursor: pointer;
25
-
26
- color: var(--yc-color-text-link);
27
-
28
- &:hover {
29
- color: var(--yc-color-text-link-hover);
30
- }
31
- }
32
- &_message {
33
- overflow: hidden;
34
- flex-shrink: 0;
35
-
36
- width: 300px;
37
-
38
- white-space: normal;
39
- }
40
- }
41
-
42
- &__field-tooltip {
43
- &#{&} {
44
- min-width: 500px;
45
- max-width: 500px;
46
- }
47
- }
48
-
49
- &__field-label {
50
- color: var(--yc-color-text-secondary);
51
- }
52
- }
53
-
54
3
  .indicator {
55
4
  width: 12px;
56
5
  height: 12px;
@@ -86,12 +35,12 @@
86
35
  }
87
36
  }
88
37
 
89
- .issue-viewer {
38
+ .issue-tree {
90
39
  display: flex;
91
40
 
92
41
  width: 820px;
93
42
 
94
- &__tree {
43
+ &__block {
95
44
  width: 100%;
96
45
  }
97
46
 
@@ -163,7 +112,7 @@
163
112
  padding-left: 0 !important;
164
113
  }
165
114
 
166
- .issue-viewer__info-panel {
115
+ .issue-tree__info-panel {
167
116
  margin-left: $calculated-margin;
168
117
  }
169
118
  }
@@ -0,0 +1,87 @@
1
+ import {useCallback, useState} from 'react';
2
+ import cn from 'bem-cn-lite';
3
+ import _omit from 'lodash/omit';
4
+
5
+ // @ts-ignore
6
+ import JSONTree from 'react-json-inspector';
7
+
8
+ import {TreeView} from 'ydb-ui-components';
9
+
10
+ import {IIssuesTree} from '../../../../../types/store/healthcheck';
11
+
12
+ import {IssueTreeItem} from './IssueTreeItem';
13
+
14
+ import './IssueTree.scss';
15
+
16
+ const b = cn('issue-tree');
17
+
18
+ interface IssuesViewerProps {
19
+ issueTree: IIssuesTree;
20
+ }
21
+
22
+ const IssueTree = ({issueTree}: IssuesViewerProps) => {
23
+ const [collapsedIssues, setCollapsedIssues] = useState<Record<string, boolean>>({});
24
+
25
+ const renderTree = useCallback(
26
+ (data: IIssuesTree[]) => {
27
+ return data.map((item) => {
28
+ const {id} = item;
29
+ const {status, message, type, reasonsItems, level, ...rest} = item;
30
+
31
+ const isCollapsed =
32
+ typeof collapsedIssues[id] === 'undefined' || collapsedIssues[id];
33
+
34
+ const toggleCollapsed = () => {
35
+ setCollapsedIssues((collapsedIssues) => ({
36
+ ...collapsedIssues,
37
+ [id]: !isCollapsed,
38
+ }));
39
+ };
40
+
41
+ return (
42
+ <TreeView
43
+ key={id}
44
+ name={<IssueTreeItem status={status} message={message} type={type} />}
45
+ collapsed={isCollapsed}
46
+ hasArrow={true}
47
+ onClick={toggleCollapsed}
48
+ onArrowClick={toggleCollapsed}
49
+ level={level - 1}
50
+ >
51
+ {renderInfoPanel(_omit(rest, ['reason']))}
52
+ {renderTree(reasonsItems || [])}
53
+ </TreeView>
54
+ );
55
+ });
56
+ },
57
+ [issueTree, collapsedIssues],
58
+ );
59
+
60
+ const renderInfoPanel = useCallback(
61
+ (info) => {
62
+ if (!info) {
63
+ return null;
64
+ }
65
+
66
+ return (
67
+ <div className={b('info-panel')}>
68
+ <JSONTree
69
+ data={info}
70
+ search={false}
71
+ isExpanded={() => true}
72
+ className={b('inspector')}
73
+ />
74
+ </div>
75
+ );
76
+ },
77
+ [issueTree],
78
+ );
79
+
80
+ return (
81
+ <div className={b()}>
82
+ <div className={b('block')}>{renderTree([issueTree])}</div>
83
+ </div>
84
+ );
85
+ };
86
+
87
+ export default IssueTree;
@@ -0,0 +1,50 @@
1
+ .issue-tree-item {
2
+ display: flex;
3
+ justify-content: space-between;
4
+ align-items: center;
5
+
6
+ height: 40px;
7
+
8
+ cursor: pointer;
9
+
10
+ &__field {
11
+ display: flex;
12
+ overflow: hidden;
13
+
14
+ &_status {
15
+ display: flex;
16
+
17
+ white-space: nowrap;
18
+ }
19
+ &_additional {
20
+ width: max-content;
21
+
22
+ cursor: pointer;
23
+
24
+ color: var(--yc-color-text-link);
25
+
26
+ &:hover {
27
+ color: var(--yc-color-text-link-hover);
28
+ }
29
+ }
30
+ &_message {
31
+ overflow: hidden;
32
+ flex-shrink: 0;
33
+
34
+ width: 300px;
35
+
36
+ white-space: normal;
37
+ }
38
+ }
39
+
40
+ &__field-tooltip {
41
+ &#{&} {
42
+ min-width: 500px;
43
+ max-width: 500px;
44
+ }
45
+ }
46
+
47
+ &__field-label {
48
+ color: var(--yc-color-text-secondary);
49
+ }
50
+ }
@@ -0,0 +1,25 @@
1
+ import cn from 'bem-cn-lite';
2
+
3
+ import EntityStatus from '../../../../../../components/EntityStatus/EntityStatus';
4
+
5
+ import './IssueTreeItem.scss';
6
+
7
+ const b = cn('issue-tree-item');
8
+
9
+ interface IssueRowProps {
10
+ status: string;
11
+ message: string;
12
+ type: string;
13
+ onClick?: VoidFunction;
14
+ }
15
+
16
+ export const IssueTreeItem = ({status, message, type, onClick}: IssueRowProps) => {
17
+ return (
18
+ <div className={b()} onClick={onClick}>
19
+ <div className={b('field', {status: true})}>
20
+ <EntityStatus mode="icons" status={status} name={type} />
21
+ </div>
22
+ <div className={b('field', {message: true})}>{message}</div>
23
+ </div>
24
+ );
25
+ };
@@ -0,0 +1 @@
1
+ export * from './IssueTreeItem';
@@ -1,4 +1,3 @@
1
- import {useMemo} from 'react';
2
1
  import cn from 'bem-cn-lite';
3
2
 
4
3
  import {Button, Icon} from '@gravity-ui/uikit';
@@ -6,34 +5,28 @@ import {Button, Icon} from '@gravity-ui/uikit';
6
5
  import updateArrow from '../../../../../assets/icons/update-arrow.svg';
7
6
 
8
7
  import {SelfCheckResult} from '../../../../../types/api/healthcheck';
9
- import type {IHealthCheck} from '../../../../../types/store/healthcheck';
8
+ import type {IIssuesTree} from '../../../../../types/store/healthcheck';
10
9
 
11
- import {IssuePreview} from '../IssuePreview';
10
+ import {PreviewItem} from './PreviewItem';
12
11
 
13
12
  import i18n from '../i18n';
14
13
 
15
14
  const b = cn('healthcheck');
16
15
 
17
16
  interface PreviewProps {
18
- data?: IHealthCheck;
17
+ selfCheckResult: SelfCheckResult;
18
+ issuesTrees?: IIssuesTree[];
19
19
  loading?: boolean;
20
- onUpdate: VoidFunction;
21
20
  onShowMore?: (id: string) => void;
21
+ onUpdate: VoidFunction;
22
22
  }
23
23
 
24
24
  export const Preview = (props: PreviewProps) => {
25
- const {data, loading, onShowMore, onUpdate} = props;
25
+ const {selfCheckResult, issuesTrees, loading, onShowMore, onUpdate} = props;
26
26
 
27
- const selfCheckResult = data?.self_check_result || SelfCheckResult.UNSPECIFIED;
28
27
  const isStatusOK = selfCheckResult === SelfCheckResult.GOOD;
29
28
 
30
- const issuesLog = data?.issue_log;
31
- const firstLevelIssues = useMemo(
32
- () => issuesLog?.filter(({level}) => level === 1),
33
- [issuesLog],
34
- );
35
-
36
- if (!data) {
29
+ if (!issuesTrees) {
37
30
  return null;
38
31
  }
39
32
 
@@ -58,8 +51,12 @@ export const Preview = (props: PreviewProps) => {
58
51
  <div className={b('preview-content')}>
59
52
  {isStatusOK
60
53
  ? i18n('status_message.ok')
61
- : firstLevelIssues?.map((issue) => (
62
- <IssuePreview key={issue.id} data={issue} onShowMore={onShowMore} />
54
+ : issuesTrees?.map((issueTree) => (
55
+ <PreviewItem
56
+ key={issueTree.id}
57
+ data={issueTree}
58
+ onShowMore={onShowMore}
59
+ />
63
60
  ))}
64
61
  </div>
65
62
  );
@@ -2,19 +2,19 @@ import cn from 'bem-cn-lite';
2
2
 
3
3
  import {Link, Text} from '@gravity-ui/uikit';
4
4
 
5
- import EntityStatus from '../../../../../components/EntityStatus/EntityStatus';
6
- import {IssueLog} from '../../../../../types/api/healthcheck';
5
+ import EntityStatus from '../../../../../../components/EntityStatus/EntityStatus';
6
+ import {IssueLog} from '../../../../../../types/api/healthcheck';
7
7
 
8
- import i18n from '../i18n';
8
+ import i18n from '../../i18n';
9
9
 
10
10
  const b = cn('healthcheck');
11
11
 
12
- interface IssuePreviewProps {
12
+ interface PreviewItemProps {
13
13
  data?: IssueLog;
14
14
  onShowMore?: (id: string) => void;
15
15
  }
16
16
 
17
- export const IssuePreview = (props: IssuePreviewProps) => {
17
+ export const PreviewItem = (props: PreviewItemProps) => {
18
18
  const {data, onShowMore} = props;
19
19
 
20
20
  if (!data) {
@@ -27,9 +27,7 @@ export const IssuePreview = (props: IssuePreviewProps) => {
27
27
  <Text as="div" color="secondary" variant="body-2">
28
28
  {data.message}
29
29
  </Text>
30
- <Link onClick={() => onShowMore && onShowMore(data.id)}>
31
- {i18n('label.show-details')}
32
- </Link>
30
+ <Link onClick={() => onShowMore?.(data.id)}>{i18n('label.show-details')}</Link>
33
31
  </div>
34
32
  );
35
33
  };
@@ -0,0 +1 @@
1
+ export * from './PreviewItem';
@@ -52,7 +52,13 @@
52
52
  &__result {
53
53
  overflow: auto;
54
54
 
55
+ // This fixes last row display for ordinary preview (not fullscreen)
55
56
  height: calc(100% - 40px);
56
57
  padding: 0 10px;
58
+
59
+ // Fix white space footer block for fullscreen preview
60
+ .kv-fullscreen & {
61
+ height: 100%;
62
+ }
57
63
  }
58
64
  }
@@ -54,8 +54,6 @@ export const RUN_ACTIONS = [
54
54
 
55
55
  const TABLE_SETTINGS = {
56
56
  sortable: false,
57
- dynamicItemSizeGetter: () => 40,
58
- dynamicRenderType: 'variable',
59
57
  };
60
58
 
61
59
  const EDITOR_OPTIONS = {
@@ -511,13 +509,7 @@ function QueryEditor(props) {
511
509
  };
512
510
 
513
511
  const renderControls = () => {
514
- const {
515
- executeQuery,
516
- explainQuery,
517
- savedQueries,
518
- selectRunAction,
519
- setSettingValue,
520
- } = props;
512
+ const {executeQuery, explainQuery, savedQueries, selectRunAction, setSettingValue} = props;
521
513
  const {runAction} = executeQuery;
522
514
  const runIsDisabled = !executeQuery.input || executeQuery.loading;
523
515
  const runText = _.find(RUN_ACTIONS, {value: runAction}).content;
@@ -15,8 +15,8 @@
15
15
  }
16
16
  &_fullscreen {
17
17
  width: 100%;
18
- margin-top: 0;
19
- padding: 10px;
18
+ margin-top: 10px;
19
+ padding: 0 10px 10px;
20
20
  }
21
21
  }
22
22
 
@@ -3,10 +3,10 @@ import {useDispatch} from 'react-redux';
3
3
 
4
4
  import {NavigationTree} from 'ydb-ui-components';
5
5
 
6
- import {setCurrentSchemaPath, getSchema, preloadSchema} from '../../../../store/reducers/schema';
6
+ import {setCurrentSchemaPath, getSchema, preloadSchemas} from '../../../../store/reducers/schema';
7
7
  import {getDescribe} from '../../../../store/reducers/describe';
8
8
  import {getSchemaAcl} from '../../../../store/reducers/schemaAcl';
9
- import type {EPathType} from '../../../../types/api/schema';
9
+ import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
10
10
 
11
11
  import {mapPathTypeToNavigationTreeType} from '../../utils/schema';
12
12
  import {getActions} from '../../utils/schemaActions';
@@ -29,15 +29,15 @@ export function SchemaTree(props: SchemaTreeProps) {
29
29
  .then((data) => {
30
30
  const {PathDescription: {Children = []} = {}} = data;
31
31
 
32
- dispatch(preloadSchema(path, data));
32
+ const preloadedData: Record<string, TEvDescribeSchemeResult> = {
33
+ [path]: data
34
+ };
33
35
 
34
- return Children.map((childData) => {
36
+ const childItems = Children.map((childData) => {
35
37
  const {Name = '', PathType, PathSubType} = childData;
36
38
 
37
39
  // not full data, but it contains PathType, which ensures seamless switch between nodes
38
- dispatch(
39
- preloadSchema(`${path}/${Name}`, {PathDescription: {Self: childData}}),
40
- );
40
+ preloadedData[`${path}/${Name}`] = {PathDescription: {Self: childData}};
41
41
 
42
42
  return {
43
43
  name: Name,
@@ -47,6 +47,10 @@ export function SchemaTree(props: SchemaTreeProps) {
47
47
  expandable: true,
48
48
  };
49
49
  });
50
+
51
+ dispatch(preloadSchemas(preloadedData));
52
+
53
+ return childItems;
50
54
  });
51
55
 
52
56
  const handleActivePathUpdate = (activePath: string) => {
@@ -4,8 +4,7 @@ import cn from 'bem-cn-lite';
4
4
  import {useLocation} from 'react-router';
5
5
  import qs from 'qs';
6
6
 
7
- import EmptyState from '../../components/EmptyState/EmptyState';
8
- import {Illustration} from '../../components/Illustration';
7
+ import {AccessDenied} from '../../components/Errors/403';
9
8
 
10
9
  import {setHeader} from '../../store/reducers/header';
11
10
  import ObjectGeneralTabs from './ObjectGeneralTabs/ObjectGeneralTabs';
@@ -129,11 +128,7 @@ function Tenant(props: TenantProps) {
129
128
  return (
130
129
  <div className={b()}>
131
130
  {showBlockingError ? (
132
- <EmptyState
133
- image={<Illustration name="403" />}
134
- title="Access denied"
135
- description="You don’t have the necessary roles to view this page."
136
- />
131
+ <AccessDenied />
137
132
  ) : (
138
133
  <>
139
134
  <ObjectGeneralTabs />
@@ -0,0 +1,71 @@
1
+ import {createSelector} from 'reselect';
2
+
3
+ import '../../services/api';
4
+ import {TEvDescribeSchemeResult} from '../../types/api/schema';
5
+ import {IConsumer} from '../../types/api/consumers';
6
+ import {createRequestActionTypes, createApiRequest, ApiRequestAction} from '../utils';
7
+
8
+ const FETCH_DESCRIBE = createRequestActionTypes('describe', 'FETCH_DESCRIBE');
9
+
10
+ const describe = (
11
+ state = {loading: false, wasLoaded: false, data: {}},
12
+ action: ApiRequestAction<typeof FETCH_DESCRIBE, TEvDescribeSchemeResult, unknown>,
13
+ ) => {
14
+ switch (action.type) {
15
+ case FETCH_DESCRIBE.REQUEST: {
16
+ return {
17
+ ...state,
18
+ loading: true,
19
+ };
20
+ }
21
+ case FETCH_DESCRIBE.SUCCESS: {
22
+ let newData;
23
+
24
+ if (action.data.Path) {
25
+ newData = JSON.parse(JSON.stringify(state.data));
26
+ newData[action.data.Path] = action.data;
27
+ } else {
28
+ newData = state.data;
29
+ }
30
+
31
+ return {
32
+ ...state,
33
+ data: newData,
34
+ currentDescribe: action.data,
35
+ loading: false,
36
+ wasLoaded: true,
37
+ error: undefined,
38
+ };
39
+ }
40
+ case FETCH_DESCRIBE.FAILURE: {
41
+ return {
42
+ ...state,
43
+ error: action.error,
44
+ loading: false,
45
+ };
46
+ }
47
+ default:
48
+ return state;
49
+ }
50
+ };
51
+
52
+ // Consumers selectors
53
+ const selectConsumersNames = (state: any, path: string): string[] | undefined =>
54
+ state.describe.data[path]?.PathDescription?.PersQueueGroup?.PQTabletConfig?.ReadRules;
55
+
56
+ export const selectConsumers = createSelector(selectConsumersNames, (names = []): IConsumer[] => {
57
+ const consumers = names.map((name) => {
58
+ return {name};
59
+ });
60
+
61
+ return consumers;
62
+ });
63
+
64
+ export function getDescribe({path}: {path: string}) {
65
+ return createApiRequest({
66
+ request: window.api.getDescribe({path}),
67
+ actions: FETCH_DESCRIBE,
68
+ });
69
+ }
70
+
71
+ export default describe;
@@ -0,0 +1,123 @@
1
+ import _flow from 'lodash/fp/flow';
2
+ import _sortBy from 'lodash/fp/sortBy';
3
+ import _uniqBy from 'lodash/fp/uniqBy';
4
+ import _omit from 'lodash/omit';
5
+ import {createSelector} from 'reselect';
6
+
7
+ import {IIssuesTree} from '../../types/store/healthcheck';
8
+ import {HealthCheckAPIResponse, IssueLog, StatusFlag} from '../../types/api/healthcheck';
9
+
10
+ import '../../services/api';
11
+ import {createRequestActionTypes, createApiRequest, ApiRequestAction} from '../utils';
12
+
13
+ const FETCH_HEALTHCHECK = createRequestActionTypes('cluster', 'FETCH_HEALTHCHECK');
14
+
15
+ const initialState = {loading: false, wasLoaded: false};
16
+
17
+ const healthcheckInfo = function (
18
+ state = initialState,
19
+ action: ApiRequestAction<typeof FETCH_HEALTHCHECK, HealthCheckAPIResponse, unknown>,
20
+ ) {
21
+ switch (action.type) {
22
+ case FETCH_HEALTHCHECK.REQUEST: {
23
+ return {
24
+ ...state,
25
+ loading: true,
26
+ };
27
+ }
28
+ case FETCH_HEALTHCHECK.SUCCESS: {
29
+ const {data} = action;
30
+
31
+ return {
32
+ ...state,
33
+ data,
34
+ wasLoaded: true,
35
+ loading: false,
36
+ error: undefined,
37
+ };
38
+ }
39
+ case FETCH_HEALTHCHECK.FAILURE: {
40
+ return {
41
+ ...state,
42
+ error: action.error,
43
+ loading: false,
44
+ };
45
+ }
46
+ default:
47
+ return state;
48
+ }
49
+ };
50
+
51
+ const mapStatusToPriority: Partial<Record<StatusFlag, number>> = {
52
+ RED: 0,
53
+ ORANGE: 1,
54
+ YELLOW: 2,
55
+ BLUE: 3,
56
+ GREEN: 4,
57
+ };
58
+
59
+ const getReasonsForIssue = ({issue, data}: {issue: IssueLog; data: IssueLog[]}) => {
60
+ return data.filter((item) => issue.reason && issue.reason.indexOf(item.id) !== -1);
61
+ };
62
+
63
+ const getRoots = (data: IssueLog[]) => {
64
+ let roots = data.filter((item) => {
65
+ return !data.find((issue) => issue.reason && issue.reason.indexOf(item.id) !== -1);
66
+ });
67
+
68
+ roots = _flow([
69
+ _uniqBy((item: IssueLog) => item.id),
70
+ _sortBy(({status}: {status: StatusFlag}) => mapStatusToPriority[status]),
71
+ ])(roots);
72
+
73
+ return roots;
74
+ };
75
+
76
+ const getInvertedConsequencesTree = ({
77
+ data,
78
+ roots,
79
+ }: {
80
+ data: IssueLog[];
81
+ roots?: IssueLog[];
82
+ }): IIssuesTree[] => {
83
+ return roots
84
+ ? roots.map((issue) => {
85
+ const reasonsItems = getInvertedConsequencesTree({
86
+ roots: getReasonsForIssue({issue, data}),
87
+ data,
88
+ });
89
+
90
+ return {
91
+ ...issue,
92
+ reasonsItems,
93
+ };
94
+ })
95
+ : [];
96
+ };
97
+
98
+ const getIssuesLog = (state: any): IssueLog[] | undefined => state.healthcheckInfo.data?.issue_log;
99
+
100
+ export const selectIssuesTreesRoots = createSelector(getIssuesLog, (issues = []) =>
101
+ getRoots(issues),
102
+ );
103
+
104
+ export const selectIssuesTrees = createSelector(
105
+ [getIssuesLog, selectIssuesTreesRoots],
106
+ (data = [], roots = []) => {
107
+ return getInvertedConsequencesTree({data, roots});
108
+ },
109
+ );
110
+
111
+ export const selectIssuesTreeById = createSelector(
112
+ [selectIssuesTrees, (_: any, id: string | undefined) => id],
113
+ (issuesTrees = [], id) => issuesTrees.find((issuesTree: IIssuesTree) => issuesTree.id === id),
114
+ );
115
+
116
+ export function getHealthcheckInfo(database: string) {
117
+ return createApiRequest({
118
+ request: window.api.getHealthcheckInfo(database),
119
+ actions: FETCH_HEALTHCHECK,
120
+ });
121
+ }
122
+
123
+ export default healthcheckInfo;