From 51e77e2d25cee008cb769415b8a21b48d4581dd6 Mon Sep 17 00:00:00 2001 From: Oliwia Gowor Date: Mon, 9 Sep 2024 13:30:13 +0200 Subject: [PATCH 1/8] add resource quotas views --- public/i18n/en.yaml | 10 ++ src/resources/Namespaces/NamespaceDetails.js | 6 +- .../ResourceQuotas/ResourceQuotaCreate.js | 12 ++- .../ResourceQuotas/ResourceQuotaDetails.scss | 38 +++++++ .../ResourceQuotas/ResourceQuotaDetails.tsx | 87 +++++++++++++++ .../ResourceQuotas/ResourceQuotaLimits.tsx | 102 ++++++++++++++++++ ...urceQuotaList.js => ResourceQuotaList.tsx} | 32 +++--- .../ResourceQuotas/ResourceQuotasList.js | 68 ++++++++++++ src/resources/ResourceQuotas/index.tsx | 28 +++++ src/resources/index.js | 2 + 10 files changed, 362 insertions(+), 23 deletions(-) create mode 100644 src/resources/ResourceQuotas/ResourceQuotaDetails.scss create mode 100644 src/resources/ResourceQuotas/ResourceQuotaDetails.tsx create mode 100644 src/resources/ResourceQuotas/ResourceQuotaLimits.tsx rename src/resources/ResourceQuotas/{ResourceQuotaList.js => ResourceQuotaList.tsx} (63%) create mode 100644 src/resources/ResourceQuotas/ResourceQuotasList.js create mode 100644 src/resources/ResourceQuotas/index.tsx diff --git a/public/i18n/en.yaml b/public/i18n/en.yaml index b6f5557682..4822323a01 100644 --- a/public/i18n/en.yaml +++ b/public/i18n/en.yaml @@ -1004,6 +1004,7 @@ resource-graph: save-as-dot: DOT format title: Resource Graph resource-quotas: + description: A <0>Resource Quota provides constraints that limit aggregate resource consumption per namespace. headers: limits: memory: Memory Limits @@ -1011,6 +1012,15 @@ resource-quotas: requests: memory: Memory Requests cpu: CPU Requests + scopes: Scopes + scope-selectors: Scope Selectors + limits-usage: Limits and Usage + resource: Resource + hard: Hard + used: Used + operator: Operator + values: Values + title: Resource Quotas name_singular: Resource Quota role-bindings: diff --git a/src/resources/Namespaces/NamespaceDetails.js b/src/resources/Namespaces/NamespaceDetails.js index 97453565c5..c7ed3de8cb 100644 --- a/src/resources/Namespaces/NamespaceDetails.js +++ b/src/resources/Namespaces/NamespaceDetails.js @@ -5,7 +5,7 @@ import { ResourceDetails } from 'shared/components/ResourceDetails/ResourceDetai import { EventsList } from 'shared/components/EventsList'; import { EVENT_MESSAGE_TYPE } from 'hooks/useMessageList'; import { LimitRangesList } from 'resources/LimitRanges/LimitRangesList'; -import { ResourceQuotaList as ResourceQuotaListComponent } from 'resources/ResourceQuotas/ResourceQuotaList'; +import { ResourceQuotasList as ResourceQuotaListComponent } from 'resources/ResourceQuotas/ResourceQuotasList'; import { showYamlUploadDialogState } from 'state/showYamlUploadDialogAtom'; import { NamespaceStatus } from './NamespaceStatus'; @@ -32,19 +32,17 @@ export function NamespaceDetails(props) { namespace: props.resourceName, isCompact: true, showTitle: true, - disableCreate: true, }; const LimitrangesList = ; const resourceQuotasParams = { - hasDetailsView: false, + hasDetailsView: true, resourceUrl: `/api/v1/namespaces/${props.resourceName}/resourcequotas`, resourceType: 'ResourceQuotas', namespace: props.resourceName, isCompact: true, showTitle: true, - disableCreate: true, }; const ResourceQuotasList = ( diff --git a/src/resources/ResourceQuotas/ResourceQuotaCreate.js b/src/resources/ResourceQuotas/ResourceQuotaCreate.js index ad39753a50..3ccb8d2719 100644 --- a/src/resources/ResourceQuotas/ResourceQuotaCreate.js +++ b/src/resources/ResourceQuotas/ResourceQuotaCreate.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useRecoilValue } from 'recoil'; import * as _ from 'lodash'; @@ -8,7 +8,7 @@ import { activeNamespaceIdState } from 'state/activeNamespaceIdAtom'; import { createResourceQuotaTemplate } from './templates'; -export function ResourceQuotaCreate({ +export default function ResourceQuotaCreate({ formElementRef, onChange, setCustomValid, @@ -17,6 +17,7 @@ export function ResourceQuotaCreate({ ...props }) { + const { t } = useTranslation(); const namespaceId = useRecoilValue(activeNamespaceIdState); const [resourceQuota, setResourceQuota] = useState( _.cloneDeep(initialResourceQuota) || @@ -26,7 +27,10 @@ export function ResourceQuotaCreate({ initialResourceQuota || createResourceQuotaTemplate({ namespaceName: namespaceId }), ); - const { t } = useTranslation(); + + const [initialUnchangedResource] = useState( + _.cloneDeep(initialResourceQuota), + ); return ( {}} /> ); } diff --git a/src/resources/ResourceQuotas/ResourceQuotaDetails.scss b/src/resources/ResourceQuotas/ResourceQuotaDetails.scss new file mode 100644 index 0000000000..ad6bd7cebd --- /dev/null +++ b/src/resources/ResourceQuotas/ResourceQuotaDetails.scss @@ -0,0 +1,38 @@ +.resource-quota-spec-row { + display: grid; + grid-template-columns: 1fr 5fr; + grid-auto-flow: row; + gap: 40px; + + > ui5-token { + width: fit-content; + } +} + +.resource-quota-spec-row-label { + text-align: end; +} + +.resource-quota-limits { + &.compact { + margin: 0 0 1rem 0 !important; + padding-right: 0.5rem; + } + text-transform: capitalize; +} + +ui5-table-cell:has(> ui5-panel.resource-quota-limits.compact) { + display: inline !important; +} + +@container (max-width: 650px) { + .resource-quota-spec-row { + display: grid; + grid-template-columns: 1fr; + gap: 5px; + } + + .resource-quota-spec-row-label { + text-align: start; + } +} diff --git a/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx b/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx new file mode 100644 index 0000000000..784ba32fdf --- /dev/null +++ b/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx @@ -0,0 +1,87 @@ +import { ResourceDetails } from 'shared/components/ResourceDetails/ResourceDetails'; +import { ResourceDescription } from '.'; +import ResourceQuotaCreate from './ResourceQuotaCreate'; +import ResourceQuotaLimits, { ResourceQuotaProps } from './ResourceQuotaLimits'; +import { UI5Panel } from 'shared/components/UI5Panel/UI5Panel'; +import { LayoutPanelRow } from 'shared/components/LayoutPanelRow/LayoutPanelRow'; +import { useTranslation } from 'react-i18next'; +import { Tokens } from 'shared/components/Tokens'; +import { GroupHeaderListItem, List, Text } from '@ui5/webcomponents-react'; +import { spacing } from '@ui5/webcomponents-react-base'; +import { ReactNode } from 'react'; + +function ResourceQuotaRow({ + label, + value, +}: { + label: string; + value: ReactNode; +}) { + return ( +
+ + {label + ':'} + + {value} +
+ ); +} + +export default function ResourceQuotaDetails(props: any) { + const { t } = useTranslation(); + + const customComponents = [ + (resource: ResourceQuotaProps) => { + return ( + <> + {(resource.spec.scopes || resource.spec.scopeSelector) && ( + + {resource.spec?.scopes && ( + } + /> + )} + {resource.spec?.scopeSelector && ( + + {resource.spec.scopeSelector?.matchExpressions?.map(scope => ( + <> + + {scope.scopeName} + + {scope.operator}} + /> + {scope.values && ( + } + /> + )} + + ))} + + )} + + )} + + ); + }, + (resource: ResourceQuotaProps) => ( + + ), + ]; + + return ( + + ); +} diff --git a/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx b/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx new file mode 100644 index 0000000000..087116bea6 --- /dev/null +++ b/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx @@ -0,0 +1,102 @@ +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { GenericList } from 'shared/components/GenericList/GenericList'; +import { EMPTY_TEXT_PLACEHOLDER } from 'shared/constants'; +import './ResourceQuotaDetails.scss'; + +export type ResourceQuotaProps = { + kind: string; + apiVersion: string; + metadata: { + name: string; + namespace: string; + }; + spec: { + scopes?: string[]; + hard: { + [key: string]: string; + }; + scopeSelector?: { + matchExpressions: { + scopeName: string; + operator: string; + values?: string[]; + }[]; + }; + }; + status: { + hard: { + [key: string]: string; + }; + used: { + [key: string]: string; + }; + }; +}; + +type ResourceTableEntry = { + resource: string; + used: string; + hard: string; +}; + +export default function ResourceQuotaLimits({ + resource, + isCompact = false, +}: { + resource: ResourceQuotaProps; + isCompact?: boolean; +}) { + const { t } = useTranslation(); + + const headerRenderer = () => [ + t('resource-quotas.headers.resource'), + t('resource-quotas.headers.hard'), + t('resource-quotas.headers.used'), + ]; + + const parsedResourceQuota = useMemo(() => { + const result: ResourceTableEntry[] = []; + + const hardResources = resource.spec.hard; + const usedResources = resource.status.used; + + for (const resource in hardResources) { + if (hardResources.hasOwnProperty(resource)) { + const formattedResource = resource + .split('.') + .map(part => part.charAt(0).toUpperCase() + part.slice(1)) + .join(' '); + + result.push({ + resource: formattedResource, + hard: hardResources[resource], + used: usedResources[resource] || '0', + }); + } + } + + return result; + }, []); + + const rowRenderer = ({ resource, used, hard }: ResourceTableEntry) => { + return [ + resource || EMPTY_TEXT_PLACEHOLDER, + hard || EMPTY_TEXT_PLACEHOLDER, + used || EMPTY_TEXT_PLACEHOLDER, + ]; + }; + + return ( + + ); +} diff --git a/src/resources/ResourceQuotas/ResourceQuotaList.js b/src/resources/ResourceQuotas/ResourceQuotaList.tsx similarity index 63% rename from src/resources/ResourceQuotas/ResourceQuotaList.js rename to src/resources/ResourceQuotas/ResourceQuotaList.tsx index 0b598cb653..4eb9e5c4e2 100644 --- a/src/resources/ResourceQuotas/ResourceQuotaList.js +++ b/src/resources/ResourceQuotas/ResourceQuotaList.tsx @@ -1,49 +1,51 @@ -import React from 'react'; +import { useTranslation } from 'react-i18next'; import { ResourcesList } from 'shared/components/ResourcesList/ResourcesList'; +import { ResourceDescription, docsURL, i18nDescriptionKey } from '.'; import { EMPTY_TEXT_PLACEHOLDER } from 'shared/constants'; -import { useTranslation } from 'react-i18next'; -import { ResourceQuotaCreate } from './ResourceQuotaCreate'; +import ResourceQuotaCreate from './ResourceQuotaCreate'; +import { ResourceQuotaProps } from './ResourceQuotaLimits'; -export function ResourceQuotaList(props) { +export default function ResourceQuotaList(props: any) { const { t } = useTranslation(); const customColumns = [ { header: t('resource-quotas.headers.limits.cpu'), - value: quota => + value: (quota: ResourceQuotaProps) => quota.spec?.hard?.['limits.cpu'] || EMPTY_TEXT_PLACEHOLDER, }, { header: t('resource-quotas.headers.limits.memory'), - value: quota => + value: (quota: ResourceQuotaProps) => quota.spec?.hard?.['limits.memory'] || EMPTY_TEXT_PLACEHOLDER, }, { header: t('resource-quotas.headers.requests.cpu'), - value: quota => + value: (quota: ResourceQuotaProps) => quota.spec?.hard?.['requests.cpu'] || quota.spec?.hard?.cpu || EMPTY_TEXT_PLACEHOLDER, }, { header: t('resource-quotas.headers.requests.memory'), - value: quota => + value: (quota: ResourceQuotaProps) => quota.spec?.hard?.['requests.memory'] || quota.spec?.hard?.memory || EMPTY_TEXT_PLACEHOLDER, }, ]; + return ( ); } - -export default ResourceQuotaList; diff --git a/src/resources/ResourceQuotas/ResourceQuotasList.js b/src/resources/ResourceQuotas/ResourceQuotasList.js new file mode 100644 index 0000000000..1d5da9b6d1 --- /dev/null +++ b/src/resources/ResourceQuotas/ResourceQuotasList.js @@ -0,0 +1,68 @@ +import { ResourcesList } from 'shared/components/ResourcesList/ResourcesList'; +import { useTranslation } from 'react-i18next'; +import ResourceQuotaCreate from './ResourceQuotaCreate'; +import { Button } from '@ui5/webcomponents-react'; +import { useNavigate } from 'react-router-dom'; +import { useRecoilState, useSetRecoilState } from 'recoil'; +import { columnLayoutState } from 'state/columnLayoutAtom'; +import { isFormOpenState } from 'state/formOpenAtom'; +import { useUrl } from 'hooks/useUrl'; +import pluralize from 'pluralize'; +import ResourceQuotaLimits from './ResourceQuotaLimits'; + +export function ResourceQuotasList(props) { + const { t } = useTranslation(); + const navigate = useNavigate(); + const [, setLayoutColumn] = useRecoilState(columnLayoutState); + const setIsFormOpen = useSetRecoilState(isFormOpenState); + const { namespaceUrl } = useUrl(); + + const customColumns = [ + { + header: 'Popin', + value: quota => , + }, + ]; + + const handleShowCreate = () => { + setLayoutColumn({ + midColumn: null, + endColumn: null, + showCreate: { + resourceType: props.resourceType, + namespaceId: props.namespace, + }, + layout: 'TwoColumnsMidExpanded', + }); + setIsFormOpen({ formOpen: true }); + navigate( + namespaceUrl(`${pluralize(props.resourceType.toLowerCase() || '')}`), + ); + }; + + const createButton = ( + + ); + + return ( + + ); +} + +export default ResourceQuotasList; diff --git a/src/resources/ResourceQuotas/index.tsx b/src/resources/ResourceQuotas/index.tsx new file mode 100644 index 0000000000..790d1084f8 --- /dev/null +++ b/src/resources/ResourceQuotas/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { ResourceRelationConfig } from 'shared/components/ResourceGraph/types'; +import { predefinedCategories } from 'state/navigation/categories'; +import { Description } from 'shared/components/Description/Description'; + +export const resourceType = 'ResourceQuotas'; +export const namespaced = true; +export const apiGroup = ''; +export const apiVersion = 'v1'; +export const category = predefinedCategories['discovery-and-network']; + +export const List = React.lazy(() => import('./ResourceQuotaList')); +export const Details = React.lazy(() => import('./ResourceQuotaDetails')); +export const Create = React.lazy(() => import('./ResourceQuotaCreate')); + +export const i18nDescriptionKey = 'resource-quotas.description'; +export const docsURL = + 'https://kubernetes.io/docs/concepts/policy/resource-quotas/'; + +export const ResourceDescription = ( + +); + +export const resourceGraphConfig = (): ResourceRelationConfig => ({ + networkFlowKind: true, + networkFlowLevel: -1, + depth: 1, +}); diff --git a/src/resources/index.js b/src/resources/index.js index 8c0a7544c5..cdd57fef95 100644 --- a/src/resources/index.js +++ b/src/resources/index.js @@ -27,6 +27,7 @@ import * as Namespaces from './Namespaces'; import * as ClusterEvents from './ClusterEvents'; import * as CustomResourceDefinitions from './CustomResourceDefinitions'; import * as ClusterRoleBindings from './ClusterRoleBindings'; +import * as ResourceQuotas from './ResourceQuotas'; export const resources = [ // namespace resources @@ -43,6 +44,7 @@ export const resources = [ Ingresses, NetworkPolicies, LimitRanges, + ResourceQuotas, // storage PersistentVolumeClaims, // configuration From 45ec0ef372e351773ba83248882a1721a6df7d84 Mon Sep 17 00:00:00 2001 From: Oliwia Gowor Date: Mon, 9 Sep 2024 14:53:53 +0200 Subject: [PATCH 2/8] add tests --- tests/integration/cypress.config.js | 1 + .../fixtures/test-resource-quotas.yaml | 16 +++++ .../namespace/test-resource-quotas.spec.js | 60 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 tests/integration/fixtures/test-resource-quotas.yaml create mode 100644 tests/integration/tests/namespace/test-resource-quotas.spec.js diff --git a/tests/integration/cypress.config.js b/tests/integration/cypress.config.js index 9862c12209..fab3050b9f 100644 --- a/tests/integration/cypress.config.js +++ b/tests/integration/cypress.config.js @@ -75,6 +75,7 @@ module.exports = defineConfig({ 'tests/namespace/test-persistent-volume-claims.spec.js', 'tests/namespace/test-custom-resources.spec.js', 'tests/namespace/test-limit-ranges.spec.js', + 'tests/namespace/test-resource-quotas.spec.js', 'tests/namespace/z-run-after.spec.js', ], supportFile: 'support/index.js', diff --git a/tests/integration/fixtures/test-resource-quotas.yaml b/tests/integration/fixtures/test-resource-quotas.yaml new file mode 100644 index 0000000000..bf5bbd949d --- /dev/null +++ b/tests/integration/fixtures/test-resource-quotas.yaml @@ -0,0 +1,16 @@ +apiVersion: 'v1' +kind: 'ResourceQuota' +metadata: + name: '' + namespace: default +spec: + hard: + limits.memory: 3Gi + limits.cpu: '4' + requests.memory: 2.8Gi + requests.cpu: '2' + scopeSelector: + matchExpressions: + - scopeName: PriorityClass + operator: In + values: ['high-priority'] diff --git a/tests/integration/tests/namespace/test-resource-quotas.spec.js b/tests/integration/tests/namespace/test-resource-quotas.spec.js new file mode 100644 index 0000000000..bd523b3e86 --- /dev/null +++ b/tests/integration/tests/namespace/test-resource-quotas.spec.js @@ -0,0 +1,60 @@ +import { loadFile } from '../../support/loadFile'; + +const QUOTA_NAME = `test-rq-${Math.floor(Math.random() * 9999) + 1000}`; +const FILE_NAME = 'test-resource-quotas.yaml'; + +async function loadRQ(name, namespace, fileName) { + const resource = await loadFile(fileName); + const newResource = { ...resource }; + + newResource.metadata.name = name; + newResource.metadata.namespace = namespace; + + return newResource; +} + +context('Test Resource Quotas', () => { + Cypress.skipAfterFail(); + + before(() => { + cy.loginAndSelectCluster(); + cy.goToNamespaceDetails(); + }); + + it('Creates a Resource Quota', () => { + cy.navigateTo('Discovery and Network', 'Resource Quotas'); + + cy.openCreate(); + + cy.wrap(loadRQ(QUOTA_NAME, Cypress.env('NAMESPACE_NAME'), FILE_NAME)).then( + RQ_CONFIG => { + const RQ = JSON.stringify(RQ_CONFIG); + cy.pasteToMonaco(RQ); + }, + ); + + cy.saveChanges('Create'); + }); + + it('Checks the details view', () => { + cy.contains(QUOTA_NAME); + + cy.contains('Limits and Usage'); + cy.contains('Scope Selectors'); + cy.contains('PriorityClass'); + }); + + it('Checks the list view', () => { + cy.getLeftNav() + .contains('Resource Quotas') + .click(); + + cy.clickGenericListLink(QUOTA_NAME); + + cy.getMidColumn().contains(QUOTA_NAME); + + cy.closeMidColumn(); + + cy.deleteFromGenericList('Resource Quota', QUOTA_NAME); + }); +}); From f163a65d9e30fa1d261e9d26c232ab697a79a665 Mon Sep 17 00:00:00 2001 From: Oliwia Gowor Date: Mon, 9 Sep 2024 15:22:15 +0200 Subject: [PATCH 3/8] change list to ts, fix deps --- src/resources/ResourceQuotas/ResourceQuotaLimits.tsx | 9 ++------- .../{ResourceQuotasList.js => ResourceQuotasList.tsx} | 10 ++++++---- 2 files changed, 8 insertions(+), 11 deletions(-) rename src/resources/ResourceQuotas/{ResourceQuotasList.js => ResourceQuotasList.tsx} (85%) diff --git a/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx b/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx index 087116bea6..48a1c1a4e5 100644 --- a/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx +++ b/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx @@ -63,13 +63,8 @@ export default function ResourceQuotaLimits({ for (const resource in hardResources) { if (hardResources.hasOwnProperty(resource)) { - const formattedResource = resource - .split('.') - .map(part => part.charAt(0).toUpperCase() + part.slice(1)) - .join(' '); - result.push({ - resource: formattedResource, + resource, hard: hardResources[resource], used: usedResources[resource] || '0', }); @@ -77,7 +72,7 @@ export default function ResourceQuotaLimits({ } return result; - }, []); + }, [resource.spec.hard, resource.status.used]); const rowRenderer = ({ resource, used, hard }: ResourceTableEntry) => { return [ diff --git a/src/resources/ResourceQuotas/ResourceQuotasList.js b/src/resources/ResourceQuotas/ResourceQuotasList.tsx similarity index 85% rename from src/resources/ResourceQuotas/ResourceQuotasList.js rename to src/resources/ResourceQuotas/ResourceQuotasList.tsx index 1d5da9b6d1..899ef7ab7c 100644 --- a/src/resources/ResourceQuotas/ResourceQuotasList.js +++ b/src/resources/ResourceQuotas/ResourceQuotasList.tsx @@ -8,9 +8,9 @@ import { columnLayoutState } from 'state/columnLayoutAtom'; import { isFormOpenState } from 'state/formOpenAtom'; import { useUrl } from 'hooks/useUrl'; import pluralize from 'pluralize'; -import ResourceQuotaLimits from './ResourceQuotaLimits'; +import ResourceQuotaLimits, { ResourceQuotaProps } from './ResourceQuotaLimits'; -export function ResourceQuotasList(props) { +export function ResourceQuotasList(props: any) { const { t } = useTranslation(); const navigate = useNavigate(); const [, setLayoutColumn] = useRecoilState(columnLayoutState); @@ -20,7 +20,9 @@ export function ResourceQuotasList(props) { const customColumns = [ { header: 'Popin', - value: quota => , + value: (quota: ResourceQuotaProps) => ( + + ), }, ]; @@ -34,7 +36,7 @@ export function ResourceQuotasList(props) { }, layout: 'TwoColumnsMidExpanded', }); - setIsFormOpen({ formOpen: true }); + setIsFormOpen({ formOpen: true, leavingForm: false }); navigate( namespaceUrl(`${pluralize(props.resourceType.toLowerCase() || '')}`), ); From c982605b5d95b56c232daaf6763adf575798604b Mon Sep 17 00:00:00 2001 From: Oliwia Gowor Date: Tue, 10 Sep 2024 08:00:22 +0200 Subject: [PATCH 4/8] add readonly --- src/resources/ResourceQuotas/ResourceQuotasList.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/resources/ResourceQuotas/ResourceQuotasList.tsx b/src/resources/ResourceQuotas/ResourceQuotasList.tsx index 899ef7ab7c..7de7636e27 100644 --- a/src/resources/ResourceQuotas/ResourceQuotasList.tsx +++ b/src/resources/ResourceQuotas/ResourceQuotasList.tsx @@ -63,6 +63,7 @@ export function ResourceQuotasList(props: any) { {...props} createResourceForm={ResourceQuotaCreate} listHeaderActions={createButton} + readOnly /> ); } From 032b593f855d804efbb3be53314fdaf21a1d744d Mon Sep 17 00:00:00 2001 From: Oliwia Gowor Date: Tue, 10 Sep 2024 08:33:31 +0200 Subject: [PATCH 5/8] adjust test --- .../tests/namespace/test-namespace-overview.spec.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/integration/tests/namespace/test-namespace-overview.spec.js b/tests/integration/tests/namespace/test-namespace-overview.spec.js index c31429e69d..2d2f1b4efe 100644 --- a/tests/integration/tests/namespace/test-namespace-overview.spec.js +++ b/tests/integration/tests/namespace/test-namespace-overview.spec.js @@ -1,10 +1,8 @@ /// import 'cypress-file-upload'; -import { loadFile } from '../../support/loadFile'; const LIMIT_NAME = `${Cypress.env('NAMESPACE_NAME')}-limits`; const QUOTA_NAME = `${Cypress.env('NAMESPACE_NAME')}-quotas`; -const NEW_LIMIT_NAME = `new-limit`; context( 'Check the namespace overview for limit ranges and resourcequotas', @@ -23,7 +21,7 @@ context( cy.contains('Container').should('be.visible'); - cy.contains('b', QUOTA_NAME) + cy.contains('span', QUOTA_NAME) .scrollIntoView() .should('be.visible'); From b64831db0b7c1f98ca2d01a49e5e614ab9f7ffba Mon Sep 17 00:00:00 2001 From: Oliwia Gowor Date: Tue, 10 Sep 2024 09:35:11 +0200 Subject: [PATCH 6/8] adjust small list and spec --- .../LimitRanges/LimitRangeSpecification.tsx | 1 - .../ResourceQuotas/ResourceQuotaDetails.scss | 5 +++ .../ResourceQuotas/ResourceQuotaDetails.tsx | 45 ++++++------------- .../ResourceQuotas/ResourceQuotasList.tsx | 27 +++++++++-- .../UI5Panel/{UI5Panel.js => UI5Panel.tsx} | 28 +++++++++--- 5 files changed, 65 insertions(+), 41 deletions(-) rename src/shared/components/UI5Panel/{UI5Panel.js => UI5Panel.tsx} (78%) diff --git a/src/resources/LimitRanges/LimitRangeSpecification.tsx b/src/resources/LimitRanges/LimitRangeSpecification.tsx index 44c0bd90de..dddde8b96b 100644 --- a/src/resources/LimitRanges/LimitRangeSpecification.tsx +++ b/src/resources/LimitRanges/LimitRangeSpecification.tsx @@ -161,7 +161,6 @@ export default function LimitRangeSpecification({ ) : ( {transLimits.map((limit: { type: string; props: FlatLimitProps[] }) => { diff --git a/src/resources/ResourceQuotas/ResourceQuotaDetails.scss b/src/resources/ResourceQuotas/ResourceQuotaDetails.scss index ad6bd7cebd..4a8cbf2456 100644 --- a/src/resources/ResourceQuotas/ResourceQuotaDetails.scss +++ b/src/resources/ResourceQuotas/ResourceQuotaDetails.scss @@ -21,6 +21,11 @@ text-transform: capitalize; } +.resource-quota-spec-subheader { + border-block-end: 0.0625rem solid var(--sapGroup_TitleBorderColor); + padding-bottom: 0.5rem; +} + ui5-table-cell:has(> ui5-panel.resource-quota-limits.compact) { display: inline !important; } diff --git a/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx b/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx index 784ba32fdf..d6d6dd7119 100644 --- a/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx +++ b/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx @@ -6,26 +6,8 @@ import { UI5Panel } from 'shared/components/UI5Panel/UI5Panel'; import { LayoutPanelRow } from 'shared/components/LayoutPanelRow/LayoutPanelRow'; import { useTranslation } from 'react-i18next'; import { Tokens } from 'shared/components/Tokens'; -import { GroupHeaderListItem, List, Text } from '@ui5/webcomponents-react'; +import { Text, Title } from '@ui5/webcomponents-react'; import { spacing } from '@ui5/webcomponents-react-base'; -import { ReactNode } from 'react'; - -function ResourceQuotaRow({ - label, - value, -}: { - label: string; - value: ReactNode; -}) { - return ( -
- - {label + ':'} - - {value} -
- ); -} export default function ResourceQuotaDetails(props: any) { const { t } = useTranslation(); @@ -35,10 +17,7 @@ export default function ResourceQuotaDetails(props: any) { return ( <> {(resource.spec.scopes || resource.spec.scopeSelector) && ( - + {resource.spec?.scopes && ( )} {resource.spec?.scopeSelector && ( - + {resource.spec.scopeSelector?.matchExpressions?.map(scope => ( <> - + {scope.scopeName} - </GroupHeaderListItem> - <ResourceQuotaRow - label={t('resource-quotas.headers.operator')} + + {scope.operator}} /> {scope.values && ( - } /> )} ))} - + )} )} diff --git a/src/resources/ResourceQuotas/ResourceQuotasList.tsx b/src/resources/ResourceQuotas/ResourceQuotasList.tsx index 7de7636e27..2b9d92fe1c 100644 --- a/src/resources/ResourceQuotas/ResourceQuotasList.tsx +++ b/src/resources/ResourceQuotas/ResourceQuotasList.tsx @@ -9,6 +9,7 @@ import { isFormOpenState } from 'state/formOpenAtom'; import { useUrl } from 'hooks/useUrl'; import pluralize from 'pluralize'; import ResourceQuotaLimits, { ResourceQuotaProps } from './ResourceQuotaLimits'; +import { EMPTY_TEXT_PLACEHOLDER } from 'shared/constants'; export function ResourceQuotasList(props: any) { const { t } = useTranslation(); @@ -19,10 +20,28 @@ export function ResourceQuotasList(props: any) { const customColumns = [ { - header: 'Popin', - value: (quota: ResourceQuotaProps) => ( - - ), + header: t('resource-quotas.headers.limits.cpu'), + value: (quota: ResourceQuotaProps) => + quota.spec?.hard?.['limits.cpu'] || EMPTY_TEXT_PLACEHOLDER, + }, + { + header: t('resource-quotas.headers.limits.memory'), + value: (quota: ResourceQuotaProps) => + quota.spec?.hard?.['limits.memory'] || EMPTY_TEXT_PLACEHOLDER, + }, + { + header: t('resource-quotas.headers.requests.cpu'), + value: (quota: ResourceQuotaProps) => + quota.spec?.hard?.['requests.cpu'] || + quota.spec?.hard?.cpu || + EMPTY_TEXT_PLACEHOLDER, + }, + { + header: t('resource-quotas.headers.requests.memory'), + value: (quota: ResourceQuotaProps) => + quota.spec?.hard?.['requests.memory'] || + quota.spec?.hard?.memory || + EMPTY_TEXT_PLACEHOLDER, }, ]; diff --git a/src/shared/components/UI5Panel/UI5Panel.js b/src/shared/components/UI5Panel/UI5Panel.tsx similarity index 78% rename from src/shared/components/UI5Panel/UI5Panel.js rename to src/shared/components/UI5Panel/UI5Panel.tsx index 23b6d06f47..169964a504 100644 --- a/src/shared/components/UI5Panel/UI5Panel.js +++ b/src/shared/components/UI5Panel/UI5Panel.tsx @@ -9,7 +9,23 @@ import { import { spacing } from '@ui5/webcomponents-react-base'; import './UI5Panel.scss'; -import { useEffect } from 'react'; +import { CSSProperties, ReactNode, useEffect } from 'react'; + +type UI5PanelProps = { + fixed?: boolean; + icon?: string; + title: string | ReactNode; + headerActions?: ReactNode; + modeActions?: ReactNode; + keyComponent?: string; + disableMargin?: boolean; + className?: string; + children: ReactNode; + description?: string; + style?: CSSProperties; + stickyHeader?: boolean; + headerTop?: string; +}; export const UI5Panel = ({ fixed = true, @@ -22,10 +38,10 @@ export const UI5Panel = ({ className = '', children, description = '', - style = null, + style = undefined, stickyHeader = false, headerTop = '0', -}) => { +}: UI5PanelProps) => { useEffect(() => { if (headerTop !== '0') setTimeout(() => { @@ -34,7 +50,7 @@ export const UI5Panel = ({ ?.shadowRoot?.querySelector('.ui5-panel-root') ?.querySelector( '.ui5-panel-heading-wrapper.ui5-panel-heading-wrapper-sticky', - ); + ) as HTMLElement; if (stickyHeader) { stickyHeader.style['top'] = headerTop; @@ -46,7 +62,9 @@ export const UI5Panel = ({ fixed={fixed} key={keyComponent} className={`${className} bsl-panel-header card-shadow`} - style={style ? style : !disableMargin ? spacing.sapUiSmallMargin : null} + style={ + style ? style : !disableMargin ? spacing.sapUiSmallMargin : undefined + } stickyHeader={stickyHeader} header={ Date: Tue, 10 Sep 2024 09:45:04 +0200 Subject: [PATCH 7/8] cleanup --- .../ResourceQuotas/ResourceQuotaDetails.scss | 27 ---------------- .../ResourceQuotas/ResourceQuotaDetails.tsx | 32 ++++++++++++++++++- .../ResourceQuotas/ResourceQuotaLimits.tsx | 31 +----------------- .../ResourceQuotas/ResourceQuotaList.tsx | 2 +- .../ResourceQuotas/ResourceQuotasList.tsx | 2 +- 5 files changed, 34 insertions(+), 60 deletions(-) diff --git a/src/resources/ResourceQuotas/ResourceQuotaDetails.scss b/src/resources/ResourceQuotas/ResourceQuotaDetails.scss index 4a8cbf2456..0c3f64b66b 100644 --- a/src/resources/ResourceQuotas/ResourceQuotaDetails.scss +++ b/src/resources/ResourceQuotas/ResourceQuotaDetails.scss @@ -1,18 +1,3 @@ -.resource-quota-spec-row { - display: grid; - grid-template-columns: 1fr 5fr; - grid-auto-flow: row; - gap: 40px; - - > ui5-token { - width: fit-content; - } -} - -.resource-quota-spec-row-label { - text-align: end; -} - .resource-quota-limits { &.compact { margin: 0 0 1rem 0 !important; @@ -29,15 +14,3 @@ ui5-table-cell:has(> ui5-panel.resource-quota-limits.compact) { display: inline !important; } - -@container (max-width: 650px) { - .resource-quota-spec-row { - display: grid; - grid-template-columns: 1fr; - gap: 5px; - } - - .resource-quota-spec-row-label { - text-align: start; - } -} diff --git a/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx b/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx index d6d6dd7119..2806beb1fd 100644 --- a/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx +++ b/src/resources/ResourceQuotas/ResourceQuotaDetails.tsx @@ -1,7 +1,7 @@ import { ResourceDetails } from 'shared/components/ResourceDetails/ResourceDetails'; import { ResourceDescription } from '.'; import ResourceQuotaCreate from './ResourceQuotaCreate'; -import ResourceQuotaLimits, { ResourceQuotaProps } from './ResourceQuotaLimits'; +import ResourceQuotaLimits from './ResourceQuotaLimits'; import { UI5Panel } from 'shared/components/UI5Panel/UI5Panel'; import { LayoutPanelRow } from 'shared/components/LayoutPanelRow/LayoutPanelRow'; import { useTranslation } from 'react-i18next'; @@ -9,6 +9,36 @@ import { Tokens } from 'shared/components/Tokens'; import { Text, Title } from '@ui5/webcomponents-react'; import { spacing } from '@ui5/webcomponents-react-base'; +export type ResourceQuotaProps = { + kind: string; + apiVersion: string; + metadata: { + name: string; + namespace: string; + }; + spec: { + scopes?: string[]; + hard: { + [key: string]: string; + }; + scopeSelector?: { + matchExpressions: { + scopeName: string; + operator: string; + values?: string[]; + }[]; + }; + }; + status: { + hard: { + [key: string]: string; + }; + used: { + [key: string]: string; + }; + }; +}; + export default function ResourceQuotaDetails(props: any) { const { t } = useTranslation(); diff --git a/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx b/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx index 48a1c1a4e5..876cf55980 100644 --- a/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx +++ b/src/resources/ResourceQuotas/ResourceQuotaLimits.tsx @@ -3,36 +3,7 @@ import { useTranslation } from 'react-i18next'; import { GenericList } from 'shared/components/GenericList/GenericList'; import { EMPTY_TEXT_PLACEHOLDER } from 'shared/constants'; import './ResourceQuotaDetails.scss'; - -export type ResourceQuotaProps = { - kind: string; - apiVersion: string; - metadata: { - name: string; - namespace: string; - }; - spec: { - scopes?: string[]; - hard: { - [key: string]: string; - }; - scopeSelector?: { - matchExpressions: { - scopeName: string; - operator: string; - values?: string[]; - }[]; - }; - }; - status: { - hard: { - [key: string]: string; - }; - used: { - [key: string]: string; - }; - }; -}; +import { ResourceQuotaProps } from './ResourceQuotaDetails'; type ResourceTableEntry = { resource: string; diff --git a/src/resources/ResourceQuotas/ResourceQuotaList.tsx b/src/resources/ResourceQuotas/ResourceQuotaList.tsx index 4eb9e5c4e2..ba4c01ba82 100644 --- a/src/resources/ResourceQuotas/ResourceQuotaList.tsx +++ b/src/resources/ResourceQuotas/ResourceQuotaList.tsx @@ -3,7 +3,7 @@ import { ResourcesList } from 'shared/components/ResourcesList/ResourcesList'; import { ResourceDescription, docsURL, i18nDescriptionKey } from '.'; import { EMPTY_TEXT_PLACEHOLDER } from 'shared/constants'; import ResourceQuotaCreate from './ResourceQuotaCreate'; -import { ResourceQuotaProps } from './ResourceQuotaLimits'; +import { ResourceQuotaProps } from './ResourceQuotaDetails'; export default function ResourceQuotaList(props: any) { const { t } = useTranslation(); diff --git a/src/resources/ResourceQuotas/ResourceQuotasList.tsx b/src/resources/ResourceQuotas/ResourceQuotasList.tsx index 2b9d92fe1c..de251aaa72 100644 --- a/src/resources/ResourceQuotas/ResourceQuotasList.tsx +++ b/src/resources/ResourceQuotas/ResourceQuotasList.tsx @@ -8,7 +8,7 @@ import { columnLayoutState } from 'state/columnLayoutAtom'; import { isFormOpenState } from 'state/formOpenAtom'; import { useUrl } from 'hooks/useUrl'; import pluralize from 'pluralize'; -import ResourceQuotaLimits, { ResourceQuotaProps } from './ResourceQuotaLimits'; +import { ResourceQuotaProps } from './ResourceQuotaDetails'; import { EMPTY_TEXT_PLACEHOLDER } from 'shared/constants'; export function ResourceQuotasList(props: any) { From 559d3dd2383afe53919d0dc3d5c0f5e4924af3d0 Mon Sep 17 00:00:00 2001 From: Oliwia Gowor Date: Wed, 11 Sep 2024 11:34:46 +0200 Subject: [PATCH 8/8] review adjustments --- public/i18n/en.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/i18n/en.yaml b/public/i18n/en.yaml index 4822323a01..28fe6c9a97 100644 --- a/public/i18n/en.yaml +++ b/public/i18n/en.yaml @@ -192,11 +192,13 @@ command-palette: gateways: Gateways horizontalpodautoscalers: Horizontal Pod Autoscalers kyma: Modules + limitranges: Limit Ranges networkpolicies: Network Policies oauth2clients: OAuth2 Clients persistentvolumeclaims: Persistent Volume Claims persistentvolumes: Persistent Volumes replicasets: Replica Sets + resourcequotas: Resource Quotas rolebindings: Role Bindings serviceaccounts: Service Accounts serviceentries: Service Entries @@ -1020,7 +1022,6 @@ resource-quotas: used: Used operator: Operator values: Values - title: Resource Quotas name_singular: Resource Quota role-bindings: