Skip to content

Commit 72fc158

Browse files
authored
feat: Add state of the Kyma modules in cluster overview (#3699)
* feat: add status to Modules card * fix deps * fix: review & small fixes * add more statuses * fix: review adjustments * cleanup
1 parent 20f2e5b commit 72fc158

File tree

7 files changed

+182
-37
lines changed

7 files changed

+182
-37
lines changed

public/i18n/en.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ cluster-overview:
4141
total-persistentvolumes: 'Total Persistent Volumes'
4242
persistent-volumes-total-capacity: 'Total Capacity'
4343
nodes: 'Nodes'
44+
modules-overview: 'Modules Overview'
4445
tooltips:
4546
cpu-used-percentage: 'CPU used: {{percentage}}'
4647
memory-used-percentage: 'Memory used: {{percentage}}'
@@ -369,6 +370,9 @@ common:
369370
statuses:
370371
error: Error
371372
ready: Ready
373+
warning: Warning
374+
processing: Processing
375+
other: Other
372376
unknown: Unknown
373377
tabs:
374378
edit: Edit

src/components/Clusters/views/ClusterOverview/ClusterDetails.js

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@ import { useGetVersions } from './useGetVersions';
55
import { useFeature } from 'hooks/useFeature';
66
import { DynamicPageComponent } from 'shared/components/DynamicPageComponent/DynamicPageComponent';
77
import ResourceDetailsCard from 'shared/components/ResourceDetails/ResourceDetailsCard';
8-
import { Button, Text } from '@ui5/webcomponents-react';
9-
import { CountingCard } from 'shared/components/CountingCard/CountingCard';
10-
import { useKymaModulesQuery } from 'components/KymaModules/kymaModulesQueries';
11-
import { useUrl } from 'hooks/useUrl';
12-
import { useNavigate } from 'react-router-dom';
8+
import { Text } from '@ui5/webcomponents-react';
9+
import ClusterModulesCard from './ClusterModulesCard';
1310

1411
const GardenerProvider = () => {
1512
const { t } = useTranslation();
@@ -33,9 +30,6 @@ export default function ClusterDetails({ currentCluster }) {
3330
const { t } = useTranslation();
3431
const { loading, kymaVersion, k8sVersion } = useGetVersions();
3532
const config = currentCluster?.config;
36-
const { modules, error, loading: loadingModules } = useKymaModulesQuery();
37-
const { clusterUrl } = useUrl();
38-
const navigate = useNavigate();
3933

4034
return (
4135
<div className="resource-details-container">
@@ -72,23 +66,7 @@ export default function ClusterDetails({ currentCluster }) {
7266
</>
7367
}
7468
/>
75-
{!error && !loadingModules && modules && (
76-
<div className="item-wrapper sap-margin-x-small">
77-
<CountingCard
78-
className="item"
79-
value={modules?.length}
80-
title={t('kyma-modules.installed-modules')}
81-
additionalContent={
82-
<Button
83-
design="Emphasized"
84-
onClick={() => navigate(clusterUrl('kymamodules'))}
85-
>
86-
{t('kyma-modules.modify-modules')}
87-
</Button>
88-
}
89-
/>
90-
</div>
91-
)}
69+
<ClusterModulesCard />
9270
</div>
9371
);
9472
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { Button } from '@ui5/webcomponents-react';
3+
import { CountingCard } from 'shared/components/CountingCard/CountingCard';
4+
import { useKymaModulesQuery } from 'components/KymaModules/kymaModulesQueries';
5+
import { useUrl } from 'hooks/useUrl';
6+
import { useNavigate } from 'react-router-dom';
7+
import { useGetAllModulesStatuses } from 'components/KymaModules/support';
8+
import { useMemo } from 'react';
9+
10+
export default function ClusterModulesCard() {
11+
const { t } = useTranslation();
12+
const { modules, error, loading: loadingModules } = useKymaModulesQuery();
13+
const { clusterUrl } = useUrl();
14+
const navigate = useNavigate();
15+
const {
16+
data: statuses,
17+
loading: loadingStatuses,
18+
error: statusesError,
19+
} = useGetAllModulesStatuses(modules);
20+
21+
const moduleStatusCounts = useMemo(() => {
22+
if (statuses && !loadingStatuses && !statusesError) {
23+
return statuses.reduce(
24+
(
25+
acc: {
26+
ready: number;
27+
error: number;
28+
warning: number;
29+
processing: number;
30+
other: number;
31+
},
32+
m: { status: string },
33+
) => {
34+
if (m?.status === 'Ready') acc.ready++;
35+
else if (m?.status === 'Error') acc.error++;
36+
else if (m?.status === 'Warning') acc.warning++;
37+
else if (m?.status === 'Processing') acc.processing++;
38+
else acc.other++;
39+
return acc;
40+
},
41+
{ ready: 0, error: 0, warning: 0, processing: 0, other: 0 },
42+
);
43+
}
44+
return { ready: 0, error: 0, warning: 0, processing: 0, other: 0 };
45+
46+
// eslint-disable-next-line react-hooks/exhaustive-deps
47+
}, [statuses, statusesError]);
48+
49+
return (
50+
<>
51+
{!error && !loadingModules && modules && (
52+
<CountingCard
53+
className="modules-statuses"
54+
value={modules?.length}
55+
title={t('cluster-overview.statistics.modules-overview')}
56+
subTitle={t('kyma-modules.installed-modules')}
57+
extraInfo={[
58+
{
59+
title: t('common.statuses.ready'),
60+
value: moduleStatusCounts.ready,
61+
},
62+
{
63+
title: t('common.statuses.warning'),
64+
value: moduleStatusCounts.warning,
65+
},
66+
{
67+
title: t('common.statuses.processing'),
68+
value: moduleStatusCounts.processing,
69+
},
70+
{
71+
title: t('common.statuses.error'),
72+
value: moduleStatusCounts.error,
73+
},
74+
moduleStatusCounts.other > 0
75+
? {
76+
title: t('common.statuses.other'),
77+
value: moduleStatusCounts.other,
78+
}
79+
: null,
80+
]}
81+
additionalContent={
82+
<Button
83+
design="Emphasized"
84+
onClick={() => navigate(clusterUrl('kymamodules'))}
85+
>
86+
{t('kyma-modules.modify-modules')}
87+
</Button>
88+
}
89+
/>
90+
)}
91+
</>
92+
);
93+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
.gardener-provider {
22
text-transform: uppercase;
33
}
4+
5+
.modules-statuses {
6+
max-width: 400px !important;
7+
width: max-content !important;
8+
9+
span {
10+
max-width: unset;
11+
}
12+
}

src/components/KymaModules/support.ts

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,9 @@ type KymaResourceType = {
2222
};
2323
};
2424

25-
export function useModuleStatus(resource: KymaResourceType) {
26-
const fetch = useFetch();
27-
const [data, setData] = useState<any>(null);
28-
const [loading, setLoading] = useState(true);
29-
const [error, setError] = useState<Error | null>(null);
30-
31-
const path = resource?.metadata?.namespace
25+
const getResourcePath = (resource: KymaResourceType) => {
26+
if (!resource) return '';
27+
return resource?.metadata?.namespace
3228
? `/apis/${resource?.apiVersion}/namespaces/${
3329
resource?.metadata?.namespace
3430
}/${pluralize(resource?.kind || '').toLowerCase()}/${
@@ -37,6 +33,15 @@ export function useModuleStatus(resource: KymaResourceType) {
3733
: `/apis/${resource?.apiVersion}/${pluralize(
3834
resource?.kind || '',
3935
).toLowerCase()}/${resource?.metadata?.name}`;
36+
};
37+
38+
export function useModuleStatus(resource: KymaResourceType) {
39+
const fetch = useFetch();
40+
const [data, setData] = useState<any>(null);
41+
const [loading, setLoading] = useState(true);
42+
const [error, setError] = useState<Error | null>(null);
43+
44+
const path = getResourcePath(resource);
4045

4146
useEffect(() => {
4247
async function fetchModule() {
@@ -61,6 +66,58 @@ export function useModuleStatus(resource: KymaResourceType) {
6166
return { data, loading, error };
6267
}
6368

69+
export function useGetAllModulesStatuses(modules: any[]) {
70+
const fetch = useFetch();
71+
const [data, setData] = useState<Record<string, any>>({});
72+
const [loading, setLoading] = useState(true);
73+
const [error, setError] = useState<Error | null>(null);
74+
75+
useEffect(() => {
76+
async function fetchModules() {
77+
if (!modules || modules.length === 0) return;
78+
setLoading(true);
79+
try {
80+
const results = await Promise.all(
81+
modules.map(async module => {
82+
const resource = module?.resource ?? module;
83+
84+
if (!resource) return null;
85+
const path = getResourcePath(resource);
86+
87+
try {
88+
const response = await fetch({ relativeUrl: path });
89+
const status = (await response.json())?.status;
90+
return {
91+
key: resource?.metadata?.name ?? resource?.name,
92+
status: status?.state || 'Unknown',
93+
};
94+
} catch (e) {
95+
return {
96+
key: resource?.metadata?.name ?? resource?.name,
97+
status: null,
98+
error: e,
99+
};
100+
}
101+
}),
102+
);
103+
104+
setData(results);
105+
} catch (e) {
106+
if (e instanceof Error) {
107+
setError(e);
108+
}
109+
} finally {
110+
setLoading(false);
111+
}
112+
}
113+
114+
fetchModules();
115+
// eslint-disable-next-line react-hooks/exhaustive-deps
116+
}, [JSON.stringify(modules)]);
117+
118+
return { data, loading, error };
119+
}
120+
64121
export const findModuleStatus = (
65122
kymaResource: KymaResourceType,
66123
moduleName: string,

src/shared/components/CountingCard/CountingCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import './CountingCard.scss';
1111

1212
type CountingCardProps = {
1313
value: number;
14-
extraInfo: [ExtraInfo];
14+
extraInfo: ExtraInfo[];
1515
title: string;
1616
subTitle: string;
1717
resourceUrl?: string;
@@ -24,7 +24,7 @@ type CountingCardProps = {
2424
type ExtraInfo = {
2525
value: string;
2626
title: string;
27-
};
27+
} | null;
2828

2929
export const CountingCard = ({
3030
value,

tests/integration/tests/kyma-cluster/test-kyma-modules.spec.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ context('Test Kyma Modules views', () => {
55
cy.loginAndSelectCluster();
66
});
77

8-
it('Test Feature card for Modules', () => {
8+
it('Test Modules Overview card', () => {
99
cy.wait(2000);
1010

1111
cy.get('ui5-card')
12-
.contains('Installed Modules')
12+
.contains('Modules Overview')
1313
.should('be.visible');
1414

1515
cy.contains('ui5-card', 'Installed Modules')
@@ -99,7 +99,7 @@ context('Test Kyma Modules views', () => {
9999
.should('be.visible');
100100
});
101101

102-
it('Test number of Modules in Feature card', () => {
102+
it('Test number of Modules in Modules Overview card', () => {
103103
cy.getLeftNav()
104104
.contains('Cluster Details')
105105
.click();
@@ -108,6 +108,10 @@ context('Test Kyma Modules views', () => {
108108
.contains('2')
109109
.should('be.visible');
110110

111+
cy.contains('ui5-card', 'Ready')
112+
.contains('2')
113+
.should('be.visible');
114+
111115
cy.get('ui5-card')
112116
.contains('Modify Modules')
113117
.click();

0 commit comments

Comments
 (0)