From 2459b8811432eb6abcc56b9e756de731ed0e4b69 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:34:16 +0800 Subject: [PATCH] /console/api/enterprise/deployment-environment-options --- web/contract/console/deployments.ts | 27 ++++++++++------ web/contract/router.ts | 2 ++ .../deployments/components/deploy-drawer.tsx | 15 ++++----- .../components/deploy-drawer/form.tsx | 7 ++-- .../components/deploy-drawer/select.tsx | 14 ++++++-- .../deployments/components/rollback-modal.tsx | 10 ++++-- .../deployments/detail/deploy-tab.tsx | 11 +++++-- .../versions-tab/deploy-release-menu.tsx | 11 +++++-- web/features/deployments/list/index.tsx | 9 ++++-- web/features/deployments/utils.ts | 32 +++++-------------- 10 files changed, 82 insertions(+), 56 deletions(-) diff --git a/web/contract/console/deployments.ts b/web/contract/console/deployments.ts index f4be75957d..d95c12aafb 100644 --- a/web/contract/console/deployments.ts +++ b/web/contract/console/deployments.ts @@ -46,12 +46,6 @@ export type DeploymentStatusCount = { count?: number } -export type AppInstanceFilter = { - id?: string - name?: string - kind?: 'all' | 'environment' | 'not_deployed' | (string & {}) -} - export type AppDeploymentSummary = { id?: string name?: string @@ -73,7 +67,6 @@ export type Pagination = { } export type ListAppDeploymentsReply = { - filters?: AppInstanceFilter[] data?: AppDeploymentSummary[] pagination?: Pagination } @@ -144,11 +137,20 @@ export type ListEnvironmentDeploymentsReply = { data?: EnvironmentDeploymentRow[] } -export type EnvironmentOption = ConsoleEnvironmentSummary & { - disabled?: boolean +export type DeploymentEnvironmentOption = ConsoleEnvironmentSummary & { + managedBy?: string + deployable?: boolean disabledReason?: string } +export type ListDeploymentEnvironmentOptionsReply = { + environments?: DeploymentEnvironmentOption[] +} + +export type EnvironmentOption = DeploymentEnvironmentOption & { + disabled?: boolean +} + export type ReleaseRuntimePreviewReply = { release?: ConsoleReleaseSummary bindings?: RuntimeBindingDisplay[] @@ -370,6 +372,13 @@ export const runtimeInstancesContract = base }>()) .output(type()) +export const deploymentEnvironmentOptionsContract = base + .route({ + path: '/enterprise/deployment-environment-options', + method: 'GET', + }) + .output(type()) + export const previewReleaseContract = base .route({ path: '/enterprise/app-instances/{appInstanceId}/releases:preview', diff --git a/web/contract/router.ts b/web/contract/router.ts index ae4411fb2c..10700b0184 100644 --- a/web/contract/router.ts +++ b/web/contract/router.ts @@ -12,6 +12,7 @@ import { createReleaseContract, deleteAppInstanceContract, deleteEnvironmentAPITokenContract, + deploymentEnvironmentOptionsContract, deploymentOverviewContract, environmentAccessPolicyContract, listAppDeploymentsContract, @@ -119,6 +120,7 @@ export const consoleRouterContract = { createInstance: createAppInstanceContract, overview: deploymentOverviewContract, environmentDeployments: runtimeInstancesContract, + deploymentEnvironmentOptions: deploymentEnvironmentOptionsContract, previewRelease: previewReleaseContract, releaseHistory: releaseHistoryContract, accessConfig: accessConfigContract, diff --git a/web/features/deployments/components/deploy-drawer.tsx b/web/features/deployments/components/deploy-drawer.tsx index df16a62135..2171cebc39 100644 --- a/web/features/deployments/components/deploy-drawer.tsx +++ b/web/features/deployments/components/deploy-drawer.tsx @@ -10,9 +10,8 @@ import { DEPLOYMENT_PAGE_SIZE, } from '../data' import { useStartDeployment } from '../hooks/use-deployment-mutations' -import { deploymentEnvironmentDeploymentsQueryOptions } from '../queries' import { useDeploymentsStore } from '../store' -import { environmentOptionsFromDeploymentRows } from '../utils' +import { environmentOptionsFromOptionsReply } from '../utils' import { DeployForm } from './deploy-drawer/form' const DeployDrawer: FC = () => { @@ -34,14 +33,14 @@ const DeployDrawer: FC = () => { : skipToken, enabled: open && Boolean(drawerAppId), })) - const { data: environmentDeployments } = useQuery({ - ...deploymentEnvironmentDeploymentsQueryOptions(drawerAppId), - enabled: open && Boolean(drawerAppId), + const { data: environmentOptionsReply } = useQuery({ + ...consoleQuery.deployments.deploymentEnvironmentOptions.queryOptions(), + enabled: open, }) const environmentOptions = useMemo( - () => environmentOptionsFromDeploymentRows(environmentDeployments?.data), - [environmentDeployments?.data], + () => environmentOptionsFromOptionsReply(environmentOptionsReply), + [environmentOptionsReply], ) const environments = environmentOptions const releases = releaseHistory?.data?.map(row => row.release ?? row).filter(release => release.id) ?? [] @@ -57,7 +56,7 @@ const DeployDrawer: FC = () => { {!drawerAppId ?
{t('deployDrawer.notFound')}
- : (!releaseHistory || !environmentDeployments) + : (!releaseHistory || !environmentOptionsReply) ? (
diff --git a/web/features/deployments/components/deploy-drawer/form.tsx b/web/features/deployments/components/deploy-drawer/form.tsx index 2eafcd9787..420bbec2f3 100644 --- a/web/features/deployments/components/deploy-drawer/form.tsx +++ b/web/features/deployments/components/deploy-drawer/form.tsx @@ -97,11 +97,12 @@ export const DeployForm: FC = ({ const isPromote = Boolean(presetReleaseId) const [selectedEnvId, setSelectedEnvId] = useState( - () => lockedEnvId ?? environments[0]?.id ?? '', + () => lockedEnvId ?? environments.find(env => !env.disabled)?.id ?? environments[0]?.id ?? '', ) const selectedEnvironmentId = selectedEnvId || lockedEnvId || environments[0]?.id || '' + const selectedEnvironment = environments.find(env => env.id === selectedEnvironmentId) const [releaseNote, setReleaseNote] = useState('') - const canDeploy = Boolean(selectedEnvironmentId && (!isPromote || displayedRelease?.id || defaultReleaseId)) + const canDeploy = Boolean(selectedEnvironmentId && selectedEnvironment && !selectedEnvironment.disabled && (!isPromote || displayedRelease?.id || defaultReleaseId)) const previewReleaseId = isPromote ? displayedRelease?.id ?? defaultReleaseId : undefined const releasePreview = useQuery(consoleQuery.deployments.previewRelease.queryOptions({ input: appId && (!isPromote || previewReleaseId) @@ -193,6 +194,8 @@ export const DeployForm: FC = ({ options={environments.filter(env => env.id).map(env => ({ value: env.id!, label: `${environmentName(env)} · ${t(environmentMode(env) === 'isolated' ? 'mode.isolated' : 'mode.shared')} · ${(env.type ?? 'env').toUpperCase()}`, + disabled: env.disabled, + disabledReason: env.disabledReason, }))} placeholder={t('deployDrawer.selectEnv')} /> diff --git a/web/features/deployments/components/deploy-drawer/select.tsx b/web/features/deployments/components/deploy-drawer/select.tsx index aeafaf08a3..2a1b57e8ab 100644 --- a/web/features/deployments/components/deploy-drawer/select.tsx +++ b/web/features/deployments/components/deploy-drawer/select.tsx @@ -24,7 +24,12 @@ export const Field: FC = ({ label, hint, children }) => (
) -type SelectOption = { value: string, label: string } +type SelectOption = { + value: string + label: string + disabled?: boolean + disabledReason?: string +} type SelectProps = { value: string @@ -57,7 +62,12 @@ export const DeploymentSelect: FC = ({ value, onChange, options, pl {options.map(opt => ( - + {opt.label} diff --git a/web/features/deployments/components/rollback-modal.tsx b/web/features/deployments/components/rollback-modal.tsx index 8c9d6ba52c..108d7dd26a 100644 --- a/web/features/deployments/components/rollback-modal.tsx +++ b/web/features/deployments/components/rollback-modal.tsx @@ -27,7 +27,7 @@ import { deployedRows, environmentId, environmentName, - environmentOptionsFromDeploymentRows, + environmentOptionsFromOptionsReply, releaseCommit, releaseLabel, toAppInfoFromOverview, @@ -67,13 +67,17 @@ const RollbackModal: FC = () => { ...deploymentEnvironmentDeploymentsQueryOptions(modal.appId), enabled: modal.open && Boolean(modal.appId), }) + const { data: environmentOptionsReply } = useQuery({ + ...consoleQuery.deployments.deploymentEnvironmentOptions.queryOptions(), + enabled: modal.open, + }) const { data: releaseHistory } = useQuery(consoleQuery.deployments.releaseHistory.queryOptions({ input: pagedInput ?? skipToken, enabled: modal.open && Boolean(modal.appId), })) const environmentOptions = useMemo( - () => environmentOptionsFromDeploymentRows(environmentDeployments?.data), - [environmentDeployments?.data], + () => environmentOptionsFromOptionsReply(environmentOptionsReply), + [environmentOptionsReply], ) const currentRow = deployedRows(environmentDeployments?.data) diff --git a/web/features/deployments/detail/deploy-tab.tsx b/web/features/deployments/detail/deploy-tab.tsx index 304ed33f1c..2dd015a420 100644 --- a/web/features/deployments/detail/deploy-tab.tsx +++ b/web/features/deployments/detail/deploy-tab.tsx @@ -11,6 +11,7 @@ import { import { 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 { deploymentEnvironmentDeploymentsQueryOptions } from '../queries' import { useDeploymentsStore } from '../store' @@ -23,7 +24,7 @@ import { environmentId, environmentMode, environmentName, - environmentOptionsFromDeploymentRows, + environmentOptionsFromOptionsReply, isUndeployedDeploymentRow, releaseCommit, releaseLabel, @@ -40,11 +41,12 @@ type DeployTabProps = { const DeployTab: FC = ({ instanceId: appId }) => { const { t } = useTranslation('deployments') const { data: environmentDeployments } = useQuery(deploymentEnvironmentDeploymentsQueryOptions(appId)) + const { data: environmentOptionsReply } = useQuery(consoleQuery.deployments.deploymentEnvironmentOptions.queryOptions()) const openDeployDrawer = useDeploymentsStore(state => state.openDeployDrawer) const undeployDeployment = useUndeployDeployment() const environmentOptions = useMemo( - () => environmentOptionsFromDeploymentRows(environmentDeployments?.data), - [environmentDeployments?.data], + () => environmentOptionsFromOptionsReply(environmentOptionsReply), + [environmentOptionsReply], ) const rows = useMemo( @@ -118,7 +120,10 @@ const DeployTab: FC = ({ instanceId: appId }) => { { + if (env.disabled) + return setDeployMenuOpen(false) openDeployDrawer({ appId, environmentId: env.id }) }} diff --git a/web/features/deployments/detail/versions-tab/deploy-release-menu.tsx b/web/features/deployments/detail/versions-tab/deploy-release-menu.tsx index def1bb02f3..9d07a1efd5 100644 --- a/web/features/deployments/detail/versions-tab/deploy-release-menu.tsx +++ b/web/features/deployments/detail/versions-tab/deploy-release-menu.tsx @@ -11,6 +11,7 @@ import { import { useQuery } from '@tanstack/react-query' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' +import { consoleQuery } from '@/service/client' import { deploymentEnvironmentDeploymentsQueryOptions } from '../../queries' import { useDeploymentsStore } from '../../store' import { @@ -20,7 +21,7 @@ import { deploymentStatus, environmentId, environmentName, - environmentOptionsFromDeploymentRows, + environmentOptionsFromOptionsReply, } from '../../utils' type DeployReleaseMenuProps = { @@ -37,10 +38,14 @@ export const DeployReleaseMenu: FC = ({ appId, releaseId ...deploymentEnvironmentDeploymentsQueryOptions(appId), enabled: open, }) + const { data: environmentOptionsReply } = useQuery({ + ...consoleQuery.deployments.deploymentEnvironmentOptions.queryOptions(), + enabled: open, + }) const environmentOptions = useMemo( - () => environmentOptionsFromDeploymentRows(environmentDeployments?.data), - [environmentDeployments?.data], + () => environmentOptionsFromOptionsReply(environmentOptionsReply), + [environmentOptionsReply], ) const environments = environmentOptions.filter(env => env.id) const deploymentRows = deployedRows(environmentDeployments?.data) diff --git a/web/features/deployments/list/index.tsx b/web/features/deployments/list/index.tsx index d748eb2b88..d3cb8e208a 100644 --- a/web/features/deployments/list/index.tsx +++ b/web/features/deployments/list/index.tsx @@ -7,6 +7,7 @@ import { debounce, parseAsString, useQueryState } from 'nuqs' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' +import { consoleQuery } from '@/service/client' import CreateInstanceModal from '../components/create-instance-modal' import DeployDrawer from '../components/deploy-drawer' import RollbackModal from '../components/rollback-modal' @@ -16,7 +17,7 @@ import { deploymentSummariesFromList, environmentId, environmentName, - environmentOptionsFromList, + environmentOptionsFromOptionsReply, sourceAppsFromList, } from '../utils' import { EnvironmentFilter } from './environment-filter' @@ -52,9 +53,13 @@ const DeploymentsMain: FC = () => { ...(envFilter === 'not-deployed' ? { notDeployed: true } : {}), ...(queryKeywords.trim() ? { query: queryKeywords.trim() } : {}), })) + const { data: environmentOptionsReply } = useQuery(consoleQuery.deployments.deploymentEnvironmentOptions.queryOptions()) const apps = useMemo(() => sourceAppsFromList(listQuery.data), [listQuery.data]) const summaries = useMemo(() => deploymentSummariesFromList(listQuery.data), [listQuery.data]) - const environmentOptions = useMemo(() => environmentOptionsFromList(listQuery.data), [listQuery.data]) + const environmentOptions = useMemo( + () => environmentOptionsFromOptionsReply(environmentOptionsReply), + [environmentOptionsReply], + ) const environments = useMemo(() => { return environmentOptions diff --git a/web/features/deployments/utils.ts b/web/features/deployments/utils.ts index 8ef2a4d619..1c7bcb928d 100644 --- a/web/features/deployments/utils.ts +++ b/web/features/deployments/utils.ts @@ -7,6 +7,7 @@ import type { EnvironmentDeploymentRow, EnvironmentOption, ListAppDeploymentsReply, + ListDeploymentEnvironmentOptionsReply, RuntimeBindingDisplay, } from '@/contract/console/deployments' import { PUBLIC_API_PREFIX } from '@/config' @@ -107,14 +108,6 @@ export const deployedRows = (rows?: EnvironmentDeploymentRow[]) => && (row.id || runtimeStatus || row.currentRelease || row.detail) }) ?? [] -type DeploymentEnvironmentFilter = { - id?: string - name?: string - kind?: string - disabled?: boolean - disabledReason?: string -} - export function toAppInfoFromSummary(summary: AppDeploymentSummary): AppInfo | undefined { if (!summary.id || !summary.name) return undefined @@ -158,22 +151,13 @@ export const deploymentSummariesFromList = (response?: ListAppDeploymentsReply): ) } -export const environmentOptionsFromList = (response?: ListAppDeploymentsReply): EnvironmentOption[] => { - return ((response?.filters ?? []) as DeploymentEnvironmentFilter[]) - .filter(filter => filter.kind === 'environment' && filter.id) - .map(filter => ({ - id: filter.id, - name: filter.name, - disabled: filter.disabled, - disabledReason: filter.disabledReason, - })) -} - -export const environmentOptionsFromDeploymentRows = (rows?: EnvironmentDeploymentRow[]): EnvironmentOption[] => { - return rows - ?.map(row => row.environment) - .filter((environment): environment is ConsoleEnvironmentSummary => Boolean(environment?.id)) - .map(environment => ({ ...environment })) ?? [] +export const environmentOptionsFromOptionsReply = (response?: ListDeploymentEnvironmentOptionsReply): EnvironmentOption[] => { + return response?.environments + ?.filter(environment => environment.id) + .map(environment => ({ + ...environment, + disabled: environment.deployable === false, + })) ?? [] } export const accessModeToPermissionKey = (mode?: string): AccessPermissionKind => {