From 58564bdfbd5a69ef1d981e16c95f05b6345826ec Mon Sep 17 00:00:00 2001 From: Montse Ortega Date: Thu, 12 Dec 2024 06:08:50 +0100 Subject: [PATCH] ECOPROJECT-2357: Adjusting UI to last changes of API (#51) * ECOPROJECT-2357: Adjusting UI to last changes of API Signed-off-by: Montse Ortega --- apps/demo/src/apis/MockAgentApi.ts | 24 +++ apps/demo/src/apis/MockSourceApi.ts | 19 +-- apps/demo/src/apis/responses/agents.json | 24 +++ apps/demo/src/main/Root.tsx | 6 +- .../src/migration-wizard/MigrationWizard.tsx | 8 +- .../@types/DiscoverySources.d.ts | 15 +- .../contexts/discovery-sources/Provider.tsx | 64 ++++++-- .../steps/connect/ConnectStep.tsx | 26 +-- .../steps/connect/sources-table/Columns.ts | 2 +- .../connect/sources-table/SourcesTable.tsx | 60 ++++--- .../empty-state/DiscoverySourceSetupModal.tsx | 26 --- .../sources-table/empty-state/EmptyState.tsx | 11 +- packages/api-client/.openapi-generator/FILES | 4 + packages/api-client/src/apis/AgentApi.ts | 129 +++++++++++++++ packages/api-client/src/apis/ImageApi.ts | 84 +++++++--- packages/api-client/src/apis/SourceApi.ts | 110 +------------ packages/api-client/src/apis/index.ts | 15 +- packages/api-client/src/models/Agent.ts | 155 ++++++++++++++++++ .../src/models/InfraNetworksInner.ts | 20 ++- packages/api-client/src/models/Inventory.ts | 15 ++ packages/api-client/src/models/Source.ts | 28 +++- .../api-client/src/models/SourceAgentItem.ts | 70 ++++++++ .../api-client/src/models/SourceCreate.ts | 11 +- packages/api-client/src/models/VCenter.ts | 61 +++++++ packages/api-client/src/models/index.ts | 3 + 25 files changed, 727 insertions(+), 263 deletions(-) create mode 100644 apps/demo/src/apis/MockAgentApi.ts create mode 100644 apps/demo/src/apis/responses/agents.json create mode 100644 packages/api-client/src/apis/AgentApi.ts create mode 100644 packages/api-client/src/models/Agent.ts create mode 100644 packages/api-client/src/models/SourceAgentItem.ts create mode 100644 packages/api-client/src/models/VCenter.ts diff --git a/apps/demo/src/apis/MockAgentApi.ts b/apps/demo/src/apis/MockAgentApi.ts new file mode 100644 index 0000000..cefb2ce --- /dev/null +++ b/apps/demo/src/apis/MockAgentApi.ts @@ -0,0 +1,24 @@ +import { AgentApiInterface, DeleteAgentRequest } from "@migration-planner-ui/api-client/apis"; +import { Agent } from "@migration-planner-ui/api-client/models"; +import { InitOverrideFunction, ApiResponse, ConfigurationParameters } from "@migration-planner-ui/api-client/runtime"; + +export class MockAgentApi implements AgentApiInterface { + constructor(_configuration: ConfigurationParameters) { + console.warn("#### CAUTION: Using MockAgentApi ####"); + } + + deleteAgentRaw(_requestParameters: DeleteAgentRequest, _initOverrides?: RequestInit | InitOverrideFunction): Promise> { + throw new Error("Method not implemented."); + } + deleteAgent(_requestParameters: DeleteAgentRequest, _initOverrides?: RequestInit | InitOverrideFunction): Promise { + throw new Error("Method not implemented."); + } + listAgentsRaw(_initOverrides?: RequestInit | InitOverrideFunction): Promise>> { + throw new Error("Method not implemented."); + } + async listAgents(_initOverrides?: RequestInit | InitOverrideFunction): Promise> { + const { default: json } = await import("./responses/agents.json"); + return json as unknown as Array; + } + +} \ No newline at end of file diff --git a/apps/demo/src/apis/MockSourceApi.ts b/apps/demo/src/apis/MockSourceApi.ts index 5eb409d..fe97c3a 100644 --- a/apps/demo/src/apis/MockSourceApi.ts +++ b/apps/demo/src/apis/MockSourceApi.ts @@ -1,8 +1,7 @@ // import { sleep, Time } from "#/common/Time"; import { - CreateSourceRequest, DeleteSourceRequest, - GetSourceImageRequest, + GetImageRequest, ReadSourceRequest, SourceApiInterface, } from "@migration-planner-ui/api-client/apis"; @@ -18,18 +17,6 @@ export class MockSourceApi implements SourceApiInterface { console.warn("#### CAUTION: Using MockSourceApi ####"); } - async createSourceRaw( - _requestParameters: CreateSourceRequest, - _initOverrides?: RequestInit | InitOverrideFunction - ): Promise> { - throw new Error("Method not implemented."); - } - async createSource( - _requestParameters: CreateSourceRequest, - _initOverrides?: RequestInit | InitOverrideFunction - ): Promise { - throw new Error("Method not implemented."); - } async deleteSourceRaw( _requestParameters: DeleteSourceRequest, _initOverrides?: RequestInit | InitOverrideFunction @@ -53,13 +40,13 @@ export class MockSourceApi implements SourceApiInterface { throw new Error("Method not implemented."); } async getSourceImageRaw( - _requestParameters: GetSourceImageRequest, + _requestParameters: GetImageRequest, _initOverrides?: RequestInit | InitOverrideFunction ): Promise> { throw new Error("Method not implemented."); } async getSourceImage( - _requestParameters: GetSourceImageRequest, + _requestParameters: GetImageRequest, _initOverrides?: RequestInit | InitOverrideFunction ): Promise { throw new Error("Method not implemented."); diff --git a/apps/demo/src/apis/responses/agents.json b/apps/demo/src/apis/responses/agents.json new file mode 100644 index 0000000..a910456 --- /dev/null +++ b/apps/demo/src/apis/responses/agents.json @@ -0,0 +1,24 @@ +[ + { + "associated": true, + "createdAt": "2024-12-11T07:22:40.264166+01:00", + "credentialUrl": "http://127.0.0.1:3333", + "sourceId": "29031FCA-3274-4067-A655-8BC4C322EA3F", + "id": "f8d28f7d-d862-47c5-b823-9763b319ff91", + "status": "up-to-date", + "statusInfo": "Inventory successfully collected", + "updatedAt": "2024-12-11T07:23:25.261977+01:00", + "version": "b1dc3f5" + }, + { + "associated": true, + "createdAt": "2024-12-11T07:22:40.264166+01:00", + "credentialUrl": "http://127.0.0.1:3333", + "sourceId": "29031FCA-3274-4067-A655-8BC4C322EA3C", + "id": "f8d28f7d-d862-47c5-b823-9763b319ff92", + "status": "gathering-initial-inventory", + "statusInfo": "Gathering inventory", + "updatedAt": "2024-12-11T07:23:25.261977+01:00", + "version": "b1dc3f5" + } +] \ No newline at end of file diff --git a/apps/demo/src/main/Root.tsx b/apps/demo/src/main/Root.tsx index 72c7cfa..5409405 100644 --- a/apps/demo/src/main/Root.tsx +++ b/apps/demo/src/main/Root.tsx @@ -4,7 +4,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { RouterProvider } from "react-router-dom"; import { Configuration } from "@migration-planner-ui/api-client/runtime"; -import { SourceApi } from "@migration-planner-ui/api-client/apis"; +import { AgentApi, SourceApi } from "@migration-planner-ui/api-client/apis"; import { Spinner } from "@patternfly/react-core"; import { Container, @@ -19,7 +19,11 @@ function getConfiguredContainer(): Container { }); const container = new Container(); container.register(Symbols.SourceApi, new SourceApi(plannerApiConfig)); + container.register(Symbols.AgentApi, new AgentApi(plannerApiConfig)); + //For UI testing we can use the mock Apis + //container.register(Symbols.SourceApi, new MockSourceApi(plannerApiConfig)); + //container.register(Symbols.AgentApi, new MockAgentApi(plannerApiConfig)); return container; } diff --git a/apps/demo/src/migration-wizard/MigrationWizard.tsx b/apps/demo/src/migration-wizard/MigrationWizard.tsx index 415e2ab..9d00b94 100644 --- a/apps/demo/src/migration-wizard/MigrationWizard.tsx +++ b/apps/demo/src/migration-wizard/MigrationWizard.tsx @@ -13,7 +13,7 @@ const openAssistedInstaller = (): void => { export const MigrationWizard: React.FC = () => { const computedHeight = useComputedHeightFromPageHeader(); const discoverSourcesContext = useDiscoverySources(); - const isDiscoverySourceUpToDate = discoverSourcesContext.sourceSelected?.status === "up-to-date"; + const isDiscoverySourceUpToDate = discoverSourcesContext.agentSelected?.status === "up-to-date"; return ( @@ -22,7 +22,7 @@ export const MigrationWizard: React.FC = () => { id="connect-step" footer={{ isCancelHidden: true, - isNextDisabled: !isDiscoverySourceUpToDate, + isNextDisabled: (!isDiscoverySourceUpToDate || discoverSourcesContext.sourceSelected===null), }} > @@ -31,7 +31,7 @@ export const MigrationWizard: React.FC = () => { name="Discover" id="discover-step" footer={{ isCancelHidden: true }} - isDisabled={discoverSourcesContext.sourceSelected?.status !== 'up-to-date'} + isDisabled={discoverSourcesContext.agentSelected?.status !== 'up-to-date' || discoverSourcesContext.sourceSelected===null} > @@ -42,7 +42,7 @@ export const MigrationWizard: React.FC = () => { nextButtonText: "Let's create a new cluster", onNext: openAssistedInstaller, }} - isDisabled={discoverSourcesContext.sourceSelected?.status !== 'up-to-date'} + isDisabled={discoverSourcesContext.agentSelected?.status !== 'up-to-date' || discoverSourcesContext.sourceSelected===null} > diff --git a/apps/demo/src/migration-wizard/contexts/discovery-sources/@types/DiscoverySources.d.ts b/apps/demo/src/migration-wizard/contexts/discovery-sources/@types/DiscoverySources.d.ts index 65b8089..67a6b37 100644 --- a/apps/demo/src/migration-wizard/contexts/discovery-sources/@types/DiscoverySources.d.ts +++ b/apps/demo/src/migration-wizard/contexts/discovery-sources/@types/DiscoverySources.d.ts @@ -5,18 +5,25 @@ declare namespace DiscoverySources { errorLoadingSources?: Error; isDeletingSource: boolean; errorDeletingSource?: Error; - isCreatingSource: boolean; - errorCreatingSource?: Error; isDownloadingSource: boolean; errorDownloadingSource?: Error; isPolling: boolean; sourceSelected: Source; listSources: () => Promise; deleteSource: (id: string) => Promise; - createSource: (name: string, sourceSshKey: string) => Promise; - downloadSource: (sourceName: string, sourceSshKey: string) => Promise; + downloadSource: (sourceSshKey: string) => Promise; startPolling: (delay: number) => void; stopPolling: () => void; selectSource: (source:Source) => void; + agents: Agent[]|undefined; + isLoadingAgents: boolean; + errorLoadingAgents?: Error; + listAgents: () => Promise; + deleteAgent: (agent: Agent) => Promise; + isDeletingAgent: boolean; + errorDeletingAgent?: Error; + selectAgent: (agent:Agent) => void; + agentSelected: Agent; + selectSourceById: (sourceId:string)=>void; }; } diff --git a/apps/demo/src/migration-wizard/contexts/discovery-sources/Provider.tsx b/apps/demo/src/migration-wizard/contexts/discovery-sources/Provider.tsx index d2386fb..53a0deb 100644 --- a/apps/demo/src/migration-wizard/contexts/discovery-sources/Provider.tsx +++ b/apps/demo/src/migration-wizard/contexts/discovery-sources/Provider.tsx @@ -4,18 +4,26 @@ import React, { type PropsWithChildren, } from "react"; import { useAsyncFn, useInterval } from "react-use"; -import { type SourceApiInterface } from "@migration-planner-ui/api-client/apis"; +import { type SourceApiInterface, type AgentApiInterface } from "@migration-planner-ui/api-client/apis"; import { useInjection } from "@migration-planner-ui/ioc"; import { Symbols } from "#/main/Symbols"; import { Context } from "./Context"; -import { Source } from "@migration-planner-ui/api-client/models"; +import { Agent, Source } from "@migration-planner-ui/api-client/models"; export const Provider: React.FC = (props) => { const { children } = props; const [sourceSelected, setSourceSelected] = useState(null) + const [agentSelected, setAgentSelected] = useState(null) + const sourceApi = useInjection(Symbols.SourceApi); + const agentsApi = useInjection(Symbols.AgentApi); + + const [listAgentsState, listAgents] = useAsyncFn(async () => { + const agents = await agentsApi.listAgents(); + return agents; + }); const [listSourcesState, listSources] = useAsyncFn(async () => { const sources = await sourceApi.listSources(); @@ -27,20 +35,11 @@ export const Provider: React.FC = (props) => { return deletedSource; }); - const [createSourceState, createSource] = useAsyncFn(async (name: string, sshKey: string) => { - const createdSource = await sourceApi.createSource({ - sourceCreate: { name, sshKey }, - }); - return createdSource; - }); const [downloadSourceState, downloadSource] = useAsyncFn( - async (sourceName: string, sourceSshKey: string): Promise => { + async (sshKey:string): Promise => { const anchor = document.createElement("a"); - anchor.download = sourceName + ".ova"; - - const newSource = await createSource(sourceName, sourceSshKey); - const imageUrl = `/planner/api/v1/sources/${newSource.id}/image`; + const imageUrl = `/planner/api/v1/image${sshKey ? '?sshKey=' + sshKey : ''}`; const response = await fetch(imageUrl, { method: 'HEAD' }); @@ -84,31 +83,60 @@ export const Provider: React.FC = (props) => { }, [isPolling]); useInterval(() => { listSources(); + listAgents(); }, pollingDelay); - const selectSource = useCallback((source: Source) => { + const selectSource = useCallback((source: Source|null) => { setSourceSelected(source); }, []); + const selectSourceById = useCallback((sourceId: string) => { + const source = listSourcesState.value?.find(source => source.id === sourceId); + setSourceSelected(source||null); + }, [listSourcesState.value]); + + + const selectAgent = useCallback((agent: Agent) => { + setAgentSelected(agent); + if (agent && agent.sourceId!==null) selectSourceById(agent.sourceId ?? ''); + }, [selectSourceById]); + + + const [deleteAgentState, deleteAgent] = useAsyncFn(async (agent: Agent) => { + if (agent && agent.sourceId !== null) { + await deleteSource(agent.sourceId ?? ''); + selectSource(null); + } + const deletedAgent = await agentsApi.deleteAgent({id: agent.id}); + return deletedAgent; + }); + const ctx: DiscoverySources.Context = { sources: listSourcesState.value ?? [], isLoadingSources: listSourcesState.loading, errorLoadingSources: listSourcesState.error, isDeletingSource: deleteSourceState.loading, errorDeletingSource: deleteSourceState.error, - isCreatingSource: createSourceState.loading, - errorCreatingSource: createSourceState.error, isDownloadingSource: downloadSourceState.loading, - errorDownloadingSource: downloadSourceState.error, + errorDownloadingSource: downloadSourceState.error, isPolling, listSources, deleteSource, - createSource, downloadSource, startPolling, stopPolling, sourceSelected: sourceSelected, selectSource, + agents: listAgentsState.value ?? [], + isLoadingAgents: listAgentsState.loading, + errorLoadingAgents: listAgentsState.error, + listAgents, + deleteAgent, + isDeletingAgent: deleteAgentState.loading, + errorDeletingAgent: deleteAgentState.error, + selectAgent, + agentSelected: agentSelected, + selectSourceById }; return {children}; diff --git a/apps/demo/src/migration-wizard/steps/connect/ConnectStep.tsx b/apps/demo/src/migration-wizard/steps/connect/ConnectStep.tsx index aad81e9..edcbd16 100644 --- a/apps/demo/src/migration-wizard/steps/connect/ConnectStep.tsx +++ b/apps/demo/src/migration-wizard/steps/connect/ConnectStep.tsx @@ -31,15 +31,17 @@ export const ConnectStep: React.FC = () => { const toggleDiscoverySourceSetupModal = useCallback((): void => { setShouldShowDiscoverySetupModal((lastState) => !lastState); }, []); - const hasSources = discoverySourcesContext.sources.length > 0; - const [firstSource, ..._otherSources] = discoverySourcesContext.sources; + const hasAgents = discoverySourcesContext.agents && discoverySourcesContext.agents.length > 0; + const [firstAgent, ..._otherAgents] = discoverySourcesContext.agents || []; useEffect(() => { - if (!discoverySourcesContext.sourceSelected && firstSource) { - discoverySourcesContext.selectSource(firstSource); + if (!discoverySourcesContext.agentSelected && firstAgent) { + if (firstAgent.status === 'up-to-date') { + discoverySourcesContext.selectAgent(firstAgent); + } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [firstSource]); + }, [firstAgent]); return ( @@ -69,7 +71,7 @@ export const ConnectStep: React.FC = () => { - {discoverySourcesContext.sourceSelected?.status === + {discoverySourcesContext.agentSelected?.status === "waiting-for-credentials" && ( { actionLinks={ - {discoverySourcesContext.sourceSelected?.credentialUrl} + {discoverySourcesContext.agentSelected?.credentialUrl} } > @@ -113,7 +115,7 @@ export const ConnectStep: React.FC = () => { - {hasSources && ( + {hasAgents && (