From 4ebe7b79ddf1331407113439dc28d6c0736b5ecb Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 8 Jan 2025 09:36:45 +0100 Subject: [PATCH 01/21] Add Utilization popover with notification --- src/components/utilization-popover/dev.tsx | 0 .../utilization-popover.tsx | 83 ++++++++++++ src/store/radix-api.ts | 125 ++++++++++-------- 3 files changed, 156 insertions(+), 52 deletions(-) create mode 100644 src/components/utilization-popover/dev.tsx create mode 100644 src/components/utilization-popover/utilization-popover.tsx diff --git a/src/components/utilization-popover/dev.tsx b/src/components/utilization-popover/dev.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/utilization-popover/utilization-popover.tsx b/src/components/utilization-popover/utilization-popover.tsx new file mode 100644 index 000000000..999d2c337 --- /dev/null +++ b/src/components/utilization-popover/utilization-popover.tsx @@ -0,0 +1,83 @@ +import { Popover, Typography } from '@equinor/eds-core-react'; +import { useEffect } from 'react'; + +import type { Application } from '../../store/service-now-api'; +import { configVariables } from '../../utils/config'; +import { ExternalLink } from '../link/external-link'; + +export interface Props { + open: boolean; + onClose: () => unknown; + anchorEl?: HTMLElement; + configurationItem: Application; +} + +function urlStringForCI(id: string): string { + return configVariables.CMDB_CI_URL.replace(/{CIID}/g, encodeURIComponent(id)); +} + +export const UtilizationPopover = ({ + open, + onClose, + anchorEl, + configurationItem, +}: Props) => { + const externalUrl = urlStringForCI(configurationItem.id); + + useEffect(() => { + const handleBodyClick = () => onClose(); + document.body.addEventListener('click', handleBodyClick); + return () => { + document.body.removeEventListener('click', handleBodyClick); + }; + }, [onClose]); + + return ( + ev.stopPropagation()} + > + + {configurationItem && ( +
+
+ + Name + + + {configurationItem.name} + +
+
+ + App ID + + {configurationItem.number || 'N/A'} +
+
+ + Product Owner + + {configurationItem.productOwner || 'N/A'} +
+
+ + Technical Contact Persons + + + {configurationItem.technicalContactPersons || 'N/A'} + +
+
+ + Description + + {configurationItem.description || 'N/A'} +
+
+ )} +
+
+ ); +}; diff --git a/src/store/radix-api.ts b/src/store/radix-api.ts index 7bab2aaf0..7239a9241 100644 --- a/src/store/radix-api.ts +++ b/src/store/radix-api.ts @@ -798,6 +798,18 @@ const injectedRtkApi = api.injectEndpoints({ }, }), }), + getEnvironmentResourcesUtilization: build.query< + GetEnvironmentResourcesUtilizationApiResponse, + GetEnvironmentResourcesUtilizationApiArg + >({ + query: (queryArg) => ({ + url: `/applications/${queryArg.appName}/environments/${queryArg.envName}/utilization`, + headers: { + 'Impersonate-User': queryArg['Impersonate-User'], + 'Impersonate-Group': queryArg['Impersonate-Group'], + }, + }), + }), getApplicationJobs: build.query< GetApplicationJobsApiResponse, GetApplicationJobsApiArg @@ -1070,21 +1082,6 @@ const injectedRtkApi = api.injectEndpoints({ }, }), }), - getResources: build.query({ - query: (queryArg) => ({ - url: `/applications/${queryArg.appName}/resources`, - headers: { - 'Impersonate-User': queryArg['Impersonate-User'], - 'Impersonate-Group': queryArg['Impersonate-Group'], - }, - params: { - environment: queryArg.environment, - component: queryArg.component, - duration: queryArg.duration, - since: queryArg.since, - }, - }), - }), restartApplication: build.mutation< RestartApplicationApiResponse, RestartApplicationApiArg @@ -1124,6 +1121,18 @@ const injectedRtkApi = api.injectEndpoints({ }, }), }), + getApplicationResourcesUtilization: build.query< + GetApplicationResourcesUtilizationApiResponse, + GetApplicationResourcesUtilizationApiArg + >({ + query: (queryArg) => ({ + url: `/applications/${queryArg.appName}/utilization`, + headers: { + 'Impersonate-User': queryArg['Impersonate-User'], + 'Impersonate-Group': queryArg['Impersonate-Group'], + }, + }), + }), }), overrideExisting: false, }); @@ -2005,6 +2014,18 @@ export type StopEnvironmentApiArg = { /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ 'Impersonate-Group'?: string; }; +export type GetEnvironmentResourcesUtilizationApiResponse = + /** status 200 Successful trigger pipeline */ ReplicaResourcesUtilizationResponse; +export type GetEnvironmentResourcesUtilizationApiArg = { + /** Name of the application */ + appName: string; + /** Name of the application environment */ + envName: string; + /** Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) */ + 'Impersonate-User'?: string; + /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ + 'Impersonate-Group'?: string; +}; export type GetApplicationJobsApiResponse = /** status 200 Successful operation */ JobSummary[]; export type GetApplicationJobsApiArg = { @@ -2274,24 +2295,6 @@ export type ResetManuallyScaledComponentsInApplicationApiArg = { /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ 'Impersonate-Group'?: string; }; -export type GetResourcesApiResponse = - /** status 200 Successful trigger pipeline */ UsedResources; -export type GetResourcesApiArg = { - /** Name of the application */ - appName: string; - /** Name of the application environment */ - environment?: string; - /** Name of the application component in an environment */ - component?: string; - /** Duration of the period, default is 30d (30 days). Example 10m, 1h, 2d, 3w, where m-minutes, h-hours, d-days, w-weeks */ - duration?: string; - /** End time-point of the period in the past, default is now. Example 10m, 1h, 2d, 3w, where m-minutes, h-hours, d-days, w-weeks */ - since?: string; - /** Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) */ - 'Impersonate-User'?: string; - /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ - 'Impersonate-Group'?: string; -}; export type RestartApplicationApiResponse = unknown; export type RestartApplicationApiArg = { /** Name of application */ @@ -2319,6 +2322,16 @@ export type StopApplicationApiArg = { /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ 'Impersonate-Group'?: string; }; +export type GetApplicationResourcesUtilizationApiResponse = + /** status 200 Successful trigger pipeline */ ReplicaResourcesUtilizationResponse; +export type GetApplicationResourcesUtilizationApiArg = { + /** Name of the application */ + appName: string; + /** Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) */ + 'Impersonate-User'?: string; + /** Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) */ + 'Impersonate-Group'?: string; +}; export type TlsAutomation = { /** Message is a human readable description of the reason for the status */ message?: string; @@ -3162,6 +3175,31 @@ export type ScheduledJobRequest = { /** Name of the Radix deployment for a job */ deploymentName?: string; }; +export type ReplicaUtilization = { + /** Average CPU Used */ + cpu_avg: number; + /** Cpu Requests */ + cpu_reqs: number; + /** Max memory used */ + mem_max: number; + /** Memory Requests */ + mem_reqs: number; +}; +export type ComponentUtilization = { + replicas?: { + [key: string]: ReplicaUtilization; + }; +}; +export type EnvironmentUtilization = { + components?: { + [key: string]: ComponentUtilization; + }; +}; +export type ReplicaResourcesUtilizationResponse = { + environments?: { + [key: string]: EnvironmentUtilization; + }; +}; export type Step = { /** Components associated components */ components?: string[]; @@ -3489,24 +3527,6 @@ export type RegenerateDeployKeyAndSecretData = { /** SharedSecret of the shared secret */ sharedSecret?: string; }; -export type UsedResource = { - /** Avg Average resource used */ - avg?: number; - /** Max resource used */ - max?: number; - /** Min resource used */ - min?: number; -}; -export type UsedResources = { - cpu?: UsedResource; - /** From timestamp */ - from: string; - memory?: UsedResource; - /** To timestamp */ - to: string; - /** Warning messages */ - warnings?: string[]; -}; export const { useShowApplicationsQuery, useRegisterApplicationMutation, @@ -3573,6 +3593,7 @@ export const { useRestartEnvironmentMutation, useStartEnvironmentMutation, useStopEnvironmentMutation, + useGetEnvironmentResourcesUtilizationQuery, useGetApplicationJobsQuery, useGetApplicationJobQuery, useGetPipelineJobStepLogsQuery, @@ -3594,8 +3615,8 @@ export const { useUpdatePrivateImageHubsSecretValueMutation, useRegenerateDeployKeyMutation, useResetManuallyScaledComponentsInApplicationMutation, - useGetResourcesQuery, useRestartApplicationMutation, useStartApplicationMutation, useStopApplicationMutation, + useGetApplicationResourcesUtilizationQuery, } = injectedRtkApi; From 2447b006d814d98f921dbb707d13096776babf49 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Thu, 9 Jan 2025 13:00:32 +0100 Subject: [PATCH 02/21] Add Utilization popover with notification --- src/components/app-overview/index.tsx | 2 - .../page-environment/component-list.tsx | 5 + src/components/page-environment/style.css | 7 +- src/components/resources/index.tsx | 157 --------------- src/components/resources/style.css | 26 --- .../utilization-popover.tsx | 180 +++++++++++------- src/store/radix-api.ts | 8 +- 7 files changed, 123 insertions(+), 262 deletions(-) delete mode 100644 src/components/resources/index.tsx delete mode 100644 src/components/resources/style.css diff --git a/src/components/app-overview/index.tsx b/src/components/app-overview/index.tsx index af6977b22..67640e35c 100644 --- a/src/components/app-overview/index.tsx +++ b/src/components/app-overview/index.tsx @@ -13,7 +13,6 @@ import { DefaultAppAlias } from '../component/default-app-alias'; import { DNSAliases } from '../component/dns-aliases'; import { EnvironmentsSummary } from '../environments-summary'; import { JobsList } from '../jobs-list'; -import { UsedResources } from '../resources'; const LATEST_JOBS_LIMIT = 5; @@ -44,7 +43,6 @@ export function AppOverview({ appName }: { appName: string }) {
-
{appAlias && } diff --git a/src/components/page-environment/component-list.tsx b/src/components/page-environment/component-list.tsx index 0f7288557..646c84f2f 100644 --- a/src/components/page-environment/component-list.tsx +++ b/src/components/page-environment/component-list.tsx @@ -183,6 +183,9 @@ export const ComponentList: FunctionComponent = ({ Replicas + + Resources + Vulnerabilities @@ -248,6 +251,7 @@ export const ComponentList: FunctionComponent = ({ } /> + resources = ({ /> + )} diff --git a/src/components/page-environment/style.css b/src/components/page-environment/style.css index 6817e0211..7c35b55c5 100644 --- a/src/components/page-environment/style.css +++ b/src/components/page-environment/style.css @@ -5,10 +5,13 @@ width: 15%; } .component-list > thead .component-list-head__vulnerabilities { - width: 35%; + width: 30%; } .component-list > thead .component-list-head__replicas { - width: 25%; + width: 20%; +} +.component-list > thead .component-list-head__resources { + width: 10%; } .component-list > tbody td { word-break: break-all; diff --git a/src/components/resources/index.tsx b/src/components/resources/index.tsx deleted file mode 100644 index 953bbb575..000000000 --- a/src/components/resources/index.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { Typography } from '@equinor/eds-core-react'; -import type { FunctionComponent } from 'react'; -import { externalUrls } from '../../externalUrls'; -import { - type GetResourcesApiResponse, - useGetResourcesQuery, -} from '../../store/radix-api'; -import { formatDateTimeYear } from '../../utils/datetime'; -import AsyncResource from '../async-resource/async-resource'; - -import './style.css'; -import { library_books } from '@equinor/eds-icons'; -import { ExternalLink } from '../link/external-link'; - -function getPeriod({ from, to }: GetResourcesApiResponse): string { - return `${formatDateTimeYear(new Date(from))} - ${formatDateTimeYear( - new Date(to) - )}`; -} - -export interface UsedResourcesProps { - appName: string; -} - -export const UsedResources: FunctionComponent = ({ - appName, -}) => { - const { data: resources, ...state } = useGetResourcesQuery( - { appName }, - { skip: !appName } - ); - const formatCpuUsage = (value?: number): string => { - if (!value) { - return '-'; - } - if (value >= 1) { - return parseFloat(value.toPrecision(3)).toString(); - } - - const millicores = value * 1000.0; - let formattedValue: string; - if (millicores >= 1.0) { - formattedValue = parseFloat(millicores.toPrecision(3)).toString(); - return `${formattedValue}m`; - } - let mcStr = millicores.toFixed(20); // Use 20 decimal places to ensure precision - mcStr = mcStr.replace(/0+$/, ''); - // Find the position of the decimal point - const decimalIndex = mcStr.indexOf('.'); - // Find the index of the first non-zero digit after the decimal point - let firstNonZeroIndex = -1; - for (let i = decimalIndex + 1; i < mcStr.length; i++) { - if (mcStr[i] !== '0') { - firstNonZeroIndex = i; - break; - } - } - if (firstNonZeroIndex === -1) { - return '0m'; - } - // Create a new number where the digit at firstNonZeroIndex becomes the first decimal digit - const digits = `0.${mcStr.substring(firstNonZeroIndex)}`; - let num = parseFloat(digits); - // Round the number to one digit - num = Math.round(num * 10) / 10; - // Handle rounding that results in num >= 1 - if (num >= 1) { - num = 1; - } - let numStr = num.toString(); - // Remove the decimal point and any following zeros - numStr = numStr.replace('0.', '').replace(/0+$/, ''); - // Replace the part of mcStr starting from firstNonZeroIndex - 1 - let zerosCount = firstNonZeroIndex - decimalIndex - 1; - // Adjust zerosCount, when num is 1 - if (num === 1) { - zerosCount -= 1; - } - const leadingDigitalZeros = '0'.repeat(Math.max(zerosCount, 0)); - const output = `0.${leadingDigitalZeros}${numStr}`; - return `${output}m`; - }; - - const formatMemoryUsage = (value?: number): string => { - if (!value) { - return '-'; - } - const units = [ - { unit: 'P', size: 1e15 }, - { unit: 'T', size: 1e12 }, - { unit: 'G', size: 1e9 }, - { unit: 'M', size: 1e6 }, - { unit: 'k', size: 1e3 }, - ]; - - let unit = ''; // Default to bytes - - // Determine the appropriate unit - for (const u of units) { - if (value >= u.size) { - value = value / u.size; - unit = u.unit; - break; - } - } - const formattedValue = parseFloat(value.toPrecision(3)).toString(); - return formattedValue + unit; - }; - - return ( -
-
- Used resources - -
- - {resources ? ( -
-
- Period - - {getPeriod(resources)} - -
- -
-
- - CPU{' '} - - min {formatCpuUsage(resources?.cpu?.min)}, avg{' '} - {formatCpuUsage(resources?.cpu?.avg)}, max{' '} - {formatCpuUsage(resources?.cpu?.max)} - - - - Memory{' '} - - min {formatMemoryUsage(resources?.memory?.min)}, avg{' '} - {formatMemoryUsage(resources?.memory?.avg)}, max{' '} - {formatMemoryUsage(resources?.memory?.max)} - - -
-
-
- ) : ( - No data - )} -
-
- ); -}; diff --git a/src/components/resources/style.css b/src/components/resources/style.css deleted file mode 100644 index 55a8a74a4..000000000 --- a/src/components/resources/style.css +++ /dev/null @@ -1,26 +0,0 @@ -.resources-section > div { - grid-auto-rows: min-content; -} - -@media (min-width: 30rem) { - .resources-section { - grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); - } -} - -.icon-justify-end { - justify-self: end; -} - -.resources__scrim .resources__scrim-content { - margin: var(--eds_spacing_medium); - margin-top: initial; - align-content: center; - min-width: 500px; -} - -.resources-content { - padding: var(--eds_spacing_medium); - padding-top: 0; - overflow: auto; -} diff --git a/src/components/utilization-popover/utilization-popover.tsx b/src/components/utilization-popover/utilization-popover.tsx index 999d2c337..e50486c49 100644 --- a/src/components/utilization-popover/utilization-popover.tsx +++ b/src/components/utilization-popover/utilization-popover.tsx @@ -1,83 +1,121 @@ -import { Popover, Typography } from '@equinor/eds-core-react'; -import { useEffect } from 'react'; +import { Chip, Popover } from '@equinor/eds-core-react'; +import { useEffect, useRef, useState } from 'react'; +import type { ReplicaResourcesUtilizationResponse } from '../../store/radix-api'; -import type { Application } from '../../store/service-now-api'; -import { configVariables } from '../../utils/config'; -import { ExternalLink } from '../link/external-link'; - -export interface Props { - open: boolean; - onClose: () => unknown; - anchorEl?: HTMLElement; - configurationItem: Application; -} - -function urlStringForCI(id: string): string { - return configVariables.CMDB_CI_URL.replace(/{CIID}/g, encodeURIComponent(id)); -} +type Props = { + appName: string; + envName: string; + data: ReplicaResourcesUtilizationResponse; +}; -export const UtilizationPopover = ({ - open, - onClose, - anchorEl, - configurationItem, -}: Props) => { - const externalUrl = urlStringForCI(configurationItem.id); +export const UtilizationPopover = ({ appName, envName, data }: Props) => { + const [open, setOpen] = useState(false); + const ref = useRef(null); useEffect(() => { - const handleBodyClick = () => onClose(); + const handleBodyClick = () => setOpen(false); document.body.addEventListener('click', handleBodyClick); return () => { document.body.removeEventListener('click', handleBodyClick); }; - }, [onClose]); + }, []); + + console.log({ data }); return ( - ev.stopPropagation()} - > - - {configurationItem && ( -
-
- - Name - - - {configurationItem.name} - -
-
- - App ID - - {configurationItem.number || 'N/A'} -
-
- - Product Owner - - {configurationItem.productOwner || 'N/A'} -
-
- - Technical Contact Persons - - - {configurationItem.technicalContactPersons || 'N/A'} - -
-
- - Description - - {configurationItem.description || 'N/A'} -
-
- )} -
-
+ <> + setOpen(true)}> + Utilization + + ev.stopPropagation()} + > + + hello world {appName} {envName} + + + ); }; + +// @ts-expect-error not used yet +const formatCpuUsage = (value?: number): string => { + if (!value) { + return '-'; + } + if (value >= 1) { + return parseFloat(value.toPrecision(3)).toString(); + } + + const millicores = value * 1000.0; + let formattedValue: string; + if (millicores >= 1.0) { + formattedValue = parseFloat(millicores.toPrecision(3)).toString(); + return `${formattedValue}m`; + } + let mcStr = millicores.toFixed(20); // Use 20 decimal places to ensure precision + mcStr = mcStr.replace(/0+$/, ''); + // Find the position of the decimal point + const decimalIndex = mcStr.indexOf('.'); + // Find the index of the first non-zero digit after the decimal point + let firstNonZeroIndex = -1; + for (let i = decimalIndex + 1; i < mcStr.length; i++) { + if (mcStr[i] !== '0') { + firstNonZeroIndex = i; + break; + } + } + if (firstNonZeroIndex === -1) { + return '0m'; + } + // Create a new number where the digit at firstNonZeroIndex becomes the first decimal digit + const digits = `0.${mcStr.substring(firstNonZeroIndex)}`; + let num = parseFloat(digits); + // Round the number to one digit + num = Math.round(num * 10) / 10; + // Handle rounding that results in num >= 1 + if (num >= 1) { + num = 1; + } + let numStr = num.toString(); + // Remove the decimal point and any following zeros + numStr = numStr.replace('0.', '').replace(/0+$/, ''); + // Replace the part of mcStr starting from firstNonZeroIndex - 1 + let zerosCount = firstNonZeroIndex - decimalIndex - 1; + // Adjust zerosCount, when num is 1 + if (num === 1) { + zerosCount -= 1; + } + const leadingDigitalZeros = '0'.repeat(Math.max(zerosCount, 0)); + const output = `0.${leadingDigitalZeros}${numStr}`; + return `${output}m`; +}; + +// @ts-expect-error not used yet +const formatMemoryUsage = (value?: number): string => { + if (!value) { + return '-'; + } + const units = [ + { unit: 'P', size: 1e15 }, + { unit: 'T', size: 1e12 }, + { unit: 'G', size: 1e9 }, + { unit: 'M', size: 1e6 }, + { unit: 'k', size: 1e3 }, + ]; + + let unit = ''; // Default to bytes + + // Determine the appropriate unit + for (const u of units) { + if (value >= u.size) { + value = value / u.size; + unit = u.unit; + break; + } + } + const formattedValue = parseFloat(value.toPrecision(3)).toString(); + return formattedValue + unit; +}; diff --git a/src/store/radix-api.ts b/src/store/radix-api.ts index 7239a9241..58e02171f 100644 --- a/src/store/radix-api.ts +++ b/src/store/radix-api.ts @@ -3177,13 +3177,13 @@ export type ScheduledJobRequest = { }; export type ReplicaUtilization = { /** Average CPU Used */ - cpu_avg: number; + cpuAverage: number; /** Cpu Requests */ - cpu_reqs: number; + cpuRequests: number; /** Max memory used */ - mem_max: number; + memoryMaximum: number; /** Memory Requests */ - mem_reqs: number; + memoryRequests: number; }; export type ComponentUtilization = { replicas?: { From 31eec0635104b031351b4369040e0332e07b4f7a Mon Sep 17 00:00:00 2001 From: Richard87 Date: Fri, 10 Jan 2025 13:26:02 +0100 Subject: [PATCH 03/21] Parse response and add StatusBadge --- .../page-environment/component-list.tsx | 9 +- .../status-badges/severity-status-badge.tsx | 69 ++++++ .../status-badges/status-badge-template.tsx | 52 +++-- .../utilization-popover.tsx | 215 ++++++++++-------- src/store/radix-api.ts | 5 - src/store/utils/index.ts | 16 ++ 6 files changed, 247 insertions(+), 119 deletions(-) create mode 100644 src/components/status-badges/severity-status-badge.tsx diff --git a/src/components/page-environment/component-list.tsx b/src/components/page-environment/component-list.tsx index 646c84f2f..ae435b894 100644 --- a/src/components/page-environment/component-list.tsx +++ b/src/components/page-environment/component-list.tsx @@ -38,6 +38,7 @@ import { ReplicaStatusTooltip } from '../status-tooltips'; import { VulnerabilitySummary } from '../vulnerability-summary'; import './style.css'; +import { UtilizationPopover } from '../utilization-popover/utilization-popover'; export interface ComponentListProps { appName: string; @@ -251,7 +252,13 @@ export const ComponentList: FunctionComponent = ({ } />
- resources + + + { + if (a > b) return a; + return b; +}; + +export const GetHighestSeverityFns = ( + data: TArgs, + fns: ((data: TArgs) => Severity)[] +): Severity => { + let highest = Severity.None; + + fns.forEach((fn) => { + const res = fn(data); + highest = GetHighestSeverity(res, highest); + }); + + return highest; +}; + +const BadgeTemplates = { + [Severity.None]: { + icon: , + children: 'Normal', + type: 'none', + }, + [Severity.Information]: { + icon: , + children: 'Information', + type: 'default', + }, + [Severity.Warning]: { + icon: , + children: 'Warning', + type: 'warning', + }, + [Severity.Critical]: { + icon: , + children: 'Critical', + type: 'danger', + }, +} satisfies Record; + +type Props = { + severity: Severity; +} & StatusBadgeTemplateProps; + +export const SeverityStatusBadge = forwardRef( + ({ severity, ...rest }: Props, ref: ForwardedRef) => { + return ( + + ); + } +); diff --git a/src/components/status-badges/status-badge-template.tsx b/src/components/status-badges/status-badge-template.tsx index 32ca55bb5..3d397701f 100644 --- a/src/components/status-badges/status-badge-template.tsx +++ b/src/components/status-badges/status-badge-template.tsx @@ -2,7 +2,7 @@ import { Chip, type ChipProps } from '@equinor/eds-core-react'; import { clsx } from 'clsx'; import './style.css'; -import type { PropsWithChildren } from 'react'; +import { type ForwardedRef, type PropsWithChildren, forwardRef } from 'react'; export type StatusBadgeTemplateType = | 'success' @@ -17,25 +17,31 @@ export type StatusBadgeTemplateProps = { } & ChipProps; /** StatusBadge template */ -export function StatusBadgeTemplate({ - className, - children, - icon, - type, - ...rest -}: PropsWithChildren) { - return ( - - {icon ?? <>} -
{children}
-
- ); -} +export const StatusBadgeTemplate = forwardRef( + ( + { + className, + children, + icon, + type, + ...rest + }: PropsWithChildren, + ref: ForwardedRef + ) => { + return ( + + {icon ?? <>} +
{children}
+
+ ); + } +); diff --git a/src/components/utilization-popover/utilization-popover.tsx b/src/components/utilization-popover/utilization-popover.tsx index e50486c49..311129205 100644 --- a/src/components/utilization-popover/utilization-popover.tsx +++ b/src/components/utilization-popover/utilization-popover.tsx @@ -1,16 +1,39 @@ -import { Chip, Popover } from '@equinor/eds-core-react'; -import { useEffect, useRef, useState } from 'react'; -import type { ReplicaResourcesUtilizationResponse } from '../../store/radix-api'; +import { Popover } from '@equinor/eds-core-react'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { pollingInterval } from '../../store/defaults'; +import { + type GetEnvironmentResourcesUtilizationApiResponse, + type ReplicaUtilization, + useGetApplicationResourcesUtilizationQuery, +} from '../../store/radix-api'; +import { + GetHighestSeverity, + GetHighestSeverityFns, + Severity, + SeverityStatusBadge, +} from '../status-badges/severity-status-badge'; + +const LowCPUThreshold = 0.2; +const HighCPUThreshold = 0.8; +const MaxCPUThreshold = 1.0; + +const LowMemoryThreshold = 0.2; +const HighMemoryThreshold = 0.7; +const MaxMemoryThreshold = 0.9; type Props = { appName: string; - envName: string; - data: ReplicaResourcesUtilizationResponse; + path: string; + style?: 'icon' | 'chip'; }; -export const UtilizationPopover = ({ appName, envName, data }: Props) => { +export const UtilizationPopover = ({ appName, path }: Props) => { const [open, setOpen] = useState(false); const ref = useRef(null); + const { data } = useGetApplicationResourcesUtilizationQuery( + { appName }, + { pollingInterval } + ); useEffect(() => { const handleBodyClick = () => setOpen(false); @@ -20,102 +43,114 @@ export const UtilizationPopover = ({ appName, envName, data }: Props) => { }; }, []); - console.log({ data }); + const { highestMemoryAlert, highestCPUAlert } = useMemo(() => { + let highestMemoryAlert = Severity.None; + let highestCPUAlert = Severity.None; + flattenAndFilterResults(data, path).forEach((replica) => { + const cpuAlert = GetHighestSeverityFns(replica, [ + HasMaxCPUtilizationPercentage, + HasHighCPUtilizationPercentage, + HasLowCPUtilizationPercentage, + ]); + + const memoryAlert = GetHighestSeverityFns(replica, [ + HasMaxMemorytilizationPercentage, + HasHighMemorytilizationPercentage, + HasLowMemorytilizationPercentage, + ]); + + highestCPUAlert = GetHighestSeverity(cpuAlert, highestCPUAlert); + highestMemoryAlert = GetHighestSeverity(memoryAlert, highestMemoryAlert); + }); + + return { highestMemoryAlert, highestCPUAlert }; + }, [data, path]); + const severity = GetHighestSeverity(highestMemoryAlert, highestCPUAlert); return ( <> - setOpen(true)}> - Utilization - ev.stopPropagation()} > - - hello world {appName} {envName} - + hello world {appName} + + setOpen(true)} + onMouseLeave={() => setOpen(false)} + /> ); }; -// @ts-expect-error not used yet -const formatCpuUsage = (value?: number): string => { - if (!value) { - return '-'; - } - if (value >= 1) { - return parseFloat(value.toPrecision(3)).toString(); - } - - const millicores = value * 1000.0; - let formattedValue: string; - if (millicores >= 1.0) { - formattedValue = parseFloat(millicores.toPrecision(3)).toString(); - return `${formattedValue}m`; - } - let mcStr = millicores.toFixed(20); // Use 20 decimal places to ensure precision - mcStr = mcStr.replace(/0+$/, ''); - // Find the position of the decimal point - const decimalIndex = mcStr.indexOf('.'); - // Find the index of the first non-zero digit after the decimal point - let firstNonZeroIndex = -1; - for (let i = decimalIndex + 1; i < mcStr.length; i++) { - if (mcStr[i] !== '0') { - firstNonZeroIndex = i; - break; - } - } - if (firstNonZeroIndex === -1) { - return '0m'; - } - // Create a new number where the digit at firstNonZeroIndex becomes the first decimal digit - const digits = `0.${mcStr.substring(firstNonZeroIndex)}`; - let num = parseFloat(digits); - // Round the number to one digit - num = Math.round(num * 10) / 10; - // Handle rounding that results in num >= 1 - if (num >= 1) { - num = 1; - } - let numStr = num.toString(); - // Remove the decimal point and any following zeros - numStr = numStr.replace('0.', '').replace(/0+$/, ''); - // Replace the part of mcStr starting from firstNonZeroIndex - 1 - let zerosCount = firstNonZeroIndex - decimalIndex - 1; - // Adjust zerosCount, when num is 1 - if (num === 1) { - zerosCount -= 1; - } - const leadingDigitalZeros = '0'.repeat(Math.max(zerosCount, 0)); - const output = `0.${leadingDigitalZeros}${numStr}`; - return `${output}m`; +const flattenAndFilterResults = ( + data: GetEnvironmentResourcesUtilizationApiResponse | undefined, + path: string +): ReplicaUtilization[] => { + if (!data || !data.environments) return []; + + const results: ReplicaUtilization[] = []; + + Object.keys(data.environments).forEach((envName) => { + const components = data.environments?.[envName].components; + if (!components) return; + + Object.keys(components).forEach((compName) => { + const replicas = components[compName].replicas; + if (!replicas) return; + + Object.keys(replicas).forEach((replicaName) => { + const key = `${envName}.${compName}.${replicaName}`; + if (!key.startsWith(path)) return; + + results.push(replicas[replicaName]); + }); + }); + }); + + return results; +}; + +const HasLowCPUtilizationPercentage = (data: ReplicaUtilization): Severity => { + return data.cpuAverage / data.cpuRequests < LowCPUThreshold + ? Severity.Information + : Severity.None; +}; + +const HasHighCPUtilizationPercentage = (data: ReplicaUtilization): Severity => { + return data.cpuAverage / data.cpuRequests > HighCPUThreshold + ? Severity.Warning + : Severity.None; +}; +const HasMaxCPUtilizationPercentage = (data: ReplicaUtilization): Severity => { + return data.cpuAverage / data.cpuRequests > MaxCPUThreshold + ? Severity.Critical + : Severity.None; }; -// @ts-expect-error not used yet -const formatMemoryUsage = (value?: number): string => { - if (!value) { - return '-'; - } - const units = [ - { unit: 'P', size: 1e15 }, - { unit: 'T', size: 1e12 }, - { unit: 'G', size: 1e9 }, - { unit: 'M', size: 1e6 }, - { unit: 'k', size: 1e3 }, - ]; - - let unit = ''; // Default to bytes - - // Determine the appropriate unit - for (const u of units) { - if (value >= u.size) { - value = value / u.size; - unit = u.unit; - break; - } - } - const formattedValue = parseFloat(value.toPrecision(3)).toString(); - return formattedValue + unit; +const HasLowMemorytilizationPercentage = ( + data: ReplicaUtilization +): Severity => { + return data.memoryMaximum / data.memoryRequests < LowMemoryThreshold + ? Severity.Information + : Severity.None; +}; + +const HasHighMemorytilizationPercentage = ( + data: ReplicaUtilization +): Severity => { + return data.memoryMaximum / data.memoryRequests > HighMemoryThreshold + ? Severity.Warning + : Severity.None; +}; +const HasMaxMemorytilizationPercentage = ( + data: ReplicaUtilization +): Severity => { + return data.memoryMaximum / data.memoryRequests > MaxMemoryThreshold + ? Severity.Critical + : Severity.None; }; diff --git a/src/store/radix-api.ts b/src/store/radix-api.ts index 58e02171f..346cb15f8 100644 --- a/src/store/radix-api.ts +++ b/src/store/radix-api.ts @@ -2494,8 +2494,6 @@ export type ReplicaSummary = { resources?: ResourceRequirements; /** RestartCount count of restarts of a component container inside a pod */ restartCount?: number; - /** The time at which the batch job's pod startedAt */ - startTime?: string; /** StatusMessage provides message describing the status of a component container inside a pod */ statusMessage?: string; /** Pod type @@ -3129,11 +3127,8 @@ export type ScheduledBatchSummary = { ended?: string; /** Jobs within the batch of ScheduledJobSummary */ jobList?: ScheduledJobSummary[]; - /** Deprecated: Message of a status, if any, of the job */ - message?: string; /** Name of the scheduled batch */ name: string; - replica?: ReplicaSummary; /** Started timestamp */ started?: string; /** Status of the job diff --git a/src/store/utils/index.ts b/src/store/utils/index.ts index bdd6fe837..f80a04c38 100644 --- a/src/store/utils/index.ts +++ b/src/store/utils/index.ts @@ -19,6 +19,14 @@ export function getFetchErrorData(error: ManagedErrors): { } } + if (IsAbortError(error)) { + return { + error: error.name, + message: "Request aborted", + code: undefined, + } + } + if (typeof error !== "object" || error === null) { console.warn("unkown error: ", error) return { @@ -158,3 +166,11 @@ function IsCustomError(e: FetchBaseQueryError): e is CustomError { return e.status === "CUSTOM_ERROR" } + +type AbortError = { + name: string; + message: string; +} +function IsAbortError(e: Error|unknown): e is AbortError { + return e != null && typeof e == "object" && 'name' in e && e.name === "AbortError" +} From 47b0c37acc3c0931a4acd6e891ab349b0945263f Mon Sep 17 00:00:00 2001 From: Richard87 Date: Fri, 10 Jan 2025 13:31:20 +0100 Subject: [PATCH 04/21] tweaks --- src/components/utilization-popover/utilization-popover.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/utilization-popover/utilization-popover.tsx b/src/components/utilization-popover/utilization-popover.tsx index 311129205..783608d00 100644 --- a/src/components/utilization-popover/utilization-popover.tsx +++ b/src/components/utilization-popover/utilization-popover.tsx @@ -70,10 +70,14 @@ export const UtilizationPopover = ({ appName, path }: Props) => { return ( <> ev.stopPropagation()} > + + Resource Status + hello world {appName} From bd1b8112222058a195d16ee7fa0374b6153884c8 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 13 Jan 2025 08:31:25 +0100 Subject: [PATCH 05/21] update dependencies, rm unused dockerfile --- Dockerfile | 2 +- auth-state.Dockerfile | 3 --- docker-compose-host-macos.yml | 6 +++--- docker-compose-host.yml | 6 +++--- docker-compose.yml | 6 +++--- 5 files changed, 10 insertions(+), 13 deletions(-) delete mode 100644 auth-state.Dockerfile diff --git a/Dockerfile b/Dockerfile index 74c09aeac..8065c0387 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN npm install COPY . . RUN npm run build -FROM docker.io/nginxinc/nginx-unprivileged:1.27-alpine3.20 +FROM docker.io/nginxinc/nginx-unprivileged:1.27-alpine3.21 WORKDIR /app COPY --from=builder /app/build /app COPY proxy/server.conf /default.conf diff --git a/auth-state.Dockerfile b/auth-state.Dockerfile deleted file mode 100644 index a819f138f..000000000 --- a/auth-state.Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM redis:7.4.0-alpine3.20 - -USER 999 diff --git a/docker-compose-host-macos.yml b/docker-compose-host-macos.yml index 43bf51ead..d5a0d54b7 100644 --- a/docker-compose-host-macos.yml +++ b/docker-compose-host-macos.yml @@ -1,6 +1,6 @@ services: web: - image: node:20.9-alpine + image: node:22.12-alpine3.21 container_name: radix-web_container read_only: true working_dir: /app @@ -31,7 +31,7 @@ services: test: "exit 0" proxy: - image: nginxinc/nginx-unprivileged:1.26-alpine + image: nginxinc/nginx-unprivileged:1.27-alpine container_name: radix-proxy_container read_only: true depends_on: @@ -103,7 +103,7 @@ services: # for use with development auth-state: - image: redis:5-alpine + image: redis:7-alpine container_name: radix-auth-state_container networks: - radix diff --git a/docker-compose-host.yml b/docker-compose-host.yml index 80f91bad0..86072b5fd 100644 --- a/docker-compose-host.yml +++ b/docker-compose-host.yml @@ -1,6 +1,6 @@ services: web: - image: node:20.9-alpine + image: node:22.12-alpine3.21 container_name: radix-web_container read_only: true working_dir: /app @@ -18,7 +18,7 @@ services: - "9222:9222" proxy: - image: nginxinc/nginx-unprivileged:1.26-alpine + image: nginxinc/nginx-unprivileged:1.27-alpine container_name: radix-proxy_container read_only: true volumes: @@ -73,7 +73,7 @@ services: # for use with development auth-state: - image: redis:5-alpine + image: redis:7-alpine container_name: radix-auth-state_container network_mode: host ports: diff --git a/docker-compose.yml b/docker-compose.yml index 1d596e260..fb3868723 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: web: - image: node:20.9-alpine + image: node:22.12-alpine3.21 container_name: radix-web_container read_only: true working_dir: /app @@ -24,7 +24,7 @@ services: - "9222:9222" proxy: - image: nginxinc/nginx-unprivileged:1.26-alpine + image: nginxinc/nginx-unprivileged:1.27-alpine container_name: radix-proxy_container read_only: true volumes: @@ -85,7 +85,7 @@ services: # for use with development auth-state: - image: redis:5-alpine + image: redis:7-alpine container_name: radix-auth-state_container networks: - radix From 80e1b1cad22b52bb7d53f7d15db42374d448257f Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 13 Jan 2025 09:42:40 +0100 Subject: [PATCH 06/21] Labeled popup --- biome.json | 3 +- .../status-badges/severity-status-badge.tsx | 20 ++++++--- src/components/utilization-popover/style.css | 5 +++ .../utilization-popover.tsx | 45 ++++++++++++++----- 4 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 src/components/utilization-popover/style.css diff --git a/biome.json b/biome.json index 64ffb0e57..1304e9c10 100644 --- a/biome.json +++ b/biome.json @@ -11,7 +11,8 @@ "dist/", "src/store/*.ts", ".idea", - "src/utils/config/index.ts" + "src/utils/config/index.ts", + ".git" ] }, "formatter": { diff --git a/src/components/status-badges/severity-status-badge.tsx b/src/components/status-badges/severity-status-badge.tsx index ea7a46362..ecf8cb857 100644 --- a/src/components/status-badges/severity-status-badge.tsx +++ b/src/components/status-badges/severity-status-badge.tsx @@ -36,34 +36,40 @@ export const GetHighestSeverityFns = ( const BadgeTemplates = { [Severity.None]: { icon: , - children: 'Normal', + message: 'Normal', type: 'none', }, [Severity.Information]: { icon: , - children: 'Information', + message: 'Information', type: 'default', }, [Severity.Warning]: { icon: , - children: 'Warning', + message: 'Warning', type: 'warning', }, [Severity.Critical]: { icon: , - children: 'Critical', + message: 'Critical', type: 'danger', }, -} satisfies Record; +} satisfies Record; type Props = { severity: Severity; } & StatusBadgeTemplateProps; export const SeverityStatusBadge = forwardRef( - ({ severity, ...rest }: Props, ref: ForwardedRef) => { + ( + { severity, children, ...rest }: Props, + ref: ForwardedRef + ) => { + const { message, ...template } = BadgeTemplates[severity]; return ( - + + {children ?? message} + ); } ); diff --git a/src/components/utilization-popover/style.css b/src/components/utilization-popover/style.css new file mode 100644 index 000000000..b7eb1ee6b --- /dev/null +++ b/src/components/utilization-popover/style.css @@ -0,0 +1,5 @@ +.utilization_popover__content { + display: flex; + flex-direction: column; + gap: 0.5em; +} diff --git a/src/components/utilization-popover/utilization-popover.tsx b/src/components/utilization-popover/utilization-popover.tsx index 783608d00..e552516c7 100644 --- a/src/components/utilization-popover/utilization-popover.tsx +++ b/src/components/utilization-popover/utilization-popover.tsx @@ -1,5 +1,6 @@ -import { Popover } from '@equinor/eds-core-react'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { Icon, Popover, Typography } from '@equinor/eds-core-react'; +import { desktop_mac } from '@equinor/eds-icons'; +import { useMemo, useRef, useState } from 'react'; import { pollingInterval } from '../../store/defaults'; import { type GetEnvironmentResourcesUtilizationApiResponse, @@ -12,6 +13,7 @@ import { Severity, SeverityStatusBadge, } from '../status-badges/severity-status-badge'; +import './style.css'; const LowCPUThreshold = 0.2; const HighCPUThreshold = 0.8; @@ -21,6 +23,20 @@ const LowMemoryThreshold = 0.2; const HighMemoryThreshold = 0.7; const MaxMemoryThreshold = 0.9; +const CPULabels = { + [Severity.None]: 'CPU utilization OK', + [Severity.Information]: 'CPU utilization Low', + [Severity.Warning]: 'CPU utilization High', + [Severity.Critical]: 'CPU utilization Critical', +} satisfies Record; + +const MemoryLabels = { + [Severity.None]: 'Memory utilization OK', + [Severity.Information]: 'Memory utilization Low', + [Severity.Warning]: 'Memory utilization High', + [Severity.Critical]: 'Memory utilization Critical', +} satisfies Record; + type Props = { appName: string; path: string; @@ -35,14 +51,6 @@ export const UtilizationPopover = ({ appName, path }: Props) => { { pollingInterval } ); - useEffect(() => { - const handleBodyClick = () => setOpen(false); - document.body.addEventListener('click', handleBodyClick); - return () => { - document.body.removeEventListener('click', handleBodyClick); - }; - }, []); - const { highestMemoryAlert, highestCPUAlert } = useMemo(() => { let highestMemoryAlert = Severity.None; let highestCPUAlert = Severity.None; @@ -66,6 +74,8 @@ export const UtilizationPopover = ({ appName, path }: Props) => { return { highestMemoryAlert, highestCPUAlert }; }, [data, path]); + console.log('rendered'); + const severity = GetHighestSeverity(highestMemoryAlert, highestCPUAlert); return ( <> @@ -78,7 +88,20 @@ export const UtilizationPopover = ({ appName, path }: Props) => { Resource Status - hello world {appName} + + + {MemoryLabels[highestMemoryAlert]} + + + {CPULabels[highestCPUAlert]} + + + + + See Monitoring for more + details. + + Date: Mon, 13 Jan 2025 10:44:18 +0100 Subject: [PATCH 07/21] Remove ref from status badge again --- .../component-replica-list.tsx | 3 ++ .../page-active-component/oauth-service.tsx | 3 ++ src/components/replica-list/index.tsx | 31 +++++++---- .../status-badges/severity-status-badge.tsx | 23 +++----- .../status-badges/status-badge-template.tsx | 52 ++++++++----------- .../utilization-popover.tsx | 42 +++++++-------- 6 files changed, 77 insertions(+), 77 deletions(-) diff --git a/src/components/page-active-component/component-replica-list.tsx b/src/components/page-active-component/component-replica-list.tsx index 36a5af3a7..29a8ed00c 100644 --- a/src/components/page-active-component/component-replica-list.tsx +++ b/src/components/page-active-component/component-replica-list.tsx @@ -32,6 +32,9 @@ export const ComponentReplicaList = ({
{replicaList && replicaList.length > 0 ? ( getReplicaUrl(appName, envName, componentName, name) diff --git a/src/components/page-active-component/oauth-service.tsx b/src/components/page-active-component/oauth-service.tsx index fe39301ca..17ff76490 100644 --- a/src/components/page-active-component/oauth-service.tsx +++ b/src/components/page-active-component/oauth-service.tsx @@ -49,6 +49,9 @@ export const OAuthService = ({ {oauth2.deployment.replicaList && oauth2.deployment.replicaList.length > 0 ? ( getOAuthReplicaUrl(appName, envName, componentName, name) diff --git a/src/components/replica-list/index.tsx b/src/components/replica-list/index.tsx index 06cbfa226..ede6617f9 100644 --- a/src/components/replica-list/index.tsx +++ b/src/components/replica-list/index.tsx @@ -1,13 +1,7 @@ import { Icon, Table, Typography } from '@equinor/eds-core-react'; import { chevron_down, chevron_up } from '@equinor/eds-icons'; import { clsx } from 'clsx'; -import { - Fragment, - type FunctionComponent, - useCallback, - useEffect, - useState, -} from 'react'; +import { Fragment, useCallback, useEffect, useState } from 'react'; import type { ReplicaSummary } from '../../store/radix-api'; import { @@ -23,12 +17,23 @@ import { Duration } from '../time/duration'; import { RelativeToNow } from '../time/relative-to-now'; import './style.css'; +import { UtilizationPopover } from '../utilization-popover/utilization-popover'; import { ReplicaName } from './replica-name'; -export const ReplicaList: FunctionComponent<{ +type Props = { + appName: string; + envName: string; + compName: string; replicaList: Array; replicaUrlFunc: (name: string) => string; -}> = ({ replicaList, replicaUrlFunc }) => { +}; +export const ReplicaList = ({ + replicaList, + replicaUrlFunc, + appName, + envName, + compName, +}: Props) => { const [sortedData, setSortedData] = useState(replicaList || []); const [dateSort, setDateSort] = useState(); const [statusSort, setStatusSort] = useState(); @@ -83,6 +88,7 @@ export const ReplicaList: FunctionComponent<{ Duration + Resources @@ -127,6 +133,13 @@ export const ReplicaList: FunctionComponent<{ + + + {expanded && ( diff --git a/src/components/status-badges/severity-status-badge.tsx b/src/components/status-badges/severity-status-badge.tsx index ecf8cb857..0835dcc18 100644 --- a/src/components/status-badges/severity-status-badge.tsx +++ b/src/components/status-badges/severity-status-badge.tsx @@ -1,7 +1,5 @@ import { Icon } from '@equinor/eds-core-react'; import { pressure } from '@equinor/eds-icons'; - -import { type ForwardedRef, forwardRef } from 'react'; import { StatusBadgeTemplate, type StatusBadgeTemplateProps, @@ -60,16 +58,11 @@ type Props = { severity: Severity; } & StatusBadgeTemplateProps; -export const SeverityStatusBadge = forwardRef( - ( - { severity, children, ...rest }: Props, - ref: ForwardedRef - ) => { - const { message, ...template } = BadgeTemplates[severity]; - return ( - - {children ?? message} - - ); - } -); +export const SeverityStatusBadge = ({ severity, children, ...rest }: Props) => { + const { message, ...template } = BadgeTemplates[severity]; + return ( + + {children ?? message} + + ); +}; diff --git a/src/components/status-badges/status-badge-template.tsx b/src/components/status-badges/status-badge-template.tsx index 3d397701f..9e8dcd063 100644 --- a/src/components/status-badges/status-badge-template.tsx +++ b/src/components/status-badges/status-badge-template.tsx @@ -2,7 +2,7 @@ import { Chip, type ChipProps } from '@equinor/eds-core-react'; import { clsx } from 'clsx'; import './style.css'; -import { type ForwardedRef, type PropsWithChildren, forwardRef } from 'react'; +import type { PropsWithChildren } from 'react'; export type StatusBadgeTemplateType = | 'success' @@ -17,31 +17,25 @@ export type StatusBadgeTemplateProps = { } & ChipProps; /** StatusBadge template */ -export const StatusBadgeTemplate = forwardRef( - ( - { - className, - children, - icon, - type, - ...rest - }: PropsWithChildren, - ref: ForwardedRef - ) => { - return ( - - {icon ?? <>} -
{children}
-
- ); - } -); +export const StatusBadgeTemplate = ({ + className, + children, + icon, + type, + ...rest +}: PropsWithChildren) => { + return ( + + {icon ?? <>} +
{children}
+
+ ); +}; diff --git a/src/components/utilization-popover/utilization-popover.tsx b/src/components/utilization-popover/utilization-popover.tsx index e552516c7..2b9055313 100644 --- a/src/components/utilization-popover/utilization-popover.tsx +++ b/src/components/utilization-popover/utilization-popover.tsx @@ -23,18 +23,11 @@ const LowMemoryThreshold = 0.2; const HighMemoryThreshold = 0.7; const MaxMemoryThreshold = 0.9; -const CPULabels = { - [Severity.None]: 'CPU utilization OK', - [Severity.Information]: 'CPU utilization Low', - [Severity.Warning]: 'CPU utilization High', - [Severity.Critical]: 'CPU utilization Critical', -} satisfies Record; - -const MemoryLabels = { - [Severity.None]: 'Memory utilization OK', - [Severity.Information]: 'Memory utilization Low', - [Severity.Warning]: 'Memory utilization High', - [Severity.Critical]: 'Memory utilization Critical', +const Labels = { + [Severity.None]: 'Utilization ok', + [Severity.Information]: 'Utilization Low', + [Severity.Warning]: 'Utilization High', + [Severity.Critical]: 'Utilization Critical', } satisfies Record; type Props = { @@ -69,16 +62,20 @@ export const UtilizationPopover = ({ appName, path }: Props) => { highestCPUAlert = GetHighestSeverity(cpuAlert, highestCPUAlert); highestMemoryAlert = GetHighestSeverity(memoryAlert, highestMemoryAlert); + + console.log({ replica, memoryAlert }); }); return { highestMemoryAlert, highestCPUAlert }; }, [data, path]); - console.log('rendered'); - const severity = GetHighestSeverity(highestMemoryAlert, highestCPUAlert); return ( - <> +
setOpen(true)} + onMouseLeave={() => setOpen(false)} + > { - {MemoryLabels[highestMemoryAlert]} + Memory {Labels[highestMemoryAlert]} - {CPULabels[highestCPUAlert]} + CPU {Labels[highestCPUAlert]} @@ -104,13 +101,10 @@ export const UtilizationPopover = ({ appName, path }: Props) => { - setOpen(true)} - onMouseLeave={() => setOpen(false)} - /> - + + {Labels[severity]} + +
); }; From cdd3ca72216456b15d4f683dffad397db8880814 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 13 Jan 2025 11:27:39 +0100 Subject: [PATCH 08/21] expand navbar by default --- src/components/app-navbar/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/app-navbar/index.tsx b/src/components/app-navbar/index.tsx index 4f348031a..a8c8606da 100644 --- a/src/components/app-navbar/index.tsx +++ b/src/components/app-navbar/index.tsx @@ -165,7 +165,7 @@ const NavbarMinimized = ({ appName, links }: NavbarProps) => ( ); export const AppNavbar: FunctionComponent = ({ appName }) => { - const [toggle, setToggle] = useLocalStorage('app-nav', false); + const [toggle, setToggle] = useLocalStorage('app-nav', true); const links: Array = [ { label: 'Environments', to: getEnvsUrl(appName), icon: world }, From b85aa589636ab347b12473077f9232f19539de30 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 14 Jan 2025 10:26:21 +0100 Subject: [PATCH 09/21] Refactor environment summary card, and add tests --- .../deployment-details.tsx | 36 +++ src/components/environments-summary/dev.tsx | 194 ++++++++++++--- .../environments-summary/environment-card.tsx | 224 +++++++----------- .../environment-headers.tsx | 69 ++++++ .../environment-ingress.tsx | 79 +++--- .../page-environment/component-list.tsx | 21 +- src/components/replica-list/index.tsx | 15 +- .../status-badges/severity-status-badge.tsx | 5 +- src/components/utilization-popover/style.css | 5 - .../utilization-popover.tsx | 32 +-- 10 files changed, 430 insertions(+), 250 deletions(-) create mode 100644 src/components/environments-summary/deployment-details.tsx create mode 100644 src/components/environments-summary/environment-headers.tsx delete mode 100644 src/components/utilization-popover/style.css diff --git a/src/components/environments-summary/deployment-details.tsx b/src/components/environments-summary/deployment-details.tsx new file mode 100644 index 000000000..bc93cb492 --- /dev/null +++ b/src/components/environments-summary/deployment-details.tsx @@ -0,0 +1,36 @@ +import { Icon, Typography } from '@equinor/eds-core-react'; +import { link, send } from '@equinor/eds-icons'; +import { Link } from 'react-router-dom'; +import type { DeploymentSummary } from '../../store/radix-api'; +import { getAppDeploymentUrl } from '../../utils/routing'; +import { RelativeToNow } from '../time/relative-to-now'; + +type DeploymentDetailsProps = { + appName: string; + deployment?: Pick; +}; +export const DeploymentDetails = ({ + appName, + deployment, +}: DeploymentDetailsProps) => + !deployment ? ( + + + No active deployment + + ) : ( + + + + deployment{' '} + + () + + + + ); diff --git a/src/components/environments-summary/dev.tsx b/src/components/environments-summary/dev.tsx index f7dd8d165..5a4a01bbe 100644 --- a/src/components/environments-summary/dev.tsx +++ b/src/components/environments-summary/dev.tsx @@ -1,7 +1,11 @@ import { Divider } from '@equinor/eds-core-react'; import { EnvironmentsSummary } from '.'; - import type { EnvironmentSummary } from '../../store/radix-api'; +import { + EnvironmentCardLayout, + type EnvironmentCardLayoutProps, +} from './environment-card'; +import { URL_VAR_NAME } from './environment-ingress'; const testData: Array> = [ [ @@ -71,36 +75,162 @@ const testData: Array> = [ ]; export default ( -
- {testData.map((x, i) => ( -
- - {i !== testData.length - 1 && } + <> +
+ {testData.map((x, i) => ( +
+ + {i !== testData.length - 1 && } +
+ ))} + + +
+
- ))} -
+
+ ); + +const cards: EnvironmentCardLayoutProps[] = [ + { + appName: 'test-component', + env: { name: 'dev', status: 'Consistent' }, + deployment: undefined, + isLoading: false, + envScan: { name: 'dev' }, + components: [], + repository: 'https://github.com/equinor/radix-web-console', + }, + { + appName: 'test-component', + env: { name: 'dev' }, + deployment: { name: 'web', activeFrom: '2020-02-02T12:00:00Z' }, + isLoading: true, + envScan: undefined, + components: undefined, + repository: undefined, + }, + { + appName: 'test-component', + env: { name: 'dev', status: 'Consistent' }, + deployment: { + name: 'web', + activeFrom: '2020-02-02T12:00:00Z', + gitTags: 'abcd1234', + }, + isLoading: false, + envScan: undefined, + components: [ + { + name: 'web', + image: 'ghcr.io/test/test:test', + type: 'component', + status: 'Consistent', + variables: { + [URL_VAR_NAME]: 'test.example.com', + }, + replicaList: [ + { + type: 'ComponentReplica', + name: 'web-abcd-1', + created: '2020-02-02T12:00:00Z', + replicaStatus: { status: 'Running' }, + }, + ], + }, + ], + repository: 'https://github.com/equinor/radix-web-console', + }, + { + appName: 'test-component', + env: { name: 'dev', status: 'Consistent', branchMapping: 'main' }, + deployment: { + name: 'web', + activeFrom: '2020-02-02T12:00:00Z', + }, + isLoading: true, + utilization: { + environments: { + dev: { + components: { + web: { + replicas: { + 'web-abcd-1': { + cpuAverage: 1.0, + cpuRequests: 1.0, + memoryMaximum: 1000, + memoryRequests: 1000, + }, + }, + }, + }, + }, + }, + }, + envScan: { + name: 'dev', + components: { + web: { + image: 'test:test', + scanSuccess: true, + scanTime: '2020-02-02T12:00:00Z', + vulnerabilitySummary: { + critical: 2, + high: 1, + medium: 2, + low: 5, + }, + }, + }, + }, + components: [ + { + name: 'web', + image: 'ghcr.io/test/test:test', + type: 'component', + status: 'Reconciling', + variables: { + [URL_VAR_NAME]: 'test.example.com', + }, + replicaList: [ + { + type: 'ComponentReplica', + name: 'web-abcd-1', + created: '2020-02-02T12:00:00Z', + replicaStatus: { status: 'Failed' }, + }, + ], + }, + ], + repository: 'https://github.com/equinor/radix-web-console', + }, +] satisfies EnvironmentCardLayoutProps[]; + +function TestEnvironmentCards() { + return cards.map((k, i) => ); +} diff --git a/src/components/environments-summary/environment-card.tsx b/src/components/environments-summary/environment-card.tsx index 9ab13d124..96cb9f1aa 100644 --- a/src/components/environments-summary/environment-card.tsx +++ b/src/components/environments-summary/environment-card.tsx @@ -1,171 +1,106 @@ -import { Divider, Icon, Typography } from '@equinor/eds-core-react'; -import { github, link, send } from '@equinor/eds-icons'; -import type React from 'react'; -import { Link } from 'react-router-dom'; - import { - EnvironmentCardStatus, - type EnvironmentCardStatusMap, - EnvironmentVulnerabilityIndicator, -} from './environment-card-status'; + Chip, + CircularProgress, + Divider, + Icon, + Typography, +} from '@equinor/eds-core-react'; +import { github } from '@equinor/eds-icons'; +import { Link } from 'react-router-dom'; import { EnvironmentIngress } from './environment-ingress'; -import { - aggregateComponentEnvironmentStatus, - aggregateComponentReplicaEnvironmentStatus, - environmentVulnerabilitySummarizer, -} from './environment-status-utils'; import { routes } from '../../routes'; import { pollingInterval } from '../../store/defaults'; import { + type Component, type DeploymentSummary, type EnvironmentSummary, - type ReplicaSummary, + type ReplicaResourcesUtilizationResponse, useComponentsQuery, + useGetApplicationResourcesUtilizationQuery, } from '../../store/radix-api'; import { - type Vulnerability, + type EnvironmentVulnerabilities, useGetEnvironmentVulnerabilitySummaryQuery, } from '../../store/scan-api'; -import { filterFields } from '../../utils/filter-fields'; import { routeWithParams } from '../../utils/string'; -import AsyncResource from '../async-resource/async-resource'; import { GitTagLinks } from '../git-tags/git-tag-links'; -import { RelativeToNow } from '../time/relative-to-now'; import './style.css'; -import { getAppDeploymentUrl } from '../../utils/routing'; - -type CardContent = { header: React.JSX.Element; body: React.JSX.Element }; - -const visibleKeys: Array> = [ - 'critical', - 'high', -]; +import { UtilizationPopover } from '../utilization-popover/utilization-popover'; +import { DeploymentDetails } from './deployment-details'; +import { DeplopymentHeader, VulnerabilityHeader } from './environment-headers'; -type Props = { +type EnvironmentCardProps = { appName: string; - deployment?: Readonly; + env: EnvironmentSummary; + repository?: string; }; -const DeploymentDetails = ({ appName, deployment }: Props) => - !deployment ? ( - - - No active deployment - - ) : ( - - - - deployment{' '} - - () - - - - ); +export const EnvironmentCard = ({ + appName, + env, + repository, +}: EnvironmentCardProps) => { + const deployment = env.activeDeployment; -function CardContentBuilder( - appName: string, - envName: string, - deploymentName: string -): CardContent { - const { data: components, ...componentsState } = useComponentsQuery( - { - appName, - deploymentName, - }, - { pollingInterval } - ); - const { data: envScan, ...envScanState } = + const { data: envScan, isLoading: isEnvScanLoading } = useGetEnvironmentVulnerabilitySummaryQuery( - { appName, envName }, + { appName, envName: env.name }, { pollingInterval: 0 } ); - const vulnerabilities = environmentVulnerabilitySummarizer(envScan); - const replicas = (components ?? []).reduce>( - (obj, { replicaList }) => (!replicaList ? obj : [...obj, ...replicaList]), - [] - ); + const { data: components, isLoading: isComponentsLoading } = + useComponentsQuery( + { + appName, + deploymentName: deployment?.name!, + }, + { pollingInterval, skip: !deployment?.name } + ); - const elements: EnvironmentCardStatusMap = { - Components: aggregateComponentEnvironmentStatus(components ?? []), - ...(replicas.length > 0 && { - Replicas: aggregateComponentReplicaEnvironmentStatus(components ?? []), - }), - }; + const { data: utilization, isLoading: isUtilizationLoading } = + useGetApplicationResourcesUtilizationQuery( + { appName }, + { pollingInterval } + ); - const statusElement = ( - - {components && components.length > 0 && ( -
- {visibleKeys.some((key) => vulnerabilities[key] > 0) && ( - - )} - -
- )} -
+ return ( + ); +}; - return { - header: ( -
- - {statusElement} - -
- ), - body: ( - - {components && components.length > 0 && ( - - )} - - ), - }; -} - -type EnvironmentCardProps = { +export type EnvironmentCardLayoutProps = { appName: string; - env: EnvironmentSummary; + isLoading: boolean; + components?: Component[]; repository?: string; + env: Pick; + deployment?: Pick; + envScan?: EnvironmentVulnerabilities; + utilization?: ReplicaResourcesUtilizationResponse; }; -export const EnvironmentCard = ({ + +export const EnvironmentCardLayout = ({ appName, env, + deployment, + isLoading, + envScan, + components, repository, -}: EnvironmentCardProps) => { - const deployment = env.activeDeployment; - const { header, body }: CardContent = !deployment?.name - ? { - header: <>, - body: ( - - - No link available - - ), - } - : CardContentBuilder(appName, env.name, deployment.name); - + utilization, +}: EnvironmentCardLayoutProps) => { return (
@@ -184,11 +119,25 @@ export const EnvironmentCard = ({
- {header} +
+ {deployment?.name && ( + <> + {isLoading && ( + + + + )} + + + + + )} +
- -
{env.status === 'Orphan' && ( )} - {body} + +
diff --git a/src/components/environments-summary/environment-headers.tsx b/src/components/environments-summary/environment-headers.tsx new file mode 100644 index 000000000..db1775928 --- /dev/null +++ b/src/components/environments-summary/environment-headers.tsx @@ -0,0 +1,69 @@ +import type { Component, ReplicaSummary } from '../../store/radix-api'; +import type { + EnvironmentVulnerabilities, + Vulnerability, +} from '../../store/scan-api'; +import { filterFields } from '../../utils/filter-fields'; +import { + EnvironmentCardStatus, + type EnvironmentCardStatusMap, + EnvironmentVulnerabilityIndicator, +} from './environment-card-status'; +import { + aggregateComponentEnvironmentStatus, + aggregateComponentReplicaEnvironmentStatus, + environmentVulnerabilitySummarizer, +} from './environment-status-utils'; + +const visibleKeys: Array> = [ + 'critical', + 'high', +]; + +type DeploymentHeaderProps = { + components?: Component[]; +}; + +export const DeplopymentHeader = ({ components }: DeploymentHeaderProps) => { + const replicas = (components ?? []).reduce>( + (obj, { replicaList }) => (!replicaList ? obj : [...obj, ...replicaList]), + [] + ); + + const elements: EnvironmentCardStatusMap = { + Components: aggregateComponentEnvironmentStatus(components ?? []), + ...(replicas.length > 0 && { + Replicas: aggregateComponentReplicaEnvironmentStatus(components ?? []), + }), + }; + + if (!components || components.length === 0) { + return null; + } + + return ( + + ); +}; + +type VulnerabilityHeaderProps = { + envScan?: EnvironmentVulnerabilities; +}; + +export const VulnerabilityHeader = ({ envScan }: VulnerabilityHeaderProps) => { + const vulnerabilities = environmentVulnerabilitySummarizer(envScan); + + return ( + visibleKeys.some((key) => vulnerabilities[key] > 0) && ( + + ) + ); +}; diff --git a/src/components/environments-summary/environment-ingress.tsx b/src/components/environments-summary/environment-ingress.tsx index fef287ece..24930323e 100644 --- a/src/components/environments-summary/environment-ingress.tsx +++ b/src/components/environments-summary/environment-ingress.tsx @@ -8,51 +8,14 @@ import { } from '../../utils/routing'; import { ExternalLink } from '../link/external-link'; -export interface EnvironmentIngressProps { - appName: string; - envName: string; - components: Readonly>; -} - -const URL_VAR_NAME = 'RADIX_PUBLIC_DOMAIN_NAME'; +export const URL_VAR_NAME = 'RADIX_PUBLIC_DOMAIN_NAME'; const MAX_DISPLAY_COMPONENTS = 2; -function getComponentUrl( - appName: string, - environmentName: string, - component: Readonly -): string { - return component.type === 'job' - ? getActiveJobComponentUrl(appName, environmentName, component.name) - : getActiveComponentUrl(appName, environmentName, component.name); +export interface EnvironmentIngressProps { + components?: Component[]; } - -type ComponentDetailsProps = { - icon: IconData; - component: Readonly; -}; -const ComponentDetails = ({ icon, component }: ComponentDetailsProps) => ( - <> - - - {component.name} - - -); - -export const EnvironmentIngress = ({ - appName, - envName, - components, -}: EnvironmentIngressProps) => { - const comps = components.reduce<{ +export const EnvironmentIngress = ({ components }: EnvironmentIngressProps) => { + const comps = components?.reduce<{ public: Array; passive: Array; }>( @@ -61,7 +24,7 @@ export const EnvironmentIngress = ({ return obj; }, { public: [], passive: [] } - ); + ) ?? { public: [], passive: [] }; const tooManyPublic = comps.public.length > MAX_DISPLAY_COMPONENTS; const tooManyPassive = comps.passive.length > MAX_DISPLAY_COMPONENTS; @@ -111,3 +74,33 @@ export const EnvironmentIngress = ({ ); }; + +type ComponentDetailsProps = { + icon: IconData; + component: Readonly; +}; +const ComponentDetails = ({ icon, component }: ComponentDetailsProps) => ( + <> + + + {component.name} + + +); + +function getComponentUrl( + appName: string, + environmentName: string, + component: Readonly +): string { + return component.type === 'job' + ? getActiveJobComponentUrl(appName, environmentName, component.name) + : getActiveComponentUrl(appName, environmentName, component.name); +} diff --git a/src/components/page-environment/component-list.tsx b/src/components/page-environment/component-list.tsx index ae435b894..978a76acb 100644 --- a/src/components/page-environment/component-list.tsx +++ b/src/components/page-environment/component-list.tsx @@ -12,11 +12,12 @@ import { Link } from 'react-router-dom'; import { chevron_down, chevron_up, security } from '@equinor/eds-icons'; import clsx from 'clsx'; -import type { - Component, - Environment, - OAuth2AuxiliaryResource, - ReplicaSummary, +import { + type Component, + type Environment, + type OAuth2AuxiliaryResource, + type ReplicaSummary, + useGetApplicationResourcesUtilizationQuery, } from '../../store/radix-api'; import { type EnvironmentVulnerabilities, @@ -38,6 +39,7 @@ import { ReplicaStatusTooltip } from '../status-tooltips'; import { VulnerabilitySummary } from '../vulnerability-summary'; import './style.css'; +import { pollingInterval } from '../../store/defaults'; import { UtilizationPopover } from '../utilization-popover/utilization-popover'; export interface ComponentListProps { @@ -143,6 +145,11 @@ export const ComponentList: FunctionComponent = ({ [] ); + const { data: utilization } = useGetApplicationResourcesUtilizationQuery( + { appName }, + { pollingInterval } + ); + useEffect(() => { const request = trigger({ appName, envName }); return () => request?.abort(); @@ -254,9 +261,9 @@ export const ComponentList: FunctionComponent = ({ diff --git a/src/components/replica-list/index.tsx b/src/components/replica-list/index.tsx index ede6617f9..0180521dc 100644 --- a/src/components/replica-list/index.tsx +++ b/src/components/replica-list/index.tsx @@ -3,7 +3,10 @@ import { chevron_down, chevron_up } from '@equinor/eds-icons'; import { clsx } from 'clsx'; import { Fragment, useCallback, useEffect, useState } from 'react'; -import type { ReplicaSummary } from '../../store/radix-api'; +import { + type ReplicaSummary, + useGetApplicationResourcesUtilizationQuery, +} from '../../store/radix-api'; import { type SortDirection, dataSorter, @@ -17,6 +20,7 @@ import { Duration } from '../time/duration'; import { RelativeToNow } from '../time/relative-to-now'; import './style.css'; +import { pollingInterval } from '../../store/defaults'; import { UtilizationPopover } from '../utilization-popover/utilization-popover'; import { ReplicaName } from './replica-name'; @@ -45,6 +49,11 @@ export const ReplicaList = ({ [] ); + const { data: utilization } = useGetApplicationResourcesUtilizationQuery( + { appName }, + { pollingInterval } + ); + // biome-ignore lint/correctness/useExhaustiveDependencies(replicaList): reset last update when replica list changes useEffect(() => { setLastUpdate(new Date()); @@ -135,9 +144,9 @@ export const ReplicaList = ({ diff --git a/src/components/status-badges/severity-status-badge.tsx b/src/components/status-badges/severity-status-badge.tsx index 0835dcc18..681146690 100644 --- a/src/components/status-badges/severity-status-badge.tsx +++ b/src/components/status-badges/severity-status-badge.tsx @@ -56,13 +56,14 @@ const BadgeTemplates = { type Props = { severity: Severity; + label?: string; } & StatusBadgeTemplateProps; -export const SeverityStatusBadge = ({ severity, children, ...rest }: Props) => { +export const SeverityStatusBadge = ({ severity, label, ...rest }: Props) => { const { message, ...template } = BadgeTemplates[severity]; return ( - {children ?? message} + {label ?? message} ); }; diff --git a/src/components/utilization-popover/style.css b/src/components/utilization-popover/style.css deleted file mode 100644 index b7eb1ee6b..000000000 --- a/src/components/utilization-popover/style.css +++ /dev/null @@ -1,5 +0,0 @@ -.utilization_popover__content { - display: flex; - flex-direction: column; - gap: 0.5em; -} diff --git a/src/components/utilization-popover/utilization-popover.tsx b/src/components/utilization-popover/utilization-popover.tsx index 2b9055313..94cc0b80b 100644 --- a/src/components/utilization-popover/utilization-popover.tsx +++ b/src/components/utilization-popover/utilization-popover.tsx @@ -1,11 +1,10 @@ import { Icon, Popover, Typography } from '@equinor/eds-core-react'; import { desktop_mac } from '@equinor/eds-icons'; import { useMemo, useRef, useState } from 'react'; -import { pollingInterval } from '../../store/defaults'; -import { - type GetEnvironmentResourcesUtilizationApiResponse, - type ReplicaUtilization, - useGetApplicationResourcesUtilizationQuery, +import type { + GetEnvironmentResourcesUtilizationApiResponse, + ReplicaResourcesUtilizationResponse, + ReplicaUtilization, } from '../../store/radix-api'; import { GetHighestSeverity, @@ -13,7 +12,6 @@ import { Severity, SeverityStatusBadge, } from '../status-badges/severity-status-badge'; -import './style.css'; const LowCPUThreshold = 0.2; const HighCPUThreshold = 0.8; @@ -31,23 +29,19 @@ const Labels = { } satisfies Record; type Props = { - appName: string; path: string; - style?: 'icon' | 'chip'; + showLabel?: boolean; + utilization?: ReplicaResourcesUtilizationResponse; }; -export const UtilizationPopover = ({ appName, path }: Props) => { +export const UtilizationPopover = ({ path, showLabel, utilization }: Props) => { const [open, setOpen] = useState(false); const ref = useRef(null); - const { data } = useGetApplicationResourcesUtilizationQuery( - { appName }, - { pollingInterval } - ); const { highestMemoryAlert, highestCPUAlert } = useMemo(() => { let highestMemoryAlert = Severity.None; let highestCPUAlert = Severity.None; - flattenAndFilterResults(data, path).forEach((replica) => { + flattenAndFilterResults(utilization, path).forEach((replica) => { const cpuAlert = GetHighestSeverityFns(replica, [ HasMaxCPUtilizationPercentage, HasHighCPUtilizationPercentage, @@ -62,12 +56,10 @@ export const UtilizationPopover = ({ appName, path }: Props) => { highestCPUAlert = GetHighestSeverity(cpuAlert, highestCPUAlert); highestMemoryAlert = GetHighestSeverity(memoryAlert, highestMemoryAlert); - - console.log({ replica, memoryAlert }); }); return { highestMemoryAlert, highestCPUAlert }; - }, [data, path]); + }, [utilization, path]); const severity = GetHighestSeverity(highestMemoryAlert, highestCPUAlert); return ( @@ -85,7 +77,7 @@ export const UtilizationPopover = ({ appName, path }: Props) => { Resource Status - + Memory {Labels[highestMemoryAlert]} @@ -101,9 +93,7 @@ export const UtilizationPopover = ({ appName, path }: Props) => { - - {Labels[severity]} - +
); }; From 4d1cd88389ff28e11c37ee3eafcfd6696047caa3 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 14 Jan 2025 13:39:46 +0100 Subject: [PATCH 10/21] use status popover instead of status-badge --- .../environments-summary/environment-card.tsx | 6 +- .../status-badges/severity-status-badge.tsx | 69 ----------- src/components/status-popover/dev.tsx | 36 ++++++ .../status-popover/status-popover.tsx | 20 +-- src/components/status-popover/style.css | 79 +++++++----- .../utilization-popover.tsx | 117 ++++++++++-------- 6 files changed, 166 insertions(+), 161 deletions(-) delete mode 100644 src/components/status-badges/severity-status-badge.tsx create mode 100644 src/components/status-popover/dev.tsx diff --git a/src/components/environments-summary/environment-card.tsx b/src/components/environments-summary/environment-card.tsx index 96cb9f1aa..13a5b6859 100644 --- a/src/components/environments-summary/environment-card.tsx +++ b/src/components/environments-summary/environment-card.tsx @@ -27,7 +27,10 @@ import { routeWithParams } from '../../utils/string'; import { GitTagLinks } from '../git-tags/git-tag-links'; import './style.css'; -import { UtilizationPopover } from '../utilization-popover/utilization-popover'; +import { + Severity, + UtilizationPopover, +} from '../utilization-popover/utilization-popover'; import { DeploymentDetails } from './deployment-details'; import { DeplopymentHeader, VulnerabilityHeader } from './environment-headers'; @@ -130,6 +133,7 @@ export const EnvironmentCardLayout = ({ diff --git a/src/components/status-badges/severity-status-badge.tsx b/src/components/status-badges/severity-status-badge.tsx deleted file mode 100644 index 681146690..000000000 --- a/src/components/status-badges/severity-status-badge.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Icon } from '@equinor/eds-core-react'; -import { pressure } from '@equinor/eds-icons'; -import { - StatusBadgeTemplate, - type StatusBadgeTemplateProps, -} from './status-badge-template'; - -export enum Severity { - None = 0, - Information = 1, - Warning = 2, - Critical = 3, -} - -export const GetHighestSeverity = (a: Severity, b: Severity): Severity => { - if (a > b) return a; - return b; -}; - -export const GetHighestSeverityFns = ( - data: TArgs, - fns: ((data: TArgs) => Severity)[] -): Severity => { - let highest = Severity.None; - - fns.forEach((fn) => { - const res = fn(data); - highest = GetHighestSeverity(res, highest); - }); - - return highest; -}; - -const BadgeTemplates = { - [Severity.None]: { - icon: , - message: 'Normal', - type: 'none', - }, - [Severity.Information]: { - icon: , - message: 'Information', - type: 'default', - }, - [Severity.Warning]: { - icon: , - message: 'Warning', - type: 'warning', - }, - [Severity.Critical]: { - icon: , - message: 'Critical', - type: 'danger', - }, -} satisfies Record; - -type Props = { - severity: Severity; - label?: string; -} & StatusBadgeTemplateProps; - -export const SeverityStatusBadge = ({ severity, label, ...rest }: Props) => { - const { message, ...template } = BadgeTemplates[severity]; - return ( - - {label ?? message} - - ); -}; diff --git a/src/components/status-popover/dev.tsx b/src/components/status-popover/dev.tsx new file mode 100644 index 000000000..fb9dfb92e --- /dev/null +++ b/src/components/status-popover/dev.tsx @@ -0,0 +1,36 @@ +import { StatusPopover, type StatusPopoverType } from './status-popover'; + +const types = [ + 'success', + 'warning', + 'danger', + 'none', + 'default', +] satisfies StatusPopoverType[]; + +const labels = ['short', undefined, 'two words']; + +export default ( +
+ {labels.map((label, i) => + types.map((type) => ( + + {label} + + )) + )} +
+); diff --git a/src/components/status-popover/status-popover.tsx b/src/components/status-popover/status-popover.tsx index aa2f245c7..97f5cef87 100644 --- a/src/components/status-popover/status-popover.tsx +++ b/src/components/status-popover/status-popover.tsx @@ -6,7 +6,6 @@ import { } from '@equinor/eds-core-react'; import { info_circle } from '@equinor/eds-icons'; import { - type FunctionComponent, type PropsWithChildren, type ReactNode, useRef, @@ -26,22 +25,26 @@ export type StatusPopoverProps = { title?: ReactNode; icon?: ReactNode; type?: StatusPopoverType; + label?: string; } & Pick; -export const StatusPopover: FunctionComponent< - PropsWithChildren -> = ({ +export const StatusPopover = ({ children, title, icon = , type, + label, placement = 'top', -}) => { +}: PropsWithChildren) => { const [popoverOpen, setPopoverOpen] = useState(false); const containerRef = useRef(null); return ( -
+
setPopoverOpen(true)} + onMouseLeave={() => setPopoverOpen(false)} + > {children} setPopoverOpen(true)} - onMouseLeave={() => setPopoverOpen(false)} > {icon} + {label ? {label} : ''}
); diff --git a/src/components/status-popover/style.css b/src/components/status-popover/style.css index e1c4279f6..3d29a96be 100644 --- a/src/components/status-popover/style.css +++ b/src/components/status-popover/style.css @@ -1,41 +1,54 @@ .status-popover { display: inline-flex; - height: 18px; - width: 18px; -} -.status-popover .status-popover-chip { - display: flex; - padding: 0; - min-width: 122%; - min-height: 122%; - justify-content: center; -} -.status-popover .status-popover-chip svg { - fill: var(--eds_interactive_primary__resting); -} + & .status-popover-chip { + display: flex; + padding: 2px; + grid-gap: 2px; + white-space: nowrap; -.status-popover .status-popover-chip.status-popover-chip-type__none svg { - fill: var(--eds_interactive_disabled__text); -} + & span { + margin-right: 2px; + } -.status-popover .status-popover-chip.status-popover-chip-type__success { - background-color: var(--eds_interactive_success__highlight); -} -.status-popover .status-popover-chip.status-popover-chip-type__success svg { - fill: var(--eds_interactive_success__text); -} + color: var(--eds_interactive_primary__resting); + & svg { + fill: var(--eds_interactive_primary__resting); + } -.status-popover .status-popover-chip.status-popover-chip-type__warning { - background-color: var(--eds_interactive_warning__highlight); -} -.status-popover .status-popover-chip.status-popover-chip-type__warning svg { - fill: var(--eds_interactive_warning__text); -} + &.status-popover-chip-type__none { + color: var(--eds_interactive_disabled__text); -.status-popover .status-popover-chip.status-popover-chip-type__danger { - background-color: var(--eds_interactive_danger__highlight); -} -.status-popover .status-popover-chip.status-popover-chip-type__danger svg { - fill: var(--eds_interactive_danger__text); + & svg { + fill: var(--eds_interactive_disabled__text); + } + } + + &.status-popover-chip-type__success { + background-color: var(--eds_interactive_success__highlight); + color: var(--eds_interactive_success__text); + + & svg { + fill: var(--eds_interactive_success__text); + } + } + + &.status-popover-chip-type__warning { + background-color: var(--eds_interactive_warning__highlight); + color: var(--eds_interactive_warning__text); + + & svg { + fill: var(--eds_interactive_warning__text); + } + } + + &.status-popover-chip-type__danger { + background-color: var(--eds_interactive_danger__highlight); + color: var(--eds_interactive_danger__text); + + & svg { + fill: var(--eds_interactive_danger__text); + } + } + } } diff --git a/src/components/utilization-popover/utilization-popover.tsx b/src/components/utilization-popover/utilization-popover.tsx index 94cc0b80b..f1c079d0f 100644 --- a/src/components/utilization-popover/utilization-popover.tsx +++ b/src/components/utilization-popover/utilization-popover.tsx @@ -1,17 +1,16 @@ -import { Icon, Popover, Typography } from '@equinor/eds-core-react'; -import { desktop_mac } from '@equinor/eds-icons'; -import { useMemo, useRef, useState } from 'react'; +import { Icon, Typography } from '@equinor/eds-core-react'; +import { desktop_mac, pressure } from '@equinor/eds-icons'; +import { useMemo } from 'react'; import type { GetEnvironmentResourcesUtilizationApiResponse, ReplicaResourcesUtilizationResponse, ReplicaUtilization, } from '../../store/radix-api'; +import { StatusBadgeTemplate } from '../status-badges/status-badge-template'; import { - GetHighestSeverity, - GetHighestSeverityFns, - Severity, - SeverityStatusBadge, -} from '../status-badges/severity-status-badge'; + StatusPopover, + type StatusPopoverType, +} from '../status-popover/status-popover'; const LowCPUThreshold = 0.2; const HighCPUThreshold = 0.8; @@ -21,23 +20,33 @@ const LowMemoryThreshold = 0.2; const HighMemoryThreshold = 0.7; const MaxMemoryThreshold = 0.9; -const Labels = { - [Severity.None]: 'Utilization ok', - [Severity.Information]: 'Utilization Low', - [Severity.Warning]: 'Utilization High', - [Severity.Critical]: 'Utilization Critical', -} satisfies Record; +export enum Severity { + None = 0, + Information = 1, + Warning = 2, + Critical = 3, +} + +const SeverityMap = { + [Severity.None]: { label: 'Utilization ok', type: 'none' }, + [Severity.Information]: { label: 'Utilization Low', type: 'default' }, + [Severity.Warning]: { label: 'Utilization High', type: 'warning' }, + [Severity.Critical]: { label: 'Utilization Critical', type: 'danger' }, +} satisfies Record; type Props = { path: string; showLabel?: boolean; + minimumSeverity?: Severity; utilization?: ReplicaResourcesUtilizationResponse; }; -export const UtilizationPopover = ({ path, showLabel, utilization }: Props) => { - const [open, setOpen] = useState(false); - const ref = useRef(null); - +export const UtilizationPopover = ({ + path, + showLabel, + utilization, + minimumSeverity, +}: Props) => { const { highestMemoryAlert, highestCPUAlert } = useMemo(() => { let highestMemoryAlert = Severity.None; let highestCPUAlert = Severity.None; @@ -62,39 +71,30 @@ export const UtilizationPopover = ({ path, showLabel, utilization }: Props) => { }, [utilization, path]); const severity = GetHighestSeverity(highestMemoryAlert, highestCPUAlert); + + if (minimumSeverity !== undefined && severity < minimumSeverity) { + return null; + } + return ( -
setOpen(true)} - onMouseLeave={() => setOpen(false)} + } + title="Resource Status" + label={showLabel ? SeverityMap[severity].label : undefined} + type={SeverityMap[severity].type} > - ev.stopPropagation()} - > - - Resource Status - - - - Memory {Labels[highestMemoryAlert]} - - - CPU {Labels[highestCPUAlert]} - - - - - See Monitoring for more - details. - - - - - -
+
+ + Memory {SeverityMap[highestMemoryAlert].label} + + + CPU {SeverityMap[highestCPUAlert].label} + + + See Monitoring for more details. + +
+ ); }; @@ -126,6 +126,25 @@ const flattenAndFilterResults = ( return results; }; +const GetHighestSeverity = (a: Severity, b: Severity): Severity => { + if (a > b) return a; + return b; +}; + +const GetHighestSeverityFns = ( + data: TArgs, + fns: ((data: TArgs) => Severity)[] +): Severity => { + let highest = Severity.None; + + fns.forEach((fn) => { + const res = fn(data); + highest = GetHighestSeverity(res, highest); + }); + + return highest; +}; + const HasLowCPUtilizationPercentage = (data: ReplicaUtilization): Severity => { return data.cpuAverage / data.cpuRequests < LowCPUThreshold ? Severity.Information From 89ccb790702de4ec22f1559088c8139ad1858afa Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 14 Jan 2025 13:43:50 +0100 Subject: [PATCH 11/21] test all alert utilization conditions --- src/components/environments-summary/dev.tsx | 78 +++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/components/environments-summary/dev.tsx b/src/components/environments-summary/dev.tsx index 5a4a01bbe..9d6f626c0 100644 --- a/src/components/environments-summary/dev.tsx +++ b/src/components/environments-summary/dev.tsx @@ -165,6 +165,24 @@ const cards: EnvironmentCardLayoutProps[] = [ }, ], repository: 'https://github.com/equinor/radix-web-console', + utilization: { + environments: { + dev: { + components: { + web: { + replicas: { + 'web-abcd-1': { + cpuAverage: 0.5, + cpuRequests: 1.0, + memoryMaximum: 500, + memoryRequests: 1000, + }, + }, + }, + }, + }, + }, + }, }, { appName: 'test-component', @@ -229,6 +247,66 @@ const cards: EnvironmentCardLayoutProps[] = [ ], repository: 'https://github.com/equinor/radix-web-console', }, + { + appName: 'test-component', + env: { name: 'dev', status: 'Consistent' }, + deployment: { + name: 'web', + activeFrom: '2020-02-02T12:00:00Z', + }, + isLoading: false, + envScan: { name: 'dev' }, + components: [], + repository: 'https://github.com/equinor/radix-web-console', + utilization: { + environments: { + dev: { + components: { + web: { + replicas: { + 'web-abcd-1': { + cpuAverage: 0.9, + cpuRequests: 1.0, + memoryMaximum: 900, + memoryRequests: 1000, + }, + }, + }, + }, + }, + }, + }, + }, + { + appName: 'test-component', + env: { name: 'dev', status: 'Consistent' }, + deployment: { + name: 'web', + activeFrom: '2020-02-02T12:00:00Z', + }, + isLoading: false, + envScan: { name: 'dev' }, + components: [], + repository: 'https://github.com/equinor/radix-web-console', + utilization: { + environments: { + dev: { + components: { + web: { + replicas: { + 'web-abcd-1': { + cpuAverage: 0.1, + cpuRequests: 1.0, + memoryMaximum: 100, + memoryRequests: 1000, + }, + }, + }, + }, + }, + }, + }, + }, ] satisfies EnvironmentCardLayoutProps[]; function TestEnvironmentCards() { From e8d0b789aa17c2b66f1f31816dc2e154171469d2 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 14 Jan 2025 13:45:43 +0100 Subject: [PATCH 12/21] fix spinner padding --- src/components/environments-summary/environment-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/environments-summary/environment-card.tsx b/src/components/environments-summary/environment-card.tsx index 13a5b6859..94ea07bfb 100644 --- a/src/components/environments-summary/environment-card.tsx +++ b/src/components/environments-summary/environment-card.tsx @@ -126,7 +126,7 @@ export const EnvironmentCardLayout = ({ {deployment?.name && ( <> {isLoading && ( - + )} From 14b569a3bacbc1226e44574e879a150911fb9d78 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 14 Jan 2025 14:17:48 +0100 Subject: [PATCH 13/21] fix icon sizing without label --- src/components/status-popover/dev.tsx | 3 +++ src/components/status-popover/style.css | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/components/status-popover/dev.tsx b/src/components/status-popover/dev.tsx index fb9dfb92e..54648b115 100644 --- a/src/components/status-popover/dev.tsx +++ b/src/components/status-popover/dev.tsx @@ -1,3 +1,5 @@ +import { Icon } from '@equinor/eds-core-react'; +import { check } from '@equinor/eds-icons'; import { StatusPopover, type StatusPopoverType } from './status-popover'; const types = [ @@ -23,6 +25,7 @@ export default ( {labels.map((label, i) => types.map((type) => ( } title={type} key={label + type} label={label} diff --git a/src/components/status-popover/style.css b/src/components/status-popover/style.css index 3d29a96be..f9162a9f9 100644 --- a/src/components/status-popover/style.css +++ b/src/components/status-popover/style.css @@ -11,6 +11,11 @@ margin-right: 2px; } + & svg { + width: 16px; + height: 16px; + } + color: var(--eds_interactive_primary__resting); & svg { fill: var(--eds_interactive_primary__resting); From 618cd1e0a276acece519c6b337b2b375c9e5dd37 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 14 Jan 2025 14:58:19 +0100 Subject: [PATCH 14/21] cleanup bug icon on env card --- src/components/environments-summary/dev.tsx | 17 ++++- .../environment-card-status.tsx | 62 +++++-------------- .../environment-headers.tsx | 15 +++-- 3 files changed, 39 insertions(+), 55 deletions(-) diff --git a/src/components/environments-summary/dev.tsx b/src/components/environments-summary/dev.tsx index 9d6f626c0..d3abba62e 100644 --- a/src/components/environments-summary/dev.tsx +++ b/src/components/environments-summary/dev.tsx @@ -255,7 +255,22 @@ const cards: EnvironmentCardLayoutProps[] = [ activeFrom: '2020-02-02T12:00:00Z', }, isLoading: false, - envScan: { name: 'dev' }, + envScan: { + name: 'dev', + components: { + web: { + image: 'test:test', + scanSuccess: true, + scanTime: '2020-02-02T12:00:00Z', + vulnerabilitySummary: { + critical: 0, + high: 1, + medium: 2, + low: 5, + }, + }, + }, + }, components: [], repository: 'https://github.com/equinor/radix-web-console', utilization: { diff --git a/src/components/environments-summary/environment-card-status.tsx b/src/components/environments-summary/environment-card-status.tsx index 080478003..8b16a383d 100644 --- a/src/components/environments-summary/environment-card-status.tsx +++ b/src/components/environments-summary/environment-card-status.tsx @@ -1,9 +1,4 @@ -import { - CircularProgress, - Icon, - type IconProps, - Popover, -} from '@equinor/eds-core-react'; +import { CircularProgress, Icon } from '@equinor/eds-core-react'; import { check, error_outlined, @@ -14,10 +9,12 @@ import { } from '@equinor/eds-icons'; import { upperFirst } from 'lodash-es'; import type React from 'react'; -import { useRef, useState } from 'react'; import type { ImageScan, Vulnerability } from '../../store/scan-api'; import { StatusBadgeTemplate } from '../status-badges/status-badge-template'; -import { StatusPopover } from '../status-popover/status-popover'; +import { + StatusPopover, + type StatusPopoverType, +} from '../status-popover/status-popover'; import { VulnerabilitySummary } from '../vulnerability-summary'; import { EnvironmentStatus, @@ -53,11 +50,12 @@ const EnvironmentStatusIcon = ({ status }: { status: EnvironmentStatus }) => { } }; +type VulnerabilitySummaryType = Required['vulnerabilitySummary']; type Props = { - title?: string; + title: string; size?: number; visibleKeys?: Array>; - summary: Required['vulnerabilitySummary']; + summary: VulnerabilitySummaryType; }; export const EnvironmentVulnerabilityIndicator = ({ title, @@ -65,45 +63,17 @@ export const EnvironmentVulnerabilityIndicator = ({ size = 24, ...rest }: Props) => { - const [popoverOpen, setPopoverOpen] = useState(false); - const containerRef = useRef(null); + let type: StatusPopoverType = 'none'; + if (summary.medium > 0 || summary.low > 0) type = 'success'; + if (summary.high > 0 || summary.unknown > 0) type = 'warning'; + if (summary.critical > 0) type = 'danger'; return ( - <> - - {title && ( - - {title} - - )} - - - - -
setPopoverOpen(true)} - onMouseLeave={() => setPopoverOpen(false)} - > - 0 - ? '--eds_interactive_danger__text' - : summary.high > 0 || summary.unknown > 0 - ? '--eds_interactive_warning__text' - : summary.medium > 0 || summary.low > 0 - ? '--eds_interactive_success__hover' - : '--eds_text_static_icons__tertiary' - })`} - /> + } type={type}> +
+
- +
); }; diff --git a/src/components/environments-summary/environment-headers.tsx b/src/components/environments-summary/environment-headers.tsx index db1775928..b1de34785 100644 --- a/src/components/environments-summary/environment-headers.tsx +++ b/src/components/environments-summary/environment-headers.tsx @@ -56,14 +56,13 @@ type VulnerabilityHeaderProps = { export const VulnerabilityHeader = ({ envScan }: VulnerabilityHeaderProps) => { const vulnerabilities = environmentVulnerabilitySummarizer(envScan); + if (!visibleKeys.some((key) => vulnerabilities[key] > 0)) return null; return ( - visibleKeys.some((key) => vulnerabilities[key] > 0) && ( - - ) + ); }; From 5a2fd933afd22adfbd140f5f734baf825c55c47a Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 14 Jan 2025 18:19:42 +0100 Subject: [PATCH 15/21] add icons to AppListItem --- src/components/app-badge/index.tsx | 13 +- src/components/app-list-item/dev.tsx | 89 +++++-- src/components/app-list-item/index.tsx | 330 +++++++++++++++---------- src/components/app-list-item/style.css | 5 +- src/components/app-list/index.tsx | 69 +++--- 5 files changed, 309 insertions(+), 197 deletions(-) diff --git a/src/components/app-badge/index.tsx b/src/components/app-badge/index.tsx index 672a5244f..a94c13741 100644 --- a/src/components/app-badge/index.tsx +++ b/src/components/app-badge/index.tsx @@ -1,12 +1,10 @@ import { type JdenticonConfig, configure, toSvg } from 'jdenticon'; -import type { FunctionComponent } from 'react'; - import './style.css'; -export interface AppBadgeProps { +type AppBadgeProps = { appName: string; size?: number; -} +}; const badgeConfig: JdenticonConfig = { hues: [207, 283, 64], @@ -21,14 +19,11 @@ const badgeConfig: JdenticonConfig = { backColor: '#ffffff00', }; -export const AppBadge: FunctionComponent = ({ - size = 64, - ...rest -}) => { +export const AppBadge = ({ size = 64, appName }: AppBadgeProps) => { const previousConfig = configure(); configure(badgeConfig); - const badge = { __html: toSvg(rest.appName, size) }; + const badge = { __html: toSvg(appName, size) }; configure(previousConfig); return
; diff --git a/src/components/app-list-item/dev.tsx b/src/components/app-list-item/dev.tsx index 99f920a86..d9228de50 100644 --- a/src/components/app-list-item/dev.tsx +++ b/src/components/app-list-item/dev.tsx @@ -1,64 +1,105 @@ import { Typography } from '@equinor/eds-core-react'; +import { addMinutes } from 'date-fns'; import { - AppListItem, - type AppListItemProps, + AppListItemLayout, + type AppListItemLayoutProps, type FavouriteClickedHandler, } from '.'; const noop: FavouriteClickedHandler = (evt) => evt.preventDefault(); -const testData: Array<{ description: string } & AppListItemProps> = [ +const testData = [ { description: 'App', - app: { name: 'test-app' }, + isLoading: false, handler: noop, - isLoaded: true, - name: 'some-app', + appName: 'some-app', }, { description: 'App, marked Favourite, with Job', - app: { - name: 'toast-app', - latestJob: { - name: 'test-job', - created: new Date().toISOString(), - started: new Date().toISOString(), - status: 'Running', - pipeline: 'build-deploy', + isLoading: false, + latestJob: { + name: 'running-job-app', + created: addMinutes(new Date(), -15).toISOString(), + started: addMinutes(new Date(), -14).toISOString(), + status: 'Running', + pipeline: 'build-deploy', + }, + vulnerabilitySummary: [ + { + components: { + web: { + image: 'test:test', + scanSuccess: true, + scanTime: '2020-02-02T12:00:00Z', + vulnerabilitySummary: { + critical: 2, + high: 1, + medium: 2, + low: 5, + }, + }, + }, + name: 'dev', + }, + ], + utilization: { + environments: { + dev: { + components: { + web: { + replicas: { + 'web-abcd-1': { + cpuAverage: 1.1, + cpuRequests: 1.0, + memoryMaximum: 900, + memoryRequests: 1000, + }, + }, + }, + }, + }, }, }, handler: noop, isFavourite: true, showStatus: true, - isLoaded: true, - name: 'some-app', + appName: 'favorite-app-with-problems', }, { description: 'App, marked Favourite, without Job', - app: { name: 'app-test' }, + isLoading: false, handler: noop, isFavourite: true, showStatus: true, - isLoaded: true, - name: 'some-app', + appName: 'fav-app-without-job', }, { description: 'App, marked Placeholder', - app: { name: 'app-placeholder' }, + isLoading: false, handler: noop, isPlaceholder: true, - isLoaded: true, - name: 'some-app', + appName: 'placeholder-app', + }, + { + description: 'Deleted App', + isLoading: false, + handler: noop, + isPlaceholder: false, + isFavourite: true, + showStatus: true, + isDeleted: true, + appName: 'deleted-app', }, -]; +] satisfies Array<{ description: string } & AppListItemLayoutProps>; export default (
{testData.map(({ description, ...rest }, i) => (
{description} - +
))}
diff --git a/src/components/app-list-item/index.tsx b/src/components/app-list-item/index.tsx index a57a84822..3c1341d00 100644 --- a/src/components/app-list-item/index.tsx +++ b/src/components/app-list-item/index.tsx @@ -8,21 +8,27 @@ import { import { error_outlined, star_filled, star_outlined } from '@equinor/eds-icons'; import { clsx } from 'clsx'; import { formatDistanceToNow } from 'date-fns'; -import type { HTMLAttributes, MouseEvent } from 'react'; +import type { MouseEvent, PropsWithChildren } from 'react'; import { Link } from 'react-router-dom'; import { routes } from '../../routes'; -import type { ApplicationSummary, Component } from '../../store/radix-api'; import { + type Component, + type JobSummary, + type ReplicaResourcesUtilizationResponse, + useGetApplicationResourcesUtilizationQuery, +} from '../../store/radix-api'; +import { + type ApplicationVulnerabilities, type Vulnerability, useGetApplicationVulnerabilitySummariesQuery, } from '../../store/scan-api'; import { filterFields } from '../../utils/filter-fields'; import { routeWithParams } from '../../utils/string'; import { AppBadge } from '../app-badge'; -import AsyncResource from '../async-resource/async-resource'; import { EnvironmentCardStatus, + type EnvironmentCardStatusMap, EnvironmentVulnerabilityIndicator, } from '../environments-summary/environment-card-status'; import { @@ -35,6 +41,10 @@ import { } from '../environments-summary/environment-status-utils'; import './style.css'; +import { + Severity, + UtilizationPopover, +} from '../utilization-popover/utilization-popover'; export type FavouriteClickedHandler = ( event: MouseEvent, @@ -42,13 +52,14 @@ export type FavouriteClickedHandler = ( ) => void; export interface AppListItemProps { - app?: Readonly; + appName: string; + latestJob?: JobSummary; + environmentActiveComponents?: { [key: string]: Component[] }; handler: FavouriteClickedHandler; isPlaceholder?: boolean; isFavourite?: boolean; showStatus?: boolean; - name: string; - isLoaded: boolean; + isDeleted?: boolean; } const visibleKeys: Array> = [ @@ -56,26 +67,59 @@ const visibleKeys: Array> = [ 'high', ]; -function aggregateEnvironmentStatus( - components: Component[] -): EnvironmentStatus { - return Math.max( - aggregateComponentEnvironmentStatus(components), - aggregateComponentReplicaEnvironmentStatus(components) - ); -} +export const AppListItem = (props: AppListItemProps) => { + const { data: vulnerabilitySummary, isLoading: isVulnSummaryLoading } = + useGetApplicationVulnerabilitySummariesQuery( + { appName: props.appName }, + { pollingInterval: 0 } + ); -const AppItemStatus = ({ - environmentActiveComponents, - latestJob, - name, -}: ApplicationSummary) => { - const state = useGetApplicationVulnerabilitySummariesQuery( - { appName: name }, - { pollingInterval: 0 } + const { data: utilization, isLoading: isUtilizationLoading } = + useGetApplicationResourcesUtilizationQuery( + { appName: props.appName }, + { pollingInterval: 0 } + ); + + return ( + ); +}; + +export type AppListItemLayoutProps = { + appName: string; + latestJob?: JobSummary; + environmentActiveComponents?: { [key: string]: Component[] }; + handler: FavouriteClickedHandler; + isPlaceholder?: boolean; + isFavourite?: boolean; + showStatus?: boolean; + isLoading: boolean; + isDeleted?: boolean; + utilization?: ReplicaResourcesUtilizationResponse; + vulnerabilitySummary?: ApplicationVulnerabilities; +}; - const vulnerabilities: VulnerabilitySummary = (state?.data ?? []).reduce( +export const AppListItemLayout = ({ + latestJob, + environmentActiveComponents, + isDeleted, + appName, + isLoading, + handler, + showStatus, + isPlaceholder, + isFavourite, + utilization, + vulnerabilitySummary, +}: AppListItemLayoutProps) => { + const vulnerabilities: VulnerabilitySummary = ( + vulnerabilitySummary ?? [] + ).reduce( (obj, x) => aggregateVulnerabilitySummaries([ obj, @@ -90,129 +134,145 @@ const AppItemStatus = ({ ? latestJob.started : latestJob.ended); + const statusElements = parseAppForStatusElements( + latestJob, + environmentActiveComponents + ); + + const latestJobIsChanging = + latestJob && + (latestJob.status === 'Running' || latestJob.status === 'Stopping'); + return ( -
-
-
- {time && ( -
- - {formatDistanceToNow(new Date(time), { addSuffix: true })} - - {latestJob && - (latestJob.status === 'Running' || - latestJob.status === 'Stopping') && ( - - )} + +
+
+ +
+
+
+ + {appName} + +
+
+
+ {isDeleted && showStatus && ( + + + )} -
+ {!isDeleted && showStatus && ( +
+
+
+ {time && ( + + {formatDistanceToNow(new Date(time), { + addSuffix: true, + })} + + )} +
-
-
- - {visibleKeys.some((key) => vulnerabilities[key] > 0) && ( - - )} - - - {(environmentActiveComponents || latestJob) && ( - - environmentActiveComponents[x]?.length > 0 - ? [...obj, ...environmentActiveComponents[x]] - : obj, - [] as Component[] - ) - ), - }), - }} - /> - )} -
+
+ {(latestJobIsChanging || isLoading) && ( + + )} + + {visibleKeys.some((key) => vulnerabilities[key] > 0) && ( + + )} + + + + {statusElements && ( + + )} +
+
+
+ )}
-
+ ); }; type WElementProps = { appName: string; isPlaceholder?: boolean; -} & HTMLAttributes< - Pick< - HTMLAnchorElement | HTMLDivElement, - keyof HTMLAnchorElement & keyof HTMLDivElement - > ->; -const WElement = ({ appName, isPlaceholder, ...rest }: WElementProps) => - isPlaceholder ? ( -
- ) : ( - + className: string; +}; +const WElement = ({ + appName, + isPlaceholder, + className, + children, +}: PropsWithChildren) => { + if (isPlaceholder) return
{children}
; + + return ( + + {children} + ); +}; -export const AppListItem = ({ - app, - handler, - isPlaceholder, - isFavourite, - showStatus, - name, - isLoaded, -}: AppListItemProps) => ( - -
-
- -
-
-
- - {name} - -
- -
-
- {isLoaded && - showStatus && - (app ? ( - - ) : ( - - - - ))} -
-
-
-); +function parseAppForStatusElements( + latestJob?: JobSummary, + environmentActiveComponents?: { [key: string]: Component[] } +): EnvironmentCardStatusMap { + return { + ...(latestJob && { + 'Latest Job': + latestJob.status == 'Failed' + ? EnvironmentStatus.Danger + : EnvironmentStatus.Consistent, + }), + ...(environmentActiveComponents && { + Environments: aggregateEnvironmentStatus( + Object.keys(environmentActiveComponents).reduce( + (obj, x) => + environmentActiveComponents[x]?.length > 0 + ? [...obj, ...environmentActiveComponents[x]] + : obj, + [] as Component[] + ) + ), + }), + }; +} + +function aggregateEnvironmentStatus( + components: Component[] +): EnvironmentStatus { + return Math.max( + aggregateComponentEnvironmentStatus(components), + aggregateComponentReplicaEnvironmentStatus(components) + ); +} diff --git a/src/components/app-list-item/style.css b/src/components/app-list-item/style.css index 058ef7663..263b6dfa0 100644 --- a/src/components/app-list-item/style.css +++ b/src/components/app-list-item/style.css @@ -34,11 +34,14 @@ width: unset; } +.app-list-status { + color: grey; +} + .app-list-status--last-job { display: grid; grid-template-columns: 1fr auto; padding-top: 4px; - padding-right: 5px; } .app-list-item--placeholder { diff --git a/src/components/app-list/index.tsx b/src/components/app-list/index.tsx index 4f286b212..6ac59f152 100644 --- a/src/components/app-list/index.tsx +++ b/src/components/app-list/index.tsx @@ -26,7 +26,7 @@ const LoadingCards: FunctionComponent<{ amount: number }> = ({ amount }) => ( {[...Array(amount || 1)].map((_, i) => ( e.preventDefault()} isPlaceholder name={''} @@ -132,20 +132,28 @@ export default function AppList() { <>
- {favouriteNames.map((appName) => ( - a.name === appName)} - handler={(e) => { - changeFavouriteApplication(appName, false); - e.preventDefault(); - }} - isFavourite - showStatus - isLoaded={favsState.isSuccess} - name={appName} - /> - ))} + {favouriteNames.map((appName) => { + const app = favsData?.find((a) => a.name === appName); + return ( + { + changeFavouriteApplication(appName, false); + e.preventDefault(); + }} + isFavourite + showStatus + isLoaded={favsState.isSuccess} + name={appName} + /> + ); + })}
@@ -196,19 +204,24 @@ export default function AppList() { } >
- {knownApps.map((app) => ( - { - changeFavouriteApplication(app.name, !app.isFavourite); - e.preventDefault(); - }} - isFavourite={app.isFavourite} - name={app.name} - isLoaded={true} - /> - ))} + {knownApps.map((app) => { + return ( + { + changeFavouriteApplication( + app.name, + !app.isFavourite + ); + e.preventDefault(); + }} + isFavourite={app.isFavourite} + name={app.name} + isLoaded={true} + /> + ); + })}
) : ( From 7e1cddab393dfa806e8151e4e485cec269cd69e2 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 15 Jan 2025 08:59:24 +0100 Subject: [PATCH 16/21] cleanup --- src/components/app-list-item/index.tsx | 4 +-- .../active-component-overview.tsx | 1 + .../component-replica-list.tsx | 1 + .../page-active-component/overview.tsx | 30 +++++++++++++++---- .../deployment-component-overview.tsx | 1 + .../page-environment/component-list.tsx | 14 +++++---- src/components/replica-list/index.tsx | 21 ++++++++----- .../status-popover/status-popover.tsx | 4 ++- .../utilization-popover.tsx | 9 +++--- 9 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/components/app-list-item/index.tsx b/src/components/app-list-item/index.tsx index 3c1341d00..f679bf0b0 100644 --- a/src/components/app-list-item/index.tsx +++ b/src/components/app-list-item/index.tsx @@ -71,13 +71,13 @@ export const AppListItem = (props: AppListItemProps) => { const { data: vulnerabilitySummary, isLoading: isVulnSummaryLoading } = useGetApplicationVulnerabilitySummariesQuery( { appName: props.appName }, - { pollingInterval: 0 } + { pollingInterval: 0, skip: !props.showStatus } ); const { data: utilization, isLoading: isUtilizationLoading } = useGetApplicationResourcesUtilizationQuery( { appName: props.appName }, - { pollingInterval: 0 } + { pollingInterval: 0, skip: !props.showStatus } ); return ( diff --git a/src/components/page-active-component/active-component-overview.tsx b/src/components/page-active-component/active-component-overview.tsx index 594768af7..43e5a2e3c 100644 --- a/src/components/page-active-component/active-component-overview.tsx +++ b/src/components/page-active-component/active-component-overview.tsx @@ -102,6 +102,7 @@ export const ActiveComponentOverview: FunctionComponent<{ /> {replicaList && replicaList.length > 0 ? ( alias.fqdn) : []; + const { data: utilization } = useGetApplicationResourcesUtilizationQuery( + { appName }, + { pollingInterval } + ); + return (
Overview @@ -65,6 +75,14 @@ export const Overview = ({ Status
+
+ Utilization + +
{component.variables?.[URL_VAR_NAME] && ( Publicly available{' '} diff --git a/src/components/page-deployment-component/deployment-component-overview.tsx b/src/components/page-deployment-component/deployment-component-overview.tsx index 4b6601fec..56674f5ff 100644 --- a/src/components/page-deployment-component/deployment-component-overview.tsx +++ b/src/components/page-deployment-component/deployment-component-overview.tsx @@ -49,6 +49,7 @@ export const DeploymentComponentOverview: FunctionComponent<{ {deployment && component && ( <> = ({ Replicas - Resources + {type === 'component' ? 'Resources' : null} Vulnerabilities @@ -260,11 +260,13 @@ export const ComponentList: FunctionComponent = ({ /> - + {type === 'component' && ( + + )} ; replicaUrlFunc: (name: string) => string; + showUtilization?: boolean; }; export const ReplicaList = ({ replicaList, @@ -37,6 +39,7 @@ export const ReplicaList = ({ appName, envName, compName, + showUtilization, }: Props) => { const [sortedData, setSortedData] = useState(replicaList || []); const [dateSort, setDateSort] = useState(); @@ -97,7 +100,7 @@ export const ReplicaList = ({ Duration - Resources + {showUtilization && Resources} @@ -142,13 +145,15 @@ export const ReplicaList = ({ - - - + {showUtilization && ( + + + + )} {expanded && ( diff --git a/src/components/status-popover/status-popover.tsx b/src/components/status-popover/status-popover.tsx index 97f5cef87..cc8211962 100644 --- a/src/components/status-popover/status-popover.tsx +++ b/src/components/status-popover/status-popover.tsx @@ -26,6 +26,7 @@ export type StatusPopoverProps = { icon?: ReactNode; type?: StatusPopoverType; label?: string; + disablePopover?: boolean; } & Pick; export const StatusPopover = ({ @@ -34,6 +35,7 @@ export const StatusPopover = ({ icon = , type, label, + disablePopover, placement = 'top', }: PropsWithChildren) => { const [popoverOpen, setPopoverOpen] = useState(false); @@ -46,7 +48,7 @@ export const StatusPopover = ({ onMouseLeave={() => setPopoverOpen(false)} > diff --git a/src/components/utilization-popover/utilization-popover.tsx b/src/components/utilization-popover/utilization-popover.tsx index f1c079d0f..a57695923 100644 --- a/src/components/utilization-popover/utilization-popover.tsx +++ b/src/components/utilization-popover/utilization-popover.tsx @@ -28,10 +28,10 @@ export enum Severity { } const SeverityMap = { - [Severity.None]: { label: 'Utilization ok', type: 'none' }, - [Severity.Information]: { label: 'Utilization Low', type: 'default' }, - [Severity.Warning]: { label: 'Utilization High', type: 'warning' }, - [Severity.Critical]: { label: 'Utilization Critical', type: 'danger' }, + [Severity.None]: { label: 'Normal', type: 'default' }, + [Severity.Information]: { label: 'Low', type: 'default' }, + [Severity.Warning]: { label: 'High', type: 'warning' }, + [Severity.Critical]: { label: 'Critical', type: 'danger' }, } satisfies Record; type Props = { @@ -82,6 +82,7 @@ export const UtilizationPopover = ({ title="Resource Status" label={showLabel ? SeverityMap[severity].label : undefined} type={SeverityMap[severity].type} + disablePopover={severity === Severity.None} >
From 725a8044313c15901e5332bf8f58d5869ae52825 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 15 Jan 2025 09:01:51 +0100 Subject: [PATCH 17/21] run alpine 3.20 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 8065c0387..74c09aeac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ RUN npm install COPY . . RUN npm run build -FROM docker.io/nginxinc/nginx-unprivileged:1.27-alpine3.21 +FROM docker.io/nginxinc/nginx-unprivileged:1.27-alpine3.20 WORKDIR /app COPY --from=builder /app/build /app COPY proxy/server.conf /default.conf From eba00de1f393c92f0226506abf5e175285d30b8a Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 15 Jan 2025 09:30:06 +0100 Subject: [PATCH 18/21] hide resource utilization for job manager, fix linting errors --- src/components/app-list-item/index.tsx | 100 ++++++++++-------- src/components/app-list/index.tsx | 11 +- .../environments-summary/environment-card.tsx | 6 +- .../environment-ingress.tsx | 8 +- .../active-component-overview.tsx | 1 + .../component-replica-list.tsx | 4 +- src/components/replica-list/index.tsx | 1 - 7 files changed, 76 insertions(+), 55 deletions(-) diff --git a/src/components/app-list-item/index.tsx b/src/components/app-list-item/index.tsx index f679bf0b0..d7c095a7e 100644 --- a/src/components/app-list-item/index.tsx +++ b/src/components/app-list-item/index.tsx @@ -60,6 +60,7 @@ export interface AppListItemProps { isFavourite?: boolean; showStatus?: boolean; isDeleted?: boolean; + isLoading: boolean; } const visibleKeys: Array> = [ @@ -67,7 +68,7 @@ const visibleKeys: Array> = [ 'high', ]; -export const AppListItem = (props: AppListItemProps) => { +export const AppListItem = ({ isLoading, ...props }: AppListItemProps) => { const { data: vulnerabilitySummary, isLoading: isVulnSummaryLoading } = useGetApplicationVulnerabilitySummariesQuery( { appName: props.appName }, @@ -84,7 +85,7 @@ export const AppListItem = (props: AppListItemProps) => { ); @@ -166,56 +167,67 @@ export const AppListItemLayout = ({
- {isDeleted && showStatus && ( - - - - )} - {!isDeleted && showStatus && ( -
-
-
- {time && ( - - {formatDistanceToNow(new Date(time), { - addSuffix: true, - })} - - )} -
-
- {(latestJobIsChanging || isLoading) && ( - + {showStatus && ( + <> +
+
+ {isDeleted && !isLoading && ( +
+ + + +
)} + {(!isDeleted || isLoading) && ( + <> +
+ {time && ( + + {formatDistanceToNow(new Date(time), { + addSuffix: true, + })} + + )} +
- {visibleKeys.some((key) => vulnerabilities[key] > 0) && ( - - )} +
+ {(latestJobIsChanging || isLoading) && ( + + )} + + {visibleKeys.some( + (key) => vulnerabilities[key] > 0 + ) && ( + + )} - + - {statusElements && ( - + {statusElements && ( + + )} +
+ )}
-
+ )}
diff --git a/src/components/app-list/index.tsx b/src/components/app-list/index.tsx index 6ac59f152..27a4d6662 100644 --- a/src/components/app-list/index.tsx +++ b/src/components/app-list/index.tsx @@ -26,11 +26,10 @@ const LoadingCards: FunctionComponent<{ amount: number }> = ({ amount }) => ( {[...Array(amount || 1)].map((_, i) => ( e.preventDefault()} isPlaceholder - name={''} - isLoaded={false} + isLoading={false} /> ))}
@@ -149,8 +148,7 @@ export default function AppList() { }} isFavourite showStatus - isLoaded={favsState.isSuccess} - name={appName} + isLoading={favsState.isLoading} /> ); })} @@ -217,8 +215,7 @@ export default function AppList() { e.preventDefault(); }} isFavourite={app.isFavourite} - name={app.name} - isLoaded={true} + isLoading={false} /> ); })} diff --git a/src/components/environments-summary/environment-card.tsx b/src/components/environments-summary/environment-card.tsx index 94ea07bfb..32f285068 100644 --- a/src/components/environments-summary/environment-card.tsx +++ b/src/components/environments-summary/environment-card.tsx @@ -153,7 +153,11 @@ export const EnvironmentCardLayout = ({ )} - + diff --git a/src/components/environments-summary/environment-ingress.tsx b/src/components/environments-summary/environment-ingress.tsx index 24930323e..08fb0502a 100644 --- a/src/components/environments-summary/environment-ingress.tsx +++ b/src/components/environments-summary/environment-ingress.tsx @@ -12,9 +12,15 @@ export const URL_VAR_NAME = 'RADIX_PUBLIC_DOMAIN_NAME'; const MAX_DISPLAY_COMPONENTS = 2; export interface EnvironmentIngressProps { + appName: string; + envName: string; components?: Component[]; } -export const EnvironmentIngress = ({ components }: EnvironmentIngressProps) => { +export const EnvironmentIngress = ({ + components, + appName, + envName, +}: EnvironmentIngressProps) => { const comps = components?.reduce<{ public: Array; passive: Array; diff --git a/src/components/page-active-component/active-component-overview.tsx b/src/components/page-active-component/active-component-overview.tsx index 43e5a2e3c..e7a83b940 100644 --- a/src/components/page-active-component/active-component-overview.tsx +++ b/src/components/page-active-component/active-component-overview.tsx @@ -113,6 +113,7 @@ export const ActiveComponentOverview: FunctionComponent<{
; isExpanded?: boolean; + showUtilization?: boolean; }; export const ComponentReplicaList = ({ title, @@ -18,6 +19,7 @@ export const ComponentReplicaList = ({ componentName, replicaList, isExpanded, + showUtilization, }: Props) => ( @@ -32,7 +34,7 @@ export const ComponentReplicaList = ({
{replicaList && replicaList.length > 0 ? ( Date: Wed, 15 Jan 2025 09:46:36 +0100 Subject: [PATCH 19/21] rm unused file --- src/components/utilization-popover/dev.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/components/utilization-popover/dev.tsx diff --git a/src/components/utilization-popover/dev.tsx b/src/components/utilization-popover/dev.tsx deleted file mode 100644 index e69de29bb..000000000 From 1b9d9136bb7d94a783d09aeac13c4698608b9cde Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 15 Jan 2025 15:10:34 +0100 Subject: [PATCH 20/21] reduce polling interval to 15min --- src/components/app-list-item/index.tsx | 3 ++- src/components/environments-summary/environment-card.tsx | 4 ++-- src/components/page-active-component/overview.tsx | 4 ++-- src/components/page-environment/component-list.tsx | 4 ++-- src/components/replica-list/index.tsx | 4 ++-- src/store/defaults.ts | 3 +++ 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/app-list-item/index.tsx b/src/components/app-list-item/index.tsx index d7c095a7e..52cea14c0 100644 --- a/src/components/app-list-item/index.tsx +++ b/src/components/app-list-item/index.tsx @@ -41,6 +41,7 @@ import { } from '../environments-summary/environment-status-utils'; import './style.css'; +import { slowPollingInterval } from '../../store/defaults'; import { Severity, UtilizationPopover, @@ -78,7 +79,7 @@ export const AppListItem = ({ isLoading, ...props }: AppListItemProps) => { const { data: utilization, isLoading: isUtilizationLoading } = useGetApplicationResourcesUtilizationQuery( { appName: props.appName }, - { pollingInterval: 0, skip: !props.showStatus } + { pollingInterval: slowPollingInterval, skip: !props.showStatus } ); return ( diff --git a/src/components/environments-summary/environment-card.tsx b/src/components/environments-summary/environment-card.tsx index 32f285068..b40ebe603 100644 --- a/src/components/environments-summary/environment-card.tsx +++ b/src/components/environments-summary/environment-card.tsx @@ -10,7 +10,7 @@ import { Link } from 'react-router-dom'; import { EnvironmentIngress } from './environment-ingress'; import { routes } from '../../routes'; -import { pollingInterval } from '../../store/defaults'; +import { pollingInterval, slowPollingInterval } from '../../store/defaults'; import { type Component, type DeploymentSummary, @@ -64,7 +64,7 @@ export const EnvironmentCard = ({ const { data: utilization, isLoading: isUtilizationLoading } = useGetApplicationResourcesUtilizationQuery( { appName }, - { pollingInterval } + { pollingInterval: slowPollingInterval } ); return ( diff --git a/src/components/page-active-component/overview.tsx b/src/components/page-active-component/overview.tsx index fd3206582..2d3ffcdb0 100644 --- a/src/components/page-active-component/overview.tsx +++ b/src/components/page-active-component/overview.tsx @@ -13,7 +13,7 @@ import { DockerImage } from '../docker-image'; import { ComponentStatusBadge } from '../status-badges'; import { DefaultAlias } from './default-alias'; import './style.css'; -import { pollingInterval } from '../../store/defaults'; +import { pollingInterval, slowPollingInterval } from '../../store/defaults'; import { IngressAllowList } from '../component/ingress-allow-list'; import { ExternalLink } from '../link/external-link'; import { ResourceRequirements } from '../resource-requirements'; @@ -48,7 +48,7 @@ export const Overview = ({ const { data: utilization } = useGetApplicationResourcesUtilizationQuery( { appName }, - { pollingInterval } + { pollingInterval: slowPollingInterval } ); return ( diff --git a/src/components/page-environment/component-list.tsx b/src/components/page-environment/component-list.tsx index bbbfe7815..72312d4e2 100644 --- a/src/components/page-environment/component-list.tsx +++ b/src/components/page-environment/component-list.tsx @@ -39,7 +39,7 @@ import { ReplicaStatusTooltip } from '../status-tooltips'; import { VulnerabilitySummary } from '../vulnerability-summary'; import './style.css'; -import { pollingInterval } from '../../store/defaults'; +import { pollingInterval, slowPollingInterval } from '../../store/defaults'; import { UtilizationPopover } from '../utilization-popover/utilization-popover'; export interface ComponentListProps { @@ -147,7 +147,7 @@ export const ComponentList: FunctionComponent = ({ const { data: utilization } = useGetApplicationResourcesUtilizationQuery( { appName }, - { pollingInterval } + { pollingInterval: slowPollingInterval } ); useEffect(() => { diff --git a/src/components/replica-list/index.tsx b/src/components/replica-list/index.tsx index c0fd67daa..ceecd3081 100644 --- a/src/components/replica-list/index.tsx +++ b/src/components/replica-list/index.tsx @@ -20,7 +20,7 @@ import { Duration } from '../time/duration'; import { RelativeToNow } from '../time/relative-to-now'; import './style.css'; -import { pollingInterval } from '../../store/defaults'; +import { pollingInterval, slowPollingInterval } from '../../store/defaults'; import { UtilizationPopover } from '../utilization-popover/utilization-popover'; import { ReplicaName } from './replica-name'; @@ -53,7 +53,7 @@ export const ReplicaList = ({ const { data: utilization } = useGetApplicationResourcesUtilizationQuery( { appName }, - { pollingInterval } + { pollingInterval: slowPollingInterval } ); // biome-ignore lint/correctness/useExhaustiveDependencies(replicaList): reset last update when replica list changes diff --git a/src/store/defaults.ts b/src/store/defaults.ts index a5d27e469..405e74ee8 100644 --- a/src/store/defaults.ts +++ b/src/store/defaults.ts @@ -1,2 +1,5 @@ // Default polling interval is 15sec export const pollingInterval = 15_000 as const; + +// Slow polling interval: 15min +export const slowPollingInterval = 15*60_000; From b3288d9f944bd6039977c9f99e37e3ca4a22dedf Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 15 Jan 2025 15:12:54 +0100 Subject: [PATCH 21/21] fix linting --- src/components/page-active-component/overview.tsx | 2 +- src/components/page-environment/component-list.tsx | 2 +- src/components/replica-list/index.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/page-active-component/overview.tsx b/src/components/page-active-component/overview.tsx index 2d3ffcdb0..2ea24ee2f 100644 --- a/src/components/page-active-component/overview.tsx +++ b/src/components/page-active-component/overview.tsx @@ -13,7 +13,7 @@ import { DockerImage } from '../docker-image'; import { ComponentStatusBadge } from '../status-badges'; import { DefaultAlias } from './default-alias'; import './style.css'; -import { pollingInterval, slowPollingInterval } from '../../store/defaults'; +import { slowPollingInterval } from '../../store/defaults'; import { IngressAllowList } from '../component/ingress-allow-list'; import { ExternalLink } from '../link/external-link'; import { ResourceRequirements } from '../resource-requirements'; diff --git a/src/components/page-environment/component-list.tsx b/src/components/page-environment/component-list.tsx index 72312d4e2..5b5ecd9c0 100644 --- a/src/components/page-environment/component-list.tsx +++ b/src/components/page-environment/component-list.tsx @@ -39,7 +39,7 @@ import { ReplicaStatusTooltip } from '../status-tooltips'; import { VulnerabilitySummary } from '../vulnerability-summary'; import './style.css'; -import { pollingInterval, slowPollingInterval } from '../../store/defaults'; +import { slowPollingInterval } from '../../store/defaults'; import { UtilizationPopover } from '../utilization-popover/utilization-popover'; export interface ComponentListProps { diff --git a/src/components/replica-list/index.tsx b/src/components/replica-list/index.tsx index ceecd3081..52bd3e643 100644 --- a/src/components/replica-list/index.tsx +++ b/src/components/replica-list/index.tsx @@ -20,7 +20,7 @@ import { Duration } from '../time/duration'; import { RelativeToNow } from '../time/relative-to-now'; import './style.css'; -import { pollingInterval, slowPollingInterval } from '../../store/defaults'; +import { slowPollingInterval } from '../../store/defaults'; import { UtilizationPopover } from '../utilization-popover/utilization-popover'; import { ReplicaName } from './replica-name';