diff --git a/web/features/deployments/components/deploy-drawer.tsx b/web/features/deployments/components/deploy-drawer.tsx index 856c09b1e4..8b7a0362fa 100644 --- a/web/features/deployments/components/deploy-drawer.tsx +++ b/web/features/deployments/components/deploy-drawer.tsx @@ -19,13 +19,13 @@ const DeployDrawer: FC = () => { const open = drawer.open const { environmentOptions } = useSourceApps({ enabled: open }) - const appDataQuery = useQuery({ + useQuery({ ...deploymentAppDataQueryOptions(drawerAppId ?? ''), queryFn: () => useDeploymentsStore.getState().fetchAppData(drawerAppId!), enabled: open && Boolean(drawerAppId) && !storedAppData, }) - const appData = storedAppData ?? (appDataQuery.data?.appId === drawerAppId ? appDataQuery.data : undefined) + const appData = storedAppData const environments = environmentOptions const releases = appData?.releaseHistory.data?.map(row => row.release ?? row).filter(release => release.id) ?? [] const defaultReleaseId = releases[0]?.id diff --git a/web/features/deployments/hooks/use-source-apps.ts b/web/features/deployments/hooks/use-source-apps.ts index a0d286188a..3b707c2150 100644 --- a/web/features/deployments/hooks/use-source-apps.ts +++ b/web/features/deployments/hooks/use-source-apps.ts @@ -1,6 +1,5 @@ 'use client' import type { AppInfo } from '../types' -import type { AppDeploymentSummary, EnvironmentOption } from '@/contract/console/deployments' import { useQuery } from '@tanstack/react-query' import { useMemo } from 'react' import { consoleQuery } from '@/service/client' @@ -20,7 +19,6 @@ export function useSourceApps(options: UseSourceAppsOptions = {}) { const { enabled = true, environmentId, keyword, notDeployed } = options const instancesById = useDeploymentsStore(deploymentsSelectors.instancesById) const listRefreshToken = useDeploymentsStore(deploymentsSelectors.listRefreshToken) - const query = useMemo(() => ({ pageNumber: 1, resultsPerPage: MAX_SOURCE_APPS, @@ -28,6 +26,7 @@ export function useSourceApps(options: UseSourceAppsOptions = {}) { ...(notDeployed ? { notDeployed: true } : {}), ...(keyword?.trim() ? { query: keyword.trim() } : {}), }), [environmentId, keyword, notDeployed]) + const sourceAppsList = useDeploymentsStore(deploymentsSelectors.sourceAppsList(query)) const listQueryOptions = consoleQuery.deployments.list.queryOptions({ input: { query }, @@ -44,44 +43,21 @@ export function useSourceApps(options: UseSourceAppsOptions = {}) { queryFn: () => useDeploymentsStore.getState().fetchSourceApps(query), }) - const appIds = useMemo(() => { - return (listQuery.data?.data ?? []) - .map(summary => summary.id) - .filter((id): id is string => Boolean(id)) - }, [listQuery.data?.data]) - const apps = useMemo(() => { - return appIds + return sourceAppsList.appIds .map(id => instancesById[id]) .filter((app): app is AppInfo => Boolean(app)) - }, [appIds, instancesById]) + }, [sourceAppsList.appIds, instancesById]) const appMap = useMemo>(() => { return new Map(apps.map(a => [a.id, a])) }, [apps]) - const summaries = useMemo>(() => { - return Object.fromEntries( - (listQuery.data?.data ?? []) - .filter(summary => summary.id) - .map(summary => [summary.id!, summary]), - ) - }, [listQuery.data?.data]) - - const environmentOptions = useMemo(() => { - return listQuery.data?.filters - ?.filter(filter => filter.kind === 'environment' && filter.id) - .map(filter => ({ - id: filter.id, - name: filter.name, - })) ?? [] - }, [listQuery.data?.filters]) - return { apps, appMap, - summaries, - environmentOptions, + summaries: sourceAppsList.summaries, + environmentOptions: sourceAppsList.environmentOptions, isLoading: listQuery.isLoading, isFetching: listQuery.isFetching, isError: listQuery.isError, diff --git a/web/features/deployments/store/actions.ts b/web/features/deployments/store/actions.ts index 8778cf7b91..74e10ab750 100644 --- a/web/features/deployments/store/actions.ts +++ b/web/features/deployments/store/actions.ts @@ -27,6 +27,10 @@ import { updateEnvironmentAccessPolicy, waitForAppInstanceInDeploymentList, } from '../data' +import { + sourceAppsListKey, + toSourceAppsList, +} from './source-apps' export type StartDeployParams = { appId: string @@ -170,12 +174,22 @@ class DeploymentsActionImpl implements DeploymentsAction { })) } + #applySourceAppsList = (query: ListAppDeploymentsQuery, response: ListAppDeploymentsReply) => { + this.#set(state => ({ + sourceAppLists: { + ...state.sourceAppLists, + [sourceAppsListKey(query)]: toSourceAppsList(response), + }, + })) + } + fetchSourceApps = async (query: ListAppDeploymentsQuery) => { const response = await listAppDeployments(query) const apps = response.data ?.map(toAppInfoFromSummary) .filter((app): app is AppInfo => Boolean(app)) ?? [] this.upsertInstances(apps) + this.#applySourceAppsList(query, response) return response } @@ -211,10 +225,16 @@ class DeploymentsActionImpl implements DeploymentsAction { this.upsertInstances([app]) }), waitForAppInstanceInDeploymentList(response.appInstanceId).then((list) => { + const query = { + pageNumber: 1, + resultsPerPage: 100, + } const apps = list?.data ?.map(toAppInfoFromSummary) .filter((app): app is AppInfo => Boolean(app)) ?? [] this.upsertInstances(apps) + if (list) + this.#applySourceAppsList(query, list) }), ]) this.bumpDeploymentListRefresh() diff --git a/web/features/deployments/store/initial-state.ts b/web/features/deployments/store/initial-state.ts index 0770072332..9cd2d2d791 100644 --- a/web/features/deployments/store/initial-state.ts +++ b/web/features/deployments/store/initial-state.ts @@ -1,5 +1,6 @@ import type { DeploymentAppData } from '../data' import type { AppInfo } from '../types' +import type { SourceAppsList } from './source-apps' import type { APIToken } from '@/contract/console/deployments' export type CreatedApiToken = Pick & { @@ -10,6 +11,7 @@ export type CreatedApiToken = Pick appData: Record + sourceAppLists: Record listRefreshToken: number createdApiToken?: CreatedApiToken @@ -32,6 +34,7 @@ export type DeploymentsState = { export const initialDeploymentsState: DeploymentsState = { instancesById: {}, appData: {}, + sourceAppLists: {}, listRefreshToken: 0, createdApiToken: undefined, diff --git a/web/features/deployments/store/selectors.ts b/web/features/deployments/store/selectors.ts index acdab1a800..8fee0b0a7c 100644 --- a/web/features/deployments/store/selectors.ts +++ b/web/features/deployments/store/selectors.ts @@ -1,4 +1,6 @@ +import type { ListAppDeploymentsQuery } from '../data' import type { DeploymentsStore } from './actions' +import { emptySourceAppsList, sourceAppsListKey } from './source-apps' export const deploymentsSelectors = { appData: (appId?: string) => (state: DeploymentsStore) => @@ -8,4 +10,6 @@ export const deploymentsSelectors = { appId ? state.instancesById[appId] : undefined, instancesById: (state: DeploymentsStore) => state.instancesById, listRefreshToken: (state: DeploymentsStore) => state.listRefreshToken, + sourceAppsList: (query: ListAppDeploymentsQuery) => (state: DeploymentsStore) => + state.sourceAppLists[sourceAppsListKey(query)] ?? emptySourceAppsList, } diff --git a/web/features/deployments/store/source-apps.ts b/web/features/deployments/store/source-apps.ts new file mode 100644 index 0000000000..bfff4beaae --- /dev/null +++ b/web/features/deployments/store/source-apps.ts @@ -0,0 +1,53 @@ +import type { ListAppDeploymentsQuery } from '../data' +import type { + AppDeploymentSummary, + EnvironmentOption, + ListAppDeploymentsReply, +} from '@/contract/console/deployments' + +export type SourceAppsList = { + appIds: string[] + summaries: Record + environmentOptions: EnvironmentOption[] +} + +export const emptySourceAppsList: SourceAppsList = { + appIds: [], + summaries: {}, + environmentOptions: [], +} + +export const sourceAppsListKey = ({ + environmentId = '', + notDeployed = false, + pageNumber = 1, + query = '', + resultsPerPage = 100, +}: ListAppDeploymentsQuery) => { + return [ + environmentId, + notDeployed ? 'not-deployed' : 'all', + pageNumber, + resultsPerPage, + query.trim(), + ].join('|') +} + +export const toSourceAppsList = (response: ListAppDeploymentsReply): SourceAppsList => { + const summaries = Object.fromEntries( + (response.data ?? []) + .filter(summary => summary.id) + .map(summary => [summary.id!, summary]), + ) + + return { + appIds: Object.keys(summaries), + summaries, + environmentOptions: response.filters + ?.filter(filter => filter.kind === 'environment' && filter.id) + .map(filter => ({ + id: filter.id, + name: filter.name, + })) ?? [], + } +}