From 9b1f6f6d45f04680b83f0ae211119af98c3b376e Mon Sep 17 00:00:00 2001 From: Kevin Cormier Date: Fri, 29 Nov 2024 17:18:41 -0500 Subject: [PATCH 1/3] Make OpenShift console Timestamp component available Signed-off-by: Kevin Cormier --- .../components/ACMNotReadyWarning.test.tsx | 25 ++-------------- .../src/components/PluginContextProvider.tsx | 28 +++++------------- frontend/src/lib/PluginContext.tsx | 29 +++++++++++-------- frontend/src/lib/test-util.ts | 6 ---- .../ApplicationDetails.test.tsx | 8 ++--- .../src/routes/Applications/Overview.test.tsx | 8 ++--- .../routes/Home/Overview/Overview.test.tsx | 23 ++------------- .../ClusterSetDetails.test.tsx | 8 ++--- .../Clusters/ClusterSets/ClusterSets.test.tsx | 8 ++--- .../CreateCluster/CreateCluster.test.tsx | 6 ++-- .../ImportCluster/ImportCluster.test.tsx | 6 ++-- .../components/StatusSummaryCount.test.tsx | 7 ++--- 12 files changed, 50 insertions(+), 112 deletions(-) diff --git a/frontend/src/components/ACMNotReadyWarning.test.tsx b/frontend/src/components/ACMNotReadyWarning.test.tsx index b0439b88a5e..7ae53c094e0 100644 --- a/frontend/src/components/ACMNotReadyWarning.test.tsx +++ b/frontend/src/components/ACMNotReadyWarning.test.tsx @@ -7,8 +7,7 @@ import { clickByRole, waitForText } from '../lib/test-util' import { SubscriptionOperator, SubscriptionOperatorApiVersion, SubscriptionOperatorKind } from '../resources' import { nockIgnoreOperatorCheck } from '../lib/nock-util' import { ACMNotReadyWarning } from './ACMNotReadyWarning' -import { PluginDataContext } from '../lib/PluginDataContext' -import { PluginContext } from '../lib/PluginContext' +import { defaultPlugin, PluginContext } from '../lib/PluginContext' const acm_unhealthy: SubscriptionOperator = { apiVersion: SubscriptionOperatorApiVersion, @@ -88,17 +87,8 @@ describe('ACMNotReadyWarning', () => { render( [[] as any, true, undefined], - }, }} > @@ -125,17 +115,8 @@ describe('ACMNotReadyWarning', () => { render( [[] as any, true, undefined], - }, }} > diff --git a/frontend/src/components/PluginContextProvider.tsx b/frontend/src/components/PluginContextProvider.tsx index 4350f1366dd..6227f87b186 100644 --- a/frontend/src/components/PluginContextProvider.tsx +++ b/frontend/src/components/PluginContextProvider.tsx @@ -1,7 +1,12 @@ /* Copyright Contributors to the Open Cluster Management project */ -import { isHrefNavItem, useResolvedExtensions, UseK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk' +import { + isHrefNavItem, + Timestamp, + useK8sWatchResource, + useResolvedExtensions, +} from '@openshift-console/dynamic-plugin-sdk' import { AcmTablePaginationContextProvider, AcmToastGroup, AcmToastProvider } from '../ui-components' -import { ReactNode, useMemo, useEffect, useState } from 'react' +import { ReactNode, useMemo } from 'react' import { PluginContext } from '../lib/PluginContext' import { useAcmExtension } from '../plugin-extensions/handler' import { LoadingPage } from './LoadingPage' @@ -15,9 +20,6 @@ const isPluginDataContext = (e: Extension): e is SharedContext => isSharedContext(e) && e.properties.id === 'mce-data-context' export function PluginContextProvider(props: { children?: ReactNode }) { - const [ocpApi, setOcpApi] = useState<{ useK8sWatchResource: UseK8sWatchResource }>({ - useK8sWatchResource: () => [[] as any, false, undefined], - }) const [hrefs] = useResolvedExtensions(isHrefNavItem) const [pluginDataContexts, extensionsReady] = useResolvedExtensions(isPluginDataContext) @@ -38,20 +40,6 @@ export function PluginContextProvider(props: { children?: ReactNode }) { const isACMAvailable = isOverviewAvailable const isSubmarinerAvailable = isOverviewAvailable - useEffect(() => { - const loadOCPAPI = async () => { - try { - const api = await import('@openshift-console/dynamic-plugin-sdk') - setOcpApi({ - useK8sWatchResource: api.useK8sWatchResource, - }) - } catch (err) { - console.error('Failed to load OCP API', err) - } - } - loadOCPAPI() - }, []) - // ACM Custom extensions const acmExtensions = useAcmExtension() @@ -66,7 +54,7 @@ export function PluginContextProvider(props: { children?: ReactNode }) { isSubmarinerAvailable, dataContext: pluginDataContext.properties.context, acmExtensions, - ocpApi, + ocpApi: { Timestamp, useK8sWatchResource }, }} > diff --git a/frontend/src/lib/PluginContext.tsx b/frontend/src/lib/PluginContext.tsx index 933d8068c3d..02eb9e9f10e 100644 --- a/frontend/src/lib/PluginContext.tsx +++ b/frontend/src/lib/PluginContext.tsx @@ -1,22 +1,25 @@ /* Copyright Contributors to the Open Cluster Management project */ -import { Context, createContext } from 'react' +import { Context, createContext, FC } from 'react' import { AcmExtension } from '../plugin-extensions/types' import { PluginData, PluginDataContext } from './PluginDataContext' -import { UseK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk/lib/extensions/console-types' +import { TimestampProps, UseK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk/lib/extensions/console-types' -export const PluginContext = createContext<{ - isACMAvailable?: boolean - isOverviewAvailable?: boolean - isSubmarinerAvailable?: boolean - isApplicationsAvailable?: boolean - isGovernanceAvailable?: boolean - isSearchAvailable?: boolean +export type Plugin = { + isACMAvailable: boolean + isOverviewAvailable: boolean + isSubmarinerAvailable: boolean + isApplicationsAvailable: boolean + isGovernanceAvailable: boolean + isSearchAvailable: boolean dataContext: Context - acmExtensions?: AcmExtension + acmExtensions: AcmExtension ocpApi: { + Timestamp?: FC useK8sWatchResource: UseK8sWatchResource } -}>({ +} + +export const defaultPlugin: Plugin = { isACMAvailable: true, isOverviewAvailable: true, isSubmarinerAvailable: true, @@ -28,4 +31,6 @@ export const PluginContext = createContext<{ ocpApi: { useK8sWatchResource: () => [[] as any, true, undefined], }, -}) +} + +export const PluginContext = createContext(defaultPlugin) diff --git a/frontend/src/lib/test-util.ts b/frontend/src/lib/test-util.ts index d2281204397..e6c56f52f8e 100644 --- a/frontend/src/lib/test-util.ts +++ b/frontend/src/lib/test-util.ts @@ -3,7 +3,6 @@ import { act, ByRoleMatcher, ByRoleOptions, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { Scope } from 'nock/types' -import { UseK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk' export const waitTimeout = 5 * 1000 @@ -375,11 +374,6 @@ export function isCardEnabled(card: HTMLElement) { return card.style.cursor === 'pointer' } -export const ocpApi: { - useK8sWatchResource: UseK8sWatchResource -} = { - useK8sWatchResource: () => [[] as any, true, undefined], -} export const getCSVExportSpies = () => { const blobConstructorSpy = jest.fn() jest.spyOn(global, 'Blob').mockImplementationOnce(blobConstructorSpy) diff --git a/frontend/src/routes/Applications/ApplicationDetails/ApplicationDetails.test.tsx b/frontend/src/routes/Applications/ApplicationDetails/ApplicationDetails.test.tsx index 69d1550ef91..dfcc5632582 100644 --- a/frontend/src/routes/Applications/ApplicationDetails/ApplicationDetails.test.tsx +++ b/frontend/src/routes/Applications/ApplicationDetails/ApplicationDetails.test.tsx @@ -18,9 +18,8 @@ import { subscriptionsState, } from '../../../atoms' import { nockIgnoreApiPaths, nockIgnoreRBAC, nockSearch } from '../../../lib/nock-util' -import { PluginContext } from '../../../lib/PluginContext' -import { PluginDataContext } from '../../../lib/PluginDataContext' -import { ocpApi, waitForText } from '../../../lib/test-util' +import { defaultPlugin, PluginContext } from '../../../lib/PluginContext' +import { waitForText } from '../../../lib/test-util' import { ApplicationActionProps } from '../../../plugin-extensions/properties' import { AcmExtension } from '../../../plugin-extensions/types' import { GetMessagesDocument, SearchSchemaDocument } from '../../Search/search-sdk/search-sdk' @@ -372,9 +371,8 @@ describe('Applications Page', () => { diff --git a/frontend/src/routes/Applications/Overview.test.tsx b/frontend/src/routes/Applications/Overview.test.tsx index 52a1f7dfbb8..25518316f00 100644 --- a/frontend/src/routes/Applications/Overview.test.tsx +++ b/frontend/src/routes/Applications/Overview.test.tsx @@ -12,9 +12,8 @@ import { nockSearch, nockAggegateRequest, } from '../../lib/nock-util' -import { PluginContext } from '../../lib/PluginContext' -import { PluginDataContext } from '../../lib/PluginDataContext' -import { ocpApi, waitForText } from '../../lib/test-util' +import { defaultPlugin, PluginContext } from '../../lib/PluginContext' +import { waitForText } from '../../lib/test-util' import { ApplicationKind, ApplicationSetKind, @@ -116,9 +115,8 @@ describe('Applications Page', () => { diff --git a/frontend/src/routes/Home/Overview/Overview.test.tsx b/frontend/src/routes/Home/Overview/Overview.test.tsx index 957667e57ef..34fef564f26 100644 --- a/frontend/src/routes/Home/Overview/Overview.test.tsx +++ b/frontend/src/routes/Home/Overview/Overview.test.tsx @@ -5,8 +5,7 @@ import { render } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom-v5-compat' import { RecoilRoot } from 'recoil' import { nockGet, nockIgnoreApiPaths } from '../../../lib/nock-util' -import { PluginContext } from '../../../lib/PluginContext' -import { PluginDataContext } from '../../../lib/PluginDataContext' +import { defaultPlugin, PluginContext } from '../../../lib/PluginContext' import { clickByText, waitForNocks, waitForText } from '../../../lib/test-util' import Overview from './Overview' import { getAddonRequest, getAddonResponse } from './Overview.sharedmocks' @@ -29,13 +28,8 @@ it('should render overview page with extension', async () => { { }, ], }, - ocpApi: { - useK8sWatchResource: () => [[] as any, true, undefined], - }, }} > @@ -81,13 +72,8 @@ it('should render overview page layout when extension tab crashes', async () => }, ], }, - ocpApi: { - useK8sWatchResource: () => [[] as any, true, undefined], - }, }} > diff --git a/frontend/src/routes/Infrastructure/Clusters/ClusterSets/ClusterSetDetails/ClusterSetDetails.test.tsx b/frontend/src/routes/Infrastructure/Clusters/ClusterSets/ClusterSetDetails/ClusterSetDetails.test.tsx index 353f727f1dc..84b465c919c 100644 --- a/frontend/src/routes/Infrastructure/Clusters/ClusterSets/ClusterSetDetails/ClusterSetDetails.test.tsx +++ b/frontend/src/routes/Infrastructure/Clusters/ClusterSets/ClusterSetDetails/ClusterSetDetails.test.tsx @@ -25,8 +25,7 @@ import { nockNamespacedList, nockPatch, } from '../../../../../lib/nock-util' -import { PluginContext } from '../../../../../lib/PluginContext' -import { PluginDataContext } from '../../../../../lib/PluginDataContext' +import { defaultPlugin, PluginContext } from '../../../../../lib/PluginContext' import { mockGlobalManagedClusterSet, mockManagedClusterSet } from '../../../../../lib/test-metadata' import { clearByTestId, @@ -39,7 +38,6 @@ import { waitForNotText, waitForTestId, waitForText, - ocpApi, } from '../../../../../lib/test-util' import { NavigationPath } from '../../../../../NavigationPath' import { @@ -2084,7 +2082,7 @@ describe('ClusterSetDetails page without Submariner', () => { nockIgnoreRBAC() nockIgnoreApiPaths() render( - + ) @@ -2108,7 +2106,7 @@ describe('ClusterSetDetails page global clusterset', () => { nockIgnoreRBAC() nockIgnoreApiPaths() render( - + ) diff --git a/frontend/src/routes/Infrastructure/Clusters/ClusterSets/ClusterSets.test.tsx b/frontend/src/routes/Infrastructure/Clusters/ClusterSets/ClusterSets.test.tsx index bc3982884dd..675d7f2db8c 100644 --- a/frontend/src/routes/Infrastructure/Clusters/ClusterSets/ClusterSets.test.tsx +++ b/frontend/src/routes/Infrastructure/Clusters/ClusterSets/ClusterSets.test.tsx @@ -12,7 +12,7 @@ import { managedClustersState, } from '../../../../atoms' import { nockCreate, nockDelete, nockIgnoreApiPaths, nockIgnoreRBAC } from '../../../../lib/nock-util' -import { PluginContext } from '../../../../lib/PluginContext' +import { defaultPlugin, PluginContext } from '../../../../lib/PluginContext' import { mockManagedClusterSet, mockGlobalClusterSet } from '../../../../lib/test-metadata' import { clickBulkAction, @@ -23,7 +23,6 @@ import { waitForText, waitForNotText, typeByTestId, - ocpApi, clickByLabel, } from '../../../../lib/test-util' import { @@ -31,7 +30,6 @@ import { mockManagedClusterInfos, mockManagedClusters, } from '../ManagedClusters/ManagedClusters.sharedmocks' -import { PluginDataContext } from '../../../../lib/PluginDataContext' import { NavigationPath } from '../../../../NavigationPath' import Clusters from '../Clusters' @@ -95,7 +93,7 @@ describe('ClusterSets page without Submariner', () => { nockIgnoreRBAC() nockIgnoreApiPaths() render( - + ) @@ -111,7 +109,7 @@ describe('ClusterSets page with csv export', () => { nockIgnoreRBAC() nockIgnoreApiPaths() render( - + ) diff --git a/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/CreateCluster/CreateCluster.test.tsx b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/CreateCluster/CreateCluster.test.tsx index 70f2c236e7a..43658328b97 100644 --- a/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/CreateCluster/CreateCluster.test.tsx +++ b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/CreateCluster/CreateCluster.test.tsx @@ -22,14 +22,12 @@ import { nockIgnoreRBAC, nockList, } from '../../../../../lib/nock-util' -import { PluginContext } from '../../../../../lib/PluginContext' -import { PluginDataContext } from '../../../../../lib/PluginDataContext' +import { defaultPlugin, PluginContext } from '../../../../../lib/PluginContext' import { clickByPlaceholderText, clickByRole, clickByTestId, clickByText, - ocpApi, pasteByTestId, typeByPlaceholderText, typeByTestId, @@ -1052,7 +1050,7 @@ describe('CreateCluster AWS', () => { // create the form const { container } = render( - + ) diff --git a/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ImportCluster/ImportCluster.test.tsx b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ImportCluster/ImportCluster.test.tsx index 5cd3ef7a2af..6cd60ab0a09 100644 --- a/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ImportCluster/ImportCluster.test.tsx +++ b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ImportCluster/ImportCluster.test.tsx @@ -71,14 +71,12 @@ import { waitForNocks, waitForNotText, waitForText, - ocpApi, } from '../../../../../lib/test-util' import { NavigationPath } from '../../../../../NavigationPath' import DiscoveredClustersPage from '../../DiscoveredClusters/DiscoveredClusters' import ImportClusterPage from './ImportCluster' -import { PluginContext } from '../../../../../lib/PluginContext' +import { defaultPlugin, PluginContext } from '../../../../../lib/PluginContext' import { AcmToastGroup, AcmToastProvider } from '../../../../../ui-components' -import { PluginDataContext } from '../../../../../lib/PluginDataContext' import { PropsWithChildren, useEffect } from 'react' const mockProject: ProjectRequest = { @@ -798,7 +796,7 @@ describe('ImportCluster', () => { const importSecretNock = nockGet(mockSecretResponse) render( - + ) diff --git a/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/components/StatusSummaryCount.test.tsx b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/components/StatusSummaryCount.test.tsx index 9fac9238577..9971c12051d 100644 --- a/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/components/StatusSummaryCount.test.tsx +++ b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/components/StatusSummaryCount.test.tsx @@ -5,9 +5,8 @@ import { MemoryRouter, Outlet, Route, Routes } from 'react-router-dom-v5-compat' import { RecoilRoot } from 'recoil' import { policiesState, policyreportState } from '../../../../../atoms' import { nockAggegateRequest, nockSearch } from '../../../../../lib/nock-util' -import { PluginContext } from '../../../../../lib/PluginContext' -import { PluginDataContext } from '../../../../../lib/PluginDataContext' -import { clickByText, waitForNotText, waitForText, ocpApi } from '../../../../../lib/test-util' +import { defaultPlugin, PluginContext } from '../../../../../lib/PluginContext' +import { clickByText, waitForNotText, waitForText } from '../../../../../lib/test-util' import { Policy, PolicyReport } from '../../../../../resources' import { Cluster, ClusterStatus } from '../../../../../resources/utils' import { @@ -330,7 +329,7 @@ describe('StatusSummaryCount', () => { test('renders without applications and governance', async () => { render( From 1cb4c30d376bee9ef55d25fd5d0aed51aeb197c8 Mon Sep 17 00:00:00 2001 From: Kevin Cormier Date: Thu, 12 Dec 2024 14:07:52 -0500 Subject: [PATCH 2/3] Implement AcmTimestamp and SimpleTimestamp Signed-off-by: Kevin Cormier --- frontend/src/lib/AcmTimestamp.tsx | 43 ++++++++++++++++++++++++++++ frontend/src/lib/SimpleTimestamp.tsx | 14 +++++++++ 2 files changed, 57 insertions(+) create mode 100644 frontend/src/lib/AcmTimestamp.tsx create mode 100644 frontend/src/lib/SimpleTimestamp.tsx diff --git a/frontend/src/lib/AcmTimestamp.tsx b/frontend/src/lib/AcmTimestamp.tsx new file mode 100644 index 00000000000..00271b014b1 --- /dev/null +++ b/frontend/src/lib/AcmTimestamp.tsx @@ -0,0 +1,43 @@ +/* Copyright Contributors to the Open Cluster Management project */ + +import { css } from '@emotion/css' +import { SimpleTimestamp } from './SimpleTimestamp' +import { PluginContext } from './PluginContext' +import { useContext } from 'react' +import { TimestampProps } from '@openshift-console/dynamic-plugin-sdk' + +type AcmTimestampProps = TimestampProps & { + showIcon?: boolean +} + +const AcmTimestamp: React.FC = ({ + timestamp, + simple, + omitSuffix, + className = '', + showIcon = false, +}) => { + const { + ocpApi: { Timestamp }, + } = useContext(PluginContext) + return Timestamp ? ( + + ) : ( + + ) +} + +export default AcmTimestamp diff --git a/frontend/src/lib/SimpleTimestamp.tsx b/frontend/src/lib/SimpleTimestamp.tsx new file mode 100644 index 00000000000..27d31312d35 --- /dev/null +++ b/frontend/src/lib/SimpleTimestamp.tsx @@ -0,0 +1,14 @@ +/* Copyright Contributors to the Open Cluster Management project */ + +import React from 'react' + +interface SimpleTimestampProps { + timestamp: string | number | Date +} + +export const SimpleTimestamp: React.FC = ({ timestamp }) => { + const date = new Date(timestamp) + return <>{date.toLocaleString()} +} + +export default SimpleTimestamp From 051b0006e62c54b7ed6f313de066687ce6d3e715 Mon Sep 17 00:00:00 2001 From: Kevin Cormier Date: Fri, 29 Nov 2024 17:20:24 -0500 Subject: [PATCH 3/3] DEMO: Use the AcmTimestamp component Signed-off-by: Kevin Cormier --- .../Clusters/ManagedClusters/ManagedClusters.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ManagedClusters.tsx b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ManagedClusters.tsx index 0e1739f3b59..7c8afeb11a0 100644 --- a/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ManagedClusters.tsx +++ b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ManagedClusters.tsx @@ -86,6 +86,7 @@ import keyBy from 'lodash/keyBy' import { HighlightSearchText } from '../../../../components/HighlightSearchText' import { SearchOperator } from '../../../../ui-components/AcmSearchInput' import { handleStandardComparison, handleSemverOperatorComparison } from '../../../../lib/search-utils' +import AcmTimestamp from '../../../../lib/AcmTimestamp' const onToggle = (acmCardID: string, setOpen: (open: boolean) => void) => { setOpen(false) @@ -141,6 +142,9 @@ export default function ManagedClusters() { onToggle(onBoardingModalID, setOpenOnboardingModal)} /> + + +