diff --git a/Dockerfile b/Dockerfile index 41964b89..025bed4b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM epamedp/headlamp:0.22.36 +FROM epamedp/headlamp:0.22.37 COPY --chown=100:101 assets/ /headlamp/frontend COPY --chown=100:101 dist/main.js /headlamp/plugins/edp/main.js diff --git a/src/components/ErrorBoundary/index.tsx b/src/components/ErrorBoundary/index.tsx index aad7f89d..3aeca619 100644 --- a/src/components/ErrorBoundary/index.tsx +++ b/src/components/ErrorBoundary/index.tsx @@ -24,6 +24,7 @@ class ErrorBoundary extends React.Component { autoHideDuration: 5000, content: (key, message) => ( this.props.closeSnackbar(key)} variant="error" diff --git a/src/components/Snackbar/index.tsx b/src/components/Snackbar/index.tsx index e1527970..45cc9a68 100644 --- a/src/components/Snackbar/index.tsx +++ b/src/components/Snackbar/index.tsx @@ -1,6 +1,7 @@ import { Icon } from '@iconify/react'; +import { Link } from '@kinvolk/headlamp-plugin/lib/CommonComponents'; import { Box, Button, IconButton, Stack, Typography } from '@mui/material'; -import { SnackbarContent, useSnackbar } from 'notistack'; +import { SnackbarContent, SnackbarKey, useSnackbar } from 'notistack'; import React from 'react'; import { ICONS } from '../../icons/iconify-icons-mapping'; @@ -14,13 +15,20 @@ const SNACKBAR_VARIANT = { export const Snackbar = React.forwardRef< HTMLDivElement, { + snackbarKey: SnackbarKey; text: string; variant: string; - pushLocation?: () => void; + pushLocation?: { + href: { + routeName: string; + params: Record; + }; + text?: string; + }; handleClose?: () => void; } >((props, ref) => { - const { text, variant, handleClose, pushLocation } = props; + const { text, variant, snackbarKey, pushLocation } = props; const { closeSnackbar } = useSnackbar(); const theme = React.useMemo(() => { @@ -56,6 +64,10 @@ export const Snackbar = React.forwardRef< }; }, [variant]); + const handleClose = () => { + closeSnackbar(snackbarKey); + }; + return ( {pushLocation && ( )} diff --git a/src/hooks/useResourceCRUDMutation/index.tsx b/src/hooks/useResourceCRUDMutation/index.tsx index f8464806..b928b47d 100644 --- a/src/hooks/useResourceCRUDMutation/index.tsx +++ b/src/hooks/useResourceCRUDMutation/index.tsx @@ -49,7 +49,6 @@ export const useResourceCRUDMutation = < showRequestErrorMessage, showRequestSuccessMessage, showRequestErrorDetailedMessage, - closeSnackbar, } = useRequestStatusMessages(); const namespace = getDefaultNamespace(); @@ -103,11 +102,7 @@ export const useResourceCRUDMutation = < }, variant: 'info', content: (key, message) => ( - closeSnackbar(key)} - variant="info" - /> + ), } as const; @@ -145,11 +140,7 @@ export const useResourceCRUDMutation = < }, variant: 'success', content: (key, message) => ( - closeSnackbar(key)} - variant="success" - /> + ), } as const; @@ -187,11 +178,7 @@ export const useResourceCRUDMutation = < }, variant: 'error', content: (key, message) => ( - closeSnackbar(key)} - variant="error" - /> + ), } as const; diff --git a/src/hooks/useResourceRequestStatusMessages/index.tsx b/src/hooks/useResourceRequestStatusMessages/index.tsx index 0acfbb34..e5f575d5 100644 --- a/src/hooks/useResourceRequestStatusMessages/index.tsx +++ b/src/hooks/useResourceRequestStatusMessages/index.tsx @@ -12,7 +12,7 @@ interface Options { } export const useRequestStatusMessages = () => { - const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + const { enqueueSnackbar } = useSnackbar(); const showBeforeRequestMessage = (mode: CRUD_TYPES, { entityName, customMessage }: Options) => { const beforeRequestMessage = (() => { @@ -34,7 +34,7 @@ export const useRequestStatusMessages = () => { }, variant: 'info', content: (key, message) => ( - closeSnackbar(key)} variant="info" /> + ), } as const; @@ -70,7 +70,7 @@ export const useRequestStatusMessages = () => { }, variant: 'success', content: (key, message) => ( - closeSnackbar(key)} variant="success" /> + ), } as const; @@ -106,7 +106,7 @@ export const useRequestStatusMessages = () => { }, variant: 'error', content: (key, message) => ( - closeSnackbar(key)} variant="error" /> + ), } as const; @@ -130,7 +130,7 @@ export const useRequestStatusMessages = () => { horizontal: 'left', }, content: (key, message) => ( - closeSnackbar(key)} variant="error" /> + ), }); }; @@ -140,6 +140,5 @@ export const useRequestStatusMessages = () => { showRequestSuccessMessage, showRequestErrorMessage, showRequestErrorDetailedMessage, - closeSnackbar, }; }; diff --git a/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateBuildPipelineRun.ts b/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateBuildPipelineRun.tsx similarity index 64% rename from src/k8s/groups/Tekton/PipelineRun/hooks/useCreateBuildPipelineRun.ts rename to src/k8s/groups/Tekton/PipelineRun/hooks/useCreateBuildPipelineRun.tsx index c83c596e..bee3876a 100644 --- a/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateBuildPipelineRun.ts +++ b/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateBuildPipelineRun.tsx @@ -1,6 +1,9 @@ import React, { useCallback } from 'react'; +import { Snackbar } from '../../../../../components/Snackbar'; import { CRUD_TYPES } from '../../../../../constants/crudTypes'; import { useResourceCRUDMutation } from '../../../../../hooks/useResourceCRUDMutation'; +import { routePipelineRunDetails } from '../../../../../pages/pipeline-details/route'; +import { getDefaultNamespace } from '../../../../../utils/getDefaultNamespace'; import { PipelineRunKubeObject } from '../index'; import { PipelineRunKubeObjectInterface } from '../types'; @@ -18,7 +21,7 @@ export const useCreateBuildPipelineRun = ({ PipelineRunKubeObjectInterface, CRUD_TYPES.CREATE >('buildPipelineRunCreateMutation', PipelineRunKubeObject, CRUD_TYPES.CREATE, { - createCustomMessages: () => ({ + createCustomMessages: (item) => ({ onMutate: { message: 'Creating build PipelineRun', }, @@ -27,6 +30,26 @@ export const useCreateBuildPipelineRun = ({ }, onSuccess: { message: 'Start building application(s)', + options: { + persist: true, + content: (key, message) => ( + + ), + }, }, }), }); diff --git a/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateCleanPipelineRun.ts b/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateCleanPipelineRun.tsx similarity index 66% rename from src/k8s/groups/Tekton/PipelineRun/hooks/useCreateCleanPipelineRun.ts rename to src/k8s/groups/Tekton/PipelineRun/hooks/useCreateCleanPipelineRun.tsx index 2268302c..ba2c2f2e 100644 --- a/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateCleanPipelineRun.ts +++ b/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateCleanPipelineRun.tsx @@ -1,6 +1,9 @@ import React from 'react'; +import { Snackbar } from '../../../../../components/Snackbar'; import { CRUD_TYPES } from '../../../../../constants/crudTypes'; import { useResourceCRUDMutation } from '../../../../../hooks/useResourceCRUDMutation'; +import { routePipelineRunDetails } from '../../../../../pages/pipeline-details/route'; +import { getDefaultNamespace } from '../../../../../utils/getDefaultNamespace'; import { PipelineRunKubeObject } from '../index'; import { PipelineRunKubeObjectInterface } from '../types'; @@ -22,7 +25,7 @@ export const useCreateCleanPipelineRun = ({ PipelineRunKubeObjectInterface, CRUD_TYPES.CREATE >('cleanPipelineRunCreateMutation', PipelineRunKubeObject, CRUD_TYPES.CREATE, { - createCustomMessages: () => ({ + createCustomMessages: (item) => ({ onMutate: { message: 'Creating clean PipelineRun', }, @@ -31,6 +34,26 @@ export const useCreateCleanPipelineRun = ({ }, onSuccess: { message: 'Start cleaning application(s)', + options: { + persist: true, + content: (key, message) => ( + + ), + }, }, }), }); diff --git a/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateDeployPipelineRun.ts b/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateDeployPipelineRun.tsx similarity index 66% rename from src/k8s/groups/Tekton/PipelineRun/hooks/useCreateDeployPipelineRun.ts rename to src/k8s/groups/Tekton/PipelineRun/hooks/useCreateDeployPipelineRun.tsx index 188ac2f4..9dea6457 100644 --- a/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateDeployPipelineRun.ts +++ b/src/k8s/groups/Tekton/PipelineRun/hooks/useCreateDeployPipelineRun.tsx @@ -1,6 +1,9 @@ import React from 'react'; +import { Snackbar } from '../../../../../components/Snackbar'; import { CRUD_TYPES } from '../../../../../constants/crudTypes'; import { useResourceCRUDMutation } from '../../../../../hooks/useResourceCRUDMutation'; +import { routePipelineRunDetails } from '../../../../../pages/pipeline-details/route'; +import { getDefaultNamespace } from '../../../../../utils/getDefaultNamespace'; import { PipelineRunKubeObject } from '../index'; import { PipelineRunKubeObjectInterface } from '../types'; @@ -22,7 +25,7 @@ export const useCreateDeployPipelineRun = ({ PipelineRunKubeObjectInterface, CRUD_TYPES.CREATE >('deployPipelineRunCreateMutation', PipelineRunKubeObject, CRUD_TYPES.CREATE, { - createCustomMessages: () => ({ + createCustomMessages: (item) => ({ onMutate: { message: 'Creating deploy PipelineRun', }, @@ -31,6 +34,26 @@ export const useCreateDeployPipelineRun = ({ }, onSuccess: { message: 'Start deploying application(s)', + options: { + persist: true, + content: (key, message) => ( + + ), + }, }, }), }); diff --git a/src/k8s/groups/Tekton/PipelineRun/utils/createBuildPipelineRunInstance/index.test.ts b/src/k8s/groups/Tekton/PipelineRun/utils/createBuildPipelineRunInstance/index.test.ts index f20ecda2..22f416e6 100644 --- a/src/k8s/groups/Tekton/PipelineRun/utils/createBuildPipelineRunInstance/index.test.ts +++ b/src/k8s/groups/Tekton/PipelineRun/utils/createBuildPipelineRunInstance/index.test.ts @@ -1,8 +1,16 @@ +import { v4 as uuidv4 } from 'uuid'; import { CodebaseKubeObjectInterface } from '../../../../EDP/Codebase/types'; import { CodebaseBranchKubeObjectInterface } from '../../../../EDP/CodebaseBranch/types'; import { GitServerKubeObjectInterface } from '../../../../EDP/GitServer/types'; import { createBuildPipelineRunInstance } from './index'; +jest.mock('uuid', () => ({ + v4: jest.fn(), +})); + +const MOCKED_UUID = '1234'; +(uuidv4 as jest.Mock).mockReturnValue(MOCKED_UUID); + describe('testing createBuildPipelineRunInstance', () => { it('should return valid kube object', () => { const object = createBuildPipelineRunInstance({ @@ -138,7 +146,7 @@ describe('testing createBuildPipelineRunInstance', () => { kind: 'PipelineRun', metadata: { annotations: { 'argocd.argoproj.io/compare-options': 'IgnoreExtraneous' }, - generateName: 'test-codebase-name-test-codebase-branch-name-build-', + name: `test-codebase-name-test-codebase-branch-name-build-${MOCKED_UUID}`, labels: { 'app.edp.epam.com/codebase': 'test-codebase-name', 'app.edp.epam.com/codebasebranch': 'test-codebase-name-test-codebase-branch-name', diff --git a/src/k8s/groups/Tekton/PipelineRun/utils/createBuildPipelineRunInstance/index.ts b/src/k8s/groups/Tekton/PipelineRun/utils/createBuildPipelineRunInstance/index.ts index 3d9cf7db..aaecbeca 100644 --- a/src/k8s/groups/Tekton/PipelineRun/utils/createBuildPipelineRunInstance/index.ts +++ b/src/k8s/groups/Tekton/PipelineRun/utils/createBuildPipelineRunInstance/index.ts @@ -1,4 +1,5 @@ import { PIPELINE_TYPES } from '../../../../../../constants/pipelineTypes'; +import { createRandomString } from '../../../../../../utils/createRandomString'; import { CodebaseKubeObjectInterface } from '../../../../EDP/Codebase/types'; import { CodebaseBranchKubeObjectInterface } from '../../../../EDP/CodebaseBranch/types'; import { GitServerKubeObjectInterface } from '../../../../EDP/GitServer/types'; @@ -36,11 +37,15 @@ export const createBuildPipelineRunInstance = ({ const base = { ...pipelineRunTemplate }; - base.metadata.generateName = base.metadata.generateName.replace( + const generateName = base.metadata.generateName.replace( '$(tt.params.codebasebranch)', codebaseBranchMetadataName ); + delete base.metadata.generateName; + + base.metadata.name = `${generateName}${createRandomString(4)}`; + base.metadata.labels[PIPELINE_RUN_LABEL_SELECTOR_CODEBASE] = codebaseName; base.metadata.labels[PIPELINE_RUN_LABEL_SELECTOR_CODEBASE_BRANCH] = codebaseBranchMetadataName; base.metadata.labels[PIPELINE_RUN_LABEL_SELECTOR_PIPELINE_TYPE] = PIPELINE_TYPES.BUILD; diff --git a/src/k8s/groups/Tekton/PipelineRun/utils/createCleanPipelineRunInstance/index.test.ts b/src/k8s/groups/Tekton/PipelineRun/utils/createCleanPipelineRunInstance/index.test.ts index a11912e8..a6eae461 100644 --- a/src/k8s/groups/Tekton/PipelineRun/utils/createCleanPipelineRunInstance/index.test.ts +++ b/src/k8s/groups/Tekton/PipelineRun/utils/createCleanPipelineRunInstance/index.test.ts @@ -1,5 +1,13 @@ +import { v4 as uuidv4 } from 'uuid'; import { createCleanPipelineRunInstance } from './index'; +jest.mock('uuid', () => ({ + v4: jest.fn(), +})); + +const MOCKED_UUID = '1234'; +(uuidv4 as jest.Mock).mockReturnValue(MOCKED_UUID); + describe('testing createCleanPipelineRunInstance', () => { it('should return valid kube object', () => { const object = createCleanPipelineRunInstance({ @@ -98,7 +106,7 @@ describe('testing createCleanPipelineRunInstance', () => { kind: 'PipelineRun', metadata: { annotations: { 'argocd.argoproj.io/compare-options': 'IgnoreExtraneous' }, - generateName: 'clean-test-pipe-sit-', + name: `clean-test-pipe-sit-${MOCKED_UUID}`, labels: { 'app.edp.epam.com/cdpipeline': 'test-pipe', 'app.edp.epam.com/cdstage': 'test-pipe-sit', diff --git a/src/k8s/groups/Tekton/PipelineRun/utils/createCleanPipelineRunInstance/index.ts b/src/k8s/groups/Tekton/PipelineRun/utils/createCleanPipelineRunInstance/index.ts index 609c17f6..bf6ed008 100644 --- a/src/k8s/groups/Tekton/PipelineRun/utils/createCleanPipelineRunInstance/index.ts +++ b/src/k8s/groups/Tekton/PipelineRun/utils/createCleanPipelineRunInstance/index.ts @@ -1,4 +1,5 @@ import { PIPELINE_TYPES } from '../../../../../../constants/pipelineTypes'; +import { createRandomString } from '../../../../../../utils/createRandomString'; import { CDPipelineKubeObjectInterface } from '../../../../EDP/CDPipeline/types'; import { StageKubeObjectInterface } from '../../../../EDP/Stage/types'; import { @@ -19,7 +20,11 @@ export const createCleanPipelineRunInstance = ({ }): PipelineRunKubeObjectInterface => { const base = { ...pipelineRunTemplate }; - base.metadata.generateName = `clean-${CDPipeline.metadata.name}-${stage.spec.name}-`; + const generateName = `clean-${CDPipeline.metadata.name}-${stage.spec.name}-`; + + delete base.metadata.generateName; + + base.metadata.name = `${generateName}${createRandomString(4)}`; base.metadata.labels[PIPELINE_RUN_LABEL_SELECTOR_CDPIPELINE] = CDPipeline.metadata.name; base.metadata.labels[PIPELINE_RUN_LABEL_SELECTOR_CDSTAGE] = stage.metadata.name; diff --git a/src/k8s/groups/Tekton/PipelineRun/utils/createDeployPipelineRunInstance/index.test.ts b/src/k8s/groups/Tekton/PipelineRun/utils/createDeployPipelineRunInstance/index.test.ts index 2aa6bb04..c3af8da4 100644 --- a/src/k8s/groups/Tekton/PipelineRun/utils/createDeployPipelineRunInstance/index.test.ts +++ b/src/k8s/groups/Tekton/PipelineRun/utils/createDeployPipelineRunInstance/index.test.ts @@ -1,5 +1,12 @@ +import { v4 as uuidv4 } from 'uuid'; import { createDeployPipelineRunInstance } from './index'; +jest.mock('uuid', () => ({ + v4: jest.fn(), +})); + +const MOCKED_UUID = '1234'; +(uuidv4 as jest.Mock).mockReturnValue(MOCKED_UUID); describe('testing createDeployPipelineRunInstance', () => { it('should return valid kube object', () => { const object = createDeployPipelineRunInstance({ @@ -116,7 +123,7 @@ describe('testing createDeployPipelineRunInstance', () => { kind: 'PipelineRun', metadata: { annotations: { 'argocd.argoproj.io/compare-options': 'IgnoreExtraneous' }, - generateName: 'deploy-test-pipe-sit', + name: `deploy-test-pipe-sit-${MOCKED_UUID}`, labels: { 'app.edp.epam.com/cdpipeline': 'test-pipe', 'app.edp.epam.com/cdstage': 'test-pipe-sit', diff --git a/src/k8s/groups/Tekton/PipelineRun/utils/createDeployPipelineRunInstance/index.ts b/src/k8s/groups/Tekton/PipelineRun/utils/createDeployPipelineRunInstance/index.ts index 6807b46d..fcdd003a 100644 --- a/src/k8s/groups/Tekton/PipelineRun/utils/createDeployPipelineRunInstance/index.ts +++ b/src/k8s/groups/Tekton/PipelineRun/utils/createDeployPipelineRunInstance/index.ts @@ -1,4 +1,5 @@ import { PIPELINE_TYPES } from '../../../../../../constants/pipelineTypes'; +import { createRandomString } from '../../../../../../utils/createRandomString'; import { CDPipelineKubeObjectInterface } from '../../../../EDP/CDPipeline/types'; import { StageKubeObjectInterface } from '../../../../EDP/Stage/types'; import { @@ -27,7 +28,11 @@ export const createDeployPipelineRunInstance = ({ }): PipelineRunKubeObjectInterface => { const base = { ...pipelineRunTemplate }; - base.metadata.generateName = `deploy-${CDPipeline.metadata.name}-${stage.spec.name}`; + const generateName = `deploy-${CDPipeline.metadata.name}-${stage.spec.name}-`; + + delete base.metadata.generateName; + + base.metadata.name = `${generateName}${createRandomString(4)}`; base.metadata.labels[PIPELINE_RUN_LABEL_SELECTOR_CDPIPELINE] = CDPipeline.metadata.name; base.metadata.labels[PIPELINE_RUN_LABEL_SELECTOR_CDSTAGE] = stage.metadata.name; diff --git a/src/utils/createRandomString.ts b/src/utils/createRandomString.ts index a1b30ad6..2d2cac43 100644 --- a/src/utils/createRandomString.ts +++ b/src/utils/createRandomString.ts @@ -1,8 +1,9 @@ -export const createRandomString = ( - stringLength: number = 5, - stringChars: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' -): string => - [...window.crypto.getRandomValues(new Uint32Array(stringLength))] - .map((x) => stringChars[x % stringChars.length]) - .join('') - .toLowerCase(); +import { v4 as uuidv4 } from 'uuid'; + +export const createRandomString = (stringLength: number = 5): string => { + const uuid = uuidv4(); + + const continuousUUID = uuid.replace(/-/g, ''); + + return continuousUUID.slice(0, stringLength); +}; diff --git a/src/widgets/PipelineRunActionsMenu/index.tsx b/src/widgets/PipelineRunActionsMenu/index.tsx index bcd49ee4..c2d2b640 100644 --- a/src/widgets/PipelineRunActionsMenu/index.tsx +++ b/src/widgets/PipelineRunActionsMenu/index.tsx @@ -1,7 +1,5 @@ -import { Router } from '@kinvolk/headlamp-plugin/lib'; import { EditorDialog } from '@kinvolk/headlamp-plugin/lib/CommonComponents'; import { KubeObjectInterface } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster'; -import { useSnackbar } from 'notistack'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { ActionsMenuList } from '../../components/ActionsMenuList'; @@ -30,7 +28,6 @@ export const PipelineRunActionsMenu = ({ permissions, }: PipelineRunActionsMenuProps) => { const history = useHistory(); - const { closeSnackbar } = useSnackbar(); const status = PipelineRunKubeObject.parseStatusReason(_pipelineRun)?.toLowerCase(); @@ -51,16 +48,18 @@ export const PipelineRunActionsMenu = ({ persist: true, content: (key, message) => ( closeSnackbar(key)} - pushLocation={() => - history.push( - createGoToRoute({ + pushLocation={{ + href: { + routeName: routePipelineRunDetails.path, + params: { namespace: item.metadata.namespace || getDefaultNamespace(), name: item.metadata.name, - }) - ) - } + }, + }, + text: 'Check status', + }} variant={'success'} /> ), @@ -110,9 +109,6 @@ export const PipelineRunActionsMenu = ({ history.push(backRoute); }, [backRoute, history]); - const createGoToRoute = (params: any) => - Router.createRouteURL(routePipelineRunDetails.path, params); - const [editor, setEditor] = React.useState<{ open: boolean; data: KubeObjectInterface | undefined; diff --git a/src/widgets/PipelineRunList/hooks/useUpperColumns.tsx b/src/widgets/PipelineRunList/hooks/useUpperColumns.tsx index b093ce33..287678ad 100644 --- a/src/widgets/PipelineRunList/hooks/useUpperColumns.tsx +++ b/src/widgets/PipelineRunList/hooks/useUpperColumns.tsx @@ -12,10 +12,12 @@ export const useUpperColumns = ({ selected, onDeleteClick, permissions, + isEmpty, }: { selected: string[]; onDeleteClick: () => void; permissions: WidgetPermissions; + isEmpty: boolean; }): TableColumn[] => { const theme = useTheme(); const numSelected = React.useMemo(() => selected.length, [selected]); @@ -34,22 +36,7 @@ export const useUpperColumns = ({ ); }, - colSpan: 4, - }, - { - id: 'placeholder-1', - label: '', - render: () => null, - }, - { - id: 'placeholder-2', - label: '', - render: () => null, - }, - { - id: 'placeholder-3', - label: '', - render: () => null, + colSpan: isEmpty ? 7 : 8, }, { id: 'placeholder-4', @@ -80,6 +67,14 @@ export const useUpperColumns = ({ ), }, ], - [numSelected, onDeleteClick, permissions.delete, theme.palette.secondary.dark, theme.typography] + [ + isEmpty, + numSelected, + onDeleteClick, + permissions.delete.PipelineRun.allowed, + permissions.delete.PipelineRun.reason, + theme.palette.secondary.dark, + theme.typography, + ] ); }; diff --git a/src/widgets/PipelineRunList/index.tsx b/src/widgets/PipelineRunList/index.tsx index 877dc308..6ce5b60f 100644 --- a/src/widgets/PipelineRunList/index.tsx +++ b/src/widgets/PipelineRunList/index.tsx @@ -87,12 +87,6 @@ export const PipelineRunList = ({ const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); - const upperColumns = useUpperColumns({ - selected, - onDeleteClick: () => setDeleteDialogOpen(true), - permissions, - }); - const onDelete = React.useCallback(() => { setSelected([]); }, []); @@ -101,6 +95,13 @@ export const PipelineRunList = ({ return pipelineRuns?.sort(sortKubeObjectByCreationTimestamp); }, [pipelineRuns]); + const upperColumns = useUpperColumns({ + selected, + onDeleteClick: () => setDeleteDialogOpen(true), + permissions, + isEmpty: !pipelineRuns?.length, + }); + const pipelineCodebases = React.useMemo(() => { return new Set( pipelineRuns diff --git a/src/widgets/ResourceQuotas/index.tsx b/src/widgets/ResourceQuotas/index.tsx index c2a42418..0ce3c204 100644 --- a/src/widgets/ResourceQuotas/index.tsx +++ b/src/widgets/ResourceQuotas/index.tsx @@ -17,11 +17,11 @@ export const ResourceQuotas = () => { return { quotas: null, highestUsedQuota: null }; } - const concatAnnotations = items.reduce((acc, item) => { - return { ...acc, ...item.metadata.annotations }; - }, {}); + const useAnnotations = Object.keys(items[0]?.metadata?.annotations || {}).some((key) => + key.includes('quota.capsule.clastix.io') + ); - return parseResourceQuota(concatAnnotations); + return parseResourceQuota(items, useAnnotations); }, [items]); const [anchorEl, setAnchorEl] = React.useState(null); diff --git a/src/widgets/ResourceQuotas/utils.ts b/src/widgets/ResourceQuotas/utils.ts index b240b99b..992e5048 100644 --- a/src/widgets/ResourceQuotas/utils.ts +++ b/src/widgets/ResourceQuotas/utils.ts @@ -1,4 +1,5 @@ import { Theme } from '@mui/material'; +import { ResourceQuotaKubeObjectInterface } from '../../k8s/groups/default/ResourceQuota/types'; import { ParsedQuotas, QuotaDetails } from './types'; export const convertToNumber = (value: string): number => { @@ -20,36 +21,60 @@ export const convertToNumber = (value: string): number => { }; export const parseResourceQuota = ( - annotations: Record + items: ResourceQuotaKubeObjectInterface[], + useAnnotations: boolean ): { quotas: ParsedQuotas; highestUsedQuota: QuotaDetails | null; } => { - const quotas: ParsedQuotas = {}; - let highestUsedQuota: QuotaDetails | null = null; + const quotas = items.reduce((acc, item) => { + if (useAnnotations) { + const annotations = item.metadata.annotations || {}; + Object.keys(annotations).forEach((key) => { + const [category, entity] = key.substring(key.lastIndexOf('/') + 1).split('-'); + const value = convertToNumber(annotations[key]); - Object.keys(annotations).forEach((key) => { - const initialVal = annotations[key]; - const value = convertToNumber(initialVal); - const subKey = key.substring(key.lastIndexOf('/') + 1); - const [category, entity] = subKey.split('-'); + if (!acc[entity]) acc[entity] = {}; + acc[entity][category] = value; + acc[entity][`${category}_initial`] = annotations[key]; + }); + } else { + const status = item.status || {}; + ['hard', 'used'].forEach((statusType) => { + const statusData = status[statusType] || {}; + Object.keys(statusData).forEach((key) => { + const entity = key; + const value = convertToNumber(statusData[key]); - if (!quotas[entity]) { - quotas[entity] = {}; + if (!acc[entity]) acc[entity] = {}; + acc[entity][statusType] = value; + acc[entity][`${statusType}_initial`] = statusData[key]; // Ensure initial value is captured similarly + }); + }); } - quotas[entity][category] = value; - quotas[entity][`${category}_initial`] = initialVal; + return acc; + }, {}); - if (quotas[entity].hard && quotas[entity].used && quotas[entity].hard > 0) { - const usedPercentage = (quotas[entity].used / quotas[entity].hard) * 100; - quotas[entity].usedPercentage = usedPercentage; + const highestUsedQuota = Object.entries(quotas).reduce( + (acc, [entity, details]) => { + const used = details.used || 0; + const hard = details.hard || 0; + const usedPercentage = (used / hard) * 100; - if (highestUsedQuota === null || usedPercentage > highestUsedQuota.usedPercentage) { - highestUsedQuota = { ...quotas[entity] }; + if (!acc || usedPercentage > acc.usedPercentage) { + return { + entity, + used, + hard, + usedPercentage, + }; } - } - }); + + return acc; + }, + null + ); return { quotas, highestUsedQuota }; };