diff --git a/frontend/src/components/ACMNotReadyWarning.test.tsx b/frontend/src/components/ACMNotReadyWarning.test.tsx index b0439b88a5..7ae53c094e 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 4350f1366d..6227f87b18 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/AcmTimestamp.tsx b/frontend/src/lib/AcmTimestamp.tsx new file mode 100644 index 0000000000..00271b014b --- /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/PluginContext.tsx b/frontend/src/lib/PluginContext.tsx index 933d8068c3..02eb9e9f10 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/SimpleTimestamp.tsx b/frontend/src/lib/SimpleTimestamp.tsx new file mode 100644 index 0000000000..27d31312d3 --- /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 diff --git a/frontend/src/lib/test-util.ts b/frontend/src/lib/test-util.ts index d228120439..e6c56f52f8 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 69d1550ef9..dfcc563258 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 52a1f7dfbb..25518316f0 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 957667e57e..34fef564f2 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 353f727f1d..84b465c919 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 bc3982884d..675d7f2db8 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 70f2c236e7..43658328b9 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 5cd3ef7a2a..6cd60ab0a0 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/ManagedClusters.tsx b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ManagedClusters.tsx index 0e1739f3b5..7c8afeb11a 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)} /> + + + { test('renders without applications and governance', async () => { render(