d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
620 lines
20 KiB
TypeScript
620 lines
20 KiB
TypeScript
import { useCallback } from 'react'
|
|
import { createLogger } from '@sim/logger'
|
|
import type { QueryClient } from '@tanstack/react-query'
|
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|
import { requestJson, requestRaw } from '@/lib/api/client/request'
|
|
import {
|
|
type ActivateDeploymentVersionResponse,
|
|
activateDeploymentVersionContract,
|
|
type ChatDeploymentStatus,
|
|
type ChatDetail,
|
|
type DeploymentInfoResponse,
|
|
type DeploymentVersionsResponse,
|
|
type DeployWorkflowResponse,
|
|
deployWorkflowContract,
|
|
getChatDeploymentStatusContract,
|
|
getChatDetailContract,
|
|
getDeployedWorkflowStateContract,
|
|
getDeploymentInfoContract,
|
|
listDeploymentVersionsContract,
|
|
type UpdateDeploymentVersionMetadataResponse,
|
|
undeployWorkflowContract,
|
|
updateDeploymentVersionMetadataContract,
|
|
updatePublicApiContract,
|
|
} from '@/lib/api/contracts/deployments'
|
|
import { wandGenerateStreamContract } from '@/lib/api/contracts/hotspots'
|
|
import { fetchDeploymentVersionState } from '@/hooks/queries/utils/fetch-deployment-version-state'
|
|
import { workflowKeys } from '@/hooks/queries/utils/workflow-keys'
|
|
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
|
|
|
const logger = createLogger('DeploymentQueries')
|
|
|
|
export type { ChatDetail, DeploymentVersionsResponse }
|
|
|
|
export const DEPLOYMENT_INFO_STALE_TIME = 30 * 1000
|
|
export const DEPLOYED_WORKFLOW_STATE_STALE_TIME = 30 * 1000
|
|
export const DEPLOYMENT_VERSIONS_STALE_TIME = 30 * 1000
|
|
export const CHAT_DEPLOYMENT_STATUS_STALE_TIME = 30 * 1000
|
|
export const CHAT_DETAIL_STALE_TIME = 30 * 1000
|
|
|
|
/**
|
|
* Query key factory for deployment-related queries
|
|
*/
|
|
export const deploymentKeys = {
|
|
all: ['deployments'] as const,
|
|
infos: () => [...deploymentKeys.all, 'info'] as const,
|
|
info: (workflowId: string | null) => [...deploymentKeys.infos(), workflowId ?? ''] as const,
|
|
deployedState: (workflowId: string | null) =>
|
|
[...deploymentKeys.all, 'deployedState', workflowId ?? ''] as const,
|
|
allVersions: () => [...deploymentKeys.all, 'versions'] as const,
|
|
versions: (workflowId: string | null) =>
|
|
[...deploymentKeys.allVersions(), workflowId ?? ''] as const,
|
|
chatStatuses: () => [...deploymentKeys.all, 'chatStatus'] as const,
|
|
chatStatus: (workflowId: string | null) =>
|
|
[...deploymentKeys.chatStatuses(), workflowId ?? ''] as const,
|
|
chatDetails: () => [...deploymentKeys.all, 'chatDetail'] as const,
|
|
chatDetail: (chatId: string | null) => [...deploymentKeys.chatDetails(), chatId ?? ''] as const,
|
|
}
|
|
|
|
/**
|
|
* Invalidates the core deployment queries (info, deployedState, versions) for a workflow.
|
|
* Used by mutation onSuccess callbacks and manual invalidation after chat deployments.
|
|
*/
|
|
export function invalidateDeploymentQueries(queryClient: QueryClient, workflowId: string) {
|
|
return Promise.all([
|
|
queryClient.invalidateQueries({ queryKey: deploymentKeys.info(workflowId) }),
|
|
queryClient.invalidateQueries({ queryKey: deploymentKeys.deployedState(workflowId) }),
|
|
queryClient.invalidateQueries({ queryKey: deploymentKeys.versions(workflowId) }),
|
|
queryClient.invalidateQueries({ queryKey: deploymentKeys.chatStatus(workflowId) }),
|
|
])
|
|
}
|
|
|
|
export async function refetchDeploymentBoundary(queryClient: QueryClient, workflowId: string) {
|
|
await invalidateDeploymentQueries(queryClient, workflowId)
|
|
await Promise.all([
|
|
queryClient.refetchQueries({ queryKey: deploymentKeys.info(workflowId) }),
|
|
queryClient.refetchQueries({ queryKey: deploymentKeys.deployedState(workflowId) }),
|
|
queryClient.refetchQueries({ queryKey: workflowKeys.state(workflowId) }),
|
|
])
|
|
}
|
|
|
|
export type WorkflowDeploymentInfo = DeploymentInfoResponse & {
|
|
deployedAt: string | null
|
|
apiKey: string | null
|
|
needsRedeployment: boolean
|
|
isPublicApi: boolean
|
|
}
|
|
|
|
/**
|
|
* Fetches deployment info for a workflow
|
|
*/
|
|
async function fetchDeploymentInfo(
|
|
workflowId: string,
|
|
signal?: AbortSignal
|
|
): Promise<WorkflowDeploymentInfo> {
|
|
const data = await requestJson(getDeploymentInfoContract, {
|
|
params: { id: workflowId },
|
|
signal,
|
|
})
|
|
return {
|
|
isDeployed: data.isDeployed ?? false,
|
|
deployedAt: data.deployedAt ?? null,
|
|
apiKey: data.apiKey ?? null,
|
|
needsRedeployment: data.needsRedeployment ?? false,
|
|
isPublicApi: data.isPublicApi ?? false,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook to fetch deployment info for a workflow.
|
|
* Provides isDeployed status, deployedAt timestamp, apiKey info, and needsRedeployment flag.
|
|
*/
|
|
export function useDeploymentInfo(
|
|
workflowId: string | null,
|
|
options?: { enabled?: boolean; refetchOnMount?: boolean | 'always' }
|
|
) {
|
|
return useQuery({
|
|
queryKey: deploymentKeys.info(workflowId),
|
|
queryFn: ({ signal }) => fetchDeploymentInfo(workflowId!, signal),
|
|
enabled: Boolean(workflowId) && (options?.enabled ?? true),
|
|
staleTime: DEPLOYMENT_INFO_STALE_TIME,
|
|
...(options?.refetchOnMount !== undefined && { refetchOnMount: options.refetchOnMount }),
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Fetches the deployed workflow state snapshot for a workflow
|
|
*/
|
|
async function fetchDeployedWorkflowState(
|
|
workflowId: string,
|
|
signal?: AbortSignal
|
|
): Promise<WorkflowState | null> {
|
|
const data = await requestJson(getDeployedWorkflowStateContract, {
|
|
params: { id: workflowId },
|
|
signal,
|
|
})
|
|
return data.deployedState || null
|
|
}
|
|
|
|
/**
|
|
* Hook to fetch the deployed workflow state snapshot.
|
|
* Returns the full workflow state at the time of the last active deployment.
|
|
*/
|
|
export function useDeployedWorkflowState(
|
|
workflowId: string | null,
|
|
options?: { enabled?: boolean }
|
|
) {
|
|
return useQuery({
|
|
queryKey: deploymentKeys.deployedState(workflowId),
|
|
queryFn: ({ signal }) => fetchDeployedWorkflowState(workflowId!, signal),
|
|
enabled: Boolean(workflowId) && (options?.enabled ?? true),
|
|
staleTime: DEPLOYED_WORKFLOW_STATE_STALE_TIME,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Fetches all deployment versions for a workflow
|
|
*/
|
|
async function fetchDeploymentVersions(
|
|
workflowId: string,
|
|
signal?: AbortSignal
|
|
): Promise<DeploymentVersionsResponse> {
|
|
const data = await requestJson(listDeploymentVersionsContract, {
|
|
params: { id: workflowId },
|
|
signal,
|
|
})
|
|
return {
|
|
versions: Array.isArray(data.versions) ? data.versions : [],
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook to fetch deployment versions for a workflow.
|
|
* Returns a list of all deployment versions with their metadata.
|
|
*/
|
|
export function useDeploymentVersions(workflowId: string | null, options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: deploymentKeys.versions(workflowId),
|
|
queryFn: ({ signal }) => fetchDeploymentVersions(workflowId!, signal),
|
|
enabled: Boolean(workflowId) && (options?.enabled ?? true),
|
|
staleTime: DEPLOYMENT_VERSIONS_STALE_TIME,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Fetches chat deployment status for a workflow
|
|
*/
|
|
async function fetchChatDeploymentStatus(
|
|
workflowId: string,
|
|
signal?: AbortSignal
|
|
): Promise<ChatDeploymentStatus> {
|
|
const data = await requestJson(getChatDeploymentStatusContract, {
|
|
params: { id: workflowId },
|
|
signal,
|
|
})
|
|
return {
|
|
isDeployed: data.isDeployed ?? false,
|
|
deployment: data.deployment ?? null,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Hook to fetch chat deployment status for a workflow.
|
|
* Returns whether a chat is deployed and basic deployment info.
|
|
*/
|
|
export function useChatDeploymentStatus(
|
|
workflowId: string | null,
|
|
options?: { enabled?: boolean }
|
|
) {
|
|
return useQuery({
|
|
queryKey: deploymentKeys.chatStatus(workflowId),
|
|
queryFn: ({ signal }) => fetchChatDeploymentStatus(workflowId!, signal),
|
|
enabled: Boolean(workflowId) && (options?.enabled ?? true),
|
|
staleTime: CHAT_DEPLOYMENT_STATUS_STALE_TIME,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Fetches chat detail by chat ID
|
|
*/
|
|
async function fetchChatDetail(chatId: string, signal?: AbortSignal): Promise<ChatDetail> {
|
|
return requestJson(getChatDetailContract, {
|
|
params: { id: chatId },
|
|
signal,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Hook to fetch chat detail by chat ID.
|
|
* Returns full chat configuration including customizations and auth settings.
|
|
*/
|
|
export function useChatDetail(chatId: string | null, options?: { enabled?: boolean }) {
|
|
return useQuery({
|
|
queryKey: deploymentKeys.chatDetail(chatId),
|
|
queryFn: ({ signal }) => fetchChatDetail(chatId!, signal),
|
|
enabled: Boolean(chatId) && (options?.enabled ?? true),
|
|
staleTime: CHAT_DETAIL_STALE_TIME,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Combined hook to fetch chat deployment info for a workflow.
|
|
* First fetches the chat status, then if deployed, fetches the chat detail.
|
|
* Returns the combined result.
|
|
*/
|
|
export function useChatDeploymentInfo(workflowId: string | null, options?: { enabled?: boolean }) {
|
|
const queryClient = useQueryClient()
|
|
const statusQuery = useChatDeploymentStatus(workflowId, options)
|
|
|
|
const chatId = statusQuery.data?.deployment?.id ?? null
|
|
|
|
const detailQuery = useChatDetail(chatId, {
|
|
enabled: Boolean(chatId) && statusQuery.isSuccess && (options?.enabled ?? true),
|
|
})
|
|
|
|
const refetch = useCallback(async () => {
|
|
const statusResult = await statusQuery.refetch()
|
|
const nextChatId = statusResult.data?.deployment?.id
|
|
if (nextChatId) {
|
|
await queryClient.fetchQuery({
|
|
queryKey: deploymentKeys.chatDetail(nextChatId),
|
|
queryFn: ({ signal }) => fetchChatDetail(nextChatId, signal),
|
|
staleTime: CHAT_DETAIL_STALE_TIME,
|
|
})
|
|
}
|
|
}, [queryClient, statusQuery.refetch])
|
|
|
|
return {
|
|
isLoading:
|
|
statusQuery.isLoading || Boolean(statusQuery.data?.isDeployed && detailQuery.isLoading),
|
|
isError: statusQuery.isError || detailQuery.isError,
|
|
error: statusQuery.error ?? detailQuery.error,
|
|
chatExists: statusQuery.data?.isDeployed ?? false,
|
|
existingChat: detailQuery.data ?? null,
|
|
refetch,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Variables for deploy workflow mutation
|
|
*/
|
|
interface DeployWorkflowVariables {
|
|
workflowId: string
|
|
}
|
|
|
|
type DeployWorkflowResult = Omit<DeployWorkflowResponse, 'deployedAt' | 'apiKey'> & {
|
|
deployedAt?: string
|
|
apiKey?: string
|
|
}
|
|
|
|
/**
|
|
* Mutation hook for deploying a workflow.
|
|
* Invalidates deployment info and versions queries on success.
|
|
*/
|
|
export function useDeployWorkflow() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ workflowId }: DeployWorkflowVariables): Promise<DeployWorkflowResult> => {
|
|
const data = await requestJson(deployWorkflowContract, {
|
|
params: { id: workflowId },
|
|
})
|
|
return {
|
|
isDeployed: data.isDeployed ?? false,
|
|
deployedAt: data.deployedAt ?? undefined,
|
|
apiKey: data.apiKey ?? undefined,
|
|
warnings: data.warnings,
|
|
}
|
|
},
|
|
onSettled: (_data, error, variables) => {
|
|
if (error) {
|
|
logger.error('Failed to deploy workflow', { error })
|
|
return invalidateDeploymentQueries(queryClient, variables.workflowId)
|
|
}
|
|
logger.info('Workflow deployed successfully', { workflowId: variables.workflowId })
|
|
return refetchDeploymentBoundary(queryClient, variables.workflowId)
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Variables for undeploy workflow mutation
|
|
*/
|
|
interface UndeployWorkflowVariables {
|
|
workflowId: string
|
|
}
|
|
|
|
/**
|
|
* Mutation hook for undeploying a workflow.
|
|
* Invalidates deployment info and versions queries on success.
|
|
*/
|
|
export function useUndeployWorkflow() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ workflowId }: UndeployWorkflowVariables) => {
|
|
return requestJson(undeployWorkflowContract, {
|
|
params: { id: workflowId },
|
|
})
|
|
},
|
|
onSettled: (_data, error, variables) => {
|
|
if (error) {
|
|
logger.error('Failed to undeploy workflow', { error })
|
|
} else {
|
|
logger.info('Workflow undeployed successfully', { workflowId: variables.workflowId })
|
|
}
|
|
return Promise.all([
|
|
invalidateDeploymentQueries(queryClient, variables.workflowId),
|
|
queryClient.invalidateQueries({
|
|
queryKey: deploymentKeys.chatStatus(variables.workflowId),
|
|
}),
|
|
])
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Variables for update deployment version mutation
|
|
*/
|
|
interface UpdateDeploymentVersionVariables {
|
|
workflowId: string
|
|
version: number
|
|
name?: string
|
|
description?: string | null
|
|
}
|
|
|
|
type UpdateDeploymentVersionResult = UpdateDeploymentVersionMetadataResponse
|
|
|
|
/**
|
|
* Mutation hook for updating a deployment version's name or description.
|
|
* Invalidates versions query on success.
|
|
*/
|
|
export function useUpdateDeploymentVersion() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({
|
|
workflowId,
|
|
version,
|
|
name,
|
|
description,
|
|
}: UpdateDeploymentVersionVariables): Promise<UpdateDeploymentVersionResult> => {
|
|
return requestJson(updateDeploymentVersionMetadataContract, {
|
|
params: { id: workflowId, version },
|
|
body: { name, description },
|
|
})
|
|
},
|
|
onSettled: (_data, error, variables) => {
|
|
if (!error) {
|
|
logger.info('Deployment version updated', {
|
|
workflowId: variables.workflowId,
|
|
version: variables.version,
|
|
})
|
|
} else {
|
|
logger.error('Failed to update deployment version', { error })
|
|
}
|
|
|
|
return queryClient.invalidateQueries({
|
|
queryKey: deploymentKeys.versions(variables.workflowId),
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Variables for generating a version description
|
|
*/
|
|
interface GenerateVersionDescriptionVariables {
|
|
workflowId: string
|
|
version: number
|
|
onStreamChunk?: (accumulated: string) => void
|
|
/** Aborts the diff fetches + SSE stream when the modal unmounts mid-generation. */
|
|
signal?: AbortSignal
|
|
}
|
|
|
|
const VERSION_DESCRIPTION_SYSTEM_PROMPT = `You are writing deployment version descriptions for a workflow automation platform.
|
|
|
|
Write a brief, factual description (1-3 sentences, under 2000 characters) that states what changed between versions.
|
|
|
|
Guidelines:
|
|
- Use the specific values provided (credential names, channel names, model names)
|
|
- Be precise: "Changes Slack channel from #general to #alerts" not "Updates channel configuration"
|
|
- Combine related changes: "Updates Agent model to claude-sonnet-4-5 and increases temperature to 0.8"
|
|
- For added/removed blocks, mention their purpose if clear from the type
|
|
|
|
Format rules:
|
|
- Plain text only, no quotes around the response
|
|
- No markdown formatting
|
|
- No filler phrases ("for improved efficiency", "streamlining the workflow")
|
|
- No version numbers or "This version" prefixes
|
|
|
|
Examples:
|
|
- Switches Agent model from gpt-4o to claude-sonnet-4-5. Changes Slack credential to Production OAuth.
|
|
- Adds Gmail notification block for sending alerts. Removes unused Function block. Updates Router conditions.
|
|
- Updates system prompt for more concise responses. Reduces temperature from 0.7 to 0.3.
|
|
- Connects Slack block to Router. Adds 2 new workflow connections. Configures error handling path.`
|
|
|
|
/**
|
|
* Hook for generating a version description using AI based on workflow diff
|
|
*/
|
|
export function useGenerateVersionDescription() {
|
|
return useMutation({
|
|
mutationFn: async ({
|
|
workflowId,
|
|
version,
|
|
onStreamChunk,
|
|
signal,
|
|
}: GenerateVersionDescriptionVariables): Promise<string> => {
|
|
const { generateWorkflowDiffSummary, formatDiffSummaryForDescriptionAsync } = await import(
|
|
'@/lib/workflows/comparison/compare'
|
|
)
|
|
|
|
const currentState = await fetchDeploymentVersionState(workflowId, version, signal)
|
|
|
|
let previousState = null
|
|
if (version > 1) {
|
|
try {
|
|
previousState = await fetchDeploymentVersionState(workflowId, version - 1, signal)
|
|
} catch {
|
|
// Previous version may not exist, continue without it
|
|
}
|
|
}
|
|
|
|
const diffSummary = generateWorkflowDiffSummary(currentState, previousState)
|
|
const diffText = await formatDiffSummaryForDescriptionAsync(
|
|
diffSummary,
|
|
currentState,
|
|
workflowId
|
|
)
|
|
|
|
const wandResponse = await requestRaw(
|
|
wandGenerateStreamContract,
|
|
{
|
|
body: {
|
|
prompt: `Generate a deployment version description based on these changes:\n\n${diffText}`,
|
|
systemPrompt: VERSION_DESCRIPTION_SYSTEM_PROMPT,
|
|
stream: true,
|
|
workflowId,
|
|
},
|
|
signal,
|
|
},
|
|
{
|
|
headers: {
|
|
'Cache-Control': 'no-cache, no-transform',
|
|
},
|
|
cache: 'no-store',
|
|
}
|
|
)
|
|
|
|
if (!wandResponse.body) {
|
|
throw new Error('Response body is null')
|
|
}
|
|
|
|
const { readSSEStream } = await import('@/lib/core/utils/sse')
|
|
const accumulatedContent = await readSSEStream(wandResponse.body, {
|
|
onAccumulated: onStreamChunk,
|
|
signal,
|
|
})
|
|
|
|
if (!accumulatedContent) {
|
|
throw new Error('Failed to generate description')
|
|
}
|
|
|
|
return accumulatedContent.trim()
|
|
},
|
|
onSuccess: (content) => {
|
|
logger.info('Generated version description', { length: content.length })
|
|
},
|
|
onError: (error) => {
|
|
if (error.name === 'AbortError') return
|
|
logger.error('Failed to generate version description', { error })
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Variables for activate version mutation
|
|
*/
|
|
interface ActivateVersionVariables {
|
|
workflowId: string
|
|
version: number
|
|
}
|
|
|
|
type ActivateVersionResult = ActivateDeploymentVersionResponse
|
|
|
|
/**
|
|
* Mutation hook for activating (promoting) a specific deployment version.
|
|
* Invalidates deployment info and versions queries on success.
|
|
*/
|
|
export function useActivateDeploymentVersion() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({
|
|
workflowId,
|
|
version,
|
|
}: ActivateVersionVariables): Promise<ActivateVersionResult> => {
|
|
return requestJson(activateDeploymentVersionContract, {
|
|
params: { id: workflowId, version },
|
|
body: { isActive: true },
|
|
})
|
|
},
|
|
onMutate: async ({ workflowId, version }) => {
|
|
await queryClient.cancelQueries({ queryKey: deploymentKeys.versions(workflowId) })
|
|
|
|
const previousVersions = queryClient.getQueryData<DeploymentVersionsResponse>(
|
|
deploymentKeys.versions(workflowId)
|
|
)
|
|
|
|
if (previousVersions) {
|
|
queryClient.setQueryData<DeploymentVersionsResponse>(deploymentKeys.versions(workflowId), {
|
|
versions: previousVersions.versions.map((v) => ({
|
|
...v,
|
|
isActive: v.version === version,
|
|
})),
|
|
})
|
|
}
|
|
|
|
return { previousVersions }
|
|
},
|
|
onError: (_, variables, context) => {
|
|
logger.error('Failed to activate deployment version')
|
|
|
|
if (context?.previousVersions) {
|
|
queryClient.setQueryData(
|
|
deploymentKeys.versions(variables.workflowId),
|
|
context.previousVersions
|
|
)
|
|
}
|
|
},
|
|
onSettled: (_data, error, variables) => {
|
|
if (!error) {
|
|
logger.info('Deployment version activated', {
|
|
workflowId: variables.workflowId,
|
|
version: variables.version,
|
|
})
|
|
}
|
|
return invalidateDeploymentQueries(queryClient, variables.workflowId)
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Variables for updating public API access
|
|
*/
|
|
interface UpdatePublicApiVariables {
|
|
workflowId: string
|
|
isPublicApi: boolean
|
|
}
|
|
|
|
/**
|
|
* Mutation hook for toggling a workflow's public API access.
|
|
* Invalidates deployment info query on success.
|
|
*/
|
|
export function useUpdatePublicApi() {
|
|
const queryClient = useQueryClient()
|
|
|
|
return useMutation({
|
|
mutationFn: async ({ workflowId, isPublicApi }: UpdatePublicApiVariables) => {
|
|
return requestJson(updatePublicApiContract, {
|
|
params: { id: workflowId },
|
|
body: { isPublicApi },
|
|
})
|
|
},
|
|
onSettled: (_data, error, variables) => {
|
|
if (!error) {
|
|
logger.info('Public API setting updated', {
|
|
workflowId: variables.workflowId,
|
|
isPublicApi: variables.isPublicApi,
|
|
})
|
|
} else {
|
|
logger.error('Failed to update public API setting', { error })
|
|
}
|
|
|
|
return queryClient.invalidateQueries({
|
|
queryKey: deploymentKeys.info(variables.workflowId),
|
|
})
|
|
},
|
|
})
|
|
}
|