From 8f4bd580211f1322f479097c5231201187bc6036 Mon Sep 17 00:00:00 2001 From: Krish Agarwal Date: Wed, 11 Feb 2026 12:32:49 -0500 Subject: [PATCH 1/3] CONSOLE-5028: Refactor dev-console and knative-plugin Packages to useK8sWatchResource(s) --- .../health-checks/HealthChecksPage.tsx | 31 +++--- .../src/components/import/DeployImage.tsx | 8 +- .../src/components/import/DeployImagePage.tsx | 27 +++-- .../src/components/import/ImportForm.tsx | 18 +++- .../src/components/import/ImportPage.tsx | 89 +++++++++-------- .../components/import/ImportSamplePage.tsx | 4 +- .../import/__tests__/DeployImage.spec.tsx | 12 +-- .../src/components/import/import-types.ts | 27 +++-- .../overview/MonitoringOverview.tsx | 8 +- .../monitoring/overview/MonitoringTab.tsx | 60 +++++++---- .../overview/__tests__/MonitoringTab.spec.tsx | 6 +- .../project-access/ProjectAccessPage.tsx | 37 ++++--- .../__tests__/ProjectAccessPage.spec.tsx | 4 +- .../components/knatify/CreateKnatifyPage.tsx | 2 +- .../DeleteRevisionModalController.tsx | 99 ++++++++++--------- .../test-function/TestFunctionController.tsx | 34 ++----- .../TrafficSplittingController.tsx | 57 ++++++----- 17 files changed, 299 insertions(+), 224 deletions(-) diff --git a/frontend/packages/dev-console/src/components/health-checks/HealthChecksPage.tsx b/frontend/packages/dev-console/src/components/health-checks/HealthChecksPage.tsx index 84410d8a944..60e00bf96f0 100644 --- a/frontend/packages/dev-console/src/components/health-checks/HealthChecksPage.tsx +++ b/frontend/packages/dev-console/src/components/health-checks/HealthChecksPage.tsx @@ -1,25 +1,26 @@ import type { FC } from 'react'; import { useParams } from 'react-router-dom-v5-compat'; -import { FirehoseResource, Firehose } from '@console/internal/components/utils'; +import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; +import { K8sResourceKind } from '@console/internal/module/k8s'; import AddHealthChecksForm from './AddHealthChecksForm'; const HealthChecksPage: FC = () => { const { ns, kind, name, containerName } = useParams(); - const resource: FirehoseResource[] = [ - { - kind, - namespace: ns, - isList: false, - name, - prop: 'resource', - }, - ]; - return ( - - - - ); + const [resourceData, loaded, loadError] = useK8sWatchResource({ + kind, + namespace: ns, + isList: false, + name, + }); + + const resource = { + data: resourceData, + loaded, + loadError, + }; + + return ; }; export default HealthChecksPage; diff --git a/frontend/packages/dev-console/src/components/import/DeployImage.tsx b/frontend/packages/dev-console/src/components/import/DeployImage.tsx index dbf91d95e50..79105c04119 100644 --- a/frontend/packages/dev-console/src/components/import/DeployImage.tsx +++ b/frontend/packages/dev-console/src/components/import/DeployImage.tsx @@ -15,12 +15,16 @@ import { createOrUpdateDeployImageResources } from './deployImage-submit-utils'; import { deployValidationSchema } from './deployImage-validation-utils'; import DeployImageForm from './DeployImageForm'; import { filterDeployedResources } from './import-submit-utils'; -import { DeployImageFormData, FirehoseList, Resources } from './import-types'; +import { DeployImageFormData, Resources } from './import-types'; import { useUpdateKnScalingDefaultValues } from './serverless/useUpdateKnScalingDefaultValues'; export interface DeployImageProps { namespace: string; - projects?: FirehoseList; + projects?: { + data: K8sResourceKind[]; + loaded: boolean; + loadError?: any; + }; contextualSource?: string; } diff --git a/frontend/packages/dev-console/src/components/import/DeployImagePage.tsx b/frontend/packages/dev-console/src/components/import/DeployImagePage.tsx index f539f7dde55..bcd97e9723e 100644 --- a/frontend/packages/dev-console/src/components/import/DeployImagePage.tsx +++ b/frontend/packages/dev-console/src/components/import/DeployImagePage.tsx @@ -1,7 +1,8 @@ import type { FunctionComponent } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams, useLocation } from 'react-router-dom-v5-compat'; -import { Firehose } from '@console/internal/components/utils'; +import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; +import { K8sResourceKind } from '@console/internal/module/k8s'; import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle'; import { PageHeading } from '@console/shared/src/components/heading/PageHeading'; import { QUERY_PROPERTIES } from '../../const'; @@ -15,19 +16,29 @@ const DeployImagePage: FunctionComponent = () => { const location = useLocation(); const params = new URLSearchParams(location.search); + const [projectsData, loaded, loadError] = useK8sWatchResource({ + kind: 'Project', + isList: true, + }); + + const projects = { + data: projectsData, + loaded, + loadError, + }; + return ( {t('devconsole~Deploy Image')} {(desiredApplication) => ( - - - + )} diff --git a/frontend/packages/dev-console/src/components/import/ImportForm.tsx b/frontend/packages/dev-console/src/components/import/ImportForm.tsx index bede633f593..d2aad11a3a0 100644 --- a/frontend/packages/dev-console/src/components/import/ImportForm.tsx +++ b/frontend/packages/dev-console/src/components/import/ImportForm.tsx @@ -8,7 +8,7 @@ import { useActivePerspective } from '@console/dynamic-plugin-sdk'; import { GitProvider, ImportStrategy } from '@console/git-service/src'; import { history, AsyncComponent, StatusBox } from '@console/internal/components/utils'; import { RouteModel } from '@console/internal/models'; -import { RouteKind } from '@console/internal/module/k8s'; +import { RouteKind, K8sResourceKind } from '@console/internal/module/k8s'; import { getActiveApplication } from '@console/internal/reducers/ui'; import { RootState } from '@console/internal/redux'; import { ALL_APPLICATIONS_KEY, usePerspectives, useTelemetry } from '@console/shared'; @@ -38,7 +38,6 @@ import { } from './import-submit-utils'; import { GitImportFormData, - FirehoseList, ImportData, Resources, BaseFormData, @@ -55,10 +54,15 @@ export interface ImportFormProps { namespace: string; importData: ImportData; contextualSource?: string; - imageStreams?: FirehoseList; + imageStreams?: { + data: K8sResourceKind | K8sResourceKind[]; + loaded: boolean; + loadError?: any; + }; projects?: { + data: K8sResourceKind[]; loaded: boolean; - data: []; + loadError?: any; }; } @@ -153,7 +157,11 @@ const ImportForm: FC = ({ const initialVals = useUpdateKnScalingDefaultValues(initialValues); const builderImages: NormalizedBuilderImages = - imageStreams && imageStreams.loaded && normalizeBuilderImages(imageStreams.data); + imageStreams && + imageStreams.loaded && + normalizeBuilderImages( + Array.isArray(imageStreams.data) ? imageStreams.data : [imageStreams.data], + ); const handleSubmit = (values: GitImportFormData, actions) => { const imageStream = builderImages && builderImages[values.image.selected]?.obj; diff --git a/frontend/packages/dev-console/src/components/import/ImportPage.tsx b/frontend/packages/dev-console/src/components/import/ImportPage.tsx index e988c3d2e76..a27f8057e1e 100644 --- a/frontend/packages/dev-console/src/components/import/ImportPage.tsx +++ b/frontend/packages/dev-console/src/components/import/ImportPage.tsx @@ -1,9 +1,11 @@ import type { FunctionComponent } from 'react'; +import { useMemo } from 'react'; import { TFunction } from 'i18next'; import { useTranslation } from 'react-i18next'; import { useParams, useLocation } from 'react-router-dom-v5-compat'; -import { Firehose, FirehoseResource } from '@console/internal/components/utils'; +import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook'; import { ImageStreamModel, ProjectModel } from '@console/internal/models'; +import { K8sResourceKind } from '@console/internal/module/k8s'; import DevPreviewBadge from '@console/shared/src/components/badges/DevPreviewBadge'; import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle'; import { PageHeading } from '@console/shared/src/components/heading/PageHeading'; @@ -44,40 +46,47 @@ const ImportPage: FunctionComponent = () => { const preselectedNamespace = searchParams.get('preselected-ns'); const importType = searchParams.get('importType'); - let importData: ImportData; - let resources: FirehoseResource[]; - if (imageStreamName && imageStreamNamespace) { - importData = ImportFlows(t).s2i; - resources = [ - { - kind: ImageStreamModel.kind, - prop: 'imageStreams', - isList: false, - name: imageStreamName, - namespace: imageStreamNamespace, - }, - { - kind: ProjectModel.kind, - prop: 'projects', - isList: true, - }, - ]; - } else { - importData = ImportFlows(t).git; - resources = [ - { - kind: ImageStreamModel.kind, - prop: 'imageStreams', - isList: true, - namespace: 'openshift', - }, - { + const isS2i = !!(imageStreamName && imageStreamNamespace); + const importData: ImportData = isS2i ? ImportFlows(t).s2i : ImportFlows(t).git; + + const watchResources = useMemo( + () => ({ + imageStreams: isS2i + ? { + kind: ImageStreamModel.kind, + isList: false, + name: imageStreamName, + namespace: imageStreamNamespace, + } + : { + kind: ImageStreamModel.kind, + isList: true, + namespace: 'openshift', + }, + projects: { kind: ProjectModel.kind, - prop: 'projects', isList: true, }, - ]; - } + }), + [isS2i, imageStreamName, imageStreamNamespace], + ); + + const resources = useK8sWatchResources<{ + imageStreams: K8sResourceKind | K8sResourceKind[]; + projects: K8sResourceKind[]; + }>(watchResources); + + const imageStreams = { + data: resources.imageStreams.data, + loaded: resources.imageStreams.loaded, + loadError: resources.imageStreams.loadError, + }; + + const projects = { + data: resources.projects.data, + loaded: resources.projects.loaded, + loadError: resources.projects.loadError, + }; return ( @@ -88,14 +97,14 @@ const ImportPage: FunctionComponent = () => { title={importData.title} badge={importType === ImportTypes.devfile ? : null} /> - - - + )} diff --git a/frontend/packages/dev-console/src/components/import/ImportSamplePage.tsx b/frontend/packages/dev-console/src/components/import/ImportSamplePage.tsx index c24d1c45c26..5ecee003dbe 100644 --- a/frontend/packages/dev-console/src/components/import/ImportSamplePage.tsx +++ b/frontend/packages/dev-console/src/components/import/ImportSamplePage.tsx @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { Formik } from 'formik'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom-v5-compat'; -import { FirehoseResource, LoadingBox, history } from '@console/internal/components/utils'; +import { LoadingBox, history } from '@console/internal/components/utils'; import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; import { ImageStreamModel } from '@console/internal/models'; import { K8sResourceKind } from '@console/internal/module/k8s'; @@ -30,7 +30,7 @@ const ImportSamplePage: FC = () => { const { t } = useTranslation(); const { ns: namespace, is: imageStreamName, isNs: imageStreamNamespace } = useParams(); - const imageStreamResource: FirehoseResource = useMemo( + const imageStreamResource = useMemo( () => ({ kind: ImageStreamModel.kind, prop: 'imageStreams', diff --git a/frontend/packages/dev-console/src/components/import/__tests__/DeployImage.spec.tsx b/frontend/packages/dev-console/src/components/import/__tests__/DeployImage.spec.tsx index 8cb27d35ac1..4e5820db88c 100644 --- a/frontend/packages/dev-console/src/components/import/__tests__/DeployImage.spec.tsx +++ b/frontend/packages/dev-console/src/components/import/__tests__/DeployImage.spec.tsx @@ -62,16 +62,12 @@ jest.mock('../../QueryFocusApplication', () => ({ }, })); +jest.mock('@console/internal/components/utils/k8s-watch-hook', () => ({ + useK8sWatchResource: () => [[], true, null], +})); + jest.mock('@console/internal/components/utils', () => ({ ...jest.requireActual('@console/internal/components/utils'), - Firehose: (props) => { - const mockProps = { - projects: { data: [], loaded: true }, - }; - return props.children && typeof props.children === 'function' - ? props.children(mockProps) - : 'Firehose Component'; - }, usePreventDataLossLock: jest.fn(), })); diff --git a/frontend/packages/dev-console/src/components/import/import-types.ts b/frontend/packages/dev-console/src/components/import/import-types.ts index 691f1745423..8a69738ba9e 100644 --- a/frontend/packages/dev-console/src/components/import/import-types.ts +++ b/frontend/packages/dev-console/src/components/import/import-types.ts @@ -13,7 +13,13 @@ import { PipelineData } from '../pipeline-section/import-types'; export interface DeployImageFormProps { builderImages?: NormalizedBuilderImages; - projects?: FirehoseList | WatchK8sResultsObject; + projects?: + | { + data: K8sResourceKind[]; + loaded: boolean; + loadError?: any; + } + | WatchK8sResultsObject; } export type ImageStreamPayload = boolean | K8sResourceKind; @@ -38,31 +44,34 @@ export interface ImageStreamContextProps { export interface SourceToImageFormProps { builderImages?: NormalizedBuilderImages; projects?: { - data: []; + data: K8sResourceKind[]; loaded: boolean; + loadError?: any; }; } export interface GitImportFormProps { builderImages?: NormalizedBuilderImages; + imageStreams?: { + data: K8sResourceKind | K8sResourceKind[]; + loaded: boolean; + loadError?: any; + }; projects?: { - data: []; + data: K8sResourceKind[]; loaded: boolean; + loadError?: any; }; } export interface DevfileImportFormProps { builderImages?: NormalizedBuilderImages; projects?: { - data: []; + data: K8sResourceKind[]; loaded: boolean; + loadError?: any; }; } -export interface FirehoseList { - data?: K8sResourceKind[]; - [key: string]: any; -} - export interface DeployImageFormData { formType?: string; project: ProjectData; diff --git a/frontend/packages/dev-console/src/components/monitoring/overview/MonitoringOverview.tsx b/frontend/packages/dev-console/src/components/monitoring/overview/MonitoringOverview.tsx index 0713a040c22..fa9b501ba9c 100644 --- a/frontend/packages/dev-console/src/components/monitoring/overview/MonitoringOverview.tsx +++ b/frontend/packages/dev-console/src/components/monitoring/overview/MonitoringOverview.tsx @@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom-v5-compat'; import { Alert } from '@console/dynamic-plugin-sdk'; import { sortEvents } from '@console/internal/components/events'; -import { FirehoseResult, LoadingBox } from '@console/internal/components/utils'; +import { LoadingBox } from '@console/internal/components/utils'; import { DeploymentConfigModel } from '@console/internal/models'; import { K8sResourceKind, EventKind, PodKind } from '@console/internal/module/k8s'; import { getFiringAlerts } from '@console/shared'; @@ -28,7 +28,11 @@ import './MonitoringOverview.scss'; type MonitoringOverviewProps = { resource: K8sResourceKind; pods?: PodKind[]; - resourceEvents?: FirehoseResult; + resourceEvents?: { + data: EventKind[]; + loaded: boolean; + loadError?: Error; + }; monitoringAlerts: Alert[]; }; diff --git a/frontend/packages/dev-console/src/components/monitoring/overview/MonitoringTab.tsx b/frontend/packages/dev-console/src/components/monitoring/overview/MonitoringTab.tsx index 27fa99af571..28a8697282b 100644 --- a/frontend/packages/dev-console/src/components/monitoring/overview/MonitoringTab.tsx +++ b/frontend/packages/dev-console/src/components/monitoring/overview/MonitoringTab.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react'; import { useMemo } from 'react'; -import { Firehose } from '@console/internal/components/utils'; +import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook'; import { PodModel } from '@console/internal/models'; -import { PodKind } from '@console/internal/module/k8s'; +import { EventKind, PodKind } from '@console/internal/module/k8s'; import { OverviewItem, usePodsWatcher } from '@console/shared'; import MonitoringOverview from './MonitoringOverview'; @@ -18,40 +18,66 @@ const MonitoringTab: FC = ({ item }) => { } = item.obj; const { podData, loadError, loaded } = usePodsWatcher(item.obj, item.obj.kind, namespace); - const resources = useMemo(() => { - const res = [ - { + const watchResources = useMemo(() => { + const res: Record< + string, + { isList: boolean; kind: string; namespace: string; fieldSelector: string } + > = { + resourceEvents: { isList: true, kind: 'Event', namespace, - prop: 'resourceEvents', fieldSelector: `involvedObject.uid=${uid},involvedObject.name=${name},involvedObject.kind=${kind}`, }, - ]; + }; if (loaded && !loadError && podData?.pods) { podData.pods.forEach((pod) => { const fieldSelector = `involvedObject.uid=${pod.metadata.uid},involvedObject.name=${pod.metadata.name},involvedObject.kind=${PodModel.kind}`; - res.push({ + res[pod.metadata.uid] = { isList: true, kind: 'Event', namespace: pod.metadata.namespace, - prop: pod.metadata.uid, fieldSelector, - }); + }; }); } return res; }, [kind, uid, name, namespace, loaded, loadError, podData]); + const resources = useK8sWatchResources>(watchResources); + + // Transform resources to the expected format for MonitoringOverview + const resourceEvents = resources.resourceEvents + ? { + data: resources.resourceEvents.data || [], + loaded: resources.resourceEvents.loaded, + loadError: resources.resourceEvents.loadError, + } + : undefined; + + // Build the props object with pod events + const podEventProps: Record = {}; + if (podData?.pods) { + podData.pods.forEach((pod) => { + const podEvents = resources[pod.metadata.uid]; + if (podEvents) { + podEventProps[pod.metadata.uid] = { + data: podEvents.data || [], + loaded: podEvents.loaded, + }; + } + }); + } + return ( - - - + ); }; diff --git a/frontend/packages/dev-console/src/components/monitoring/overview/__tests__/MonitoringTab.spec.tsx b/frontend/packages/dev-console/src/components/monitoring/overview/__tests__/MonitoringTab.spec.tsx index ac748500c63..f62d2b64cee 100644 --- a/frontend/packages/dev-console/src/components/monitoring/overview/__tests__/MonitoringTab.spec.tsx +++ b/frontend/packages/dev-console/src/components/monitoring/overview/__tests__/MonitoringTab.spec.tsx @@ -1,8 +1,10 @@ import { render, screen } from '@testing-library/react'; import MonitoringTab from '../MonitoringTab'; -jest.mock('@console/internal/components/utils', () => ({ - Firehose: (props) => props.children, +jest.mock('@console/internal/components/utils/k8s-watch-hook', () => ({ + useK8sWatchResources: jest.fn(() => ({ + resourceEvents: { data: [], loaded: true, loadError: null }, + })), })); jest.mock('@console/internal/models', () => ({ diff --git a/frontend/packages/dev-console/src/components/project-access/ProjectAccessPage.tsx b/frontend/packages/dev-console/src/components/project-access/ProjectAccessPage.tsx index 3a671885f2b..fda9ae134d6 100644 --- a/frontend/packages/dev-console/src/components/project-access/ProjectAccessPage.tsx +++ b/frontend/packages/dev-console/src/components/project-access/ProjectAccessPage.tsx @@ -1,7 +1,8 @@ import type { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams, useLocation } from 'react-router-dom-v5-compat'; -import { Firehose } from '@console/internal/components/utils'; +import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; +import { K8sResourceKind } from '@console/internal/module/k8s'; import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle'; import { useProjectAccessRoles } from './hooks'; import ProjectAccess from './ProjectAccess'; @@ -13,22 +14,30 @@ const ProjectAccessPage: FC = (props) => { const namespace = params.ns; const roles = useProjectAccessRoles(); const showFullForm = location.pathname.includes('project-access'); + + const [roleBindingsData, loaded, loadError] = useK8sWatchResource({ + namespace, + kind: 'RoleBinding', + isList: true, + optional: true, + }); + + const roleBindings = { + data: roleBindingsData || [], + loaded, + loadError, + }; + return ( <> {t('devconsole~Project access')} - - - + ); }; diff --git a/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccessPage.spec.tsx b/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccessPage.spec.tsx index 234924b1023..02fc23e2ddf 100644 --- a/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccessPage.spec.tsx +++ b/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccessPage.spec.tsx @@ -2,8 +2,8 @@ import { render, screen } from '@testing-library/react'; import { useParams, useLocation } from 'react-router-dom-v5-compat'; import ProjectAccessPage from '../ProjectAccessPage'; -jest.mock('@console/internal/components/utils', () => ({ - Firehose: (props) => props.children, +jest.mock('@console/internal/components/utils/k8s-watch-hook', () => ({ + useK8sWatchResource: jest.fn(() => [[], true, null]), })); jest.mock('@console/shared/src/components/document-title/DocumentTitle', () => ({ diff --git a/frontend/packages/knative-plugin/src/components/knatify/CreateKnatifyPage.tsx b/frontend/packages/knative-plugin/src/components/knatify/CreateKnatifyPage.tsx index ec2adeefce0..260953d6dab 100644 --- a/frontend/packages/knative-plugin/src/components/knatify/CreateKnatifyPage.tsx +++ b/frontend/packages/knative-plugin/src/components/knatify/CreateKnatifyPage.tsx @@ -139,7 +139,7 @@ const CreateKnatifyPage: FunctionComponent = () => { {(formikProps) => ( ) ?? {}} + projects={resources?.projects as WatchK8sResultsObject} /> )} diff --git a/frontend/packages/knative-plugin/src/components/revisions/DeleteRevisionModalController.tsx b/frontend/packages/knative-plugin/src/components/revisions/DeleteRevisionModalController.tsx index 98caedffc1e..1d4dada6a79 100644 --- a/frontend/packages/knative-plugin/src/components/revisions/DeleteRevisionModalController.tsx +++ b/frontend/packages/knative-plugin/src/components/revisions/DeleteRevisionModalController.tsx @@ -1,5 +1,5 @@ import type { FC } from 'react'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { ActionGroup, Button } from '@patternfly/react-core'; import { Formik, FormikHelpers, FormikValues } from 'formik'; import { useTranslation } from 'react-i18next'; @@ -12,12 +12,8 @@ import { ModalTitle, ModalWrapper, } from '@console/internal/components/factory'; -import { - Firehose, - FirehoseResult, - history, - resourceListPathFromModel, -} from '@console/internal/components/utils'; +import { history, resourceListPathFromModel } from '@console/internal/components/utils'; +import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook'; import { k8sKill, k8sPatch, @@ -26,34 +22,64 @@ import { } from '@console/internal/module/k8s'; import { RedExclamationCircleIcon } from '@console/shared'; import { KNATIVE_SERVING_LABEL } from '../../const'; -import { RevisionModel, ServiceModel } from '../../models'; +import { ConfigurationModel, RevisionModel, ServiceModel } from '../../models'; import { getKnativeRevisionsData } from '../../topology/knative-topology-utils'; import { Traffic } from '../../types'; -import { - knativeServingResourcesTrafficSplitting, - getRevisionItems, - trafficDataForPatch, -} from '../../utils/traffic-splitting-utils'; +import { getRevisionItems, trafficDataForPatch } from '../../utils/traffic-splitting-utils'; import { TrafficSplittingType } from '../traffic-splitting/TrafficSplitting'; import DeleteRevisionModal from './DeleteRevisionModal'; -type ControllerProps = { - loaded?: boolean; - revision?: K8sResourceKind; - resources?: { - configurations: FirehoseResult; - revisions: FirehoseResult; - services: FirehoseResult; - }; +type DeleteRevisionModalControllerProps = { + revision: K8sResourceKind; cancel?: () => void; close?: () => void; }; -const Controller: FC = ({ loaded, resources, revision, cancel, close }) => { +const DeleteRevisionModalController: FC = ({ + revision, + cancel, + close, +}) => { const { t } = useTranslation(); + const { namespace } = revision.metadata; + + const watchResources = useMemo( + () => ({ + revisions: { + isList: true, + kind: referenceForModel(RevisionModel), + namespace, + optional: true, + }, + configurations: { + isList: true, + kind: referenceForModel(ConfigurationModel), + namespace, + optional: true, + }, + services: { + isList: true, + kind: referenceForModel(ServiceModel), + namespace, + }, + }), + [namespace], + ); + + const resources = useK8sWatchResources<{ + revisions: K8sResourceKind[]; + configurations: K8sResourceKind[]; + services: K8sResourceKind[]; + }>(watchResources); + + const loaded = + Object.keys(resources).length > 0 && + Object.keys(resources).every((key) => resources[key].loaded); + if (!loaded) { return null; } + const service = resources.services.data.find((s: K8sResourceKind) => { return revision.metadata.labels[KNATIVE_SERVING_LABEL] === s.metadata.name; }); @@ -124,7 +150,7 @@ const Controller: FC = ({ loaded, resources, revision, cancel, }); } - const deleteRevision = (action: FormikHelpers) => { + const deleteRevisionAction = (action: FormikHelpers) => { return k8sKill(RevisionModel, revision) .then(() => { close(); @@ -143,12 +169,12 @@ const Controller: FC = ({ loaded, resources, revision, cancel, const handleSubmit = (values: FormikValues, action: FormikHelpers) => { const ksvcPatch = trafficDataForPatch(values.trafficSplitting, service); if (!deleteTraffic || deleteTraffic.percent === 0) { - return deleteRevision(action); + return deleteRevisionAction(action); } return k8sPatch(ServiceModel, service, ksvcPatch) .then(() => { - deleteRevision(action); + deleteRevisionAction(action); }) .catch((err) => { const errMessage = err.message || t('knative-plugin~An error occurred. Please try again'); @@ -176,29 +202,6 @@ const Controller: FC = ({ loaded, resources, revision, cancel, ); }; -type DeleteRevisionModalControllerProps = { - revision: K8sResourceKind; -}; - -const DeleteRevisionModalController: FC = (props) => { - const { - metadata: { namespace }, - } = props.revision; - const resources = knativeServingResourcesTrafficSplitting(namespace); - resources.push({ - isList: true, - kind: referenceForModel(ServiceModel), - namespace, - prop: 'services', - }); - - return ( - - - - ); -}; - type Props = DeleteRevisionModalControllerProps & ModalComponentProps; const DeleteRevisionModalProvider: OverlayComponent = (props) => { diff --git a/frontend/packages/knative-plugin/src/components/test-function/TestFunctionController.tsx b/frontend/packages/knative-plugin/src/components/test-function/TestFunctionController.tsx index 3ba75ea5978..1ebafd81545 100644 --- a/frontend/packages/knative-plugin/src/components/test-function/TestFunctionController.tsx +++ b/frontend/packages/knative-plugin/src/components/test-function/TestFunctionController.tsx @@ -3,21 +3,12 @@ import { useCallback } from 'react'; import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { ModalComponentProps, ModalWrapper } from '@console/internal/components/factory'; -import { Firehose } from '@console/internal/components/utils'; +import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; +import { referenceForModel } from '@console/internal/module/k8s'; import { ServiceModel } from '../../models'; import { ServiceKind } from '../../types'; import TestFunction from './TestFunction'; -type ControllerProps = { - loaded?: boolean; - obj: ServiceKind; -}; - -const Controller: FC = (props) => { - const { loaded, obj } = props; - return loaded ? : null; -}; - type TestFunctionControllerProps = { obj: ServiceKind; }; @@ -25,21 +16,14 @@ type TestFunctionControllerProps = { const TestFunctionController: FC = (props) => { const { obj } = props; - const serverlessResources = [ - { - kind: ServiceModel.kind, - isList: false, - prop: `obj`, - namespace: obj.metadata.namespace, - name: obj.metadata.name, - }, - ]; + const [service, loaded] = useK8sWatchResource({ + kind: referenceForModel(ServiceModel), + isList: false, + namespace: obj.metadata.namespace, + name: obj.metadata.name, + }); - return ( - - - - ); + return loaded ? : null; }; type Props = TestFunctionControllerProps & ModalComponentProps; diff --git a/frontend/packages/knative-plugin/src/components/traffic-splitting/TrafficSplittingController.tsx b/frontend/packages/knative-plugin/src/components/traffic-splitting/TrafficSplittingController.tsx index 8408f8267f9..e0bce257db5 100644 --- a/frontend/packages/knative-plugin/src/components/traffic-splitting/TrafficSplittingController.tsx +++ b/frontend/packages/knative-plugin/src/components/traffic-splitting/TrafficSplittingController.tsx @@ -1,29 +1,14 @@ import type { FC } from 'react'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider'; import { useOverlay } from '@console/dynamic-plugin-sdk/src/app/modal-support/useOverlay'; import { ModalComponentProps, ModalWrapper } from '@console/internal/components/factory'; -import { Firehose, FirehoseResult } from '@console/internal/components/utils'; -import { K8sResourceKind } from '@console/internal/module/k8s'; +import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook'; +import { K8sResourceKind, referenceForModel } from '@console/internal/module/k8s'; +import { ConfigurationModel, RevisionModel } from '../../models'; import { getKnativeRevisionsData } from '../../topology/knative-topology-utils'; -import { knativeServingResourcesTrafficSplitting } from '../../utils/traffic-splitting-utils'; import TrafficSplitting from './TrafficSplitting'; -type ControllerProps = { - loaded?: boolean; - obj: K8sResourceKind; - resources?: { - configurations: FirehoseResult; - revisions: FirehoseResult; - }; -}; - -const Controller: FC = (props) => { - const { loaded, obj, resources } = props; - const revisions = getKnativeRevisionsData(obj, resources); - return loaded ? : null; -}; - type TrafficSplittingControllerProps = { obj: K8sResourceKind; }; @@ -32,13 +17,37 @@ const TrafficSplittingController: FC = (props) const { metadata: { namespace }, } = props.obj; - const resources = knativeServingResourcesTrafficSplitting(namespace); - return ( - - - + const watchResources = useMemo( + () => ({ + revisions: { + isList: true, + kind: referenceForModel(RevisionModel), + namespace, + optional: true, + }, + configurations: { + isList: true, + kind: referenceForModel(ConfigurationModel), + namespace, + optional: true, + }, + }), + [namespace], ); + + const resources = useK8sWatchResources<{ + revisions: K8sResourceKind[]; + configurations: K8sResourceKind[]; + }>(watchResources); + + const loaded = + Object.keys(resources).length > 0 && + Object.keys(resources).every((key) => resources[key].loaded); + + const revisions = getKnativeRevisionsData(props.obj, resources); + + return loaded ? : null; }; type Props = TrafficSplittingControllerProps & ModalComponentProps; From ff0ebb0dda3199cf2821beeabd414b952a2d7cba Mon Sep 17 00:00:00 2001 From: Krish Agarwal Date: Thu, 12 Feb 2026 16:29:29 -0500 Subject: [PATCH 2/3] Addressed CI build failures --- .../monitoring/overview/__tests__/mockData.ts | 6 +++--- .../src/components/project-access/ProjectAccess.tsx | 10 +++++----- .../project-access/__tests__/ProjectAccess.spec.tsx | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/packages/dev-console/src/components/monitoring/overview/__tests__/mockData.ts b/frontend/packages/dev-console/src/components/monitoring/overview/__tests__/mockData.ts index ef11babc608..a6657e0657a 100644 --- a/frontend/packages/dev-console/src/components/monitoring/overview/__tests__/mockData.ts +++ b/frontend/packages/dev-console/src/components/monitoring/overview/__tests__/mockData.ts @@ -99,7 +99,7 @@ export const mockPodEvents = { }, ], filters: {}, - loadError: '', + loadError: undefined, loaded: true, selected: null, }, @@ -255,7 +255,7 @@ export const mockPodEvents = { }, ], filters: {}, - loadError: '', + loadError: undefined, loaded: true, selected: null, }, @@ -319,7 +319,7 @@ export const mockResourceEvents = { }, ], filters: {}, - loadError: '', + loadError: undefined, loaded: true, selected: null, }; diff --git a/frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx b/frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx index e373d02fa2f..e2b54ed5a74 100644 --- a/frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx +++ b/frontend/packages/dev-console/src/components/project-access/ProjectAccess.tsx @@ -1,7 +1,6 @@ import type { FC } from 'react'; import { Content, ContentVariants } from '@patternfly/react-core'; import { Formik } from 'formik'; -import * as _ from 'lodash'; import { useTranslation, Trans } from 'react-i18next'; import { Link } from 'react-router-dom-v5-compat'; import { @@ -13,6 +12,7 @@ import { StatusBox, } from '@console/internal/components/utils'; import { RoleBindingModel, RoleModel } from '@console/internal/models'; +import type { K8sResourceKind } from '@console/internal/module/k8s'; import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle'; import { PageHeading } from '@console/shared/src/components/heading/PageHeading'; import { ExternalLink } from '@console/shared/src/components/links/ExternalLink'; @@ -25,13 +25,13 @@ import { getRolesToUpdate, } from './project-access-form-submit-utils'; import { getUserRoleBindings, Roles } from './project-access-form-utils'; -import { Verb, UserRoleBinding } from './project-access-form-utils-types'; +import { Verb, UserRoleBinding, RoleBinding } from './project-access-form-utils-types'; import { validationSchema } from './project-access-form-validation-utils'; import ProjectAccessForm from './ProjectAccessForm'; export interface ProjectAccessProps { namespace: string; - roleBindings?: { data: []; loaded: boolean; loadError: {} }; + roleBindings?: { data: K8sResourceKind[]; loaded: boolean; loadError?: Error }; roles: { data: Roles; loaded: boolean }; fullFormView?: boolean; } @@ -43,12 +43,12 @@ const ProjectAccess: FC = ({ fullFormView, }) => { const { t } = useTranslation(); - if ((!roleBindings.loaded && _.isEmpty(roleBindings.loadError)) || !roles.loaded) { + if ((!roleBindings.loaded && !roleBindings.loadError) || !roles.loaded) { return ; } const userRoleBindings: UserRoleBinding[] = getUserRoleBindings( - roleBindings.data, + roleBindings.data as RoleBinding[], Object.keys(roles.data), namespace, ); diff --git a/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccess.spec.tsx b/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccess.spec.tsx index ee4417b167f..ec0e8f400d1 100644 --- a/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccess.spec.tsx +++ b/frontend/packages/dev-console/src/components/project-access/__tests__/ProjectAccess.spec.tsx @@ -94,7 +94,7 @@ describe('Project Access', () => { roleBindings: { data: [], loaded: false, - loadError: {}, + loadError: undefined, }, roles: { data: defaultAccessRoles, @@ -111,7 +111,7 @@ describe('Project Access', () => { }); it('should show the StatusBox when there is error loading the role bindings', () => { - projectAccessProps.roleBindings.loadError = { error: 'user has no access to role bindigs' }; + projectAccessProps.roleBindings.loadError = new Error('user has no access to role bindigs'); render(); expect(screen.getByText(/StatusBox/)).toBeInTheDocument(); From cb2baa1b411db44c04ff66b0dd3cc4f461f4589c Mon Sep 17 00:00:00 2001 From: Krish Agarwal Date: Fri, 13 Feb 2026 13:01:28 -0500 Subject: [PATCH 3/3] Added loadError to handle error state and undefined service --- .../test-function/TestFunctionController.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/packages/knative-plugin/src/components/test-function/TestFunctionController.tsx b/frontend/packages/knative-plugin/src/components/test-function/TestFunctionController.tsx index 1ebafd81545..7ca5c7867d0 100644 --- a/frontend/packages/knative-plugin/src/components/test-function/TestFunctionController.tsx +++ b/frontend/packages/knative-plugin/src/components/test-function/TestFunctionController.tsx @@ -16,14 +16,22 @@ type TestFunctionControllerProps = { const TestFunctionController: FC = (props) => { const { obj } = props; - const [service, loaded] = useK8sWatchResource({ + const [service, loaded, loadError] = useK8sWatchResource({ kind: referenceForModel(ServiceModel), isList: false, namespace: obj.metadata.namespace, name: obj.metadata.name, }); - return loaded ? : null; + if (!loaded) { + return null; + } + + if (loadError || !service) { + return null; + } + + return ; }; type Props = TestFunctionControllerProps & ModalComponentProps;