fix state

This commit is contained in:
Stephen Zhou 2026-04-29 16:50:23 +08:00
parent 64bacd1e5f
commit 79591ca7bd
No known key found for this signature in database
3 changed files with 111 additions and 11 deletions

View File

@ -9,10 +9,11 @@ import type {
} from '@/contract/console/deployments'
import { queryOptions } from '@tanstack/react-query'
import { getQueryClient } from '@/context/get-query-client'
import { consoleClient } from '@/service/client'
import { consoleClient, consoleQuery } from '@/service/client'
const DEPLOYMENT_PAGE_SIZE = 100
const DEPLOYMENT_APP_DATA_STALE_TIME = 30 * 1000
const DEPLOYMENT_READINESS_RETRY_DELAYS = [0, 300, 700, 1200]
export type DeploymentAppData = {
appId: string
@ -91,6 +92,65 @@ export const refreshDeploymentAppData = async (appId: string): Promise<Deploymen
})
}
const wait = (delay: number) => new Promise(resolve => setTimeout(resolve, delay))
export const refreshDeploymentAppDataWhenReady = async (appId: string): Promise<DeploymentAppData> => {
let lastError: unknown
for (const delay of DEPLOYMENT_READINESS_RETRY_DELAYS) {
if (delay > 0)
await wait(delay)
try {
return await refreshDeploymentAppData(appId)
}
catch (error) {
lastError = error
}
}
throw lastError
}
export const refreshDeploymentLists = async () => {
await getQueryClient().invalidateQueries({
queryKey: consoleQuery.deployments.list.key(),
})
}
export const waitForAppInstanceInDeploymentList = async (appInstanceId: string) => {
let lastError: unknown
for (const delay of DEPLOYMENT_READINESS_RETRY_DELAYS) {
if (delay > 0)
await wait(delay)
try {
const response = await getQueryClient().fetchQuery({
...consoleQuery.deployments.list.queryOptions({
input: {
query: {
pageNumber: 1,
resultsPerPage: DEPLOYMENT_PAGE_SIZE,
},
},
}),
staleTime: 0,
})
if (response.data?.some(app => app.id === appInstanceId))
return
}
catch (error) {
lastError = error
}
}
await refreshDeploymentLists()
if (lastError)
throw lastError
}
export const createRelease = async (appId: string, releaseNote?: string): Promise<ConsoleReleaseSummary> => {
const trimmedReleaseNote = releaseNote?.trim()
const response = await consoleClient.deployments.createRelease({

View File

@ -1,7 +1,9 @@
'use client'
import type { FC, ReactNode } from 'react'
import type { AppInfo, AppMode } from '../types'
import type { InstanceDetailTabKey } from './tabs'
import type { AppInstanceOverview } from '@/contract/console/deployments'
import { Button } from '@langgenius/dify-ui/button'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
@ -17,6 +19,22 @@ import { deployedRows, deploymentStatus } from '../utils'
import { DeploymentSidebar } from './deployment-sidebar'
import { isInstanceDetailTabKey } from './tabs'
function toAppInfoFromOverview(instance?: AppInstanceOverview): AppInfo | undefined {
if (!instance?.id)
return undefined
return {
id: instance.id,
name: instance.name ?? instance.id,
mode: (instance.mode || 'workflow') as AppMode,
iconType: 'emoji',
icon: instance.icon,
description: instance.description ?? undefined,
sourceAppId: instance.sourceAppId,
sourceAppName: instance.sourceAppName,
}
}
type InstanceDetailProps = {
instanceId: string
children: ReactNode
@ -34,18 +52,29 @@ const InstanceDetail: FC<InstanceDetailProps> = ({ instanceId, children }) => {
const { appMap, isLoading: isLoadingApps } = useSourceApps()
useDocumentTitle(t('documentTitle.detail'))
const app = useMemo(
() => sourceApps.find(item => item.id === instanceId) ?? appMap.get(instanceId),
[sourceApps, instanceId, appMap],
const appDataForInstance = appData[instanceId]
const appFromData = useMemo(
() => toAppInfoFromOverview(appDataForInstance?.overview.instance),
[appDataForInstance?.overview.instance],
)
const detailApps = useMemo(() => app ? [app] : [], [app])
useDeploymentData(detailApps, { enabled: detailApps.length > 0 })
const app = useMemo(
() => sourceApps.find(item => item.id === instanceId) ?? appMap.get(instanceId) ?? appFromData,
[sourceApps, instanceId, appMap, appFromData],
)
const detailApps = useMemo<AppInfo[]>(() => [
app ?? {
id: instanceId,
name: instanceId,
mode: 'workflow',
},
], [app, instanceId])
const detailQuery = useDeploymentData(detailApps, { enabled: Boolean(instanceId) })
const appDeployments = useMemo(
() => deployedRows(appData[instanceId]?.environmentDeployments.data),
[appData, instanceId],
)
if (isLoadingApps && !app) {
if (!app && (isLoadingApps || detailQuery.isLoading || detailQuery.isFetching)) {
return (
<div className="flex h-full items-center justify-center bg-background-body">
<span className="h-6 w-6 animate-spin rounded-full border-2 border-components-panel-border border-t-transparent" />

View File

@ -12,9 +12,12 @@ import {
patchAccessChannel,
patchDeveloperAPI,
refreshDeploymentAppData,
refreshDeploymentAppDataWhenReady,
refreshDeploymentLists,
undeployEnvironment,
updateAppInstance,
updateEnvironmentAccessPolicy,
waitForAppInstanceInDeploymentList,
} from './data'
export type StartDeployParams = {
@ -138,11 +141,8 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
openCreateInstanceModal: () => set({ createInstanceModal: { open: true } }),
closeCreateInstanceModal: () => set({ createInstanceModal: { open: false } }),
seedInstancesFromApps: apps => set(state => ({
seedInstancesFromApps: apps => set(() => ({
sourceApps: apps,
appData: Object.fromEntries(
Object.entries(state.appData).filter(([appId]) => apps.some(app => app.id === appId)),
),
})),
applyAppData: data => set(state => ({
@ -162,6 +162,11 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
if (!response.appInstanceId)
throw new Error('Create app instance did not return an appInstanceId.')
set({ createInstanceModal: { open: false } })
await Promise.allSettled([
refreshDeploymentAppDataWhenReady(response.appInstanceId)
.then(data => get().applyAppData(data)),
waitForAppInstanceInDeploymentList(response.appInstanceId),
])
return {
appInstanceId: response.appInstanceId,
initialRelease: response.initialRelease,
@ -174,6 +179,7 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
description: patch.description,
})
await get().refreshAppData(appId)
await refreshDeploymentLists()
set(state => ({
sourceApps: state.sourceApps.map(app => app.id === appId ? { ...app, ...patch } : app),
}))
@ -190,23 +196,27 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
appData,
}
})
await refreshDeploymentLists()
},
startDeploy: async ({ appId, environmentId, releaseId, releaseNote }) => {
set({ deployDrawer: { open: false } })
await createDeployment({ appId, environmentId, releaseId, releaseNote })
await get().refreshAppData(appId)
await refreshDeploymentLists()
},
retryDeploy: async (appId, environmentId, targetReleaseId) => {
await createDeployment({ appId, environmentId, releaseId: targetReleaseId })
await get().refreshAppData(appId)
await refreshDeploymentLists()
},
rollbackDeployment: async (appId, environmentId, targetReleaseId) => {
set({ rollbackModal: { open: false } })
await createDeployment({ appId, environmentId, releaseId: targetReleaseId })
await get().refreshAppData(appId)
await refreshDeploymentLists()
},
undeployDeployment: async (appId, _environmentId, runtimeInstanceId, isDeploying) => {
@ -217,6 +227,7 @@ export const useDeploymentsStore = create<DeploymentsState>((set, get) => ({
else
await undeployEnvironment(appId, runtimeInstanceId)
await get().refreshAppData(appId)
await refreshDeploymentLists()
},
generateApiKey: async (appId, environmentId) => {