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.
- package/CHANGELOG.md +24 -0
- package/dist/components/Errors/403/AccessDenied.tsx +19 -0
- package/dist/components/Errors/403/index.ts +1 -0
- package/dist/components/Errors/i18n/en.json +4 -0
- package/dist/components/Errors/i18n/index.ts +11 -0
- package/dist/components/Errors/i18n/ru.json +4 -0
- package/dist/components/QueryResultTable/QueryResultTable.tsx +16 -21
- package/dist/{containers/Storage/StorageFilter/StorageFilter.tsx → components/Search/Search.tsx} +22 -22
- package/dist/components/Search/index.ts +1 -0
- package/dist/containers/Nodes/Nodes.js +5 -0
- package/dist/containers/Storage/Storage.js +11 -3
- package/dist/containers/TabletsFilters/TabletsFilters.js +5 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.scss +6 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +82 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/i18n/en.json +6 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/i18n/index.ts +11 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +6 -0
- package/dist/containers/Tenant/Diagnostics/Consumers/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -0
- package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +15 -8
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/Details.tsx +55 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Details/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.scss +5 -5
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Healthcheck.tsx +16 -6
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/{IssueViewer.scss → IssueTree.scss} +3 -54
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTree.tsx +87 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/IssueTreeItem.scss +50 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/IssueTreeItem.tsx +25 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/Preview.tsx +13 -16
- package/dist/containers/Tenant/Diagnostics/Healthcheck/{IssuePreview/IssuePreview.tsx → Preview/PreviewItem/PreviewItem.tsx} +6 -8
- package/dist/containers/Tenant/Diagnostics/Healthcheck/Preview/PreviewItem/index.ts +1 -0
- package/dist/containers/Tenant/Preview/Preview.scss +6 -0
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +1 -9
- package/dist/containers/Tenant/QueryEditor/QueryResult/QueryResult.scss +2 -2
- package/dist/containers/Tenant/Schema/SchemaTree/SchemaTree.tsx +11 -7
- package/dist/containers/Tenant/Tenant.tsx +2 -7
- package/dist/store/reducers/describe.ts +71 -0
- package/dist/store/reducers/healthcheckInfo.ts +123 -0
- package/dist/store/reducers/schema.js +10 -13
- package/dist/store/reducers/storage.js +6 -6
- package/dist/store/utils.ts +21 -13
- package/dist/types/api/consumers.ts +3 -0
- package/dist/types/api/healthcheck.ts +1 -1
- package/dist/types/store/healthcheck.ts +5 -1
- package/package.json +1 -1
- package/dist/containers/Storage/StorageFilter/index.ts +0 -1
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuePreview/index.ts +0 -1
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesList/IssuesList.tsx +0 -62
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesList/index.ts +0 -1
- package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssuesViewer.js +0 -151
- package/dist/store/reducers/describe.js +0 -45
- 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-
|
38
|
+
.issue-tree {
|
90
39
|
display: flex;
|
91
40
|
|
92
41
|
width: 820px;
|
93
42
|
|
94
|
-
&
|
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-
|
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;
|
package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/IssueTreeItem.scss
ADDED
@@ -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
|
+
}
|
package/dist/containers/Tenant/Diagnostics/Healthcheck/IssuesViewer/IssueTreeItem/IssueTreeItem.tsx
ADDED
@@ -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 {
|
8
|
+
import type {IIssuesTree} from '../../../../../types/store/healthcheck';
|
10
9
|
|
11
|
-
import {
|
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
|
-
|
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 {
|
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
|
-
|
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
|
-
:
|
62
|
-
<
|
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 '
|
6
|
-
import {IssueLog} from '
|
5
|
+
import EntityStatus from '../../../../../../components/EntityStatus/EntityStatus';
|
6
|
+
import {IssueLog} from '../../../../../../types/api/healthcheck';
|
7
7
|
|
8
|
-
import i18n from '
|
8
|
+
import i18n from '../../i18n';
|
9
9
|
|
10
10
|
const b = cn('healthcheck');
|
11
11
|
|
12
|
-
interface
|
12
|
+
interface PreviewItemProps {
|
13
13
|
data?: IssueLog;
|
14
14
|
onShowMore?: (id: string) => void;
|
15
15
|
}
|
16
16
|
|
17
|
-
export const
|
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
|
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;
|
@@ -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,
|
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
|
-
|
32
|
+
const preloadedData: Record<string, TEvDescribeSchemeResult> = {
|
33
|
+
[path]: data
|
34
|
+
};
|
33
35
|
|
34
|
-
|
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
|
-
|
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
|
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
|
-
<
|
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;
|