ydb-embedded-ui 2.3.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
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;