diff --git a/web/features/deployments/components/create-instance-modal.tsx b/web/features/deployments/components/create-instance-modal.tsx index 6e69fea392..ad0ef0348f 100644 --- a/web/features/deployments/components/create-instance-modal.tsx +++ b/web/features/deployments/components/create-instance-modal.tsx @@ -7,7 +7,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { toast } from '@langgenius/dify-ui/toast' -import { useQuery } from '@tanstack/react-query' +import { useMutation, useQuery } from '@tanstack/react-query' import { useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { AppTypeIcon } from '@/app/components/app/type-selector' @@ -15,7 +15,6 @@ import AppIcon from '@/app/components/base/app-icon' import Input from '@/app/components/base/input' import { useRouter } from '@/next/navigation' import { consoleQuery } from '@/service/client' -import { useCreateDeploymentInstance } from '../hooks/use-deployment-mutations' import { useDeploymentsStore } from '../store' const MAX_STUDIO_SOURCE_APPS = 100 @@ -208,7 +207,7 @@ export const AppPicker: FC = ({ apps, isLoading, value, onChange const CreateInstanceForm: FC<{ onClose: () => void }> = ({ onClose }) => { const { t } = useTranslation('deployments') const router = useRouter() - const createInstance = useCreateDeploymentInstance() + const createInstance = useMutation(consoleQuery.enterprise.appDeploy.createAppInstance.mutationOptions()) const { data: appList, isLoading } = useQuery(consoleQuery.apps.list.queryOptions({ input: { query: { @@ -225,31 +224,30 @@ const CreateInstanceForm: FC<{ onClose: () => void }> = ({ onClose }) => { const [appId, setAppId] = useState('') const [name, setName] = useState('') const [description, setDescription] = useState('') - const [isSubmitting, setIsSubmitting] = useState(false) const selectedApp = apps.find(a => a.id === appId) - const canCreate = Boolean(appId && name.trim() && !isSubmitting) + const canCreate = Boolean(appId && name.trim() && !createInstance.isPending) const handleCreate = async () => { if (!canCreate) return - setIsSubmitting(true) try { const result = await createInstance.mutateAsync({ - sourceAppId: appId, - name: name.trim(), - description: description.trim() || undefined, + body: { + sourceAppId: appId, + name: name.trim(), + description: description.trim() || undefined, + }, }) + if (!result.appInstanceId) + throw new Error('Create app instance did not return an appInstanceId.') onClose() router.push(`/deployments/${result.appInstanceId}/overview`) } catch { toast.error(t('createModal.createFailed')) } - finally { - setIsSubmitting(false) - } } return ( diff --git a/web/features/deployments/components/deploy-drawer.tsx b/web/features/deployments/components/deploy-drawer.tsx index 80ce0adcb6..7cc12f45d0 100644 --- a/web/features/deployments/components/deploy-drawer.tsx +++ b/web/features/deployments/components/deploy-drawer.tsx @@ -3,12 +3,11 @@ import type { FC } from 'react' import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog' import { toast } from '@langgenius/dify-ui/toast' -import { skipToken, useQuery } from '@tanstack/react-query' +import { skipToken, useMutation, useQuery } from '@tanstack/react-query' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { consoleQuery } from '@/service/client' import { DEPLOYMENT_PAGE_SIZE } from '../data' -import { useStartDeployment } from '../hooks/use-deployment-mutations' import { useDeploymentsStore } from '../store' import { environmentOptionsFromOptionsReply } from '../utils' import { DeployForm } from './deploy-drawer/form' @@ -18,7 +17,7 @@ const DeployDrawer: FC = () => { const drawer = useDeploymentsStore(state => state.deployDrawer) const drawerAppInstanceId = drawer.appInstanceId const closeDeployDrawer = useDeploymentsStore(state => state.closeDeployDrawer) - const startDeploy = useStartDeployment() + const startDeploy = useMutation(consoleQuery.enterprise.appDeploy.createDeployment.mutationOptions()) const open = drawer.open const { data: releaseHistory } = useQuery(consoleQuery.enterprise.appDeploy.listReleases.queryOptions({ input: drawerAppInstanceId @@ -75,10 +74,14 @@ const DeployDrawer: FC = () => { onSubmit={async ({ environmentId, releaseId, bindings }) => { try { await startDeploy.mutateAsync({ - appInstanceId: drawerAppInstanceId, - environmentId, - releaseId, - bindings, + params: { + appInstanceId: drawerAppInstanceId, + }, + body: { + environmentId, + releaseId, + bindings, + }, }) closeDeployDrawer() } diff --git a/web/features/deployments/components/rollback-modal.tsx b/web/features/deployments/components/rollback-modal.tsx index 6c6036a473..baa9bf3289 100644 --- a/web/features/deployments/components/rollback-modal.tsx +++ b/web/features/deployments/components/rollback-modal.tsx @@ -9,12 +9,11 @@ import { AlertDialogDescription, AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' -import { skipToken, useQuery } from '@tanstack/react-query' +import { skipToken, useMutation, useQuery } from '@tanstack/react-query' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { consoleQuery } from '@/service/client' import { DEPLOYMENT_PAGE_SIZE } from '../data' -import { useStartDeployment } from '../hooks/use-deployment-mutations' import { useDeploymentsStore } from '../store' import { activeRelease, @@ -40,7 +39,7 @@ const RollbackModal: FC = () => { const { t } = useTranslation('deployments') const modal = useDeploymentsStore(state => state.rollbackModal) const closeRollbackModal = useDeploymentsStore(state => state.closeRollbackModal) - const rollbackDeployment = useStartDeployment() + const rollbackDeployment = useMutation(consoleQuery.enterprise.appDeploy.createDeployment.mutationOptions()) const appInput = modal.appInstanceId ? { params: { appInstanceId: modal.appInstanceId } } : skipToken @@ -87,10 +86,14 @@ const RollbackModal: FC = () => { return closeRollbackModal() rollbackDeployment.mutate({ - appInstanceId: modal.appInstanceId, - environmentId: modal.environmentId, - releaseId: modal.targetReleaseId, - bindings: [], + params: { + appInstanceId: modal.appInstanceId, + }, + body: { + environmentId: modal.environmentId, + releaseId: modal.targetReleaseId, + bindings: [], + }, }) } diff --git a/web/features/deployments/detail/access-tab.tsx b/web/features/deployments/detail/access-tab.tsx index 826a9523a5..80216a6029 100644 --- a/web/features/deployments/detail/access-tab.tsx +++ b/web/features/deployments/detail/access-tab.tsx @@ -6,16 +6,9 @@ import type { AccessSubject, ConsoleEnvironmentSummary, } from '@/features/deployments/types' -import { useQuery } from '@tanstack/react-query' +import { useMutation, useQuery } from '@tanstack/react-query' import { useMemo, useState } from 'react' import { consoleClient, consoleQuery } from '@/service/client' -import { - useGenerateDeploymentApiKey, - useRevokeDeploymentApiKey, - useSetEnvironmentAccessPolicy, - useToggleDeploymentAccessChannel, - useToggleDeploymentDeveloperAPI, -} from '../hooks/use-deployment-mutations' import { deployedRows, } from '../utils' @@ -50,11 +43,11 @@ const AccessTab: FC = ({ instanceId: appId }) => { appId: string token: string }>() - const generateApiKey = useGenerateDeploymentApiKey() - const revokeApiKey = useRevokeDeploymentApiKey() - const toggleAccessChannel = useToggleDeploymentAccessChannel() - const toggleDeveloperAPI = useToggleDeploymentDeveloperAPI() - const setEnvironmentAccessPolicy = useSetEnvironmentAccessPolicy() + const generateApiKey = useMutation(consoleQuery.enterprise.appDeploy.createDeveloperApiKey.mutationOptions()) + const revokeApiKey = useMutation(consoleQuery.enterprise.appDeploy.deleteDeveloperApiKey.mutationOptions()) + const toggleAccessChannel = useMutation(consoleQuery.enterprise.appDeploy.updateAccessChannels.mutationOptions()) + const toggleDeveloperAPI = useMutation(consoleQuery.enterprise.appDeploy.updateDeveloperApi.mutationOptions()) + const setEnvironmentAccessPolicy = useMutation(consoleQuery.enterprise.appDeploy.updateEnvironmentAccessPolicy.mutationOptions()) const deploymentRows = useMemo( () => deployedRows(environmentDeployments?.data), diff --git a/web/features/deployments/detail/deploy-tab.tsx b/web/features/deployments/detail/deploy-tab.tsx index 143ec232f1..7fcc783a5f 100644 --- a/web/features/deployments/detail/deploy-tab.tsx +++ b/web/features/deployments/detail/deploy-tab.tsx @@ -8,11 +8,10 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@langgenius/dify-ui/dropdown-menu' -import { useQuery } from '@tanstack/react-query' +import { useMutation, useQuery } from '@tanstack/react-query' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { consoleQuery } from '@/service/client' -import { useUndeployDeployment } from '../hooks/use-deployment-mutations' import { useDeploymentsStore } from '../store' import { activeRelease, @@ -46,7 +45,8 @@ const DeployTab: FC = ({ instanceId: appInstanceId }) => { })) const { data: environmentOptionsReply } = useQuery(consoleQuery.enterprise.appDeploy.listDeploymentEnvironmentOptions.queryOptions()) const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer) - const undeployDeployment = useUndeployDeployment() + const cancelDeployment = useMutation(consoleQuery.enterprise.appDeploy.cancelRuntimeDeployment.mutationOptions()) + const undeployDeployment = useMutation(consoleQuery.enterprise.appDeploy.undeployRuntimeInstance.mutationOptions()) const environmentOptions = useMemo( () => environmentOptionsFromOptionsReply(environmentOptionsReply), [environmentOptionsReply], @@ -193,11 +193,33 @@ const DeployTab: FC = ({ instanceId: appInstanceId }) => { undeployDeployment.mutate({ - appInstanceId, - runtimeInstanceId: deploymentId(row), - isDeploying: status === 'deploying', - })} + onClick={() => { + const runtimeInstanceId = deploymentId(row) + if (status === 'deploying') { + cancelDeployment.mutate({ + params: { + appInstanceId, + runtimeInstanceId, + }, + body: { + appInstanceId, + runtimeInstanceId, + }, + }) + return + } + + undeployDeployment.mutate({ + params: { + appInstanceId, + runtimeInstanceId, + }, + body: { + appInstanceId, + runtimeInstanceId, + }, + }) + }} > {status === 'deploying' ? t('deployTab.cancelDeployment') : t('deployTab.undeploy')} diff --git a/web/features/deployments/detail/settings-tab.tsx b/web/features/deployments/detail/settings-tab.tsx index fbf3ed91aa..95ec407e85 100644 --- a/web/features/deployments/detail/settings-tab.tsx +++ b/web/features/deployments/detail/settings-tab.tsx @@ -13,15 +13,11 @@ import { } from '@langgenius/dify-ui/alert-dialog' import { Button } from '@langgenius/dify-ui/button' import { toast } from '@langgenius/dify-ui/toast' -import { useQuery } from '@tanstack/react-query' +import { useMutation, useQuery } from '@tanstack/react-query' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { useRouter } from '@/next/navigation' import { consoleQuery } from '@/service/client' -import { - useDeleteDeploymentInstance, - useUpdateDeploymentInstance, -} from '../hooks/use-deployment-mutations' import { deployedRows, toAppInfoFromOverview, @@ -181,8 +177,8 @@ const SettingsForm: FC = ({ app, settings, hasDeployments, on const SettingsTab: FC = ({ instanceId }) => { const router = useRouter() - const updateInstance = useUpdateDeploymentInstance() - const deleteInstance = useDeleteDeploymentInstance() + const updateInstance = useMutation(consoleQuery.enterprise.appDeploy.updateAppInstance.mutationOptions()) + const deleteInstance = useMutation(consoleQuery.enterprise.appDeploy.deleteAppInstance.mutationOptions()) const appInput = { params: { appInstanceId: instanceId } } const { data: overview } = useQuery(consoleQuery.enterprise.appDeploy.getAppInstanceOverview.queryOptions({ input: appInput, diff --git a/web/features/deployments/detail/versions-tab.tsx b/web/features/deployments/detail/versions-tab.tsx index 490ed722b5..cb5c22f264 100644 --- a/web/features/deployments/detail/versions-tab.tsx +++ b/web/features/deployments/detail/versions-tab.tsx @@ -5,14 +5,13 @@ import { cn } from '@langgenius/dify-ui/cn' import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog' import { toast } from '@langgenius/dify-ui/toast' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' -import { useQuery } from '@tanstack/react-query' +import { useMutation, useQuery } from '@tanstack/react-query' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' import Textarea from '@/app/components/base/textarea' import { consoleQuery } from '@/service/client' import { DEPLOYMENT_PAGE_SIZE } from '../data' -import { useCreateDeploymentRelease } from '../hooks/use-deployment-mutations' import { deployedRows, formatDate, @@ -47,7 +46,7 @@ const VersionsTab: FC = ({ instanceId: appId }) => { const { data: environmentDeployments } = useQuery(consoleQuery.enterprise.appDeploy.listRuntimeInstances.queryOptions({ input, })) - const createRelease = useCreateDeploymentRelease() + const createRelease = useMutation(consoleQuery.enterprise.appDeploy.createRelease.mutationOptions()) const [isCreating, setIsCreating] = useState(false) const [releaseName, setReleaseName] = useState('') const [releaseDescription, setReleaseDescription] = useState('') @@ -68,11 +67,17 @@ const VersionsTab: FC = ({ instanceId: appId }) => { return try { - await createRelease.mutateAsync({ - appInstanceId: appId, - name: trimmedReleaseName, - description: releaseDescription.trim() || undefined, + const response = await createRelease.mutateAsync({ + params: { + appInstanceId: appId, + }, + body: { + name: trimmedReleaseName, + description: releaseDescription.trim() || undefined, + }, }) + if (!response.release?.id) + throw new Error('Create release did not return a release.') setReleaseName('') setReleaseDescription('') setIsCreating(false) diff --git a/web/features/deployments/hooks/use-deployment-mutations.ts b/web/features/deployments/hooks/use-deployment-mutations.ts deleted file mode 100644 index e98aab50f1..0000000000 --- a/web/features/deployments/hooks/use-deployment-mutations.ts +++ /dev/null @@ -1,376 +0,0 @@ -'use client' - -import type { DeploymentRuntimeBinding } from '@dify/contracts/enterprise/types.gen' -import type { QueryClient, QueryKey } from '@tanstack/react-query' -import { useMutation, useQueryClient } from '@tanstack/react-query' -import { consoleClient, consoleQuery } from '@/service/client' -import { SOURCE_APPS_PAGE_SIZE } from '../data' - -export type CreateDeploymentInstanceResult = { - appInstanceId: string -} - -type CreateDeploymentParams = { - appInstanceId: string - environmentId: string - releaseId: string - bindings: DeploymentRuntimeBinding[] -} - -type CreateReleaseParams = { - appInstanceId: string - name: string - description?: string -} - -type CreateInstanceParams = { - sourceAppId: string - name: string - description?: string -} - -type UndeployDeploymentParams = { - appInstanceId: string - runtimeInstanceId: string - isDeploying?: boolean -} - -const DEPLOYMENT_READINESS_RETRY_DELAYS = [0, 300, 700, 1200] - -const wait = (delay: number) => new Promise(resolve => setTimeout(resolve, delay)) - -const invalidateQueries = async (queryClient: QueryClient, queryKeys: readonly QueryKey[]): Promise => { - await Promise.all(queryKeys.map(queryKey => queryClient.invalidateQueries({ queryKey }))) -} - -const removeQueries = (queryClient: QueryClient, queryKeys: readonly QueryKey[]): void => { - queryKeys.forEach(queryKey => queryClient.removeQueries({ queryKey })) -} - -const invalidateInstanceList = (queryClient: QueryClient): Promise => { - return queryClient.invalidateQueries({ - queryKey: consoleQuery.enterprise.appDeploy.listAppInstances.key({ type: 'query' }), - }) -} - -const invalidateInstanceIdentity = (queryClient: QueryClient, appInstanceId: string): Promise => { - return invalidateQueries(queryClient, [ - consoleQuery.enterprise.appDeploy.listAppInstances.key({ type: 'query' }), - consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.getAppInstanceSettings.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - ]) -} - -const invalidateDeploymentState = (queryClient: QueryClient, appInstanceId: string): Promise => { - return invalidateQueries(queryClient, [ - consoleQuery.enterprise.appDeploy.listAppInstances.key({ type: 'query' }), - consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.listRuntimeInstances.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.listReleases.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - ]) -} - -const invalidateAccessState = (queryClient: QueryClient, appInstanceId: string): Promise => { - return invalidateQueries(queryClient, [ - consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - ]) -} - -const invalidateEnvironmentAccessPolicy = ( - queryClient: QueryClient, - appInstanceId: string, - environmentId: string, -): Promise => { - return invalidateQueries(queryClient, [ - consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.getEnvironmentAccessPolicy.key({ - type: 'query', - input: { - params: { - appInstanceId, - environmentId, - }, - }, - }), - ]) -} - -const removeDeletedInstanceState = (queryClient: QueryClient, appInstanceId: string): Promise => { - removeQueries(queryClient, [ - consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.getAppInstanceSettings.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.listRuntimeInstances.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.listReleases.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.getEnvironmentAccessPolicy.key({ - type: 'query', - input: { params: { appInstanceId } }, - }), - ]) - return invalidateInstanceList(queryClient) -} - -export const useCreateDeploymentInstance = () => { - const queryClient = useQueryClient() - - return useMutation({ - mutationKey: consoleQuery.enterprise.appDeploy.createAppInstance.mutationKey(), - mutationFn: async (params: CreateInstanceParams): Promise => { - const response = await consoleClient.enterprise.appDeploy.createAppInstance({ - body: { - sourceAppId: params.sourceAppId, - name: params.name, - description: params.description, - }, - }) - if (!response.appInstanceId) - throw new Error('Create app instance did not return an appInstanceId.') - - for (const delay of DEPLOYMENT_READINESS_RETRY_DELAYS) { - if (delay > 0) - await wait(delay) - - const listResponse = await queryClient - .fetchQuery(consoleQuery.enterprise.appDeploy.listAppInstances.queryOptions({ - input: { - query: { - pageNumber: 1, - resultsPerPage: SOURCE_APPS_PAGE_SIZE, - }, - }, - })) - .catch(() => undefined) - if (listResponse?.data?.some(app => app.id === response.appInstanceId)) - break - } - - return { - appInstanceId: response.appInstanceId, - } - }, - onSuccess: () => { - return invalidateInstanceList(queryClient) - }, - }) -} - -export const useCreateDeploymentRelease = () => { - const queryClient = useQueryClient() - - return useMutation({ - mutationKey: consoleQuery.enterprise.appDeploy.createRelease.mutationKey(), - mutationFn: async ({ appInstanceId, name, description }: CreateReleaseParams) => { - const response = await consoleClient.enterprise.appDeploy.createRelease({ - params: { - appInstanceId, - }, - body: { - name, - description, - }, - }) - if (!response.release?.id) - throw new Error('Create release did not return a release.') - - return response.release - }, - onSuccess: (_data, variables) => { - return invalidateQueries(queryClient, [ - consoleQuery.enterprise.appDeploy.listReleases.key({ - type: 'query', - input: { params: { appInstanceId: variables.appInstanceId } }, - }), - consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ - type: 'query', - input: { params: { appInstanceId: variables.appInstanceId } }, - }), - ]) - }, - }) -} - -export const useUpdateDeploymentInstance = () => { - const queryClient = useQueryClient() - - return useMutation(consoleQuery.enterprise.appDeploy.updateAppInstance.mutationOptions({ - onSuccess: (_data, variables) => { - return invalidateInstanceIdentity(queryClient, variables.params.appInstanceId) - }, - })) -} - -export const useDeleteDeploymentInstance = () => { - const queryClient = useQueryClient() - - return useMutation(consoleQuery.enterprise.appDeploy.deleteAppInstance.mutationOptions({ - onSuccess: (_data, variables) => { - return removeDeletedInstanceState(queryClient, variables.params.appInstanceId) - }, - })) -} - -export const useStartDeployment = () => { - const queryClient = useQueryClient() - - return useMutation({ - mutationKey: consoleQuery.enterprise.appDeploy.createDeployment.mutationKey(), - mutationFn: async ({ - appInstanceId, - environmentId, - releaseId, - bindings, - }: CreateDeploymentParams) => { - if (!releaseId) - throw new Error('releaseId is required to start a deployment.') - - return consoleClient.enterprise.appDeploy.createDeployment({ - params: { - appInstanceId, - }, - body: { - environmentId, - releaseId, - bindings, - }, - }) - }, - onSuccess: (_data, variables) => { - return invalidateDeploymentState(queryClient, variables.appInstanceId) - }, - }) -} - -export const useUndeployDeployment = () => { - const queryClient = useQueryClient() - - return useMutation({ - mutationKey: consoleQuery.enterprise.appDeploy.undeployRuntimeInstance.mutationKey(), - mutationFn: ({ appInstanceId, runtimeInstanceId, isDeploying }: UndeployDeploymentParams) => { - if (!runtimeInstanceId) - throw new Error('runtimeInstanceId is required to undeploy a deployment.') - if (isDeploying) { - return consoleClient.enterprise.appDeploy.cancelRuntimeDeployment({ - params: { - appInstanceId, - runtimeInstanceId, - }, - body: { - appInstanceId, - runtimeInstanceId, - }, - }) - } - return consoleClient.enterprise.appDeploy.undeployRuntimeInstance({ - params: { - appInstanceId, - runtimeInstanceId, - }, - body: { - appInstanceId, - runtimeInstanceId, - }, - }) - }, - onSuccess: (_data, variables) => { - return invalidateDeploymentState(queryClient, variables.appInstanceId) - }, - }) -} - -export const useGenerateDeploymentApiKey = () => { - const queryClient = useQueryClient() - - return useMutation(consoleQuery.enterprise.appDeploy.createDeveloperApiKey.mutationOptions({ - onSuccess: (_data, variables) => { - return invalidateAccessState(queryClient, variables.params.appInstanceId) - }, - })) -} - -export const useRevokeDeploymentApiKey = () => { - const queryClient = useQueryClient() - - return useMutation(consoleQuery.enterprise.appDeploy.deleteDeveloperApiKey.mutationOptions({ - onSuccess: (_data, variables) => { - return invalidateAccessState(queryClient, variables.params.appInstanceId) - }, - })) -} - -export const useToggleDeploymentAccessChannel = () => { - const queryClient = useQueryClient() - - return useMutation(consoleQuery.enterprise.appDeploy.updateAccessChannels.mutationOptions({ - onSuccess: (_data, variables) => { - return invalidateAccessState(queryClient, variables.params.appInstanceId) - }, - })) -} - -export const useToggleDeploymentDeveloperAPI = () => { - const queryClient = useQueryClient() - - return useMutation(consoleQuery.enterprise.appDeploy.updateDeveloperApi.mutationOptions({ - onSuccess: (_data, variables) => { - return invalidateAccessState(queryClient, variables.params.appInstanceId) - }, - })) -} - -export const useSetEnvironmentAccessPolicy = () => { - const queryClient = useQueryClient() - - return useMutation(consoleQuery.enterprise.appDeploy.updateEnvironmentAccessPolicy.mutationOptions({ - onSuccess: (_data, variables) => { - return invalidateEnvironmentAccessPolicy( - queryClient, - variables.params.appInstanceId, - variables.params.environmentId, - ) - }, - })) -} diff --git a/web/service/client.ts b/web/service/client.ts index 00fc09ec3e..64a5c5bc2d 100644 --- a/web/service/client.ts +++ b/web/service/client.ts @@ -1,5 +1,6 @@ import type { ContractRouterClient } from '@orpc/contract' import type { JsonifiedClient } from '@orpc/openapi-client' +import type { RouterUtils } from '@orpc/tanstack-query' import { createORPCClient, onError } from '@orpc/client' import { OpenAPILink } from '@orpc/openapi-client/fetch' import { createTanstackQueryUtils } from '@orpc/tanstack-query' @@ -64,6 +65,9 @@ const marketplaceLink = new OpenAPILink(marketplaceRouterContract, { export const marketplaceClient: JsonifiedClient> = createORPCClient(marketplaceLink) export const marketplaceQuery = createTanstackQueryUtils(marketplaceClient, { path: ['marketplace'] }) +const APP_DEPLOY_SOURCE_APPS_PAGE_SIZE = 100 +const APP_DEPLOY_READINESS_RETRY_DELAYS = [0, 300, 700, 1200] + const consoleLink = new OpenAPILink(consoleRouterContract, { url: getBaseURL(API_PREFIX), fetch: (input, init) => { @@ -84,7 +88,342 @@ const consoleLink = new OpenAPILink(consoleRouterContract, { }) export const consoleClient: JsonifiedClient> = createORPCClient(consoleLink) -export const consoleQuery = createTanstackQueryUtils(consoleClient, { +export const consoleQuery: RouterUtils = createTanstackQueryUtils(consoleClient, { path: ['console'], - experimental_defaults: { }, + experimental_defaults: { + enterprise: { + appDeploy: { + createAppInstance: { + mutationOptions: { + onSuccess: async (data, _variables, _result, context) => { + if (data.appInstanceId) { + for (const delay of APP_DEPLOY_READINESS_RETRY_DELAYS) { + if (delay > 0) + await new Promise(resolve => setTimeout(resolve, delay)) + + const listResponse = await context.client + .fetchQuery(consoleQuery.enterprise.appDeploy.listAppInstances.queryOptions({ + input: { + query: { + pageNumber: 1, + resultsPerPage: APP_DEPLOY_SOURCE_APPS_PAGE_SIZE, + }, + }, + })) + .catch(() => undefined) + + if (listResponse?.data?.some(app => app.id === data.appInstanceId)) + break + } + } + + await context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listAppInstances.key({ type: 'query' }), + }) + }, + }, + }, + updateAppInstance: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const appInstanceId = variables.params.appInstanceId + return Promise.all([ + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listAppInstances.key({ type: 'query' }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceSettings.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + ]) + }, + }, + }, + deleteAppInstance: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const appInstanceId = variables.params.appInstanceId + ;[ + consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + consoleQuery.enterprise.appDeploy.getAppInstanceSettings.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + consoleQuery.enterprise.appDeploy.listRuntimeInstances.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + consoleQuery.enterprise.appDeploy.listReleases.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + consoleQuery.enterprise.appDeploy.getEnvironmentAccessPolicy.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + ].forEach(queryKey => context.client.removeQueries({ queryKey })) + + return context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listAppInstances.key({ type: 'query' }), + }) + }, + }, + }, + createRelease: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const appInstanceId = variables.params.appInstanceId + return Promise.all([ + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listReleases.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + ]) + }, + }, + }, + createDeployment: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const appInstanceId = variables.params.appInstanceId + return Promise.all([ + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listAppInstances.key({ type: 'query' }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listRuntimeInstances.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listReleases.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + ]) + }, + }, + }, + cancelRuntimeDeployment: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const appInstanceId = variables.params.appInstanceId + return Promise.all([ + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listAppInstances.key({ type: 'query' }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listRuntimeInstances.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listReleases.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + ]) + }, + }, + }, + undeployRuntimeInstance: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const appInstanceId = variables.params.appInstanceId + return Promise.all([ + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listAppInstances.key({ type: 'query' }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listRuntimeInstances.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.listReleases.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + ]) + }, + }, + }, + createDeveloperApiKey: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const appInstanceId = variables.params.appInstanceId + return Promise.all([ + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + ]) + }, + }, + }, + deleteDeveloperApiKey: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const appInstanceId = variables.params.appInstanceId + return Promise.all([ + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + ]) + }, + }, + }, + updateAccessChannels: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const appInstanceId = variables.params.appInstanceId + return Promise.all([ + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + ]) + }, + }, + }, + updateDeveloperApi: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const appInstanceId = variables.params.appInstanceId + return Promise.all([ + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceOverview.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + ]) + }, + }, + }, + updateEnvironmentAccessPolicy: { + mutationOptions: { + onSuccess: (_data, variables, _result, context) => { + const { appInstanceId, environmentId } = variables.params + return Promise.all([ + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getAppInstanceAccess.key({ + type: 'query', + input: { params: { appInstanceId } }, + }), + }), + context.client.invalidateQueries({ + queryKey: consoleQuery.enterprise.appDeploy.getEnvironmentAccessPolicy.key({ + type: 'query', + input: { + params: { + appInstanceId, + environmentId, + }, + }, + }), + }), + ]) + }, + }, + }, + }, + }, + }, })