diff --git a/frontend/packages/console-dynamic-plugin-sdk/docs/console-extensions.md b/frontend/packages/console-dynamic-plugin-sdk/docs/console-extensions.md index a151e33d9b1..72a9bb7a9aa 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/docs/console-extensions.md +++ b/frontend/packages/console-dynamic-plugin-sdk/docs/console-extensions.md @@ -387,7 +387,7 @@ Adds an activity to the Activity Card of Overview Dashboard where the triggering | Name | Value Type | Optional | Description | | ---- | ---------- | -------- | ----------- | -| `k8sResource` | `CodeRef` | no | The utilization item to be replaced. | +| `k8sResource` | `CodeRef` | no | The utilization item to be replaced. | | `component` | `CodeRef>>` | no | The action component. | | `isActivity` | `CodeRef<(resource: T) => boolean>` | yes | Function which determines if the given resource represents the action. If not defined, every resource represents activity. | | `getTimestamp` | `CodeRef<(resource: T) => Date>` | yes | Timestamp for the given action, which will be used for ordering. | @@ -405,7 +405,7 @@ Adds a health subsystem to the status card of Overview dashboard where the sourc | Name | Value Type | Optional | Description | | ---- | ---------- | -------- | ----------- | | `title` | `string` | no | Title of operators section in the popup. | -| `resources` | `CodeRef` | no | Kubernetes resources which will be fetched and passed to `healthHandler`. | +| `resources` | `CodeRef` | no | Kubernetes resources which will be fetched and passed to `healthHandler`. | | `getOperatorsWithStatuses` | `CodeRef>` | yes | Resolves status for the operators. | | `operatorRowLoader` | `CodeRef>>` | yes | Loader for popup row component. | | `viewAllLink` | `string` | yes | Links to all resources page. If not provided then a list page of the first resource from resources prop is used. | @@ -425,7 +425,7 @@ Adds a health subsystem to the status card of Overview dashboard where the sourc | `title` | `string` | no | The display name of the subsystem. | | `queries` | `string[]` | no | The Prometheus queries | | `healthHandler` | `CodeRef` | no | Resolve the subsystem's health. | -| `additionalResource` | `CodeRef` | yes | Additional resource which will be fetched and passed to `healthHandler`. | +| `additionalResource` | `CodeRef` | yes | Additional resource which will be fetched and passed to `healthHandler`. | | `popupComponent` | `CodeRef>` | yes | Loader for popup content. If defined, a health item will be represented as a link which opens popup with given content. | | `popupTitle` | `string` | yes | The title of the popover. | | `popupClassname` | `string` | yes | Optional classname for the popup top-level component. | @@ -466,8 +466,8 @@ Adds a health subsystem to the status card of Overview dashboard where the sourc | `url` | `string` | no | The URL to fetch data from. It will be prefixed with base k8s URL. | | `healthHandler` | `CodeRef>` | no | Resolve the subsystem's health. | | `fetch` | `CodeRef` | yes | Custom function to fetch data from the URL.
If none is specified, default one (`coFetchJson`) will be used.
Response is then parsed by `healthHandler`. | -| `additionalResource` | `CodeRef` | yes | Additional resource which will be fetched and passed to `healthHandler`. | -| `popupComponent` | `CodeRef; }>>` | yes | Loader for popup content. If defined, a health item will be represented as a link which opens popup with given content. | +| `additionalResource` | `CodeRef` | yes | Additional resource which will be fetched and passed to `healthHandler`. | +| `popupComponent` | `CodeRef; }>>` | yes | Loader for popup content. If defined, a health item will be represented as a link which opens popup with given content. | | `popupTitle` | `string` | yes | The title of the popover. | --- diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-types.ts index a728e9c4ca4..7e344585bfc 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/api/internal-types.ts @@ -78,7 +78,7 @@ export type HealthItemProps = WithClassNameProps<{ export type ResourceInventoryItemProps = { resources: K8sResourceCommon[]; - additionalResources?: { [key: string]: [] }; + additionalResources?: { [key: string]: K8sResourceCommon[] }; mapper?: StatusGroupMapper; kind: K8sModel; isLoading: boolean; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts index 6e2ee814326..ffa5ded2ff9 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/console-types.ts @@ -226,6 +226,22 @@ export type WatchK8sResourcesGeneric = { }; }; +/** + * WatchK8sResource with a `prop` field that serves as a key to identify + * this resource in multi-resource watch results. Used by dashboard extensions + * and legacy components that watch multiple K8s resources simultaneously. + */ +export type WatchK8sResourceWithProp = WatchK8sResource & { + prop: string; +}; + +export type WatchK8sResult = [R, boolean, any]; + +/** + * @deprecated Use WatchK8sResource with useK8sWatchResource hook instead. + * FirehoseResource will be removed in a future release. + * @see WatchK8sResource + */ export type FirehoseResource = { kind: K8sResourceKindReference; name?: string; @@ -239,6 +255,10 @@ export type FirehoseResource = { fieldSelector?: string; }; +/** + * @deprecated Use WatchK8sResultsObject instead. FirehoseResult will be removed in a future release. + * @see WatchK8sResultsObject + */ export type FirehoseResult< R extends K8sResourceCommon | K8sResourceCommon[] = K8sResourceCommon[] > = { @@ -249,12 +269,14 @@ export type FirehoseResult< kind?: string; }; +/** + * @deprecated Use WatchK8sResults instead. FirehoseResourcesResult will be removed in a future release. + * @see WatchK8sResults + */ export type FirehoseResourcesResult = { [key: string]: FirehoseResult; }; -export type WatchK8sResult = [R, boolean, any]; - export type UseK8sWatchResource = ( initResource: WatchK8sResource | null, ) => WatchK8sResult; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/dashboard-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/dashboard-types.ts index 229a890afd4..bb217d5b0b9 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/dashboard-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/dashboard-types.ts @@ -6,8 +6,7 @@ import type { PrometheusResponse, ResourcesObject, WatchK8sResults, - FirehoseResourcesResult, - FirehoseResult, + WatchK8sResultsObject, OverviewCardSpan, K8sResourceKind, } from './console-types'; @@ -18,7 +17,7 @@ import type { export type CardSpan = OverviewCardSpan; export type GetOperatorsWithStatuses = ( - resources: FirehoseResourcesResult, + resources: WatchK8sResults, ) => OperatorStatusWithResources[]; export type K8sActivityProps = { @@ -53,13 +52,13 @@ export type OperatorHealth = { export type PrometheusHealthHandler = ( responses: { response: PrometheusResponse; error: any }[], t?: TFunction, - additionalResource?: FirehoseResult, + additionalResource?: WatchK8sResultsObject, infrastructure?: K8sResourceKind, ) => SubsystemHealth; export type PrometheusHealthPopupProps = { responses: { response: PrometheusResponse; error: any }[]; - k8sResult?: FirehoseResult; + k8sResult?: WatchK8sResultsObject; hide: () => void; }; @@ -80,7 +79,7 @@ export type SubsystemHealth = { export type URLHealthHandler< R, T extends K8sResourceCommon | K8sResourceCommon[] = K8sResourceCommon | K8sResourceCommon[] -> = (response: R, error: any, additionalResource?: FirehoseResult) => SubsystemHealth; +> = (response: R, error: any, additionalResource?: WatchK8sResultsObject) => SubsystemHealth; export type StatusPopupItemProps = { children: ReactNode; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/dashboards.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/dashboards.ts index e80f6e60cc5..db714822ee0 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/dashboards.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/dashboards.ts @@ -8,8 +8,8 @@ import type { StatusGroupMapper, WatchK8sResources, WatchK8sResults, - FirehoseResource, - FirehoseResult, + WatchK8sResourceWithProp, + WatchK8sResultsObject, } from './console-types'; import type { CardSpan, @@ -62,7 +62,7 @@ export type DashboardsOverviewHealthPrometheusSubsystem = Extension< /** Resolve the subsystem's health. */ healthHandler: CodeRef; /** Additional resource which will be fetched and passed to `healthHandler`. */ - additionalResource?: CodeRef; + additionalResource?: CodeRef; /** Loader for popup content. If defined, a health item will be represented as a link which opens popup with given content. */ popupComponent?: CodeRef>; /** The title of the popover. */ @@ -96,13 +96,13 @@ export type DashboardsOverviewHealthURLSubsystem< */ fetch?: CodeRef; /** Additional resource which will be fetched and passed to `healthHandler`. */ - additionalResource?: CodeRef; + additionalResource?: CodeRef; /** Loader for popup content. If defined, a health item will be represented as a link which opens popup with given content. */ popupComponent?: CodeRef< React.ComponentType<{ healthResult?: T; healthResultError?: any; - k8sResult?: FirehoseResult; + k8sResult?: WatchK8sResultsObject; }> >; /** The title of the popover. */ @@ -138,7 +138,7 @@ export type DashboardsOverviewHealthOperator< /** Title of operators section in the popup. */ title: string; /** Kubernetes resources which will be fetched and passed to `healthHandler`. */ - resources: CodeRef; + resources: CodeRef; /** Resolves status for the operators. */ getOperatorsWithStatuses?: CodeRef>; /** Loader for popup row component. */ @@ -193,7 +193,7 @@ export type DashboardsOverviewResourceActivity< 'console.dashboards/overview/activity/resource', { /** The utilization item to be replaced. */ - k8sResource: CodeRef; + k8sResource: CodeRef; /** Function which determines if the given resource represents the action. If not defined, every resource represents activity. */ isActivity?: CodeRef<(resource: T) => boolean>; /** Timestamp for the given action, which will be used for ordering. */ diff --git a/frontend/packages/console-shared/src/components/dashboard/status-card/OperatorStatusBody.tsx b/frontend/packages/console-shared/src/components/dashboard/status-card/OperatorStatusBody.tsx index 6662fc4374a..ad884911c63 100644 --- a/frontend/packages/console-shared/src/components/dashboard/status-card/OperatorStatusBody.tsx +++ b/frontend/packages/console-shared/src/components/dashboard/status-card/OperatorStatusBody.tsx @@ -3,9 +3,13 @@ import { useCallback } from 'react'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router'; -import type { GetOperatorsWithStatuses, OperatorRowProps } from '@console/dynamic-plugin-sdk'; +import type { + GetOperatorsWithStatuses, + OperatorRowProps, + WatchK8sResults, + K8sResourceCommon, +} from '@console/dynamic-plugin-sdk'; import type { LazyLoader } from '@console/internal/components/utils/async'; -import type { FirehoseResourcesResult } from '@console/internal/components/utils/types'; import { getMostImportantStatuses } from './state-utils'; import { HealthState } from './states'; import StatusItem, { StatusPopupSection } from './StatusPopup'; @@ -75,7 +79,7 @@ export const OperatorsSection: FC = ({ }; type OperatorsSectionProps = { - resources: FirehoseResourcesResult; + resources: WatchK8sResults<{ [key: string]: K8sResourceCommon | K8sResourceCommon[] }>; getOperatorsWithStatuses: GetOperatorsWithStatuses; title: string; linkTo: string; diff --git a/frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsx b/frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsx index 0556fc07cf6..4ca88557e89 100644 --- a/frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsx +++ b/frontend/packages/console-shared/src/components/dropdown/ResourceDropdown.tsx @@ -3,14 +3,19 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import * as fuzzy from 'fuzzysearch'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; import type { ConsoleSelectProps } from '@console/internal/components/utils/console-select'; import { ConsoleSelect } from '@console/internal/components/utils/console-select'; import { ResourceIcon } from '@console/internal/components/utils/resource-icon'; import { LoadingInline } from '@console/internal/components/utils/status-box'; -import type { FirehoseResult } from '@console/internal/components/utils/types'; import type { K8sResourceKind, K8sKind } from '@console/internal/module/k8s'; import { referenceForModel, modelFor, referenceFor } from '@console/internal/module/k8s'; +/** Extended result type that includes optional kind for badge display fallback */ +type ResourceDropdownResult = WatchK8sResultsObject & { + kind?: string; +}; + type DropdownItemProps = { model: K8sKind; name: string; @@ -57,7 +62,7 @@ export interface ResourceDropdownProps { transformLabel?: Function; loaded?: boolean; loadError?: unknown; - resources?: FirehoseResult[]; + resources?: ResourceDropdownResult[]; autoSelect?: boolean; resourceFilter?: (resource: K8sResourceKind) => boolean; onChange?: ( diff --git a/frontend/packages/console-shared/src/components/formik-fields/ResourceDropdownField.tsx b/frontend/packages/console-shared/src/components/formik-fields/ResourceDropdownField.tsx index 6eb73a19826..d11fca15faa 100644 --- a/frontend/packages/console-shared/src/components/formik-fields/ResourceDropdownField.tsx +++ b/frontend/packages/console-shared/src/components/formik-fields/ResourceDropdownField.tsx @@ -3,27 +3,30 @@ import { useMemo } from 'react'; import { FormGroup, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; import type { FormikValues } from 'formik'; import { useField, useFormikContext } from 'formik'; -import type { FirehoseResult } from '@console/internal/components/utils/types'; import { useFormikValidationFix } from '../../hooks/useFormikValidationFix'; import type { ResourceDropdownProps } from '../dropdown/ResourceDropdown'; import { ResourceDropdown } from '../dropdown/ResourceDropdown'; import type { DropdownFieldProps } from './field-types'; import { getFieldId } from './field-utils'; -export interface ResourceDropdownFieldProps extends DropdownFieldProps { - dataSelector: ResourceDropdownProps['dataSelector']; - resources: FirehoseResult[]; - showBadge?: ResourceDropdownProps['showBadge']; - onLoad?: ResourceDropdownProps['onLoad']; - onChange?: ResourceDropdownProps['onChange']; - resourceFilter?: ResourceDropdownProps['resourceFilter']; - autoSelect?: ResourceDropdownProps['autoSelect']; - placeholder?: string; - actionItems?: ResourceDropdownProps['actionItems']; - appendItems?: ResourceDropdownProps['appendItems']; - customResourceKey?: ResourceDropdownProps['customResourceKey']; +export interface ResourceDropdownFieldProps + extends Omit, + Pick< + ResourceDropdownProps, + | 'dataSelector' + | 'resources' + | 'showBadge' + | 'onLoad' + | 'onChange' + | 'resourceFilter' + | 'autoSelect' + | 'placeholder' + | 'actionItems' + | 'appendItems' + | 'customResourceKey' + | 'menuClassName' + > { dataTest?: string; - menuClassName?: ResourceDropdownProps['menuClassName']; } const ResourceDropdownField: FC = ({ diff --git a/frontend/packages/console-shared/src/hooks/useDynamicK8sWatchResources.ts b/frontend/packages/console-shared/src/hooks/useDynamicK8sWatchResources.ts index 9419f440dcc..94b3c3d831a 100644 --- a/frontend/packages/console-shared/src/hooks/useDynamicK8sWatchResources.ts +++ b/frontend/packages/console-shared/src/hooks/useDynamicK8sWatchResources.ts @@ -1,10 +1,14 @@ import { useState, useCallback, useMemo } from 'react'; import * as _ from 'lodash'; -import type { WatchK8sResource, WatchK8sResults } from '@console/dynamic-plugin-sdk'; +import type { + WatchK8sResource, + K8sResourceCommon, + WatchK8sResultsObject, +} from '@console/dynamic-plugin-sdk'; import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook'; type UseDynamicK8sWatchResourcesResult = { - results: WatchK8sResults>; + results: Record>; watchResource: (key: string, resource: WatchK8sResource) => void; stopWatchResource: (key: string) => void; }; diff --git a/frontend/packages/console-shared/src/hooks/useResourceSidebarSamples.ts b/frontend/packages/console-shared/src/hooks/useResourceSidebarSamples.ts index 98ec99aa5c7..1c160e09e21 100644 --- a/frontend/packages/console-shared/src/hooks/useResourceSidebarSamples.ts +++ b/frontend/packages/console-shared/src/hooks/useResourceSidebarSamples.ts @@ -3,9 +3,13 @@ import YAML from 'js-yaml'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { PodDisruptionBudgetModel } from '@console/app/src/models'; -import type { AddAction, CatalogItemType, Perspective } from '@console/dynamic-plugin-sdk'; +import type { + AddAction, + CatalogItemType, + Perspective, + WatchK8sResultsObject, +} from '@console/dynamic-plugin-sdk'; import { isAddAction, isCatalogItemType, isPerspective } from '@console/dynamic-plugin-sdk'; -import type { FirehoseResult } from '@console/internal/components/utils/types'; import { BuildConfigModel, ClusterRoleModel, @@ -331,7 +335,10 @@ const useDefaultSamples = () => { ); }; -export const useResourceSidebarSamples = (kindObj: K8sKind, yamlSamplesList: FirehoseResult) => { +export const useResourceSidebarSamples = ( + kindObj: K8sKind, + yamlSamplesList: WatchK8sResultsObject, +) => { const defaultSamples = useDefaultSamples(); if (!kindObj) { diff --git a/frontend/packages/console-shared/src/types/pod.ts b/frontend/packages/console-shared/src/types/pod.ts index c79d891d1aa..416a15587fc 100644 --- a/frontend/packages/console-shared/src/types/pod.ts +++ b/frontend/packages/console-shared/src/types/pod.ts @@ -1,8 +1,9 @@ import type { ExtPodKind, + K8sResourceCommon, PodControllerOverviewItem, + WatchK8sResultsObject, } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; -import type { FirehoseResult } from '@console/internal/components/utils/types'; import type { DeploymentKind, PodKind } from '@console/internal/module/k8s'; export type { @@ -15,19 +16,19 @@ export type { } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; export interface PodDataResources { - replicationControllers: FirehoseResult; - replicaSets: FirehoseResult; - pods: FirehoseResult; - deploymentConfigs?: FirehoseResult; - deployments?: FirehoseResult; + replicationControllers: WatchK8sResultsObject; + replicaSets: WatchK8sResultsObject; + pods: WatchK8sResultsObject; + deploymentConfigs?: WatchK8sResultsObject; + deployments?: WatchK8sResultsObject; } export interface PodRingResources { - pods: FirehoseResult; - replicaSets: FirehoseResult; - replicationControllers: FirehoseResult; - deployments?: FirehoseResult; - deploymentConfigs?: FirehoseResult; + pods: WatchK8sResultsObject; + replicaSets: WatchK8sResultsObject; + replicationControllers: WatchK8sResultsObject; + deployments?: WatchK8sResultsObject; + deploymentConfigs?: WatchK8sResultsObject; } export interface PodRingData { diff --git a/frontend/packages/console-shared/src/utils/__tests__/test-resource-data.ts b/frontend/packages/console-shared/src/utils/__tests__/test-resource-data.ts index dcfaaa08a24..715d3d08c27 100644 --- a/frontend/packages/console-shared/src/utils/__tests__/test-resource-data.ts +++ b/frontend/packages/console-shared/src/utils/__tests__/test-resource-data.ts @@ -1,8 +1,8 @@ -import type { FirehoseResult } from '@console/internal/components/utils'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; import type { DeploymentKind, PodKind, K8sResourceKind } from '@console/internal/module/k8s'; import { ImagePullPolicy } from '@console/internal/module/k8s'; -export const sampleDeploymentConfigs: FirehoseResult = { +export const sampleDeploymentConfigs: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -175,7 +175,7 @@ export const sampleDeploymentConfigs: FirehoseResult = { }, ], }; -export const sampleDeployments: FirehoseResult = { +export const sampleDeployments: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -450,7 +450,7 @@ export const sampleDeployments: FirehoseResult = { ], }; -export const samplePods: FirehoseResult = { +export const samplePods: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -1258,7 +1258,7 @@ export const samplePods: FirehoseResult = { ], }; -export const sampleReplicationControllers: FirehoseResult = { +export const sampleReplicationControllers: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -1304,7 +1304,7 @@ export const sampleReplicationControllers: FirehoseResult = { ], }; -export const sampleReplicaSets: FirehoseResult = { +export const sampleReplicaSets: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -1394,7 +1394,7 @@ export const sampleReplicaSets: FirehoseResult = { ], }; -export const sampleServices: FirehoseResult = { +export const sampleServices: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -1511,7 +1511,7 @@ export const sampleServices: FirehoseResult = { }, ], }; -export const sampleRoutes: FirehoseResult = { +export const sampleRoutes: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -1629,7 +1629,7 @@ export const sampleRoutes: FirehoseResult = { ], }; -export const sampleBuildConfigs: FirehoseResult = { +export const sampleBuildConfigs: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -1822,7 +1822,7 @@ export const sampleBuildConfigs: FirehoseResult = { ], }; -export const sampleBuilds: FirehoseResult = { +export const sampleBuilds: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -1919,7 +1919,7 @@ export const sampleBuilds: FirehoseResult = { ], }; -export const sampleDaemonSets: FirehoseResult = { +export const sampleDaemonSets: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -1995,7 +1995,7 @@ export const sampleDaemonSets: FirehoseResult = { ], }; -export const sampleStatefulSets: FirehoseResult = { +export const sampleStatefulSets: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -2139,7 +2139,7 @@ export const sampleStatefulSets: FirehoseResult = { ], }; -export const sampleJobs: FirehoseResult = { +export const sampleJobs: WatchK8sResultsObject = { data: [ { kind: 'Job', @@ -2623,7 +2623,7 @@ export const sampleJobs: FirehoseResult = { loadError: '', }; -export const sampleCronJobs: FirehoseResult = { +export const sampleCronJobs: WatchK8sResultsObject = { data: [ { kind: 'CronJob', @@ -2800,7 +2800,7 @@ export const sampleCronJobs: FirehoseResult = { loadError: '', }; -export const samplePipeline: FirehoseResult = { +export const samplePipeline: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -2832,7 +2832,7 @@ export const samplePipeline: FirehoseResult = { ], }; -export const samplePipelineRun: FirehoseResult = { +export const samplePipelineRun: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -2909,7 +2909,7 @@ export const samplePipelineRun: FirehoseResult = { ], }; -export const sampleClusterServiceVersions: FirehoseResult = { +export const sampleClusterServiceVersions: WatchK8sResultsObject = { data: [ { apiVersion: 'operators.coreos.com/v1alpha1', diff --git a/frontend/packages/console-shared/src/utils/utils.ts b/frontend/packages/console-shared/src/utils/utils.ts index 3e718ccfed2..e5a933bd096 100644 --- a/frontend/packages/console-shared/src/utils/utils.ts +++ b/frontend/packages/console-shared/src/utils/utils.ts @@ -1,7 +1,7 @@ import i18next from 'i18next'; import type { JSONSchema7 } from 'json-schema'; import { startCase, toPath } from 'lodash'; -import type { FirehoseResult } from '@console/internal/components/utils/types'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; import type { K8sKind, K8sResourceKind } from '@console/internal/module/k8s'; import { modelFor } from '@console/internal/module/k8s'; import { getUID } from '../selectors/common'; @@ -22,7 +22,7 @@ export const createBasicLookup = (list: A[], getKey: KeyResolver): EntityM }; export const createLookup = ( - loadingList: FirehoseResult, + loadingList: WatchK8sResultsObject, getKey?: KeyResolver, ): K8sEntityMap => { if (loadingList && loadingList.loaded) { diff --git a/frontend/packages/container-security/src/components/ImageVulnerabilitiesList.tsx b/frontend/packages/container-security/src/components/ImageVulnerabilitiesList.tsx index 1772b274b9b..1e4ac24fba5 100644 --- a/frontend/packages/container-security/src/components/ImageVulnerabilitiesList.tsx +++ b/frontend/packages/container-security/src/components/ImageVulnerabilitiesList.tsx @@ -2,9 +2,8 @@ import type { FC } from 'react'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router'; -import type { RowFilter } from '@console/dynamic-plugin-sdk'; +import type { RowFilter, WatchK8sResults } from '@console/dynamic-plugin-sdk'; import { MultiListPage } from '@console/internal/components/factory'; -import type { FirehoseResourcesResult } from '@console/internal/components/utils'; import { referenceForModel } from '@console/internal/module/k8s'; import { Priority, priorityFor } from '../const'; import { ImageManifestVulnModel } from '../models'; @@ -76,9 +75,7 @@ const ImageVulnerabilitiesList: FC = (props) => { }, ]} title={t('container-security~Vulnerabilities')} - flatten={( - resources: FirehoseResourcesResult<{ imageVulnerabilities: ImageManifestVuln }>, - ) => { + flatten={(resources: WatchK8sResults<{ imageVulnerabilities: ImageManifestVuln }>) => { return _.sortBy( _.flatten( (resources?.imageVulnerabilities?.data?.spec?.features ?? []).map((feature) => diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SecretsSection.spec.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SecretsSection.spec.tsx index cf045280371..df0d8aec088 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SecretsSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SecretsSection.spec.tsx @@ -8,12 +8,6 @@ import store from '@console/internal/redux'; import type { SecretsSectionFormData } from '../SecretsSection'; import SecretsSection from '../SecretsSection'; -// Skip Firehose fetching and render just the children -jest.mock('@console/internal/components/utils/firehose', () => ({ - ...jest.requireActual('@console/internal/components/utils/firehose'), - Firehose: ({ children }) => children, -})); - interface WrapperProps extends FormikConfig { children?: ReactNode; } diff --git a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SourceSection.spec.tsx b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SourceSection.spec.tsx index 2a03b3ae268..3dc06f8839c 100644 --- a/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SourceSection.spec.tsx +++ b/frontend/packages/dev-console/src/components/buildconfig/sections/__tests__/SourceSection.spec.tsx @@ -12,12 +12,6 @@ import { BuildStrategyType } from '../../types'; import type { SourceSectionFormData } from '../SourceSection'; import SourceSection from '../SourceSection'; -// Skip Firehose fetching and render just the children -jest.mock('@console/internal/components/utils/firehose', () => ({ - ...jest.requireActual('@console/internal/components/utils/firehose'), - Firehose: ({ children }) => children, -})); - // Skip network calls to any external git service jest.mock('@console/git-service', () => ({ ...jest.requireActual('@console/git-service'), diff --git a/frontend/packages/dev-console/src/components/catalog/providers/useTemplates.tsx b/frontend/packages/dev-console/src/components/catalog/providers/useTemplates.tsx index 6a6cb5743fe..602cd5774e6 100644 --- a/frontend/packages/dev-console/src/components/catalog/providers/useTemplates.tsx +++ b/frontend/packages/dev-console/src/components/catalog/providers/useTemplates.tsx @@ -79,9 +79,8 @@ const useTemplates: ExtensionHook[]> = ({ const [projectTemplatesLoaded, setProjectTemplatesLoaded] = useState(false); const [projectTemplatesError, setProjectTemplatesError] = useState(); - // Load templates from the shared `openshift` namespace. Don't use Firehose - // for templates so that we can request only metadata. This keeps the request - // much smaller. + // Load templates from the shared `openshift` namespace. + // Request only metadata to keep the request much smaller. useEffect(() => { k8sListPartialMetadata(TemplateModel, { ns: 'openshift' }) .then((metadata) => { diff --git a/frontend/packages/dev-console/src/components/dropdown/SourceSecretDropdown.tsx b/frontend/packages/dev-console/src/components/dropdown/SourceSecretDropdown.tsx index 83ec899c14b..c6b2ba23ee6 100644 --- a/frontend/packages/dev-console/src/components/dropdown/SourceSecretDropdown.tsx +++ b/frontend/packages/dev-console/src/components/dropdown/SourceSecretDropdown.tsx @@ -1,7 +1,9 @@ import type { FC } from 'react'; +import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { Firehose } from '@console/internal/components/utils/firehose'; +import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; import { SecretModel } from '@console/internal/models'; +import type { SecretKind } from '@console/internal/module/k8s'; import type { ResourceDropdownProps } from '@console/shared/src/components/dropdown/ResourceDropdown'; import { ResourceDropdown } from '@console/shared/src/components/dropdown/ResourceDropdown'; @@ -12,26 +14,39 @@ interface SourceSecretDropdownProps const SourceSecretDropdown: FC = (props) => { const { t } = useTranslation(); - const filterData = (item) => { + const filterData = (item: SecretKind) => { return item.type === 'kubernetes.io/basic-auth' || item.type === 'kubernetes.io/ssh-auth'; }; - const resources = [ - { - isList: true, - namespace: props.namespace, - kind: SecretModel.kind, - prop: 'secrets', - }, - ]; + + const [secrets, secretsLoaded, secretsLoadError] = useK8sWatchResource({ + isList: true, + namespace: props.namespace, + kind: SecretModel.kind, + optional: true, + }); + + const resources = useMemo( + () => [ + { + data: secrets, + loaded: secretsLoaded, + loadError: secretsLoadError, + kind: SecretModel.kind, + }, + ], + [secrets, secretsLoaded, secretsLoadError], + ); + return ( - - - + ); }; diff --git a/frontend/packages/dev-console/src/components/edit-application/edit-application-types.ts b/frontend/packages/dev-console/src/components/edit-application/edit-application-types.ts index 202fbe164a2..25467571c4d 100644 --- a/frontend/packages/dev-console/src/components/edit-application/edit-application-types.ts +++ b/frontend/packages/dev-console/src/components/edit-application/edit-application-types.ts @@ -1,16 +1,16 @@ -import type { FirehoseResult } from '@console/internal/components/utils'; +import type { K8sResourceCommon, WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; import type { K8sResourceKind } from '@console/internal/module/k8s'; import type { PipelineKind } from '../../types/pipeline'; export interface AppResources { - service?: FirehoseResult; - route?: FirehoseResult; - buildConfig?: FirehoseResult; - shipwrightBuild?: FirehoseResult; - pipeline?: FirehoseResult; - imageStream?: FirehoseResult; - editAppResource?: FirehoseResult; - imageStreams?: FirehoseResult; + service?: WatchK8sResultsObject; + route?: WatchK8sResultsObject; + buildConfig?: WatchK8sResultsObject; + shipwrightBuild?: WatchK8sResultsObject; + pipeline?: WatchK8sResultsObject; + imageStream?: WatchK8sResultsObject; + editAppResource?: WatchK8sResultsObject; + imageStreams?: WatchK8sResultsObject; } export interface EditApplicationProps { diff --git a/frontend/packages/dev-console/src/components/health-checks/AddHealthChecksForm.tsx b/frontend/packages/dev-console/src/components/health-checks/AddHealthChecksForm.tsx index a9f0e02ca6e..7c009925bc9 100644 --- a/frontend/packages/dev-console/src/components/health-checks/AddHealthChecksForm.tsx +++ b/frontend/packages/dev-console/src/components/health-checks/AddHealthChecksForm.tsx @@ -5,7 +5,7 @@ import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; import * as yup from 'yup'; -import type { FirehoseResult } from '@console/internal/components/utils'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; import { LoadingBox, StatusBox } from '@console/internal/components/utils'; import type { K8sResourceKind } from '@console/internal/module/k8s'; import { k8sUpdate, modelFor, referenceFor } from '@console/internal/module/k8s'; @@ -16,7 +16,7 @@ import { healthChecksProbesValidationSchema } from './health-checks-probe-valida import { updateHealthChecksProbe } from './health-checks-utils'; type AddHealthChecksFormProps = { - resource?: FirehoseResult; + resource: WatchK8sResultsObject; currentContainer: string; }; diff --git a/frontend/packages/dev-console/src/utils/imagestream-utils.ts b/frontend/packages/dev-console/src/utils/imagestream-utils.ts index 760d0fb24a9..9eef5f8e8e6 100644 --- a/frontend/packages/dev-console/src/utils/imagestream-utils.ts +++ b/frontend/packages/dev-console/src/utils/imagestream-utils.ts @@ -1,6 +1,7 @@ import type { TFunction } from 'i18next'; import * as _ from 'lodash'; import * as semver from 'semver'; +import type { WatchK8sResourceWithProp } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import { getImageStreamIcon, getImageForIconClass, @@ -10,7 +11,6 @@ import { getMostRecentBuilderTag, getBuilderTagsSortedByVersion, } from '@console/internal/components/image-stream'; -import type { FirehoseResource } from '@console/internal/components/utils'; import { ProjectModel, ImageStreamModel } from '@console/internal/models'; import type { ContainerPort, @@ -190,7 +190,7 @@ export const getImageStreamTags = (imageStream: K8sResourceKind) => { }, {}); }; -export const getProjectResource = (): FirehoseResource[] => { +export const getProjectResource = (): WatchK8sResourceWithProp[] => { return [ { isList: true, @@ -200,7 +200,7 @@ export const getProjectResource = (): FirehoseResource[] => { ]; }; -export const getImageStreamResource = (namespace: string): FirehoseResource[] => { +export const getImageStreamResource = (namespace: string): WatchK8sResourceWithProp[] => { const resource = []; if (namespace) { resource.push({ diff --git a/frontend/packages/helm-plugin/src/components/__tests__/helm-release-mock-data.ts b/frontend/packages/helm-plugin/src/components/__tests__/helm-release-mock-data.ts index e1696d2b666..bd97a62af82 100644 --- a/frontend/packages/helm-plugin/src/components/__tests__/helm-release-mock-data.ts +++ b/frontend/packages/helm-plugin/src/components/__tests__/helm-release-mock-data.ts @@ -1,5 +1,5 @@ -import type { FirehoseResourcesResult } from '@console/internal/components/utils/types'; -import type { K8sResourceCommon, K8sResourceKind } from '@console/internal/module/k8s'; +import type { K8sResourceCommon, WatchK8sResults } from '@console/dynamic-plugin-sdk'; +import type { K8sResourceKind } from '@console/internal/module/k8s'; import type { HelmRelease, HelmChartMetaData, HelmChartEntries } from '../../types/helm-types'; /* eslint-disable @typescript-eslint/naming-convention */ @@ -260,7 +260,7 @@ export const mockChartEntries1: HelmChartEntries = { 'rh-hazelcast-enterprise--redhat-helm-repo': mockRedhatHelmChartData, }; -export const mockReleaseResources: FirehoseResourcesResult<{ +export const mockReleaseResources: WatchK8sResults<{ Deployment: K8sResourceCommon; StatefulSet: K8sResourceCommon; Pod: K8sResourceCommon; diff --git a/frontend/packages/helm-plugin/src/components/details-page/HelmReleaseDetails.tsx b/frontend/packages/helm-plugin/src/components/details-page/HelmReleaseDetails.tsx index 8b42fc89731..6045a18e0a6 100755 --- a/frontend/packages/helm-plugin/src/components/details-page/HelmReleaseDetails.tsx +++ b/frontend/packages/helm-plugin/src/components/details-page/HelmReleaseDetails.tsx @@ -4,9 +4,9 @@ import { Badge } from '@patternfly/react-core'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { useParams, useLocation } from 'react-router'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; import { ErrorPage404 } from '@console/internal/components/error'; import { DetailsPage } from '@console/internal/components/factory'; -import type { FirehoseResult } from '@console/internal/components/utils'; import { navFactory, LoadingBox, StatusBox } from '@console/internal/components/utils'; import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; import { SecretModel } from '@console/internal/models'; @@ -23,7 +23,7 @@ import HelmReleaseResources from './resources/HelmReleaseResources'; const SecretReference: K8sResourceKindReference = 'Secret'; const HelmReleaseReference = 'HelmRelease'; interface HelmReleaseDetailsProps { - secrets?: FirehoseResult; + secrets?: WatchK8sResultsObject; } interface LoadedHelmReleaseDetailsProps extends HelmReleaseDetailsProps { @@ -202,13 +202,13 @@ const HelmReleaseDetails: FC = () => { data: helmReleaseData, }; - const secretsFirehoseResult: FirehoseResult = { + const secretsResult: WatchK8sResultsObject = { loaded: secretLoaded, loadError: secretLoadError, data: secrets, }; - return ; + return ; }; export default HelmReleaseDetails; diff --git a/frontend/packages/helm-plugin/src/components/details-page/__tests__/HelmReleaseDetails.spec.tsx b/frontend/packages/helm-plugin/src/components/details-page/__tests__/HelmReleaseDetails.spec.tsx index 87981d1e98a..a1dffd2f20d 100644 --- a/frontend/packages/helm-plugin/src/components/details-page/__tests__/HelmReleaseDetails.spec.tsx +++ b/frontend/packages/helm-plugin/src/components/details-page/__tests__/HelmReleaseDetails.spec.tsx @@ -26,11 +26,6 @@ jest.mock('@console/shared/src/hooks/useClusterVersion', () => ({ useClusterVersion: jest.fn(), })); -jest.mock('@console/internal/components/utils/firehose', () => ({ - ...jest.requireActual('@console/internal/components/utils/firehose'), - Firehose: ({ children }) => children, -})); - describe('HelmReleaseDetails', () => { beforeEach(() => { helmReleaseDetailsProps = { diff --git a/frontend/packages/helm-plugin/src/components/details-page/resources/HelmReleaseResources.tsx b/frontend/packages/helm-plugin/src/components/details-page/resources/HelmReleaseResources.tsx index 3300a3e67f5..b5658d73b74 100644 --- a/frontend/packages/helm-plugin/src/components/details-page/resources/HelmReleaseResources.tsx +++ b/frontend/packages/helm-plugin/src/components/details-page/resources/HelmReleaseResources.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router'; +import type { WatchK8sResourceWithProp } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import { MultiListPage } from '@console/internal/components/factory'; -import type { FirehoseResource } from '@console/internal/components/utils'; import type { K8sResourceKind } from '@console/internal/module/k8s'; import { referenceFor, modelFor, referenceForModel } from '@console/internal/module/k8s'; import type { HelmRelease } from '../../../types/helm-types'; @@ -18,7 +18,7 @@ const HelmReleaseResources: FC = ({ customData }) => const params = useParams(); const namespace = params.ns; const helmManifestResources = loadHelmManifestResources(customData); - const firehoseResources: FirehoseResource[] = helmManifestResources.map( + const watchResources: WatchK8sResourceWithProp[] = helmManifestResources.map( (resource: K8sResourceKind) => { const resourceKind = referenceFor(resource); const model = modelFor(resourceKind); @@ -34,7 +34,7 @@ const HelmReleaseResources: FC = ({ customData }) => ); return ( { // mockHelmReleases[0] has an empty manifest, so no resources to watch renderWithProviders(); - // Verify useK8sWatchResources hook was called (confirms migration from Firehose to hooks) + // Verify useK8sWatchResources hook was called expect(mockUseK8sWatchResources).toHaveBeenCalled(); // Verify empty state message is displayed (user-visible content) diff --git a/frontend/packages/helm-plugin/src/topology/__tests__/helm-data-transformer.spec.ts b/frontend/packages/helm-plugin/src/topology/__tests__/helm-data-transformer.spec.ts index 379d357994c..8b96de38faf 100644 --- a/frontend/packages/helm-plugin/src/topology/__tests__/helm-data-transformer.spec.ts +++ b/frontend/packages/helm-plugin/src/topology/__tests__/helm-data-transformer.spec.ts @@ -40,11 +40,11 @@ export function getTransformedTopologyData(mockData: TopologyDataResources) { app: 'nodejs', 'app.kubernetes.io/part-of': 'app-1', }; - const fireHoseDcs = { + const modifiedDcs = { ...sampleDeploymentConfigs, data: [dc, sampleHelmChartDeploymentConfig], }; - const data = { ...mockData, deploymentConfigs: fireHoseDcs }; + const data = { ...mockData, deploymentConfigs: modifiedDcs }; const workloadResources = getWorkloadResources(data, TEST_KINDS_MAP, WORKLOAD_TYPES); const model = getHelmGraphModelFromMap(sampleHelmResourcesMap, data); diff --git a/frontend/packages/knative-plugin/src/components/sink-pubsub/SinkPubsubModal.tsx b/frontend/packages/knative-plugin/src/components/sink-pubsub/SinkPubsubModal.tsx index cbe20343bb5..71abae93597 100644 --- a/frontend/packages/knative-plugin/src/components/sink-pubsub/SinkPubsubModal.tsx +++ b/frontend/packages/knative-plugin/src/components/sink-pubsub/SinkPubsubModal.tsx @@ -5,14 +5,15 @@ import type { FormikProps, FormikValues } from 'formik'; import * as fuzzy from 'fuzzysearch'; import { Trans, useTranslation } from 'react-i18next'; import FormSection from '@console/dev-console/src/components/import/section/FormSection'; -import type { FirehoseResult } from '@console/internal/components/utils/types'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; +import type { K8sResourceKind } from '@console/internal/module/k8s'; import { ResourceDropdownField } from '@console/shared'; import { ModalFooterWithAlerts } from '@console/shared/src/components/modals/ModalFooterWithAlerts'; import { craftResourceKey } from '../pub-sub/pub-sub-utils'; export interface SinkPubsubModalProps { resourceName: string; - resourceDropdown: FirehoseResult[]; + resourceDropdown: WatchK8sResultsObject[]; labelTitle: string; cancel?: () => void; } diff --git a/frontend/packages/knative-plugin/src/topology/__tests__/topology-knative-test-data.ts b/frontend/packages/knative-plugin/src/topology/__tests__/topology-knative-test-data.ts index 8f7cb4afe67..4f69ebb8188 100644 --- a/frontend/packages/knative-plugin/src/topology/__tests__/topology-knative-test-data.ts +++ b/frontend/packages/knative-plugin/src/topology/__tests__/topology-knative-test-data.ts @@ -1,4 +1,4 @@ -import type { FirehoseResult } from '@console/internal/components/utils'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; import type { DeploymentKind, PodKind, K8sResourceKind } from '@console/internal/module/k8s'; import { K8sResourceConditionStatus, referenceForModel } from '@console/internal/module/k8s'; import type { TopologyDataResources } from '@console/topology/src/topology-types'; @@ -41,7 +41,7 @@ import { URI_KIND } from '../const'; import type { KnativeServiceOverviewItem, KnativeTopologyDataObject } from '../topology-types'; import { NodeType } from '../topology-types'; -export const sampleDeploymentsCamelConnector: FirehoseResult = { +export const sampleDeploymentsCamelConnector: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -190,7 +190,7 @@ export const sampleDeploymentsCamelConnector: FirehoseResult = ], }; -export const sampleKnativeDeployments: FirehoseResult = { +export const sampleKnativeDeployments: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -332,7 +332,7 @@ export const sampleKnativeDeployments: FirehoseResult = { ], }; -export const sampleKnativeReplicaSets: FirehoseResult = { +export const sampleKnativeReplicaSets: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -386,43 +386,43 @@ export const sampleKnativeReplicaSets: FirehoseResult = { ], }; -export const sampleKnativePods: FirehoseResult = { +export const sampleKnativePods: WatchK8sResultsObject = { loaded: true, loadError: '', data: [], }; -export const sampleKnativeReplicationControllers: FirehoseResult = { +export const sampleKnativeReplicationControllers: WatchK8sResultsObject = { loaded: true, loadError: '', data: [], }; -export const sampleKnativeDeploymentConfigs: FirehoseResult = { +export const sampleKnativeDeploymentConfigs: WatchK8sResultsObject = { loaded: true, loadError: '', data: [], }; -export const sampleRoutes: FirehoseResult = { +export const sampleRoutes: WatchK8sResultsObject = { loaded: true, loadError: '', data: [], }; -const sampleKnativeBuildConfigs: FirehoseResult = { +const sampleKnativeBuildConfigs: WatchK8sResultsObject = { loaded: true, loadError: '', data: [], }; -const sampleKnativeBuilds: FirehoseResult = { +const sampleKnativeBuilds: WatchK8sResultsObject = { loaded: true, loadError: '', data: [], }; -export const sampleKnativeBuildConfigs2: FirehoseResult = { +export const sampleKnativeBuildConfigs2: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -521,7 +521,7 @@ export const sampleKnativeBuildConfigs2: FirehoseResult = { ], }; -export const sampleKnativeConfigurations: FirehoseResult = { +export const sampleKnativeConfigurations: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -619,7 +619,7 @@ export const revisionObj: RevisionKind = { ], }, }; -export const sampleKnativeRevisions: FirehoseResult = { +export const sampleKnativeRevisions: WatchK8sResultsObject = { loaded: true, loadError: '', data: [revisionObj], @@ -662,7 +662,7 @@ export const knativeRouteObj: RouteKind = { }, }; -export const sampleKnativeRoutes: FirehoseResult = { +export const sampleKnativeRoutes: WatchK8sResultsObject = { loaded: true, loadError: '', data: [knativeRouteObj], @@ -715,7 +715,7 @@ export const serverlessFunctionObj = { metadata: { ...knativeServiceObj.metadata, labels: { [SERVERLESS_FUNCTION_LABEL]: 'true' } }, }; -export const sampleKnativeServices: FirehoseResult = { +export const sampleKnativeServices: WatchK8sResultsObject = { loaded: true, loadError: '', data: [knativeServiceObj], @@ -725,7 +725,7 @@ export const getEventSourceResponse = ( apiGroup: string, apiVersion: string, kind: string, -): FirehoseResult => { +): WatchK8sResultsObject => { return { loaded: true, loadError: '', @@ -785,7 +785,7 @@ export const kafkaConnectionData = { ], }; -export const sampleEventSourceSinkbinding: FirehoseResult = { +export const sampleEventSourceSinkbinding: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -821,7 +821,7 @@ export const sampleEventSourceSinkbinding: FirehoseResult = { ], }; -export const sampleSourceKameletBinding: FirehoseResult = { +export const sampleSourceKameletBinding: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -868,7 +868,7 @@ export const sampleSourceKameletBinding: FirehoseResult = { ], }; -export const sampleSourceKafkaSink: FirehoseResult = { +export const sampleSourceKafkaSink: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -906,7 +906,7 @@ export const sampleSourceKafkaSink: FirehoseResult = { ], }; -export const sampleDomainMapping: FirehoseResult = { +export const sampleDomainMapping: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -928,7 +928,7 @@ export const sampleDomainMapping: FirehoseResult = { ], }; -export const sampleServices: FirehoseResult = { +export const sampleServices: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -1007,7 +1007,7 @@ export const sampleServices: FirehoseResult = { ], }; -export const sampleClusterServiceVersions: FirehoseResult = { +export const sampleClusterServiceVersions: WatchK8sResultsObject = { loaded: true, loadError: '', data: [], @@ -1046,7 +1046,7 @@ export const knativeTopologyDataModel = { }, }; -export const sampleEventSourceDeployments: FirehoseResult = { +export const sampleEventSourceDeployments: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ @@ -1170,7 +1170,7 @@ export const EventIMCObj: EventChannelKind = { }, }; -export const sampleKnativeChannels: FirehoseResult = { +export const sampleKnativeChannels: WatchK8sResultsObject = { loaded: true, loadError: '', data: [EventIMCObj], @@ -1223,19 +1223,19 @@ export const EventTriggerObj: EventTriggerKind = { }, }; -const sampleBrokers: FirehoseResult = { +const sampleBrokers: WatchK8sResultsObject = { loaded: true, loadError: '', data: [EventBrokerObj], }; -const sampleTriggers: FirehoseResult = { +const sampleTriggers: WatchK8sResultsObject = { loaded: true, loadError: '', data: [EventTriggerObj], }; -const sampleKamelets: FirehoseResult = { +const sampleKamelets: WatchK8sResultsObject = { loaded: true, loadError: '', data: [], diff --git a/frontend/packages/knative-plugin/src/utils/__tests__/get-knative-resources.spec.ts b/frontend/packages/knative-plugin/src/utils/__tests__/get-knative-resources.spec.ts index d4b18847878..2a02313cb3c 100644 --- a/frontend/packages/knative-plugin/src/utils/__tests__/get-knative-resources.spec.ts +++ b/frontend/packages/knative-plugin/src/utils/__tests__/get-knative-resources.spec.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import type { FirehoseResource } from '@console/internal/components/utils'; +import type { WatchK8sResourceWithProp } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import { MockResources } from '@console/shared/src/utils/__tests__/test-resource-data'; import { knativeServiceObj, @@ -163,7 +163,7 @@ describe('Get knative resources', () => { describe('knative Serving Resources', () => { const SAMPLE_NAMESPACE = 'mynamespace'; it('expect knativeServingResource to return service with proper namespace', () => { - const serviceServingResource: FirehoseResource[] = knativeServingResourcesServices( + const serviceServingResource: WatchK8sResourceWithProp[] = knativeServingResourcesServices( SAMPLE_NAMESPACE, ); expect(serviceServingResource).toHaveLength(1); @@ -172,7 +172,7 @@ describe('Get knative resources', () => { }); it('expect knativeServingResourcesRevision to return revision with proper namespace', () => { - const revisionServingResource: FirehoseResource[] = knativeServingResourcesRevision( + const revisionServingResource: WatchK8sResourceWithProp[] = knativeServingResourcesRevision( SAMPLE_NAMESPACE, ); expect(revisionServingResource).toHaveLength(1); @@ -181,7 +181,7 @@ describe('Get knative resources', () => { }); it('expect knativeServingResourcesConfigurations to return configurations with proper namespace', () => { - const configServingResource: FirehoseResource[] = knativeServingResourcesConfigurations( + const configServingResource: WatchK8sResourceWithProp[] = knativeServingResourcesConfigurations( SAMPLE_NAMESPACE, ); expect(configServingResource).toHaveLength(1); @@ -190,7 +190,7 @@ describe('Get knative resources', () => { }); it('expect knativeServingResourcesRoutes to return routes with proper namespace', () => { - const routeServingResource: FirehoseResource[] = knativeServingResourcesRoutes( + const routeServingResource: WatchK8sResourceWithProp[] = knativeServingResourcesRoutes( SAMPLE_NAMESPACE, ); expect(routeServingResource).toHaveLength(1); diff --git a/frontend/packages/knative-plugin/src/utils/get-knative-resources.ts b/frontend/packages/knative-plugin/src/utils/get-knative-resources.ts index 3ce5937ecbd..c7be475fd43 100644 --- a/frontend/packages/knative-plugin/src/utils/get-knative-resources.ts +++ b/frontend/packages/knative-plugin/src/utils/get-knative-resources.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash'; import type { WatchK8sResources, WatchK8sResourcesGeneric } from '@console/dynamic-plugin-sdk'; -import type { FirehoseResource } from '@console/internal/components/utils'; +import type { WatchK8sResourceWithProp } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import type { K8sResourceKind, PodKind } from '@console/internal/module/k8s'; import { referenceForModel } from '@console/internal/module/k8s'; import { GLOBAL_OPERATOR_NS, KNATIVE_SERVING_LABEL } from '../const'; @@ -116,7 +116,7 @@ export const getKnativeServingServices = (dc: K8sResourceKind, props): KnativeIt return ksservices && ksservices.length > 0 ? { ksservices } : undefined; }; -export const knativeServingResourcesRevision = (namespace: string): FirehoseResource[] => { +export const knativeServingResourcesRevision = (namespace: string): WatchK8sResourceWithProp[] => { const knativeResource = [ { isList: true, @@ -129,7 +129,9 @@ export const knativeServingResourcesRevision = (namespace: string): FirehoseReso return knativeResource; }; -export const knativeServingResourcesConfigurations = (namespace: string): FirehoseResource[] => { +export const knativeServingResourcesConfigurations = ( + namespace: string, +): WatchK8sResourceWithProp[] => { const knativeResource = [ { isList: true, @@ -142,7 +144,7 @@ export const knativeServingResourcesConfigurations = (namespace: string): Fireho return knativeResource; }; -export const knativeServingResourcesRoutes = (namespace: string): FirehoseResource[] => { +export const knativeServingResourcesRoutes = (namespace: string): WatchK8sResourceWithProp[] => { const knativeResource = [ { isList: true, @@ -155,7 +157,7 @@ export const knativeServingResourcesRoutes = (namespace: string): FirehoseResour return knativeResource; }; -export const k8sServices = (namespace: string, limit?: number): FirehoseResource[] => { +export const k8sServices = (namespace: string, limit?: number): WatchK8sResourceWithProp[] => { const knativeResource = [ { isList: true, @@ -172,7 +174,7 @@ export const k8sServices = (namespace: string, limit?: number): FirehoseResource export const knativeServingResourcesServices = ( namespace: string, limit?: number, -): FirehoseResource[] => { +): WatchK8sResourceWithProp[] => { const knativeResource = [ { isList: true, @@ -186,7 +188,10 @@ export const knativeServingResourcesServices = ( return knativeResource; }; -export const knativeKafkaSinks = (namespace: string, limit?: number): FirehoseResource[] => { +export const knativeKafkaSinks = ( + namespace: string, + limit?: number, +): WatchK8sResourceWithProp[] => { const knativeResource = [ { isList: true, @@ -200,7 +205,9 @@ export const knativeKafkaSinks = (namespace: string, limit?: number): FirehoseRe return knativeResource; }; -export const knativeEventingResourcesSubscription = (namespace: string): FirehoseResource[] => { +export const knativeEventingResourcesSubscription = ( + namespace: string, +): WatchK8sResourceWithProp[] => { const knativeResource = [ { isList: true, @@ -216,7 +223,7 @@ export const knativeEventingResourcesSubscription = (namespace: string): Firehos export const knativeEventingResourcesBroker = ( namespace: string, limit?: number, -): FirehoseResource[] => { +): WatchK8sResourceWithProp[] => { const knativeResource = [ { isList: true, @@ -426,7 +433,7 @@ export const getTrafficByRevision = (revName: string, service: K8sResourceKind) }; }; -export const getSinkableResources = (namespace: string): FirehoseResource[] => { +export const getSinkableResources = (namespace: string): WatchK8sResourceWithProp[] => { return namespace ? [ ...k8sServices(namespace), diff --git a/frontend/packages/knative-plugin/src/utils/traffic-splitting-utils.ts b/frontend/packages/knative-plugin/src/utils/traffic-splitting-utils.ts index ab62c8b1a85..4418126a488 100644 --- a/frontend/packages/knative-plugin/src/utils/traffic-splitting-utils.ts +++ b/frontend/packages/knative-plugin/src/utils/traffic-splitting-utils.ts @@ -1,4 +1,4 @@ -import type { FirehoseResource } from '@console/internal/components/utils'; +import type { WatchK8sResourceWithProp } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import type { K8sResourceKind, Patch } from '@console/internal/module/k8s'; import type { Traffic } from '../types'; import { @@ -25,7 +25,9 @@ export const trafficDataForPatch = (traffic: Traffic[], service: K8sResourceKind }, ]; -export const knativeServingResourcesTrafficSplitting = (namespace: string): FirehoseResource[] => [ +export const knativeServingResourcesTrafficSplitting = ( + namespace: string, +): WatchK8sResourceWithProp[] => [ ...knativeServingResourcesRevision(namespace), ...knativeServingResourcesConfigurations(namespace), ]; diff --git a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/BareMetalHostDetailsPage.tsx b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/BareMetalHostDetailsPage.tsx index 80f14010729..0ba242a29d0 100644 --- a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/BareMetalHostDetailsPage.tsx +++ b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/BareMetalHostDetailsPage.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react'; import { useTranslation } from 'react-i18next'; +import type { WatchK8sResourceWithProp } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import { ResourceEventStream } from '@console/internal/components/events'; import { DetailsPage } from '@console/internal/components/factory'; -import type { FirehoseResource } from '@console/internal/components/utils'; import { navFactory } from '@console/internal/components/utils'; import { MachineModel, MachineSetModel, NodeModel } from '@console/internal/models'; import type { @@ -45,7 +45,7 @@ const BareMetalHostDetailsPage: FC = (props) => { const { t } = useTranslation(); const [maintenanceModel] = useMaintenanceCapability(); const bmoEnabled = useFlag(BMO_ENABLED_FLAG); - const resources: FirehoseResource[] = [ + const resources: WatchK8sResourceWithProp[] = [ { kind: referenceForModel(MachineModel), namespaced: true, diff --git a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/BareMetalHostsPage.tsx b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/BareMetalHostsPage.tsx index 2d8704f9555..4dc7dc26d1c 100644 --- a/frontend/packages/metal3-plugin/src/components/baremetal-hosts/BareMetalHostsPage.tsx +++ b/frontend/packages/metal3-plugin/src/components/baremetal-hosts/BareMetalHostsPage.tsx @@ -2,10 +2,16 @@ import type { FC } from 'react'; import type { TFunction } from 'i18next'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; +import type { WatchK8sResourceWithProp } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import { MultiListPage } from '@console/internal/components/factory'; -import type { FirehoseResource, FirehoseResult } from '@console/internal/components/utils'; import { MachineModel, MachineSetModel, NodeModel } from '@console/internal/models'; -import type { MachineKind, MachineSetKind, NodeKind } from '@console/internal/module/k8s'; +import type { + K8sResourceCommon, + MachineKind, + MachineSetKind, + NodeKind, +} from '@console/internal/module/k8s'; import { referenceForModel } from '@console/internal/module/k8s'; import { getName, createLookup, getNodeMachineName } from '@console/shared'; import { useMaintenanceCapability } from '../../hooks/useMaintenanceCapability'; @@ -19,19 +25,17 @@ import BareMetalHostsTable from './BareMetalHostsTable'; import { hostStatusFilter } from './table-filters'; type Resources = { - hosts: FirehoseResult; - machines: FirehoseResult; - machineSets: FirehoseResult; - nodes: FirehoseResult; - nodeMaintenances: FirehoseResult; + hosts: WatchK8sResultsObject; + machines: WatchK8sResultsObject; + machineSets: WatchK8sResultsObject; + nodes: WatchK8sResultsObject; + nodeMaintenances: WatchK8sResultsObject; }; const flattenResources = (resources: Resources) => { // TODO(jtomasek): Remove loaded check once ListPageWrapper_ is updated to call flatten only // when resources are loaded - const loaded = _.every(resources, (resource) => - resource.optional ? resource.loaded || !_.isEmpty(resource.loadError) : resource.loaded, - ); + const loaded = _.every(resources, (resource) => resource.loaded); if (loaded) { const { hosts, machines, machineSets, nodes, nodeMaintenances } = resources; @@ -100,7 +104,7 @@ const BareMetalHostsPage: FC = (props) => { const { t } = useTranslation(); const [model] = useMaintenanceCapability(); const { namespace } = props; - const resources: FirehoseResource[] = [ + const resources: WatchK8sResourceWithProp[] = [ { kind: referenceForModel(BareMetalHostModel), namespaced: true, diff --git a/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx b/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx index d6675d4146d..d12af7128e6 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx @@ -23,7 +23,7 @@ import { sortable, wrappable } from '@patternfly/react-table'; import * as _ from 'lodash'; import { Trans, useTranslation } from 'react-i18next'; import { useParams, useLocation, Link } from 'react-router'; -import type { WatchK8sResource } from '@console/dynamic-plugin-sdk'; +import type { WatchK8sResource, WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; import { ResourceStatus, StatusIconAndText, @@ -36,7 +36,7 @@ import { Conditions, ConditionTypes } from '@console/internal/components/conditi import { ResourceEventStream } from '@console/internal/components/events'; import type { RowFunctionArgs, Flatten } from '@console/internal/components/factory'; import { DetailsPage, Table, TableData, MultiListPage } from '@console/internal/components/factory'; -import type { FirehoseResult, Page } from '@console/internal/components/utils'; +import type { Page } from '@console/internal/components/utils'; import { AsyncComponent, DOC_URL_OPERATORFRAMEWORK_SDK, @@ -1366,10 +1366,10 @@ export type ClusterServiceVersionsPageProps = { export type ClusterServiceVersionListProps = { loaded: boolean; - loadError?: string; + loadError?: unknown; data: ClusterServiceVersionKind[]; - subscriptions: FirehoseResult; - catalogSources: FirehoseResult; + subscriptions: WatchK8sResultsObject; + catalogSources: WatchK8sResultsObject; activeNamespace?: string; }; diff --git a/frontend/packages/operator-lifecycle-manager/src/components/k8s-resource.tsx b/frontend/packages/operator-lifecycle-manager/src/components/k8s-resource.tsx index 669759861a3..0f4c1638d06 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/k8s-resource.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/k8s-resource.tsx @@ -4,9 +4,9 @@ import { sortable } from '@patternfly/react-table'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router'; +import type { WatchK8sResourceWithProp } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import type { Flatten, RowFunctionArgs } from '@console/internal/components/factory'; import { MultiListPage, Table, TableData } from '@console/internal/components/factory'; -import type { FirehoseResource } from '@console/internal/components/utils'; import { ResourceLink, ConsoleEmptyState } from '@console/internal/components/utils'; import { ConfigMapModel, @@ -148,8 +148,8 @@ export const Resources: FC = (props) => { const { plural } = useParams(); const providedAPI = providedAPIForReference(props.customData, plural); - const firehoseResources = (providedAPI?.resources ?? DEFAULT_RESOURCES).map( - ({ name, kind, version }): FirehoseResource => { + const watchResources = (providedAPI?.resources ?? DEFAULT_RESOURCES).map( + ({ name, kind, version }): WatchK8sResourceWithProp => { const group = name ? name.substring(name.indexOf('.') + 1) : ''; const reference = group ? referenceForGroupVersionKind(group)(version)(kind) : kind; const model = modelFor(reference); @@ -172,13 +172,13 @@ export const Resources: FC = (props) => { return ( kindForReference(kind), - items: firehoseResources.map(({ kind }) => ({ + items: watchResources.map(({ kind }) => ({ id: kindForReference(kind), title: kindForReference(kind), })), diff --git a/frontend/packages/operator-lifecycle-manager/src/components/operand/index.tsx b/frontend/packages/operator-lifecycle-manager/src/components/operand/index.tsx index 737c9a3b403..100127eb01a 100644 --- a/frontend/packages/operator-lifecycle-manager/src/components/operand/index.tsx +++ b/frontend/packages/operator-lifecycle-manager/src/components/operand/index.tsx @@ -361,7 +361,7 @@ export const ProvidedAPIsPage = (props: ProvidedAPIsPageProps) => { const dispatch = useConsoleDispatch(); const [apiRefreshed, setAPIRefreshed] = useState(false); - // Map APIs provided by this CSV to Firehose resources. Exclude APIs that do not have a model. + // Map APIs provided by this CSV to watch resources. Exclude APIs that do not have a model. const providedAPIs = providedAPIsForCSV(obj); const owners = (ownerRefs: OwnerReference[], items: K8sResourceKind[]) => diff --git a/frontend/packages/topology/src/__tests__/topology-test-data.ts b/frontend/packages/topology/src/__tests__/topology-test-data.ts index 7980ffb6534..d0343c6452d 100644 --- a/frontend/packages/topology/src/__tests__/topology-test-data.ts +++ b/frontend/packages/topology/src/__tests__/topology-test-data.ts @@ -1,5 +1,5 @@ import type { Model } from '@patternfly/react-topology'; -import type { FirehoseResult } from '@console/internal/components/utils'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; import type { EventKind } from '@console/internal/module/k8s'; import { CamelKameletBindingModel, KafkaSinkModel } from '@console/knative-plugin'; import { sampleDeployments } from '@console/shared/src/utils/__tests__/test-resource-data'; @@ -203,7 +203,7 @@ export const sampleHelmResourcesMap = { }, }; -export const sampleEventsResource: FirehoseResult = { +export const sampleEventsResource: WatchK8sResultsObject = { loaded: true, loadError: '', data: [ diff --git a/frontend/public/components/__tests__/storage-class-form.spec.tsx b/frontend/public/components/__tests__/storage-class-form.spec.tsx index 9e50de81f16..43dca1e9551 100644 --- a/frontend/public/components/__tests__/storage-class-form.spec.tsx +++ b/frontend/public/components/__tests__/storage-class-form.spec.tsx @@ -11,11 +11,6 @@ jest.mock('react-router', () => ({ useNavigate: jest.fn(), })); -// Mock Firehose -jest.mock('../utils/firehose', () => ({ - Firehose: ({ children }) => children, -})); - describe('StorageClassForm', () => { let onClose: jest.Mock; diff --git a/frontend/public/components/cron-job.tsx b/frontend/public/components/cron-job.tsx index 7a4c462e7de..a7d47d92441 100644 --- a/frontend/public/components/cron-job.tsx +++ b/frontend/public/components/cron-job.tsx @@ -412,6 +412,9 @@ export const CronJobsPage: FC = (props) => ( export const CronJobsDetailsPage: FC = (props) => { const customActionMenu = (kindObj, obj) => { + if (!kindObj || !obj) { + return null; + } const resourceKind = referenceForModel(kindObj); const context = { [resourceKind]: obj }; return ( diff --git a/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/health-item.tsx b/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/health-item.tsx index 091f0cb37da..012510f4ee9 100644 --- a/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/health-item.tsx +++ b/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/health-item.tsx @@ -28,7 +28,6 @@ import { import { useDynamicK8sWatchResources } from '@console/shared/src/hooks/useDynamicK8sWatchResources'; import { useDashboardResources } from '@console/shared/src/hooks/useDashboardResources'; import { K8sKind } from '../../../../module/k8s'; -import { FirehoseResourcesResult } from '../../../utils/types'; import { AsyncComponent, LazyLoader } from '../../../utils/async'; import { resourcePath } from '../../../utils/resource-link'; import { useK8sWatchResource, useK8sWatchResources } from '../../../utils/k8s-watch-hook'; @@ -310,6 +309,6 @@ type ResourceHealthItemProps = { }; type OperatorsPopupProps = { - resources: FirehoseResourcesResult; + resources: WatchK8sResults; operatorSubsystems: ResolvedExtension['properties'][]; }; diff --git a/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/inventory-card.tsx b/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/inventory-card.tsx index 2258f1f205a..c62f5ab8ae8 100644 --- a/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/inventory-card.tsx +++ b/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/inventory-card.tsx @@ -28,7 +28,7 @@ const mergeItems = ( (item) => replacements.find((r) => r.properties.model === item.properties.model) || item, ); -const getFirehoseResource = (model: K8sKind) => ({ +const getWatchResource = (model: K8sKind) => ({ isList: true, kind: model.crd ? referenceForModel(model) : model.kind, prop: 'resource', @@ -36,7 +36,7 @@ const getFirehoseResource = (model: K8sKind) => ({ const ClusterInventoryItem = memo( ({ model, resolvedMapper, mapperLoader, additionalResources }) => { - const mainResource = useMemo(() => getFirehoseResource(model), [model]); + const mainResource = useMemo(() => getWatchResource(model), [model]); const otherResources = useMemo(() => additionalResources || {}, [additionalResources]); const [mapper, setMapper] = useState(); const [resourceData, resourceLoaded, resourceLoadError] = useK8sWatchResource< diff --git a/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/utils.ts b/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/utils.ts index b085d927ea2..9452afb38ec 100644 --- a/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/utils.ts +++ b/frontend/public/components/dashboard/dashboards-page/cluster-dashboard/utils.ts @@ -1,9 +1,9 @@ -import type { FirehoseResource } from '../../../utils/types'; +import type { WatchK8sResource } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; -export const uniqueResource = ( - resource: FirehoseResource, +export const uniqueResource = ( + resource: T, prefix: string | number, -): FirehoseResource => ({ +): T => ({ ...resource, prop: `${prefix}-${resource.prop}`, }); diff --git a/frontend/public/components/dashboard/project-dashboard/inventory-card.tsx b/frontend/public/components/dashboard/project-dashboard/inventory-card.tsx index e706f95035b..37aefcc2ab1 100644 --- a/frontend/public/components/dashboard/project-dashboard/inventory-card.tsx +++ b/frontend/public/components/dashboard/project-dashboard/inventory-card.tsx @@ -1,5 +1,4 @@ import { useEffect, useContext } from 'react'; -import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { useDynamicK8sWatchResources } from '@console/shared/src/hooks/useDynamicK8sWatchResources'; import { Card, CardBody, CardHeader, CardTitle, Stack, StackItem } from '@patternfly/react-core'; @@ -24,13 +23,8 @@ import { getPVCStatusGroups, getVSStatusGroups, } from '@console/shared/src/components/dashboard/inventory-card/utils'; -import type { FirehoseResource } from '../../utils/types'; import { useAccessReview } from '../../utils/rbac'; -import { - K8sKind, - K8sResourceCommon as K8sResourceCommonInternal, - referenceForModel, -} from '../../../module/k8s'; +import { K8sKind, referenceForModel } from '../../../module/k8s'; import { getName } from '@console/shared/src/selectors/common'; import { ProjectDashboardContext } from './project-dashboard-context'; import { @@ -38,6 +32,7 @@ import { DashboardsProjectOverviewInventoryItem, isDashboardsProjectOverviewInventoryItem, K8sResourceCommon, + WatchK8sResource, WatchK8sResources, ProjectOverviewInventoryItem, isProjectOverviewInventoryItem, @@ -45,7 +40,10 @@ import { import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook'; import { ErrorBoundary } from '@console/shared/src/components/error'; -const createFirehoseResource = (model: K8sKind, projectName: string): FirehoseResource => ({ +const createWatchResource = ( + model: K8sKind, + projectName: string, +): WatchK8sResource & { prop: string } => ({ kind: model.crd ? referenceForModel(model) : model.kind, isList: true, prop: 'resource', @@ -66,7 +64,7 @@ const ProjectInventoryItem: React.FC = ({ return; } - const resource = createFirehoseResource(model, projectName); + const resource = createWatchResource(model, projectName); const { prop, ...resourceConfig } = resource; watchResource(prop, resourceConfig); if (additionalResources) { @@ -84,25 +82,25 @@ const ProjectInventoryItem: React.FC = ({ }; }, [watchResource, stopWatchResource, projectName, model, additionalResources]); - const resourceData = _.get(resources.resource, 'data', []) as K8sResourceCommonInternal[]; - const resourceLoaded = _.get(resources.resource, 'loaded'); - const resourceLoadError = _.get(resources.resource, 'loadError'); + const resourceResult = resources.resource?.data; + const resourceData = Array.isArray(resourceResult) ? resourceResult : []; + const resourceLoaded = resources.resource?.loaded ?? false; + const resourceLoadError = resources.resource?.loadError; const additionalResourcesData = additionalResources - ? additionalResources.reduce((acc, r) => { - acc[r.prop] = _.get(resources[r.prop], 'data'); + ? additionalResources.reduce<{ [key: string]: K8sResourceCommon[] }>((acc, r) => { + const data = resources[r.prop]?.data; + acc[r.prop] = Array.isArray(data) ? data : []; return acc; }, {}) : {}; const additionalResourcesLoaded = additionalResources ? additionalResources .filter((r) => !r.optional) - .every((r) => _.get(resources[r.prop], 'loaded')) + .every((r) => resources[r.prop]?.loaded ?? false) : true; const additionalResourcesLoadError = additionalResources - ? additionalResources - .filter((r) => !r.optional) - .some((r) => !!_.get(resources[r.prop], 'loadError')) + ? additionalResources.filter((r) => !r.optional).some((r) => !!resources[r.prop]?.loadError) : false; const dynamicResources = useK8sWatchResources(additionalDynamicResources || {}); @@ -202,7 +200,7 @@ type ProjectInventoryItemProps = { projectName: string; model: K8sKind; mapper?: StatusGroupMapper; - additionalResources?: FirehoseResource[]; + additionalResources?: (WatchK8sResource & { prop: string })[]; additionalDynamicResources?: WatchK8sResources<{ [key: string]: K8sResourceCommon[]; }>; diff --git a/frontend/public/components/factory/__tests__/list-page.spec.tsx b/frontend/public/components/factory/__tests__/list-page.spec.tsx index 6f061fb1e2f..5475009380e 100644 --- a/frontend/public/components/factory/__tests__/list-page.spec.tsx +++ b/frontend/public/components/factory/__tests__/list-page.spec.tsx @@ -84,12 +84,12 @@ describe('TextFilter component', () => { describe('FireMan component', () => { it('does not render title when not provided', () => { - renderWithProviders(); + renderWithProviders(); expect(screen.queryByText('My pods')).not.toBeInTheDocument(); }); it('renders title when provided', () => { - renderWithProviders(); + renderWithProviders(); expect(screen.getByText('My pods')).toBeVisible(); }); @@ -98,7 +98,7 @@ describe('FireMan component', () => { renderWithProviders( (({ pages = [], ...prop }, []); const allPages = pages.length ? pages : null; - const objResource = useMemo( + const objResource = useMemo( () => ({ kind: props.kind, name: props.name, @@ -94,6 +93,7 @@ export const DetailsPage = withFallback(({ pages = [], ...prop const key = r.prop || r.kind; acc[key] = { kind: r.kind, + groupVersionKind: r.groupVersionKind, name: r.name, namespace: r.namespace, isList: r.isList, @@ -102,6 +102,7 @@ export const DetailsPage = withFallback(({ pages = [], ...prop limit: r.limit, namespaced: r.namespaced, optional: r.optional, + partialMetadata: r.partialMetadata, }; return acc; }, {} as Record); @@ -125,7 +126,6 @@ export const DetailsPage = withFallback(({ pages = [], ...prop )} (({ pages = [], ...prop OverrideTitle={props.OverrideTitle} helpText={props.helpText} helpAlert={props.helpAlert} + {...watchedResources} /> (({ pages = [], ...prop resourceKeys={resourceKeys} customData={props.customData} createRedirect={props.createRedirect} + {...watchedResources} /> ); }, ErrorBoundaryFallbackPage); export type DetailsPageProps = { - obj?: FirehoseResult; + obj?: WatchK8sResultsObject; title?: string | JSX.Element; titleFunc?: (obj: K8sResourceKind) => string | JSX.Element; menuActions?: KebabAction[] | KebabOptionsCreator; @@ -178,7 +179,7 @@ export type DetailsPageProps = { label?: string; name?: string; namespace?: string; - resources?: FirehoseResource[]; + resources?: (WatchK8sResource & { prop?: string })[]; breadcrumbsFor?: ( obj: K8sResourceKind, ) => ({ name: string; path: string } | { name: string; path: Location })[]; diff --git a/frontend/public/components/factory/list-page.tsx b/frontend/public/components/factory/list-page.tsx index f7997a68a82..3c698c8f38c 100644 --- a/frontend/public/components/factory/list-page.tsx +++ b/frontend/public/components/factory/list-page.tsx @@ -24,7 +24,11 @@ import { K8sKind } from '../../module/k8s/types'; import { getReferenceForModel as referenceForModel } from '@console/dynamic-plugin-sdk/src/utils/k8s/k8s-ref'; import { Selector } from '@console/dynamic-plugin-sdk/src/api/common-types'; import { useK8sWatchResources } from '../utils/k8s-watch-hook'; -import { FirehoseResource, FirehoseResourcesResult, FirehoseResultObject } from '../utils/types'; +import type { + ResourcesObject, + WatchK8sResourceWithProp, + WatchK8sResults, +} from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import { inject, kindObj } from '../utils/inject'; import { makeQuery, @@ -59,9 +63,6 @@ type ListPageWrapperProps = { hideLabelFilter?: boolean; columnLayout?: ColumnLayout; name?: string; - /** @deprecated - use watchedResources instead */ - resources?: FirehoseResourcesResult; - /** Resources fetched via useK8sWatchResources */ watchedResources?: Record>; loaded?: boolean; loadError?: unknown; @@ -91,7 +92,6 @@ export const ListPageWrapper: FC = (props) => { hideLabelFilter, columnLayout, name, - resources, watchedResources, nameFilter, omitFilterToolbar, @@ -105,10 +105,7 @@ export const ListPageWrapper: FC = (props) => { } }, [dispatch, nameFilter, memoizedIds]); - // TODO: Remove the resources prop and the fallback ?? resources after all components are migrated from Firehose to hooks. - // Use watchedResources (from useK8sWatchResources) if available, fallback to resources (from Firehose) - const resourceData = watchedResources ?? resources; - const data = flatten ? flatten(resourceData) : []; + const data = flatten ? flatten(watchedResources) : []; const Filter = ( = (p FireMan.displayName = 'FireMan'; export type Flatten< - F extends FirehoseResultObject = { [key: string]: K8sResourceCommon | K8sResourceCommon[] }, + F extends ResourcesObject = { [key: string]: K8sResourceCommon | K8sResourceCommon[] }, R = any -> = (resources: FirehoseResourcesResult) => R; +> = (resources: WatchK8sResults) => R; export type ListPageProps = PageCommonProps & { kind: string; @@ -490,7 +487,9 @@ export type MultiListPageProps = PageCommonProps & { hideTextFilter?: boolean; helpText?: ReactNode; helpAlert?: ReactNode; - resources: (Omit & { prop?: FirehoseResource['prop'] })[]; + resources: (Omit & { + prop?: WatchK8sResourceWithProp['prop']; + })[]; staticFilters?: { key: string; value: string }[]; nameFilter?: string; omitFilterToolbar?: boolean; @@ -551,6 +550,7 @@ export const MultiListPage: FC = (props) => { const key = r.prop || r.kind; acc[key] = { kind: r.kind, + groupVersionKind: r.groupVersionKind, name: r.name, namespace: r.namespace, isList: r.isList, @@ -559,6 +559,7 @@ export const MultiListPage: FC = (props) => { limit: r.limit, namespaced: r.namespaced, optional: r.optional, + partialMetadata: r.partialMetadata, }; return acc; }, {} as Record); diff --git a/frontend/public/components/sidebars/resource-sidebar-samples.tsx b/frontend/public/components/sidebars/resource-sidebar-samples.tsx index 2512f66ba44..60f6be8c0a6 100644 --- a/frontend/public/components/sidebars/resource-sidebar-samples.tsx +++ b/frontend/public/components/sidebars/resource-sidebar-samples.tsx @@ -11,8 +11,8 @@ import { PasteIcon } from '@patternfly/react-icons/dist/esm/icons/paste-icon'; import { Sample } from '@console/shared/src/hooks/useResourceSidebarSamples'; import { useTranslation } from 'react-i18next'; -import { K8sKind, referenceFor } from '../../module/k8s'; -import { FirehoseResult } from '../utils/types'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk'; +import { K8sKind, K8sResourceKind, referenceFor } from '../../module/k8s'; const ResourceSidebarSample: FC = ({ sample, @@ -214,6 +214,6 @@ type ResourceSidebarSamplesProps = { samples: Sample[]; loadSampleYaml: LoadSampleYaml; downloadSampleYaml: DownloadSampleYaml; - yamlSamplesList?: FirehoseResult; + yamlSamplesList?: WatchK8sResultsObject; kindObj: K8sKind; }; diff --git a/frontend/public/components/utils/__tests__/firehose.data.tsx b/frontend/public/components/utils/__tests__/firehose.data.tsx deleted file mode 100644 index e90789d5ad0..00000000000 --- a/frontend/public/components/utils/__tests__/firehose.data.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Map as ImmutableMap } from 'immutable'; - -export { PodModel } from '../../../models'; - -export const podData = { - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name: 'my-pod', - namespace: 'my-namespace', - resourceVersion: '123', - }, -}; - -export const podList = { - apiVersion: 'v1', - kind: 'PodList', - items: ['my-pod1', 'my-pod2', 'my-pod3'].map((name) => ({ - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name, - namespace: 'my-namespace', - resourceVersion: '123', - }, - })), - metadata: { resourceVersion: '123' }, -}; - -export const firehoseChildPropsWithoutModels = { - inFlight: false, - k8sModels: ImmutableMap({}), - reduxIDs: [], - resources: {}, - loaded: true, - loadError: undefined, - filters: {}, - watchK8sList: expect.any(Function), - watchK8sObject: expect.any(Function), - stopK8sWatch: expect.any(Function), -}; diff --git a/frontend/public/components/utils/__tests__/firehose.spec.tsx b/frontend/public/components/utils/__tests__/firehose.spec.tsx deleted file mode 100644 index 9443328896c..00000000000 --- a/frontend/public/components/utils/__tests__/firehose.spec.tsx +++ /dev/null @@ -1,1601 +0,0 @@ -import type { ReactNode, FC } from 'react'; -import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -import { combineReducers, createStore, applyMiddleware } from 'redux'; -import { thunk } from 'redux-thunk'; -import { Provider } from 'react-redux'; -import { act, cleanup, render } from '@testing-library/react'; -import { SDKReducers } from '@console/dynamic-plugin-sdk/src/app'; -import { k8sList, k8sGet } from '@console/dynamic-plugin-sdk/src/utils/k8s/k8s-resource'; -import { k8sWatch } from '@console/dynamic-plugin-sdk/src/utils/k8s'; -import { WatchK8sResources } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; -import { useK8sWatchResources } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks/useK8sWatchResources'; -import { receivedResources } from '../../../actions/k8s'; -import { processReduxId, Firehose } from '../firehose'; -import { PodModel, podData, podList, firehoseChildPropsWithoutModels } from './firehose.data'; - -// Mock network calls -jest.mock('@console/dynamic-plugin-sdk/src/utils/k8s/k8s-resource', () => ({ - k8sList: jest.fn(() => {}), - k8sGet: jest.fn(), -})); -jest.mock('@console/dynamic-plugin-sdk/src/utils/k8s', () => ({ - ...jest.requireActual('@console/dynamic-plugin-sdk/src/utils/k8s'), - k8sWatch: jest.fn(), -})); -const k8sListMock = k8sList as jest.Mock; -const k8sGetMock = k8sGet as jest.Mock; -const k8sWatchMock = k8sWatch as jest.Mock; - -// Redux wrapper -let store; - -interface WrapperProps { - children?: ReactNode; -} - -const Wrapper: FC = ({ children }) => {children}; - -describe('processReduxId', () => { - const k8s = ImmutableMap({ - ['Pods']: ImmutableMap({ - data: ImmutableList( - ['my-pod1', 'my-pod2', 'my-pod3'].map((name) => - ImmutableMap({ - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name, - namespace: 'my-namespace', - resourceVersion: '123', - }, - }), - ), - ), - }), - ['Pods~~~my-pod']: ImmutableMap({ - data: ImmutableMap({ - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name: 'my-pod', - namespace: 'my-namespace', - resourceVersion: '123', - }, - }), - }), - }); - - it('should return an empty object when reduxID prop is missing', () => { - const props = { kind: 'UnknownKind' }; - expect(processReduxId({ k8s }, props)).toEqual({}); - }); - - it("should return an object without data when extract a list which doesn't exist", () => { - const props = { - reduxID: 'Unknown', - kind: 'Pod', - isList: true, - }; - expect(processReduxId({ k8s }, props)).toEqual({ - data: undefined, - filters: {}, - kind: 'Pod', - loadError: undefined, - loaded: undefined, - optional: undefined, - selected: undefined, - }); - }); - - it("should return an empty object when extract a single item which doesn't exist", () => { - const props = { - reduxID: 'Unknown', - kind: 'Pod', - isList: false, - }; - expect(processReduxId({ k8s }, props)).toEqual({}); - }); - - it('should return an Firehose object with data when extract a list', () => { - const props = { - reduxID: 'Pods', - kind: 'Pod', - isList: true, - }; - expect(processReduxId({ k8s }, props)).toEqual({ - kind: 'Pod', - data: [ - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod1', namespace: 'my-namespace', resourceVersion: '123' }, - }, - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod2', namespace: 'my-namespace', resourceVersion: '123' }, - }, - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod3', namespace: 'my-namespace', resourceVersion: '123' }, - }, - ], - filters: {}, - loadError: undefined, - loaded: undefined, - optional: undefined, - selected: undefined, - }); - }); - - it('should return the same object twice when calling it twice for a list', () => { - const props = { - reduxID: 'Pods', - kind: 'Pod', - isList: true, - }; - const firstTime = processReduxId({ k8s }, props); - const secondTime = processReduxId({ k8s }, props); - // Exact JSON is tested above. - // It returns always a new result object - expect(firstTime).not.toBe(secondTime); - // But at least the data should be the same - expect(firstTime.data).toBe(secondTime.data); - }); - - it('should return an Firehose object with data when extract a single item', () => { - const props = { - reduxID: 'Pods~~~my-pod', - kind: 'Pod', - isList: false, - }; - expect(processReduxId({ k8s }, props)).toEqual({ - data: { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod', namespace: 'my-namespace', resourceVersion: '123' }, - }, - optional: undefined, - }); - }); - - it('should return the same object twice when calling it twice for a single item', () => { - const props = { - reduxID: 'Pods~~~my-pod', - kind: 'Pod', - isList: false, - }; - const firstTime = processReduxId({ k8s }, props); - const secondTime = processReduxId({ k8s }, props); - // Exact JSON is tested above. - // It returns always a new result object - // And it could not be the same because optional parameter could change! - expect(firstTime).not.toBe(secondTime); - // But at least the data should be the same - expect(firstTime.data).toBe(secondTime.data); - }); - - it('should return different data for isList true and false, but same data when calling multiple times', () => {}); -}); - -xdescribe('Firehose', () => { - // Object under test - const resourceUpdate = jest.fn(); - const Child: FC = (props) => { - resourceUpdate(props); - return null; - }; - - beforeEach(() => { - // Init k8s redux store with just one model - store = createStore(combineReducers(SDKReducers), {}, applyMiddleware(thunk)); - store.dispatch( - receivedResources({ - models: [PodModel], - adminResources: [], - allResources: [], - configResources: [], - clusterOperatorConfigResources: [], - namespacedSet: null, - safeResources: [], - groupVersionMap: {}, - }), - ); - - jest.useFakeTimers({ legacyFakeTimers: true }); - jest.resetAllMocks(); - - k8sListMock.mockReturnValue(Promise.resolve(podList)); - k8sGetMock.mockReturnValue(Promise.resolve(podData)); - const wsMock = { - onclose: () => wsMock, - ondestroy: () => wsMock, - onbulkmessage: () => wsMock, - destroy: () => wsMock, - }; - k8sWatchMock.mockReturnValue(wsMock); - }); - - afterEach(async () => { - // Ensure that there is no timer left which triggers a rerendering - await act(async () => { - jest.runAllTimers(); - }); - - cleanup(); - - // Ensure that there is no unexpected api calls - expect(k8sListMock).toHaveBeenCalledTimes(0); - expect(k8sGetMock).toHaveBeenCalledTimes(0); - expect(k8sWatchMock).toHaveBeenCalledTimes(0); - expect(resourceUpdate).toHaveBeenCalledTimes(0); - - jest.clearAllTimers(); - jest.useRealTimers(); - }); - - it('should return an empty object when reduxID prop is missing (also when rerender or unmount)', async () => { - const { rerender, unmount } = render( - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - rerender( - - - - - , - ); - unmount(); - - expect(resourceUpdate).toHaveBeenCalledTimes(2); - expect(resourceUpdate.mock.calls[0][0]).toEqual(firehoseChildPropsWithoutModels); - expect(resourceUpdate.mock.calls[1][0]).toEqual(firehoseChildPropsWithoutModels); - resourceUpdate.mockClear(); - }); - - it('should fetch and update child props when requesting a list of resources successfully', async () => { - const resources = [ - { - prop: 'pods', - kind: 'Pod', - isList: true, - namespace: 'my-namespace', - }, - ]; - const { rerender, unmount } = render( - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - expect(k8sListMock).toHaveBeenCalledTimes(1); - expect(k8sListMock.mock.calls[0]).toEqual([ - PodModel, - { limit: 250, ns: 'my-namespace' }, - true, - {}, - ]); - k8sListMock.mockClear(); - - // Expect initial render child-props - const podsNotLoadedYet = { - kind: 'Pod', - data: [], - loaded: false, - loadError: '', - filters: {}, - selected: null, - optional: undefined, - }; - const podsNotLoadedYetProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: ['core~v1~Pod---{"ns":"my-namespace"}'], - loaded: false, - // Yes, same data twice at the moment. - pods: podsNotLoadedYet, - resources: { pods: podsNotLoadedYet }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(1); - expect(resourceUpdate.mock.calls[0][0]).toEqual(podsNotLoadedYetProps); - - // Finish API call - await act(async () => { - jest.runAllTimers(); - }); - - // Expect updated child-props - const podsLoaded = { - kind: 'Pod', - data: ['my-pod1', 'my-pod2', 'my-pod3'].map((name) => ({ - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name, - namespace: 'my-namespace', - resourceVersion: '123', - }, - })), - loaded: true, - loadError: '', - filters: {}, - selected: null, - optional: undefined, - }; - const podsLoadedProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: ['core~v1~Pod---{"ns":"my-namespace"}'], - loaded: true, - // Yes, same data twice at the moment. - pods: podsLoaded, - resources: { pods: podsLoaded }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(2); - expect(resourceUpdate.mock.calls[1][0]).toEqual(podsLoadedProps); - - // Check rerender and unmount - rerender( - - - - - , - ); - unmount(); - expect(resourceUpdate).toHaveBeenCalledTimes(3); - expect(resourceUpdate.mock.calls[2][0]).toEqual(podsLoadedProps); - - resourceUpdate.mockClear(); - }); - - it('should fetch and update child props when requesting a single resource successfully', async () => { - const resources = [ - { - prop: 'pod', - kind: 'Pod', - namespace: 'my-namespace', - name: 'my-pod', - }, - ]; - const { rerender, unmount } = render( - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - expect(k8sGetMock).toHaveBeenCalledTimes(1); - expect(k8sGetMock.mock.calls[0]).toEqual([PodModel, 'my-pod', 'my-namespace', {}, {}]); - k8sGetMock.mockClear(); - - // Expect initial render child-props - const podNotLoadedYet = { - data: {}, - loaded: false, - loadError: '', - optional: undefined, - }; - const podsNotLoadedYetProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: ['core~v1~Pod---{"ns":"my-namespace","name":"my-pod"}'], - loaded: false, - // Yes, same data twice at the moment. - pod: podNotLoadedYet, - resources: { pod: podNotLoadedYet }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(1); - expect(resourceUpdate.mock.calls[0][0]).toEqual(podsNotLoadedYetProps); - - // Finish API call - await act(async () => { - jest.runAllTimers(); - }); - - // Expect updated child-props - const podLoaded = { - data: { - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name: 'my-pod', - namespace: 'my-namespace', - resourceVersion: '123', - }, - }, - loaded: true, - loadError: '', - optional: undefined, - }; - const podLoadedProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: ['core~v1~Pod---{"ns":"my-namespace","name":"my-pod"}'], - loaded: true, - // Yes, same data twice at the moment. - pod: podLoaded, - resources: { pod: podLoaded }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(2); - expect(resourceUpdate.mock.calls[1][0]).toEqual(podLoadedProps); - - // Check rerender and unmount - rerender( - - - - - , - ); - unmount(); - expect(resourceUpdate).toHaveBeenCalledTimes(3); - expect(resourceUpdate.mock.calls[2][0]).toEqual(podLoadedProps); - - resourceUpdate.mockClear(); - }); - - it('should fetch and update child props when requesting a list of resources fails', async () => { - k8sListMock.mockReturnValue(Promise.reject(new Error('Network issue'))); - const resources = [ - { - prop: 'pods', - kind: 'Pod', - isList: true, - namespace: 'my-namespace', - }, - ]; - const { rerender, unmount } = render( - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - expect(k8sListMock).toHaveBeenCalledTimes(1); - expect(k8sListMock.mock.calls[0]).toEqual([ - PodModel, - { limit: 250, ns: 'my-namespace' }, - true, - {}, - ]); - k8sListMock.mockClear(); - - // Expect initial render child-props - const podsNotLoadedYet = { - kind: 'Pod', - data: [], - loaded: false, - loadError: '', - filters: {}, - selected: null, - optional: undefined, - }; - const podsNotLoadedYetProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: ['core~v1~Pod---{"ns":"my-namespace"}'], - loaded: false, - // Yes, same data twice at the moment. - pods: podsNotLoadedYet, - resources: { pods: podsNotLoadedYet }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(1); - expect(resourceUpdate.mock.calls[0][0]).toEqual(podsNotLoadedYetProps); - - // Finish API call - await act(async () => { - jest.runAllTimers(); - }); - - // Expect updated child-props - const podsLoaded = { - kind: 'Pod', - data: [], - loaded: false, - loadError: new Error('Network issue'), - filters: {}, - selected: null, - optional: undefined, - }; - const podsLoadedProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: ['core~v1~Pod---{"ns":"my-namespace"}'], - loaded: false, - loadError: new Error('Network issue'), - // Yes, same data twice at the moment. - pods: podsLoaded, - resources: { pods: podsLoaded }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(2); - expect(resourceUpdate.mock.calls[1][0]).toEqual(podsLoadedProps); - - // Check rerender and unmount - rerender( - - - - - , - ); - unmount(); - expect(resourceUpdate).toHaveBeenCalledTimes(3); - expect(resourceUpdate.mock.calls[2][0]).toEqual(podsLoadedProps); - - resourceUpdate.mockClear(); - }); - - it('should fetch and update child props when requesting a single resource fails', async () => { - k8sGetMock.mockReturnValue(Promise.reject(new Error('Network issue'))); - const resources = [ - { - prop: 'pod', - kind: 'Pod', - namespace: 'my-namespace', - name: 'my-pod', - }, - ]; - const { rerender, unmount } = render( - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - expect(k8sGetMock).toHaveBeenCalledTimes(1); - expect(k8sGetMock.mock.calls[0]).toEqual([PodModel, 'my-pod', 'my-namespace', {}, {}]); - k8sGetMock.mockClear(); - - // Expect initial render child-props - const podNotLoadedYet = { - data: {}, - loaded: false, - loadError: '', - optional: undefined, - }; - const podsNotLoadedYetProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: ['core~v1~Pod---{"ns":"my-namespace","name":"my-pod"}'], - loaded: false, - // Yes, same data twice at the moment. - pod: podNotLoadedYet, - resources: { pod: podNotLoadedYet }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(1); - expect(resourceUpdate.mock.calls[0][0]).toEqual(podsNotLoadedYetProps); - - // Finish API call - await act(async () => { - jest.runAllTimers(); - }); - - // Expect updated child-props - const podLoaded = { - data: {}, - loaded: false, - loadError: new Error('Network issue'), - optional: undefined, - }; - const podLoadedProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: ['core~v1~Pod---{"ns":"my-namespace","name":"my-pod"}'], - loaded: false, - loadError: new Error('Network issue'), - // Yes, same data twice at the moment. - pod: podLoaded, - resources: { pod: podLoaded }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(2); - expect(resourceUpdate.mock.calls[1][0]).toEqual(podLoadedProps); - - // Check rerender and unmount - rerender( - - - - - , - ); - unmount(); - expect(resourceUpdate).toHaveBeenCalledTimes(3); - expect(resourceUpdate.mock.calls[2][0]).toEqual(podLoadedProps); - - resourceUpdate.mockClear(); - }); - - it('should set the props to all childrens and fetch the data just once', async () => { - const resources = [ - { - prop: 'pods', - kind: 'Pod', - isList: true, - namespace: 'my-namespace', - }, - { - prop: 'pod', - kind: 'Pod', - namespace: 'my-namespace', - name: 'my-pod', - }, - ]; - render( - - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - // Assert that API calls are just triggered once - expect(k8sListMock).toHaveBeenCalledTimes(1); - expect(k8sListMock.mock.calls[0]).toEqual([ - PodModel, - { limit: 250, ns: 'my-namespace' }, - true, - {}, - ]); - k8sListMock.mockClear(); - expect(k8sGetMock).toHaveBeenCalledTimes(1); - expect(k8sGetMock.mock.calls[0]).toEqual([PodModel, 'my-pod', 'my-namespace', {}, {}]); - k8sGetMock.mockClear(); - - // Expect initial render child-props - const podsNotLoadedYet = { - kind: 'Pod', - data: [], - loaded: false, - loadError: '', - filters: {}, - selected: null, - optional: undefined, - }; - const podNotLoadedYet = { - data: {}, - loaded: false, - loadError: '', - optional: undefined, - }; - const notLoadedYetProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: [ - 'core~v1~Pod---{"ns":"my-namespace"}', - 'core~v1~Pod---{"ns":"my-namespace","name":"my-pod"}', - ], - loaded: false, - // Yes, same data twice at the moment. - pods: podsNotLoadedYet, - pod: podNotLoadedYet, - resources: { pods: podsNotLoadedYet, pod: podNotLoadedYet }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(2); - expect(resourceUpdate.mock.calls[0][0]).toEqual(notLoadedYetProps); - expect(resourceUpdate.mock.calls[1][0]).toEqual(notLoadedYetProps); - - // Finish API call - await act(async () => { - jest.runAllTimers(); - }); - - // Expect updated child-props - const podsLoaded = { - kind: 'Pod', - data: ['my-pod1', 'my-pod2', 'my-pod3'].map((name) => ({ - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name, - namespace: 'my-namespace', - resourceVersion: '123', - }, - })), - loaded: true, - loadError: '', - filters: {}, - selected: null, - optional: undefined, - }; - const podLoaded = { - data: { - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name: 'my-pod', - namespace: 'my-namespace', - resourceVersion: '123', - }, - }, - loaded: true, - loadError: '', - optional: undefined, - }; - const loadedProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: [ - 'core~v1~Pod---{"ns":"my-namespace"}', - 'core~v1~Pod---{"ns":"my-namespace","name":"my-pod"}', - ], - loaded: true, - // Yes, same data twice at the moment. - pods: podsLoaded, - pod: podLoaded, - resources: { pods: podsLoaded, pod: podLoaded }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(6); - // skip rerendering 2 so that both data sets are loaded - expect(resourceUpdate.mock.calls[4][0]).toEqual(loadedProps); - expect(resourceUpdate.mock.calls[5][0]).toEqual(loadedProps); - const propsChildA = resourceUpdate.mock.calls[4][0]; - const propsChildB = resourceUpdate.mock.calls[5][0]; - resourceUpdate.mockClear(); - - // Check that all data shares the same identity for the loaded data. - expect(propsChildA).toEqual(propsChildB); - expect(propsChildA).not.toBe(propsChildB); // TODO: These props could be the same, or? - - // pods 'resource' object (with data, loaded, etc.) object - expect(propsChildA.pods).toBe(propsChildB.pods); - expect(propsChildA.pods.data).toBe(propsChildB.pods.data); - expect(propsChildA.pods.data[0]).toBe(propsChildB.pods.data[0]); - expect(propsChildA.resources.pods).toBe(propsChildB.resources.pods); - expect(propsChildA.resources.pods.data).toBe(propsChildB.resources.pods.data); - expect(propsChildA.resources.pods.data[0]).toBe(propsChildB.resources.pods.data[0]); - - // pod 'resource' object (with data, loaded, etc.) object - expect(propsChildA.pod).toBe(propsChildB.pod); - expect(propsChildA.pod.data).toBe(propsChildB.pod.data); - expect(propsChildA.resources.pod).toBe(propsChildB.resources.pod); - expect(propsChildA.resources.pod.data).toBe(propsChildB.resources.pod.data); - }); - - it('should fetch data just once when two Firehose components requests the same data', async () => { - const resources = [ - { - prop: 'pods', - kind: 'Pod', - isList: true, - namespace: 'my-namespace', - }, - { - prop: 'pod', - kind: 'Pod', - namespace: 'my-namespace', - name: 'my-pod', - }, - ]; - render( - - - - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - // Assert that API calls are just triggered once - expect(k8sListMock).toHaveBeenCalledTimes(1); - expect(k8sListMock.mock.calls[0]).toEqual([ - PodModel, - { limit: 250, ns: 'my-namespace' }, - true, - {}, - ]); - k8sListMock.mockClear(); - expect(k8sGetMock).toHaveBeenCalledTimes(1); - expect(k8sGetMock.mock.calls[0]).toEqual([PodModel, 'my-pod', 'my-namespace', {}, {}]); - k8sGetMock.mockClear(); - - // Expect initial render child-props - const podsNotLoadedYet = { - kind: 'Pod', - data: [], - loaded: false, - loadError: '', - filters: {}, - selected: null, - optional: undefined, - }; - const podNotLoadedYet = { - data: {}, - loaded: false, - loadError: '', - optional: undefined, - }; - const podsNotLoadedYetProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: [ - 'core~v1~Pod---{"ns":"my-namespace"}', - 'core~v1~Pod---{"ns":"my-namespace","name":"my-pod"}', - ], - loaded: false, - // Yes, same data twice at the moment. - pods: podsNotLoadedYet, - pod: podNotLoadedYet, - resources: { pods: podsNotLoadedYet, pod: podNotLoadedYet }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(2); - expect(resourceUpdate.mock.calls[0][0]).toEqual(podsNotLoadedYetProps); - expect(resourceUpdate.mock.calls[1][0]).toEqual(podsNotLoadedYetProps); - - // Finish API call - await act(async () => { - jest.runAllTimers(); - }); - - // Expect updated child-props - const podsLoaded = { - kind: 'Pod', - data: ['my-pod1', 'my-pod2', 'my-pod3'].map((name) => ({ - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name, - namespace: 'my-namespace', - resourceVersion: '123', - }, - })), - loaded: true, - loadError: '', - filters: {}, - selected: null, - optional: undefined, - }; - const podLoaded = { - data: { - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name: 'my-pod', - namespace: 'my-namespace', - resourceVersion: '123', - }, - }, - loaded: true, - loadError: '', - optional: undefined, - }; - const podsLoadedProps = { - ...firehoseChildPropsWithoutModels, - k8sModels: ImmutableMap({ Pod: PodModel }), - reduxIDs: [ - 'core~v1~Pod---{"ns":"my-namespace"}', - 'core~v1~Pod---{"ns":"my-namespace","name":"my-pod"}', - ], - loaded: true, - // Yes, same data twice at the moment. - pods: podsLoaded, - pod: podLoaded, - resources: { pods: podsLoaded, pod: podLoaded }, - }; - expect(resourceUpdate).toHaveBeenCalledTimes(6); - // skip rerendering 2 so that both data sets are loaded - expect(resourceUpdate.mock.calls[4][0]).toEqual(podsLoadedProps); - expect(resourceUpdate.mock.calls[5][0]).toEqual(podsLoadedProps); - const propsChildA = resourceUpdate.mock.calls[4][0]; - const propsChildB = resourceUpdate.mock.calls[5][0]; - resourceUpdate.mockClear(); - - // Check that all data shares the same identity for the loaded data. - expect(propsChildA).not.toEqual(propsChildB); // Compared values have no visual difference, but should be equal, or? - expect(propsChildA).not.toBe(propsChildB); // Compared values have no visual difference, but should be the same, or? - - // pods 'resource' object (with data, loaded, etc.) object - expect(propsChildA.pods).toEqual(propsChildB.pods); - expect(propsChildA.pods).not.toBe(propsChildB.pods); // Could be the same? - expect(propsChildA.pods.data).toBe(propsChildB.pods.data); - expect(propsChildA.pods.data[0]).toBe(propsChildB.pods.data[0]); - - expect(propsChildA.resources.pods).toEqual(propsChildB.resources.pods); - expect(propsChildA.resources.pods).not.toBe(propsChildB.resources.pods); // Could be the same? - expect(propsChildA.resources.pods.data).toBe(propsChildB.resources.pods.data); - expect(propsChildA.resources.pods.data[0]).toBe(propsChildB.resources.pods.data[0]); - - // pod 'resource' object (with data, loaded, etc.) object - expect(propsChildA.pod).not.toBe(propsChildB.pod); // Could be the same? - expect(propsChildA.data).toBe(propsChildB.data); - expect(propsChildA.resources.pod).not.toBe(propsChildB.resources.pod); // Could be the same? - expect(propsChildA.resources.pod.data).toBe(propsChildB.resources.pod.data); - }); -}); - -xdescribe('Firehose together with useK8sWatchResources', () => { - // Objects under test - const firehoseUpdate = jest.fn(); - const Child: FC = (props) => { - firehoseUpdate(props); - return null; - }; - - const resourcesUpdate = jest.fn(); - const WatchResources: FC<{ initResources: WatchK8sResources<{}> }> = ({ initResources }) => { - resourcesUpdate(useK8sWatchResources(initResources)); - return null; - }; - - beforeEach(() => { - // Init k8s redux store with just one model - store = createStore(combineReducers(SDKReducers), {}, applyMiddleware(thunk)); - store.dispatch( - receivedResources({ - models: [PodModel], - adminResources: [], - allResources: [], - configResources: [], - clusterOperatorConfigResources: [], - namespacedSet: null, - safeResources: [], - groupVersionMap: {}, - }), - ); - - jest.useFakeTimers({ legacyFakeTimers: true }); - jest.resetAllMocks(); - - k8sListMock.mockReturnValue(Promise.resolve(podList)); - k8sGetMock.mockReturnValue(Promise.resolve(podData)); - const wsMock = { - onclose: () => wsMock, - ondestroy: () => wsMock, - onbulkmessage: () => wsMock, - destroy: () => wsMock, - }; - k8sWatchMock.mockReturnValue(wsMock); - }); - - afterEach(async () => { - // Ensure that there is no timer left which triggers a rerendering - await act(async () => { - jest.runAllTimers(); - }); - - cleanup(); - - // Ensure that there is no unexpected api calls - expect(k8sListMock).toHaveBeenCalledTimes(0); - expect(k8sGetMock).toHaveBeenCalledTimes(0); - expect(k8sWatchMock).toHaveBeenCalledTimes(0); - expect(firehoseUpdate).toHaveBeenCalledTimes(0); - expect(resourcesUpdate).toHaveBeenCalledTimes(0); - - jest.clearAllTimers(); - jest.useRealTimers(); - }); - - it('should fetch data just once and return the same data for both (Firehose first)', async () => { - const resources = [ - { - prop: 'pods', - kind: 'Pod', - isList: true, - namespace: 'my-namespace', - }, - { - prop: 'pod', - kind: 'Pod', - namespace: 'my-namespace', - name: 'my-pod', - }, - ]; - const initResources: WatchK8sResources<{}> = { - pods: { - kind: 'Pod', - namespace: 'my-namespace', - isList: true, - }, - pod: { - kind: 'Pod', - namespace: 'my-namespace', - name: 'my-pod', - }, - }; - - render( - - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - // Finish API calls - await act(async () => { - jest.runAllTimers(); - }); - - // Assert that API calls are just triggered once - expect(k8sListMock).toHaveBeenCalledTimes(1); - expect(k8sListMock.mock.calls[0]).toEqual([ - PodModel, - { limit: 250, ns: 'my-namespace' }, - true, - {}, - ]); - k8sListMock.mockClear(); - - expect(k8sGetMock).toHaveBeenCalledTimes(1); - expect(k8sGetMock.mock.calls[0]).toEqual([PodModel, 'my-pod', 'my-namespace', {}, {}]); - k8sGetMock.mockClear(); - - // Components was rendered the right amount of time (loaded: false, loaded: true) - expect(firehoseUpdate).toHaveBeenCalledTimes(3); - expect(resourcesUpdate).toHaveBeenCalledTimes(3); - const lastFirehoseChildProps = firehoseUpdate.mock.calls[2][0]; - const lastUseResourcesHookResult = resourcesUpdate.mock.calls[2][0]; - firehoseUpdate.mockClear(); - resourcesUpdate.mockClear(); - - // Tests earlier checks the exact format, we focus here on comparing the data instances - expect(lastFirehoseChildProps.pods).toBeTruthy(); - expect(lastFirehoseChildProps.pod).toBeTruthy(); - expect(lastUseResourcesHookResult.pods).toBeTruthy(); - expect(lastUseResourcesHookResult.pod).toBeTruthy(); - - // Result objects looks different for list (not a requirement, but the status quo) - expect(lastFirehoseChildProps.pods).not.toEqual(lastUseResourcesHookResult.pods); - // but is the same for single items at the moment (also not a requirement, but the status quo) - expect(lastFirehoseChildProps.pod).toEqual(lastUseResourcesHookResult.pod); - expect(lastFirehoseChildProps.pod).not.toBe(lastUseResourcesHookResult.pod); - - // The data should be the same! - expect(lastFirehoseChildProps.pods.data).toEqual(lastUseResourcesHookResult.pods.data); - expect(lastFirehoseChildProps.pod.data).toEqual(lastUseResourcesHookResult.pod.data); - - // And they also should return the same instance for lists - expect(lastFirehoseChildProps.pods.data).toBe(lastUseResourcesHookResult.pods.data); - expect(lastFirehoseChildProps.pods.data[0]).toBe(lastUseResourcesHookResult.pods.data[0]); - expect(lastFirehoseChildProps.pods.data[1]).toBe(lastUseResourcesHookResult.pods.data[1]); - expect(lastFirehoseChildProps.pods.data[2]).toBe(lastUseResourcesHookResult.pods.data[2]); - - // And they also should return the same instance for single items - expect(lastFirehoseChildProps.pod.data).not.toBe(lastUseResourcesHookResult.pod.data); // Should be the same, or? - }); - - it('should fetch data just once and return the same data for both (useK8sWatchResources first)', async () => { - const initResources: WatchK8sResources<{}> = { - pods: { - kind: 'Pod', - namespace: 'my-namespace', - isList: true, - }, - pod: { - kind: 'Pod', - namespace: 'my-namespace', - name: 'my-pod', - }, - }; - const resources = [ - { - prop: 'pods', - kind: 'Pod', - isList: true, - namespace: 'my-namespace', - }, - { - prop: 'pod', - kind: 'Pod', - namespace: 'my-namespace', - name: 'my-pod', - }, - ]; - - render( - - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - // Finish API calls - await act(async () => { - jest.runAllTimers(); - }); - - // Assert that API calls are just triggered once - expect(k8sListMock).toHaveBeenCalledTimes(1); - expect(k8sListMock.mock.calls[0]).toEqual([ - PodModel, - { limit: 250, ns: 'my-namespace' }, - true, - {}, - ]); - k8sListMock.mockClear(); - - expect(k8sGetMock).toHaveBeenCalledTimes(1); - expect(k8sGetMock.mock.calls[0]).toEqual([PodModel, 'my-pod', 'my-namespace', {}, {}]); - k8sGetMock.mockClear(); - - // Components was rendered the right amount of time (loaded: false, loaded: true) - expect(firehoseUpdate).toHaveBeenCalledTimes(3); - expect(resourcesUpdate).toHaveBeenCalledTimes(4); - const lastFirehoseChildProps = firehoseUpdate.mock.calls[2][0]; - const lastUseResourcesHookResult = resourcesUpdate.mock.calls[3][0]; - firehoseUpdate.mockClear(); - resourcesUpdate.mockClear(); - - // Tests earlier checks the exact format, we focus here on comparing the data instances - expect(lastFirehoseChildProps.pods).toBeTruthy(); - expect(lastFirehoseChildProps.pod).toBeTruthy(); - expect(lastUseResourcesHookResult.pods).toBeTruthy(); - expect(lastUseResourcesHookResult.pod).toBeTruthy(); - - // Result objects looks different for list (not a requirement, but the status quo) - expect(lastFirehoseChildProps.pods).not.toEqual(lastUseResourcesHookResult.pods); - // but is the same for single items at the moment (also not a requirement, but the status quo) - expect(lastFirehoseChildProps.pod).toEqual(lastUseResourcesHookResult.pod); - expect(lastFirehoseChildProps.pod).not.toBe(lastUseResourcesHookResult.pod); - - // The data should be the same! - expect(lastFirehoseChildProps.pods.data).toEqual(lastUseResourcesHookResult.pods.data); - expect(lastFirehoseChildProps.pod.data).toEqual(lastUseResourcesHookResult.pod.data); - - // And they also should return the same instance for lists - expect(lastFirehoseChildProps.pods.data).toBe(lastUseResourcesHookResult.pods.data); - expect(lastFirehoseChildProps.pods.data[0]).toBe(lastUseResourcesHookResult.pods.data[0]); - expect(lastFirehoseChildProps.pods.data[1]).toBe(lastUseResourcesHookResult.pods.data[1]); - expect(lastFirehoseChildProps.pods.data[2]).toBe(lastUseResourcesHookResult.pods.data[2]); - - // And they also should return the same instance for single items - expect(lastFirehoseChildProps.pod.data).not.toBe(lastUseResourcesHookResult.pod.data); // Should be the same, or? - }); - - // Regression test for "Git import page crashes after load" on 4.9 - // https://bugzilla.redhat.com/show_bug.cgi?id=2069621 - describe('regression test for bug #2069621', () => { - // This reproduce the original issue - it('should return an array for Firehose isList=true even when useK8sWatchResources isList=false is called without a name (Firehose first)', async () => { - const resources = [ - { - prop: 'pods', - kind: 'Pod', - isList: true, - namespace: 'my-namespace', - }, - ]; - const initResources: WatchK8sResources<{}> = { - pods: { - kind: 'Pod', - namespace: 'my-namespace', - name: '', // Should not be supported by the API, but this happens sometimes - isList: false, - optional: true, - }, - }; - - render( - - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - // Finish API calls - await act(async () => { - jest.runAllTimers(); - }); - - // Assert that API calls are just triggered once - expect(k8sListMock).toHaveBeenCalledTimes(1); - expect(k8sListMock.mock.calls[0]).toEqual([ - PodModel, - { limit: 250, ns: 'my-namespace' }, - true, - {}, - ]); - k8sListMock.mockClear(); - - // Components was rendered the right amount of time (loaded: false, loaded: true) - expect(firehoseUpdate).toHaveBeenCalledTimes(2); - const lastFirehoseChildProps = firehoseUpdate.mock.calls[1][0]; - firehoseUpdate.mockClear(); - - expect(resourcesUpdate).toHaveBeenCalledTimes(2); - const lastUseResourcesHookResult = resourcesUpdate.mock.calls[1][0]; - resourcesUpdate.mockClear(); - - // But the Firehose call defines isList correctly and should still work - // and should return an array. - expect(lastFirehoseChildProps.pods).toEqual({ - kind: 'Pod', - data: ['my-pod1', 'my-pod2', 'my-pod3'].map((name) => ({ - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name, - namespace: 'my-namespace', - resourceVersion: '123', - }, - })), - loaded: true, - loadError: '', - filters: {}, - selected: null, - optional: undefined, - }); - - // The hook should not return any data because the name is missing! - // Instead it returns the internal redux state of the list above as object. - expect(lastUseResourcesHookResult.pods).toEqual({ - loaded: true, - loadError: '', - data: { - '(my-namespace)-my-pod1': { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod1', namespace: 'my-namespace', resourceVersion: '123' }, - }, - '(my-namespace)-my-pod2': { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod2', namespace: 'my-namespace', resourceVersion: '123' }, - }, - '(my-namespace)-my-pod3': { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod3', namespace: 'my-namespace', resourceVersion: '123' }, - }, - }, - }); - }); - - // And this 3 cases tests against other call orders / isList=true/false combinations... - it('should return an array for Firehose isList=true even when useK8sWatchResources isList=false is called without a name (useK8sWatchResources first)', async () => { - const initResources: WatchK8sResources<{}> = { - pods: { - kind: 'Pod', - namespace: 'my-namespace', - name: '', // Should not be supported by the API, but this happens sometimes - isList: false, - optional: true, - }, - }; - const resources = [ - { - prop: 'pods', - kind: 'Pod', - isList: true, - namespace: 'my-namespace', - }, - ]; - - render( - - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - // Finish API calls - await act(async () => { - jest.runAllTimers(); - }); - - // Assert that API calls are just triggered once - expect(k8sListMock).toHaveBeenCalledTimes(1); - expect(k8sListMock.mock.calls[0]).toEqual([ - PodModel, - { limit: 250, ns: 'my-namespace' }, - true, - {}, - ]); - k8sListMock.mockClear(); - - // Components was rendered the right amount of time (loaded: false, loaded: true) - expect(resourcesUpdate).toHaveBeenCalledTimes(3); - const lastUseResourcesHookResult = resourcesUpdate.mock.calls[2][0]; - resourcesUpdate.mockClear(); - - expect(firehoseUpdate).toHaveBeenCalledTimes(2); - const lastFirehoseChildProps = firehoseUpdate.mock.calls[1][0]; - firehoseUpdate.mockClear(); - - // The hook could not return any data because the name is missing. - expect(lastUseResourcesHookResult.pods).toEqual({ - loaded: true, - loadError: '', - data: { - '(my-namespace)-my-pod1': { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod1', namespace: 'my-namespace', resourceVersion: '123' }, - }, - '(my-namespace)-my-pod2': { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod2', namespace: 'my-namespace', resourceVersion: '123' }, - }, - '(my-namespace)-my-pod3': { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod3', namespace: 'my-namespace', resourceVersion: '123' }, - }, - }, - }); - - // But the Firehose call defines isList correctly and should still work - // and should return an array. - expect(lastFirehoseChildProps.pods).toEqual({ - kind: 'Pod', - data: ['my-pod1', 'my-pod2', 'my-pod3'].map((name) => ({ - apiVersion: 'v1', - kind: 'Pod', - metadata: { - name, - namespace: 'my-namespace', - resourceVersion: '123', - }, - })), - loaded: true, - loadError: '', - filters: {}, - selected: null, - optional: undefined, - }); - }); - - it('should return an array for useK8sWatchResources isList=true even when Firehose isList=false is called without a name (Firehose first)', async () => { - // Without a name the k8sGet API is called, but it returns a list anyway. - k8sGetMock.mockReturnValue(Promise.resolve(podList)); - - const resources = [ - { - prop: 'pods', - kind: 'Pod', - isList: false, - namespace: 'my-namespace', - name: '', - }, - ]; - const initResources: WatchK8sResources<{}> = { - pods: { - kind: 'Pod', - namespace: 'my-namespace', - isList: true, - }, - }; - - render( - - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - // Finish API calls - await act(async () => { - jest.runAllTimers(); - }); - - // Assert that API calls are just triggered once - expect(k8sGetMock).toHaveBeenCalledTimes(1); - expect(k8sGetMock.mock.calls[0]).toEqual([ - PodModel, - '', // Without a name above this calls the get api, but it still returns a list. - 'my-namespace', - {}, - {}, - ]); - k8sGetMock.mockClear(); - - // Components was rendered the right amount of time (loaded: false, loaded: true) - expect(firehoseUpdate).toHaveBeenCalledTimes(2); - const lastFirehoseChildProps = firehoseUpdate.mock.calls[1][0]; - firehoseUpdate.mockClear(); - - expect(resourcesUpdate).toHaveBeenCalledTimes(2); - const lastUseResourcesHookResult = resourcesUpdate.mock.calls[1][0]; - resourcesUpdate.mockClear(); - - // The Firehose call defines isList=false, so it returns the full API response. - expect(lastFirehoseChildProps.pods).toEqual({ - data: { - apiVersion: 'v1', - items: [ - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod1', namespace: 'my-namespace', resourceVersion: '123' }, - }, - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod2', namespace: 'my-namespace', resourceVersion: '123' }, - }, - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod3', namespace: 'my-namespace', resourceVersion: '123' }, - }, - ], - kind: 'PodList', - metadata: { resourceVersion: '123' }, - }, - loaded: true, - loadError: '', - optional: undefined, - }); - - // But the hook defines isList=true and converts it automatically to an array. - // At the moment it doesn't extract the 'values' key. - expect(lastUseResourcesHookResult.pods).toEqual({ - loaded: true, - loadError: '', - data: [ - 'v1', - 'PodList', - [ - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod1', namespace: 'my-namespace', resourceVersion: '123' }, - }, - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod2', namespace: 'my-namespace', resourceVersion: '123' }, - }, - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod3', namespace: 'my-namespace', resourceVersion: '123' }, - }, - ], - { resourceVersion: '123' }, - ], - }); - }); - - it('should return an array for useK8sWatchResources isList=true even when Firehose isList=false is called without a name (useK8sWatchResources first)', async () => { - // Without a name the k8sGet API is called, but it returns a list anyway. - k8sGetMock.mockReturnValue(Promise.resolve(podList)); - - const initResources: WatchK8sResources<{}> = { - pods: { - kind: 'Pod', - namespace: 'my-namespace', - isList: true, - optional: true, - }, - }; - const resources = [ - { - prop: 'pods', - kind: 'Pod', - isList: false, - namespace: 'my-namespace', - name: '', - }, - ]; - - render( - - - - - - , - { legacyRoot: true }, // TODO(react18): Remove Firehose before using ReactDOM.createRoot - ); - - // Finish API calls - await act(async () => { - jest.runAllTimers(); - }); - - expect(k8sGetMock).toHaveBeenCalledTimes(1); - expect(k8sGetMock.mock.calls[0]).toEqual([ - PodModel, - '', // Without a name above this calls the get api, but it still returns a list. - 'my-namespace', - {}, - {}, - ]); - k8sGetMock.mockClear(); - - // Components was rendered the right amount of time (loaded: false, loaded: true) - expect(resourcesUpdate).toHaveBeenCalledTimes(3); - const lastUseResourcesHookResult = resourcesUpdate.mock.calls[2][0]; - resourcesUpdate.mockClear(); - - expect(firehoseUpdate).toHaveBeenCalledTimes(2); - const lastFirehoseChildProps = firehoseUpdate.mock.calls[1][0]; - firehoseUpdate.mockClear(); - - // The hook could not return any data because the name is missing. - expect(lastUseResourcesHookResult.pods).toEqual({ - data: [ - 'v1', - 'PodList', - [ - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod1', namespace: 'my-namespace', resourceVersion: '123' }, - }, - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod2', namespace: 'my-namespace', resourceVersion: '123' }, - }, - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod3', namespace: 'my-namespace', resourceVersion: '123' }, - }, - ], - { resourceVersion: '123' }, - ], - loadError: '', - loaded: true, - }); - - // But Firehose can return a pod when call defines isList correctly and should still work - // and should get an array. - expect(lastFirehoseChildProps.pods).toEqual({ - data: { - apiVersion: 'v1', - items: [ - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod1', namespace: 'my-namespace', resourceVersion: '123' }, - }, - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod2', namespace: 'my-namespace', resourceVersion: '123' }, - }, - { - apiVersion: 'v1', - kind: 'Pod', - metadata: { name: 'my-pod3', namespace: 'my-namespace', resourceVersion: '123' }, - }, - ], - kind: 'PodList', - metadata: { resourceVersion: '123' }, - }, - loaded: true, - loadError: '', - optional: undefined, - }); - }); - }); -}); diff --git a/frontend/public/components/utils/firehose.jsx b/frontend/public/components/utils/firehose.jsx deleted file mode 100644 index 8b0bb7eb3e3..00000000000 --- a/frontend/public/components/utils/firehose.jsx +++ /dev/null @@ -1,312 +0,0 @@ -/* eslint-disable tsdoc/syntax */ -import * as _ from 'lodash'; -import { memo, Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { Map as ImmutableMap } from 'immutable'; - -import { inject } from './inject'; -import { makeReduxID, makeQuery } from './k8s-watcher'; -import * as k8sActions from '../../actions/k8s'; - -import { - INTERNAL_REDUX_IMMUTABLE_TOARRAY_CACHE_SYMBOL, - INTERNAL_REDUX_IMMUTABLE_TOJSON_CACHE_SYMBOL, -} from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks/k8s-watcher'; -import { getK8sModel } from '@console/dynamic-plugin-sdk/src/utils/k8s/hooks/useK8sModel'; - -const shallowMapEquals = (a, b) => { - if (a === b || (a.size === 0 && b.size === 0)) { - return true; - } - if (a.size !== b.size) { - return false; - } - return a.every((v, k) => b.get(k) === v); -}; - -export const processReduxId = ({ k8s }, props) => { - const { reduxID, isList, filters } = props; - - if (!reduxID) { - return {}; - } - - if (!isList) { - let stuff = k8s.get(reduxID); - if (!stuff) { - return {}; - } - if (!stuff[INTERNAL_REDUX_IMMUTABLE_TOJSON_CACHE_SYMBOL]) { - stuff[INTERNAL_REDUX_IMMUTABLE_TOJSON_CACHE_SYMBOL] = stuff.toJSON(); - } - stuff = stuff[INTERNAL_REDUX_IMMUTABLE_TOJSON_CACHE_SYMBOL]; - return { ...stuff, optional: props.optional }; - } - - let data = k8s.getIn([reduxID, 'data']); - const _filters = k8s.getIn([reduxID, 'filters']); - const selected = k8s.getIn([reduxID, 'selected']); - - if (data && data.toArray) { - if (!data[INTERNAL_REDUX_IMMUTABLE_TOARRAY_CACHE_SYMBOL]) { - data[INTERNAL_REDUX_IMMUTABLE_TOARRAY_CACHE_SYMBOL] = data.toArray().map((a) => { - if (a.toJSON) { - if (!a[INTERNAL_REDUX_IMMUTABLE_TOJSON_CACHE_SYMBOL]) { - a[INTERNAL_REDUX_IMMUTABLE_TOJSON_CACHE_SYMBOL] = a.toJSON(); - } - return a[INTERNAL_REDUX_IMMUTABLE_TOJSON_CACHE_SYMBOL]; - } - return a; - }); - } - data = data[INTERNAL_REDUX_IMMUTABLE_TOARRAY_CACHE_SYMBOL]; - } - - return { - data, - // This is a hack to allow filters passed down from props to make it to - // the injected component. Ideally filters should all come from redux. - filters: _.extend({}, _filters && _filters.toJS(), filters), - kind: props.kind, - loadError: k8s.getIn([reduxID, 'loadError']), - loaded: k8s.getIn([reduxID, 'loaded']), - optional: props.optional, - selected, - }; -}; - -const worstError = (errors) => { - let worst = errors && errors[0]; - for (const e of errors) { - if (e.status === 403) { - return e; - } - if (e.status === 401) { - worst = e; - continue; - } - if (worst.status === 401) { - continue; - } - if (e.status > worst.status) { - worst = e; - continue; - } - } - return worst; -}; - -const mapStateToProps = ({ k8s }) => ({ - k8s, -}); - -const propsAreEqual = (prevProps, nextProps) => { - if (nextProps.children === prevProps.children && nextProps.reduxes === prevProps.reduxes) { - return nextProps.reduxes.every( - ({ reduxID }) => prevProps.k8s.get(reduxID) === nextProps.k8s.get(reduxID), - ); - } - return false; -}; - -// A wrapper Component that takes data out of redux for a list or object at some reduxID ... -// passing it to children -const ConnectToState = connect(mapStateToProps)( - memo(({ k8s, reduxes, children }) => { - const resources = {}; - - reduxes.forEach((redux) => { - resources[redux.prop] = processReduxId({ k8s }, redux); - }); - - const required = _.filter(resources, (r) => !r.optional); - const loaded = _.every(resources, (resource) => - resource.optional ? resource.loaded || !_.isEmpty(resource.loadError) : resource.loaded, - ); - const loadError = worstError(_.map(required, 'loadError').filter(Boolean)); - - const k8sResults = Object.assign({}, resources, { - filters: Object.assign({}, ..._.map(resources, 'filters')), - loaded, - loadError, - reduxIDs: _.map(reduxes, 'reduxID'), - resources, - }); - - return inject(children, k8sResults); - }, propsAreEqual), -); - -const stateToProps = (state, { resources }) => { - const { k8s } = state; - const k8sModels = resources.reduce( - (models, { kind }) => models.set(kind, getK8sModel(k8s, kind)), - ImmutableMap(), - ); - const loaded = (r) => - r.optional || - k8s.getIn([ - makeReduxID( - k8sModels.get(r.kind), - makeQuery(r.namespace, r.selector, r.fieldSelector, r.name), - ), - 'loaded', - ]); - - return { - k8sModels, - loaded: resources.every(loaded), - inFlight: k8s.getIn(['RESOURCES', 'inFlight']), - }; -}; - -export const Firehose = connect( - stateToProps, - { - stopK8sWatch: k8sActions.stopK8sWatch, - watchK8sObject: k8sActions.watchK8sObject, - watchK8sList: k8sActions.watchK8sList, - }, - null, - { - areStatesEqual: (next, prev) => next.k8s === prev.k8s, - areStatePropsEqual: (next, prev) => - next.loaded === prev.loaded && - next.inFlight === prev.inFlight && - shallowMapEquals(next.k8sModels, prev.k8sModels), - }, -)( - /** @augments {React.Component, doNotConnectToState?: boolean}>> */ - class Firehose extends Component { - state = { - firehoses: [], - }; - - // TODO: Convert this to `componentDidMount` - // eslint-disable-next-line camelcase - UNSAFE_componentWillMount() { - this.start(); - } - - componentWillUnmount() { - this.clear(); - } - - shouldComponentUpdate(nextProps, nextState) { - if ( - Object.keys(nextProps).length === Object.keys(this.props).length && - Object.keys(nextProps) - .filter((key) => key !== 'inFlight') - .every((key) => nextProps[key] === this.props[key]) && - (nextState === this.state || - (nextState.firehoses.length === 0 && this.state.firehoses.length === 0)) - ) { - return this.props.loaded ? false : this.props.inFlight !== nextProps.inFlight; - } - return true; - } - - componentDidUpdate(prevProps) { - const discoveryComplete = - !this.props.inFlight && !this.props.loaded && this.state.firehoses.length === 0; - const resourcesChanged = - prevProps.resources !== this.props.resources && - _.intersectionWith(prevProps.resources, this.props.resources, _.isEqual).length !== - this.props.resources.length; - - if (discoveryComplete || resourcesChanged) { - this.clear(); - this.start(); - } - } - - start() { - const { watchK8sList, watchK8sObject, resources, k8sModels, inFlight } = this.props; - - let firehoses = []; - if (!(inFlight && _.some(resources, ({ kind }) => !k8sModels.get(kind)))) { - firehoses = resources - .map((resource) => { - const query = makeQuery( - resource.namespace, - resource.selector, - resource.fieldSelector, - resource.name, - resource.limit, - ); - const k8sKind = k8sModels.get(resource.kind); - const id = makeReduxID(k8sKind, query); - return _.extend({}, resource, { query, id, k8sKind }); - }) - .filter((f) => { - if (_.isEmpty(f.k8sKind)) { - // eslint-disable-next-line no-console - console.warn(`No model registered for ${f.kind}`); - } - return !_.isEmpty(f.k8sKind); - }); - } - - firehoses.forEach(({ id, query, k8sKind, isList, name, namespace, partialMetadata }) => - isList - ? watchK8sList(id, query, k8sKind, null, partialMetadata) - : watchK8sObject(id, name, namespace, query, k8sKind, partialMetadata), - ); - this.setState({ firehoses }); - } - - clear() { - this.state.firehoses.forEach(({ id }) => this.props.stopK8sWatch(id)); - } - - render() { - if (this.props.loaded || this.state.firehoses.length > 0) { - const children = inject(this.props.children, _.omit(this.props, ['children', 'resources'])); - - if (this.props.doNotConnectToState) { - return children; - } - - const reduxes = this.state.firehoses.map( - ({ id, prop, isList, filters, optional, kind }) => ({ - reduxID: id, - prop, - isList, - filters, - optional, - kind, - }), - ); - return {children}; - } - return null; - } - }, -); -Firehose.WrappedComponent.contextTypes = { - router: PropTypes.object, -}; - -Firehose.contextTypes = { - store: PropTypes.object, -}; - -Firehose.propTypes = { - children: PropTypes.node, - expand: PropTypes.bool, - doNotConnectToState: PropTypes.bool, - resources: PropTypes.arrayOf( - PropTypes.shape({ - kind: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, - name: PropTypes.string, - namespace: PropTypes.string, - selector: PropTypes.object, - fieldSelector: PropTypes.string, - isList: PropTypes.bool, - optional: PropTypes.bool, // do not block children-rendering while resource is still being loaded; do not fail if resource is missing (404) - limit: PropTypes.number, - partialMetadata: PropTypes.bool, - }), - ).isRequired, -}; diff --git a/frontend/public/components/utils/headings.tsx b/frontend/public/components/utils/headings.tsx index a608ae709fb..1d670c432f5 100644 --- a/frontend/public/components/utils/headings.tsx +++ b/frontend/public/components/utils/headings.tsx @@ -24,7 +24,7 @@ import { K8sResourceKindReference, referenceForExtensionModel, } from '../../module/k8s'; -import type { FirehoseResult } from './types'; +import type { WatchK8sResultsObject } from '@console/dynamic-plugin-sdk/src/extensions/console-types'; import { ResourceIcon } from './resource-icon'; import { ManagedByOperatorLink } from './managed-by'; import { Action } from '@console/dynamic-plugin-sdk/src/lib-core'; @@ -250,7 +250,7 @@ export type ConnectedPageHeadingProps = Omit kind?: K8sResourceKindReference; kindObj?: K8sKind; menuActions?: Function[] | KebabOptionsCreator; // FIXME should be "KebabAction[] |" refactor pipeline-actions.tsx, etc. - obj?: FirehoseResult; + obj?: WatchK8sResultsObject; /** A component to override the title of the page */ OverrideTitle?: ComponentType<{ obj?: K8sResourceKind }>; resourceKeys?: string[]; diff --git a/frontend/public/components/utils/index.tsx b/frontend/public/components/utils/index.tsx index 62e9a47b60d..339f7cd18c9 100644 --- a/frontend/public/components/utils/index.tsx +++ b/frontend/public/components/utils/index.tsx @@ -9,7 +9,6 @@ export * from './resource-log'; export * from './horizontal-nav'; export * from './details-page'; export * from './inject'; -export * from './firehose'; export * from './status-box'; export * from './headings'; export * from './units'; diff --git a/frontend/public/components/utils/list-dropdown.tsx b/frontend/public/components/utils/list-dropdown.tsx index a417a5983a6..4cc8c2922d1 100644 --- a/frontend/public/components/utils/list-dropdown.tsx +++ b/frontend/public/components/utils/list-dropdown.tsx @@ -15,7 +15,6 @@ import { useTranslation } from 'react-i18next'; import { useCreateNamespaceModal } from '@console/shared/src/hooks/useCreateNamespaceModal'; import { useCreateProjectModal } from '@console/shared/src/hooks/useCreateProjectModal'; import { - FirehoseResource, K8sResourceCommon, K8sModel, K8sResourceKind, @@ -26,7 +25,7 @@ const getKey = (key, keyKind) => { return keyKind ? `${key}-${keyKind}` : key; }; -interface ListDropdownResource extends Partial { +interface ListDropdownResource extends Partial { data?: K8sResourceCommon[]; } @@ -207,7 +206,7 @@ export const ListDropdown: FC = (props) => { return {}; } return props.resources.reduce((acc, resource) => { - // Use prop as key if provided, otherwise fallback to kind (matches original Firehose behavior) + // Use prop as key if provided, otherwise fallback to kind const key = resource.prop || resource.kind; acc[key] = { kind: resource.kind, @@ -228,11 +227,9 @@ export const ListDropdown: FC = (props) => { ); const loaded = useMemo(() => { - const resourceValues = Object.values(watchedResources); - if (resourceValues.length === 0) { - return true; - } - return resourceValues.every((r) => r.loaded || r.loadError); + return Object.values(watchedResources) + .filter((r) => !r.loadError) + .every((r) => r.loaded); }, [watchedResources]); const loadError = useMemo(() => { @@ -266,11 +263,6 @@ export const NsDropdown: FC = (props) => { const [selectedKey, setSelectedKey] = useState(props.selectedKey); const [model, canCreate] = useProjectOrNamespaceModel(); - // Sync internal state with prop changes - useEffect(() => { - setSelectedKey(props.selectedKey); - }, [props.selectedKey]); - const actionItems = model && canCreate ? [ diff --git a/frontend/public/components/utils/types.ts b/frontend/public/components/utils/types.ts index b677106ce47..3f7f06029ff 100644 --- a/frontend/public/components/utils/types.ts +++ b/frontend/public/components/utils/types.ts @@ -1,28 +1,3 @@ -import type { - K8sResourceKindReference, - K8sResourceCommon, -} from '@console/dynamic-plugin-sdk/src/extensions/console-types'; -import type { Selector } from '@console/dynamic-plugin-sdk/src/api/common-types'; -import type { K8sResourceKind } from '../../module/k8s/types'; - -export type FirehoseResult< - R extends K8sResourceCommon | K8sResourceCommon[] = K8sResourceKind[] -> = { - loaded: boolean; - loadError: string; - optional?: boolean; - data: R; - kind?: string; -}; - -export type FirehoseResultObject = { [key: string]: K8sResourceCommon | K8sResourceCommon[] }; - -export type FirehoseResourcesResult< - R extends FirehoseResultObject = { [key: string]: K8sResourceCommon | K8sResourceCommon[] } -> = { - [k in keyof R]: FirehoseResult; -}; - /* Add the enum for NameValueEditorPair here and not in its namesake file because the editor should always be loaded asynchronously in order not to bloat the vendor file. The enum reference into the editor @@ -47,19 +22,6 @@ export const enum EnvType { ENV_FROM = 1, } -export type FirehoseResource = { - kind: K8sResourceKindReference; - name?: string; - namespace?: string; - isList?: boolean; - selector?: Selector; - prop: string; - namespaced?: boolean; - optional?: boolean; - limit?: number; - fieldSelector?: string; -}; - export type HumanizeResult = { string: string; value: number;