From 6899c7720f26ffa7b27407ff727063d21d487704 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Wed, 12 Aug 2026 16:49:51 +0800 Subject: [PATCH] refactor(web): remove deprecated deployments pages (#40593) --- knip.config.ts | 2 - pnpm-lock.yaml | 6 - .../[appInstanceId]/access/page.tsx | 5 - .../[appInstanceId]/api-tokens/page.tsx | 5 - .../[appInstanceId]/instances/page.tsx | 5 - .../[appInstanceId]/overview/page.tsx | 5 - .../deployments/[appInstanceId]/page.tsx | 5 - .../[appInstanceId]/releases/page.tsx | 5 - .../[appInstanceId]/sidebar-page.tsx | 5 - .../deployments/create/page.tsx | 3 - .../[appInstanceId]/access/page.tsx | 5 - .../[appInstanceId]/api-tokens/page.tsx | 5 - .../[appInstanceId]/instances/page.tsx | 5 - .../deployments/[appInstanceId]/layout.tsx | 6 - .../[appInstanceId]/overview/page.tsx | 5 - .../deployments/[appInstanceId]/page.tsx | 10 - .../[appInstanceId]/releases/page.tsx | 5 - .../deployments/__tests__/layout.spec.tsx | 23 - .../deployments/create/page.tsx | 12 - web/app/(commonLayout)/deployments/layout.tsx | 5 - web/app/(commonLayout)/deployments/page.tsx | 10 - web/app/(commonLayout)/layout.tsx | 17 +- .../main-nav/__tests__/index.spec.tsx | 28 +- .../main-nav/__tests__/layout.spec.tsx | 91 +-- web/app/components/main-nav/index.tsx | 9 +- web/app/components/main-nav/layout.tsx | 10 +- web/app/components/main-nav/routes.ts | 16 +- web/app/components/next-route-state/atoms.ts | 50 -- web/app/components/next-route-state/index.tsx | 27 - web/context/workspace-state.ts | 5 + web/features/deployments/README.md | 101 --- .../deployments/create-guide/README.md | 18 - .../deployments/create-guide/index.tsx | 38 -- .../deployments/create-guide/link.tsx | 12 - .../state/__tests__/index.spec.ts | 202 ------ .../create-guide/state/environment.ts | 8 - .../create-guide/state/primitives.ts | 37 -- .../create-guide/state/provider.tsx | 9 - .../deployments/create-guide/state/queries.ts | 225 ------- .../deployments/create-guide/state/release.ts | 91 --- .../deployments/create-guide/state/scoped.ts | 41 -- .../deployments/create-guide/state/source.ts | 145 ----- .../create-guide/state/submission.ts | 221 ------- .../deployments/create-guide/state/target.ts | 172 ------ .../deployments/create-guide/state/types.ts | 7 - .../deployments/create-guide/state/utils.ts | 107 ---- .../create-guide/state/workflow.ts | 130 ---- .../ui/__tests__/source-step.spec.tsx | 178 ------ .../deployments/create-guide/ui/layout.tsx | 220 ------- .../create-guide/ui/release-step.tsx | 222 ------- .../deployments/create-guide/ui/shell.tsx | 69 --- .../create-guide/ui/source-step.tsx | 418 ------------- .../create-guide/ui/target-step.tsx | 389 ------------ .../deployments/create-release/README.md | 18 - .../deployments/create-release/index.tsx | 82 --- .../state/__tests__/dsl-enabled.spec.ts | 205 ------- .../state/__tests__/index.spec.ts | 513 ---------------- .../deployments/create-release/state/index.ts | 575 ------------------ .../ui/__tests__/source-app-picker.spec.tsx | 178 ------ .../deployments/create-release/ui/actions.tsx | 61 -- .../create-release/ui/content-feedback.tsx | 41 -- .../deployments/create-release/ui/dialog.tsx | 115 ---- .../create-release/ui/metadata-fields.tsx | 129 ---- .../create-release/ui/source-app-picker.tsx | 247 -------- .../create-release/ui/source-section.tsx | 125 ---- .../deployments/deploy-drawer/README.md | 15 - .../deployments/deploy-drawer/index.tsx | 64 -- .../state/__tests__/index.spec.ts | 438 ------------- .../deployments/deploy-drawer/state/index.ts | 521 ---------------- .../deploy-drawer/state/release-options.ts | 26 - .../deploy-drawer/ui/form-sections.tsx | 195 ------ .../deploy-drawer/ui/form-skeleton.tsx | 42 -- .../deployments/deploy-drawer/ui/form.tsx | 285 --------- .../deployments/deploy-drawer/ui/select.tsx | 145 ----- .../deploy-drawer/ui/status-badge.tsx | 17 - .../deployments/deployment-actions/README.md | 11 - .../__tests__/delete-dialog.spec.tsx | 117 ---- .../__tests__/edit-dialog.spec.tsx | 138 ----- .../deployment-actions/delete-dialog.tsx | 84 --- .../deployment-actions/edit-dialog.tsx | 160 ----- .../deployments/deployment-actions/index.tsx | 100 --- .../deployments/deployment-actions/state.ts | 27 - .../deployments/deployment-actions/types.ts | 3 - web/features/deployments/detail/README.md | 20 - .../detail/__tests__/index.spec.tsx | 84 --- .../detail/__tests__/state.spec.ts | 162 ----- .../deployments/detail/access/README.md | 15 - .../detail/access/__tests__/state.spec.ts | 70 --- .../detail/access/channels/README.md | 17 - .../detail/access/channels/section.tsx | 250 -------- .../deployments/detail/access/channels/url.ts | 8 - .../deployments/detail/access/index.tsx | 13 - .../detail/access/permissions/README.md | 18 - .../__tests__/access-control-dialog.spec.tsx | 128 ---- .../__tests__/access-policy.spec.ts | 208 ------- .../__tests__/permissions.spec.tsx | 300 --------- .../permissions/access-control-dialog.tsx | 260 -------- .../access/permissions/access-policy.ts | 170 ------ .../access-subject-selector/README.md | 17 - .../__tests__/add-button.spec.tsx | 127 ---- .../access-subject-selector/add-button.tsx | 218 ------- .../selection-list.tsx | 226 ------- .../subject-options.tsx | 225 ------- .../access-subject-selector/types.ts | 12 - .../access-subject-selector/utils.ts | 55 -- .../environment-permission-row.tsx | 143 ----- .../permissions/permission-summary-button.tsx | 74 --- .../detail/access/permissions/section.tsx | 80 --- .../deployments/detail/access/state.ts | 30 - .../deployments/detail/api-tokens/README.md | 14 - .../detail/api-tokens/__tests__/state.spec.ts | 70 --- .../detail/api-tokens/api-keys/README.md | 15 - .../__tests__/api-key-generate-menu.spec.tsx | 96 --- .../api-keys/api-key-generate-menu.tsx | 239 -------- .../api-tokens/api-keys/api-key-list.tsx | 250 -------- .../api-tokens/api-keys/api-token-name.ts | 39 -- .../api-keys/created-token-dialog.tsx | 100 --- .../api-tokens/api-token-management/README.md | 17 - .../api-token-management/section.tsx | 164 ----- .../api-token-management/skeleton.tsx | 120 ---- .../developer-api-header-switch.tsx | 71 --- .../detail/api-tokens/docs/README.md | 19 - .../detail/api-tokens/docs/docs-drawer.tsx | 116 ---- .../deployments/detail/api-tokens/index.tsx | 11 - .../deployments/detail/api-tokens/state.ts | 33 - .../detail/api-tokens/table-styles.ts | 6 - .../deployments/detail/deployment-sidebar.tsx | 307 ---------- .../deployments/detail/detail-sidebar.tsx | 15 - web/features/deployments/detail/index.tsx | 95 --- .../deployments/detail/instances/README.md | 18 - .../instances/environment-list/README.md | 15 - .../deployment-environment-list.tsx | 152 ----- .../deployment-status-summary.tsx | 116 ---- .../detail/instances/header-actions/README.md | 15 - .../header-actions/new-deployment-button.tsx | 44 -- .../deployments/detail/instances/index.tsx | 129 ---- .../detail/instances/row-actions/README.md | 15 - .../deployment-row-actions-menu.spec.tsx | 46 -- .../row-actions/deployment-error-dialog.tsx | 78 --- .../deployment-row-actions-menu.tsx | 136 ----- .../row-actions/deployment-row-actions.tsx | 112 ---- .../undeploy-deployment-dialog.tsx | 60 -- .../detail/instances/table-styles.ts | 6 - .../deployments/detail/overview/README.md | 17 - .../detail/overview/__tests__/state.spec.ts | 76 --- .../detail/overview/access-summary/README.md | 15 - .../access-summary/access-status-section.tsx | 244 -------- .../overview/environment-status/README.md | 16 - .../__tests__/environment-tile-utils.spec.ts | 158 ----- .../environment-status/environment-strip.tsx | 154 ----- .../environment-tile-utils.ts | 162 ----- .../environment-status/environment-tile.tsx | 181 ------ .../environment-status/overview-drift.ts | 25 - .../deployments/detail/overview/index.tsx | 116 ---- .../detail/overview/release-summary/README.md | 16 - .../overview/release-summary/release-hero.tsx | 188 ------ .../deployments/detail/overview/state.ts | 30 - .../deployments/detail/releases/README.md | 14 - .../detail/releases/__tests__/state.spec.ts | 100 --- .../deployments/detail/releases/index.tsx | 19 - .../detail/releases/release-actions/README.md | 17 - .../deploy-release-menu-utils.spec.ts | 65 -- .../__tests__/deploy-release-menu.spec.tsx | 154 ----- .../release-actions/__tests__/state.spec.ts | 121 ---- .../release-actions/delete-release-dialog.tsx | 57 -- .../deploy-release-menu-utils.ts | 157 ----- .../release-actions/deploy-release-menu.tsx | 260 -------- .../release-actions/edit-release-dialog.tsx | 180 ------ .../release-actions/release-dsl-export.ts | 50 -- .../releases/release-actions/release-dsl.ts | 11 - .../detail/releases/release-actions/state.ts | 86 --- .../detail/releases/release-history/README.md | 17 - .../release-history/deployed-to-badge.tsx | 39 -- .../release-history/release-deployments.ts | 58 -- .../release-history-deployments.tsx | 12 - .../release-history/release-history-rows.tsx | 245 -------- .../release-history-table-skeleton.tsx | 110 ---- .../release-history/release-history-table.tsx | 81 --- .../release-history/release-history-types.ts | 10 - .../releases/release-history/table-styles.ts | 8 - .../deployments/detail/releases/state.ts | 56 -- web/features/deployments/detail/state.ts | 85 --- web/features/deployments/detail/tabs.ts | 15 - web/features/deployments/list/README.md | 21 - web/features/deployments/list/index.tsx | 12 - web/features/deployments/list/state/index.ts | 174 ------ .../ui/__tests__/environment-filter.spec.tsx | 39 -- .../list/ui/create-deployment-button.tsx | 21 - .../list/ui/environment-filter.tsx | 109 ---- .../list/ui/instance-card-sections.tsx | 274 --------- .../list/ui/instance-card-utils.ts | 17 - .../deployments/list/ui/instance-card.tsx | 122 ---- web/features/deployments/list/ui/shell.tsx | 219 ------- web/features/deployments/route-state.ts | 10 - web/features/deployments/shared/README.md | 19 - .../__tests__/env-var-bindings-utils.spec.ts | 58 -- .../runtime-credential-bindings-utils.spec.ts | 124 ---- .../__tests__/title-tooltip.spec.tsx | 64 -- .../shared/components/detail-table-styles.ts | 8 - .../shared/components/detail-table.tsx | 106 ---- .../shared/components/empty-state.tsx | 146 ----- .../shared/components/endpoint.tsx | 76 --- .../components/env-var-bindings-utils.ts | 30 - .../shared/components/env-var-bindings.tsx | 268 -------- .../runtime-credential-bindings-utils.ts | 115 ---- .../runtime-credential-bindings.tsx | 200 ------ .../deployments/shared/components/section.tsx | 84 --- .../shared/components/title-tooltip.tsx | 68 --- .../unsupported-dsl-nodes-alert.tsx | 120 ---- .../shared/domain/__tests__/dsl.spec.ts | 115 ---- web/features/deployments/shared/domain/dsl.ts | 140 ----- .../deployments/shared/domain/error.ts | 125 ---- .../shared/domain/feature-flags.ts | 2 - .../deployments/shared/domain/idempotency.ts | 5 - .../shared/domain/release-action.ts | 68 --- .../deployments/shared/domain/release.ts | 20 - .../shared/domain/runtime-status.ts | 39 -- .../shared/ui/deployment-status-badge.tsx | 112 ---- .../shared/ui/deployment-status-style.ts | 84 --- web/i18n/ar-TN/common.json | 1 - web/i18n/ar-TN/deployments.json | 364 +---------- web/i18n/de-DE/common.json | 1 - web/i18n/de-DE/deployments.json | 364 +---------- web/i18n/en-US/common.json | 1 - web/i18n/en-US/deployments.json | 364 +---------- web/i18n/es-ES/common.json | 1 - web/i18n/es-ES/deployments.json | 364 +---------- web/i18n/fa-IR/common.json | 1 - web/i18n/fa-IR/deployments.json | 364 +---------- web/i18n/fr-FR/common.json | 1 - web/i18n/fr-FR/deployments.json | 364 +---------- web/i18n/hi-IN/common.json | 1 - web/i18n/hi-IN/deployments.json | 364 +---------- web/i18n/id-ID/common.json | 1 - web/i18n/id-ID/deployments.json | 364 +---------- web/i18n/it-IT/common.json | 1 - web/i18n/it-IT/deployments.json | 364 +---------- web/i18n/ja-JP/common.json | 1 - web/i18n/ja-JP/deployments.json | 364 +---------- web/i18n/ko-KR/common.json | 1 - web/i18n/ko-KR/deployments.json | 364 +---------- web/i18n/lo-LA/common.json | 1 - web/i18n/lo-LA/deployments.json | 364 +---------- web/i18n/nl-NL/common.json | 1 - web/i18n/nl-NL/deployments.json | 364 +---------- web/i18n/pl-PL/common.json | 1 - web/i18n/pl-PL/deployments.json | 364 +---------- web/i18n/pt-BR/common.json | 1 - web/i18n/pt-BR/deployments.json | 364 +---------- web/i18n/ro-RO/common.json | 1 - web/i18n/ro-RO/deployments.json | 364 +---------- web/i18n/ru-RU/common.json | 1 - web/i18n/ru-RU/deployments.json | 364 +---------- web/i18n/sl-SI/common.json | 1 - web/i18n/sl-SI/deployments.json | 364 +---------- web/i18n/th-TH/common.json | 1 - web/i18n/th-TH/deployments.json | 364 +---------- web/i18n/tr-TR/common.json | 1 - web/i18n/tr-TR/deployments.json | 364 +---------- web/i18n/uk-UA/common.json | 1 - web/i18n/uk-UA/deployments.json | 364 +---------- web/i18n/vi-VN/common.json | 1 - web/i18n/vi-VN/deployments.json | 364 +---------- web/i18n/zh-Hans/common.json | 1 - web/i18n/zh-Hans/deployments.json | 364 +---------- web/i18n/zh-Hant/common.json | 1 - web/i18n/zh-Hant/deployments.json | 364 +---------- web/package.json | 2 - .../access-control/use-access-subjects.ts | 51 -- 269 files changed, 61 insertions(+), 29187 deletions(-) delete mode 100644 web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/access/page.tsx delete mode 100644 web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/api-tokens/page.tsx delete mode 100644 web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/instances/page.tsx delete mode 100644 web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/overview/page.tsx delete mode 100644 web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/page.tsx delete mode 100644 web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/releases/page.tsx delete mode 100644 web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/sidebar-page.tsx delete mode 100644 web/app/(commonLayout)/@detailSidebar/deployments/create/page.tsx delete mode 100644 web/app/(commonLayout)/deployments/[appInstanceId]/access/page.tsx delete mode 100644 web/app/(commonLayout)/deployments/[appInstanceId]/api-tokens/page.tsx delete mode 100644 web/app/(commonLayout)/deployments/[appInstanceId]/instances/page.tsx delete mode 100644 web/app/(commonLayout)/deployments/[appInstanceId]/layout.tsx delete mode 100644 web/app/(commonLayout)/deployments/[appInstanceId]/overview/page.tsx delete mode 100644 web/app/(commonLayout)/deployments/[appInstanceId]/page.tsx delete mode 100644 web/app/(commonLayout)/deployments/[appInstanceId]/releases/page.tsx delete mode 100644 web/app/(commonLayout)/deployments/__tests__/layout.spec.tsx delete mode 100644 web/app/(commonLayout)/deployments/create/page.tsx delete mode 100644 web/app/(commonLayout)/deployments/layout.tsx delete mode 100644 web/app/(commonLayout)/deployments/page.tsx delete mode 100644 web/app/components/next-route-state/atoms.ts delete mode 100644 web/app/components/next-route-state/index.tsx delete mode 100644 web/features/deployments/README.md delete mode 100644 web/features/deployments/create-guide/README.md delete mode 100644 web/features/deployments/create-guide/index.tsx delete mode 100644 web/features/deployments/create-guide/link.tsx delete mode 100644 web/features/deployments/create-guide/state/__tests__/index.spec.ts delete mode 100644 web/features/deployments/create-guide/state/environment.ts delete mode 100644 web/features/deployments/create-guide/state/primitives.ts delete mode 100644 web/features/deployments/create-guide/state/provider.tsx delete mode 100644 web/features/deployments/create-guide/state/queries.ts delete mode 100644 web/features/deployments/create-guide/state/release.ts delete mode 100644 web/features/deployments/create-guide/state/scoped.ts delete mode 100644 web/features/deployments/create-guide/state/source.ts delete mode 100644 web/features/deployments/create-guide/state/submission.ts delete mode 100644 web/features/deployments/create-guide/state/target.ts delete mode 100644 web/features/deployments/create-guide/state/types.ts delete mode 100644 web/features/deployments/create-guide/state/utils.ts delete mode 100644 web/features/deployments/create-guide/state/workflow.ts delete mode 100644 web/features/deployments/create-guide/ui/__tests__/source-step.spec.tsx delete mode 100644 web/features/deployments/create-guide/ui/layout.tsx delete mode 100644 web/features/deployments/create-guide/ui/release-step.tsx delete mode 100644 web/features/deployments/create-guide/ui/shell.tsx delete mode 100644 web/features/deployments/create-guide/ui/source-step.tsx delete mode 100644 web/features/deployments/create-guide/ui/target-step.tsx delete mode 100644 web/features/deployments/create-release/README.md delete mode 100644 web/features/deployments/create-release/index.tsx delete mode 100644 web/features/deployments/create-release/state/__tests__/dsl-enabled.spec.ts delete mode 100644 web/features/deployments/create-release/state/__tests__/index.spec.ts delete mode 100644 web/features/deployments/create-release/state/index.ts delete mode 100644 web/features/deployments/create-release/ui/__tests__/source-app-picker.spec.tsx delete mode 100644 web/features/deployments/create-release/ui/actions.tsx delete mode 100644 web/features/deployments/create-release/ui/content-feedback.tsx delete mode 100644 web/features/deployments/create-release/ui/dialog.tsx delete mode 100644 web/features/deployments/create-release/ui/metadata-fields.tsx delete mode 100644 web/features/deployments/create-release/ui/source-app-picker.tsx delete mode 100644 web/features/deployments/create-release/ui/source-section.tsx delete mode 100644 web/features/deployments/deploy-drawer/README.md delete mode 100644 web/features/deployments/deploy-drawer/index.tsx delete mode 100644 web/features/deployments/deploy-drawer/state/__tests__/index.spec.ts delete mode 100644 web/features/deployments/deploy-drawer/state/index.ts delete mode 100644 web/features/deployments/deploy-drawer/state/release-options.ts delete mode 100644 web/features/deployments/deploy-drawer/ui/form-sections.tsx delete mode 100644 web/features/deployments/deploy-drawer/ui/form-skeleton.tsx delete mode 100644 web/features/deployments/deploy-drawer/ui/form.tsx delete mode 100644 web/features/deployments/deploy-drawer/ui/select.tsx delete mode 100644 web/features/deployments/deploy-drawer/ui/status-badge.tsx delete mode 100644 web/features/deployments/deployment-actions/README.md delete mode 100644 web/features/deployments/deployment-actions/__tests__/delete-dialog.spec.tsx delete mode 100644 web/features/deployments/deployment-actions/__tests__/edit-dialog.spec.tsx delete mode 100644 web/features/deployments/deployment-actions/delete-dialog.tsx delete mode 100644 web/features/deployments/deployment-actions/edit-dialog.tsx delete mode 100644 web/features/deployments/deployment-actions/index.tsx delete mode 100644 web/features/deployments/deployment-actions/state.ts delete mode 100644 web/features/deployments/deployment-actions/types.ts delete mode 100644 web/features/deployments/detail/README.md delete mode 100644 web/features/deployments/detail/__tests__/index.spec.tsx delete mode 100644 web/features/deployments/detail/__tests__/state.spec.ts delete mode 100644 web/features/deployments/detail/access/README.md delete mode 100644 web/features/deployments/detail/access/__tests__/state.spec.ts delete mode 100644 web/features/deployments/detail/access/channels/README.md delete mode 100644 web/features/deployments/detail/access/channels/section.tsx delete mode 100644 web/features/deployments/detail/access/channels/url.ts delete mode 100644 web/features/deployments/detail/access/index.tsx delete mode 100644 web/features/deployments/detail/access/permissions/README.md delete mode 100644 web/features/deployments/detail/access/permissions/__tests__/access-control-dialog.spec.tsx delete mode 100644 web/features/deployments/detail/access/permissions/__tests__/access-policy.spec.ts delete mode 100644 web/features/deployments/detail/access/permissions/__tests__/permissions.spec.tsx delete mode 100644 web/features/deployments/detail/access/permissions/access-control-dialog.tsx delete mode 100644 web/features/deployments/detail/access/permissions/access-policy.ts delete mode 100644 web/features/deployments/detail/access/permissions/access-subject-selector/README.md delete mode 100644 web/features/deployments/detail/access/permissions/access-subject-selector/__tests__/add-button.spec.tsx delete mode 100644 web/features/deployments/detail/access/permissions/access-subject-selector/add-button.tsx delete mode 100644 web/features/deployments/detail/access/permissions/access-subject-selector/selection-list.tsx delete mode 100644 web/features/deployments/detail/access/permissions/access-subject-selector/subject-options.tsx delete mode 100644 web/features/deployments/detail/access/permissions/access-subject-selector/types.ts delete mode 100644 web/features/deployments/detail/access/permissions/access-subject-selector/utils.ts delete mode 100644 web/features/deployments/detail/access/permissions/environment-permission-row.tsx delete mode 100644 web/features/deployments/detail/access/permissions/permission-summary-button.tsx delete mode 100644 web/features/deployments/detail/access/permissions/section.tsx delete mode 100644 web/features/deployments/detail/access/state.ts delete mode 100644 web/features/deployments/detail/api-tokens/README.md delete mode 100644 web/features/deployments/detail/api-tokens/__tests__/state.spec.ts delete mode 100644 web/features/deployments/detail/api-tokens/api-keys/README.md delete mode 100644 web/features/deployments/detail/api-tokens/api-keys/__tests__/api-key-generate-menu.spec.tsx delete mode 100644 web/features/deployments/detail/api-tokens/api-keys/api-key-generate-menu.tsx delete mode 100644 web/features/deployments/detail/api-tokens/api-keys/api-key-list.tsx delete mode 100644 web/features/deployments/detail/api-tokens/api-keys/api-token-name.ts delete mode 100644 web/features/deployments/detail/api-tokens/api-keys/created-token-dialog.tsx delete mode 100644 web/features/deployments/detail/api-tokens/api-token-management/README.md delete mode 100644 web/features/deployments/detail/api-tokens/api-token-management/section.tsx delete mode 100644 web/features/deployments/detail/api-tokens/api-token-management/skeleton.tsx delete mode 100644 web/features/deployments/detail/api-tokens/developer-api-header-switch.tsx delete mode 100644 web/features/deployments/detail/api-tokens/docs/README.md delete mode 100644 web/features/deployments/detail/api-tokens/docs/docs-drawer.tsx delete mode 100644 web/features/deployments/detail/api-tokens/index.tsx delete mode 100644 web/features/deployments/detail/api-tokens/state.ts delete mode 100644 web/features/deployments/detail/api-tokens/table-styles.ts delete mode 100644 web/features/deployments/detail/deployment-sidebar.tsx delete mode 100644 web/features/deployments/detail/detail-sidebar.tsx delete mode 100644 web/features/deployments/detail/index.tsx delete mode 100644 web/features/deployments/detail/instances/README.md delete mode 100644 web/features/deployments/detail/instances/environment-list/README.md delete mode 100644 web/features/deployments/detail/instances/environment-list/deployment-environment-list.tsx delete mode 100644 web/features/deployments/detail/instances/environment-list/deployment-status-summary.tsx delete mode 100644 web/features/deployments/detail/instances/header-actions/README.md delete mode 100644 web/features/deployments/detail/instances/header-actions/new-deployment-button.tsx delete mode 100644 web/features/deployments/detail/instances/index.tsx delete mode 100644 web/features/deployments/detail/instances/row-actions/README.md delete mode 100644 web/features/deployments/detail/instances/row-actions/__tests__/deployment-row-actions-menu.spec.tsx delete mode 100644 web/features/deployments/detail/instances/row-actions/deployment-error-dialog.tsx delete mode 100644 web/features/deployments/detail/instances/row-actions/deployment-row-actions-menu.tsx delete mode 100644 web/features/deployments/detail/instances/row-actions/deployment-row-actions.tsx delete mode 100644 web/features/deployments/detail/instances/row-actions/undeploy-deployment-dialog.tsx delete mode 100644 web/features/deployments/detail/instances/table-styles.ts delete mode 100644 web/features/deployments/detail/overview/README.md delete mode 100644 web/features/deployments/detail/overview/__tests__/state.spec.ts delete mode 100644 web/features/deployments/detail/overview/access-summary/README.md delete mode 100644 web/features/deployments/detail/overview/access-summary/access-status-section.tsx delete mode 100644 web/features/deployments/detail/overview/environment-status/README.md delete mode 100644 web/features/deployments/detail/overview/environment-status/__tests__/environment-tile-utils.spec.ts delete mode 100644 web/features/deployments/detail/overview/environment-status/environment-strip.tsx delete mode 100644 web/features/deployments/detail/overview/environment-status/environment-tile-utils.ts delete mode 100644 web/features/deployments/detail/overview/environment-status/environment-tile.tsx delete mode 100644 web/features/deployments/detail/overview/environment-status/overview-drift.ts delete mode 100644 web/features/deployments/detail/overview/index.tsx delete mode 100644 web/features/deployments/detail/overview/release-summary/README.md delete mode 100644 web/features/deployments/detail/overview/release-summary/release-hero.tsx delete mode 100644 web/features/deployments/detail/overview/state.ts delete mode 100644 web/features/deployments/detail/releases/README.md delete mode 100644 web/features/deployments/detail/releases/__tests__/state.spec.ts delete mode 100644 web/features/deployments/detail/releases/index.tsx delete mode 100644 web/features/deployments/detail/releases/release-actions/README.md delete mode 100644 web/features/deployments/detail/releases/release-actions/__tests__/deploy-release-menu-utils.spec.ts delete mode 100644 web/features/deployments/detail/releases/release-actions/__tests__/deploy-release-menu.spec.tsx delete mode 100644 web/features/deployments/detail/releases/release-actions/__tests__/state.spec.ts delete mode 100644 web/features/deployments/detail/releases/release-actions/delete-release-dialog.tsx delete mode 100644 web/features/deployments/detail/releases/release-actions/deploy-release-menu-utils.ts delete mode 100644 web/features/deployments/detail/releases/release-actions/deploy-release-menu.tsx delete mode 100644 web/features/deployments/detail/releases/release-actions/edit-release-dialog.tsx delete mode 100644 web/features/deployments/detail/releases/release-actions/release-dsl-export.ts delete mode 100644 web/features/deployments/detail/releases/release-actions/release-dsl.ts delete mode 100644 web/features/deployments/detail/releases/release-actions/state.ts delete mode 100644 web/features/deployments/detail/releases/release-history/README.md delete mode 100644 web/features/deployments/detail/releases/release-history/deployed-to-badge.tsx delete mode 100644 web/features/deployments/detail/releases/release-history/release-deployments.ts delete mode 100644 web/features/deployments/detail/releases/release-history/release-history-deployments.tsx delete mode 100644 web/features/deployments/detail/releases/release-history/release-history-rows.tsx delete mode 100644 web/features/deployments/detail/releases/release-history/release-history-table-skeleton.tsx delete mode 100644 web/features/deployments/detail/releases/release-history/release-history-table.tsx delete mode 100644 web/features/deployments/detail/releases/release-history/release-history-types.ts delete mode 100644 web/features/deployments/detail/releases/release-history/table-styles.ts delete mode 100644 web/features/deployments/detail/releases/state.ts delete mode 100644 web/features/deployments/detail/state.ts delete mode 100644 web/features/deployments/detail/tabs.ts delete mode 100644 web/features/deployments/list/README.md delete mode 100644 web/features/deployments/list/index.tsx delete mode 100644 web/features/deployments/list/state/index.ts delete mode 100644 web/features/deployments/list/ui/__tests__/environment-filter.spec.tsx delete mode 100644 web/features/deployments/list/ui/create-deployment-button.tsx delete mode 100644 web/features/deployments/list/ui/environment-filter.tsx delete mode 100644 web/features/deployments/list/ui/instance-card-sections.tsx delete mode 100644 web/features/deployments/list/ui/instance-card-utils.ts delete mode 100644 web/features/deployments/list/ui/instance-card.tsx delete mode 100644 web/features/deployments/list/ui/shell.tsx delete mode 100644 web/features/deployments/route-state.ts delete mode 100644 web/features/deployments/shared/README.md delete mode 100644 web/features/deployments/shared/components/__tests__/env-var-bindings-utils.spec.ts delete mode 100644 web/features/deployments/shared/components/__tests__/runtime-credential-bindings-utils.spec.ts delete mode 100644 web/features/deployments/shared/components/__tests__/title-tooltip.spec.tsx delete mode 100644 web/features/deployments/shared/components/detail-table-styles.ts delete mode 100644 web/features/deployments/shared/components/detail-table.tsx delete mode 100644 web/features/deployments/shared/components/empty-state.tsx delete mode 100644 web/features/deployments/shared/components/endpoint.tsx delete mode 100644 web/features/deployments/shared/components/env-var-bindings-utils.ts delete mode 100644 web/features/deployments/shared/components/env-var-bindings.tsx delete mode 100644 web/features/deployments/shared/components/runtime-credential-bindings-utils.ts delete mode 100644 web/features/deployments/shared/components/runtime-credential-bindings.tsx delete mode 100644 web/features/deployments/shared/components/section.tsx delete mode 100644 web/features/deployments/shared/components/title-tooltip.tsx delete mode 100644 web/features/deployments/shared/components/unsupported-dsl-nodes-alert.tsx delete mode 100644 web/features/deployments/shared/domain/__tests__/dsl.spec.ts delete mode 100644 web/features/deployments/shared/domain/dsl.ts delete mode 100644 web/features/deployments/shared/domain/error.ts delete mode 100644 web/features/deployments/shared/domain/feature-flags.ts delete mode 100644 web/features/deployments/shared/domain/idempotency.ts delete mode 100644 web/features/deployments/shared/domain/release-action.ts delete mode 100644 web/features/deployments/shared/domain/release.ts delete mode 100644 web/features/deployments/shared/domain/runtime-status.ts delete mode 100644 web/features/deployments/shared/ui/deployment-status-badge.tsx delete mode 100644 web/features/deployments/shared/ui/deployment-status-style.ts delete mode 100644 web/service/access-control/use-access-subjects.ts diff --git a/knip.config.ts b/knip.config.ts index 887534fa1ab..63e60aeab63 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -10,8 +10,6 @@ const config: KnipConfig = { workspaces: { web: { entry: [ - // todo: Keep the deploy drawer analyzed while the deployments routes are disabled. Delete this entry when relative files are deleted. - 'features/deployments/deploy-drawer/index.tsx!', 'scripts/**/*.{js,ts,mjs}', 'bin/**/*.{js,ts,mjs}', 'tsslint.config.ts', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2104938791..2334544b137 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1242,9 +1242,6 @@ importers: '@tailwindcss/typography': specifier: 'catalog:' version: 0.5.20(tailwindcss@4.3.3) - '@tanstack/form-core': - specifier: 'catalog:' - version: 1.33.3 '@tanstack/query-core': specifier: 'catalog:' version: 5.101.4 @@ -1341,9 +1338,6 @@ importers: jotai-scope: specifier: 'catalog:' version: 0.11.0(jotai@2.20.2(@babel/core@7.29.7(supports-color@11.0.0))(@babel/template@7.29.7)(@types/react@19.2.18)(react@19.2.8))(react@19.2.8) - jotai-tanstack-form: - specifier: workspace:* - version: link:../packages/jotai-tanstack-form jotai-tanstack-query: specifier: 'catalog:' version: 0.11.0(@tanstack/query-core@5.101.4)(@tanstack/react-query@5.101.4(react@19.2.8))(jotai@2.20.2(@babel/core@7.29.7(supports-color@11.0.0))(@babel/template@7.29.7)(@types/react@19.2.18)(react@19.2.8))(react@19.2.8) diff --git a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/access/page.tsx b/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/access/page.tsx deleted file mode 100644 index 20a44044ef7..00000000000 --- a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/access/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentDetailSidebarSlot } from '../sidebar-page' - -export default function DeploymentAccessDetailSidebarSlot() { - return -} diff --git a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/api-tokens/page.tsx b/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/api-tokens/page.tsx deleted file mode 100644 index 806e69b471a..00000000000 --- a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/api-tokens/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentDetailSidebarSlot } from '../sidebar-page' - -export default function DeploymentApiTokensDetailSidebarSlot() { - return -} diff --git a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/instances/page.tsx b/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/instances/page.tsx deleted file mode 100644 index 0d17b43799c..00000000000 --- a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/instances/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentDetailSidebarSlot } from '../sidebar-page' - -export default function DeploymentInstancesDetailSidebarSlot() { - return -} diff --git a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/overview/page.tsx b/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/overview/page.tsx deleted file mode 100644 index dbbcc7a93fa..00000000000 --- a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/overview/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentDetailSidebarSlot } from '../sidebar-page' - -export default function DeploymentOverviewDetailSidebarSlot() { - return -} diff --git a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/page.tsx b/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/page.tsx deleted file mode 100644 index 9672050cee0..00000000000 --- a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentDetailSidebarSlot } from './sidebar-page' - -export default function DeploymentDetailPageSidebarSlot() { - return -} diff --git a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/releases/page.tsx b/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/releases/page.tsx deleted file mode 100644 index 1e9c907a376..00000000000 --- a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/releases/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentDetailSidebarSlot } from '../sidebar-page' - -export default function DeploymentReleasesDetailSidebarSlot() { - return -} diff --git a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/sidebar-page.tsx b/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/sidebar-page.tsx deleted file mode 100644 index 720cef52a6b..00000000000 --- a/web/app/(commonLayout)/@detailSidebar/deployments/[appInstanceId]/sidebar-page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentDetailSidebar } from '@/features/deployments/detail/detail-sidebar' - -export function DeploymentDetailSidebarSlot() { - return -} diff --git a/web/app/(commonLayout)/@detailSidebar/deployments/create/page.tsx b/web/app/(commonLayout)/@detailSidebar/deployments/create/page.tsx deleted file mode 100644 index f552d57c415..00000000000 --- a/web/app/(commonLayout)/@detailSidebar/deployments/create/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function DeploymentCreateDetailSidebarSlot() { - return null -} diff --git a/web/app/(commonLayout)/deployments/[appInstanceId]/access/page.tsx b/web/app/(commonLayout)/deployments/[appInstanceId]/access/page.tsx deleted file mode 100644 index eff309f8e85..00000000000 --- a/web/app/(commonLayout)/deployments/[appInstanceId]/access/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentAccess } from '@/features/deployments/detail/access' - -export default function InstanceDetailAccessPage() { - return -} diff --git a/web/app/(commonLayout)/deployments/[appInstanceId]/api-tokens/page.tsx b/web/app/(commonLayout)/deployments/[appInstanceId]/api-tokens/page.tsx deleted file mode 100644 index 1adba300386..00000000000 --- a/web/app/(commonLayout)/deployments/[appInstanceId]/api-tokens/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentApiTokens } from '@/features/deployments/detail/api-tokens' - -export default function InstanceDetailApiTokensPage() { - return -} diff --git a/web/app/(commonLayout)/deployments/[appInstanceId]/instances/page.tsx b/web/app/(commonLayout)/deployments/[appInstanceId]/instances/page.tsx deleted file mode 100644 index 0c4cbafc18e..00000000000 --- a/web/app/(commonLayout)/deployments/[appInstanceId]/instances/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentInstances } from '@/features/deployments/detail/instances' - -export default function InstanceDetailInstancesPage() { - return -} diff --git a/web/app/(commonLayout)/deployments/[appInstanceId]/layout.tsx b/web/app/(commonLayout)/deployments/[appInstanceId]/layout.tsx deleted file mode 100644 index a6d2dfacd61..00000000000 --- a/web/app/(commonLayout)/deployments/[appInstanceId]/layout.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import type { ReactNode } from 'react' -import { InstanceDetail } from '@/features/deployments/detail' - -export default function InstanceDetailLayout({ children }: { children: ReactNode }) { - return {children} -} diff --git a/web/app/(commonLayout)/deployments/[appInstanceId]/overview/page.tsx b/web/app/(commonLayout)/deployments/[appInstanceId]/overview/page.tsx deleted file mode 100644 index 2ba3deb2202..00000000000 --- a/web/app/(commonLayout)/deployments/[appInstanceId]/overview/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentOverview } from '@/features/deployments/detail/overview' - -export default function InstanceDetailOverviewPage() { - return -} diff --git a/web/app/(commonLayout)/deployments/[appInstanceId]/page.tsx b/web/app/(commonLayout)/deployments/[appInstanceId]/page.tsx deleted file mode 100644 index 95530c5e4ff..00000000000 --- a/web/app/(commonLayout)/deployments/[appInstanceId]/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { redirect } from '@/next/navigation' - -export default async function InstanceDetailPage({ - params, -}: { - params: Promise<{ appInstanceId: string }> -}) { - const { appInstanceId } = await params - redirect(`/deployments/${appInstanceId}/overview`) -} diff --git a/web/app/(commonLayout)/deployments/[appInstanceId]/releases/page.tsx b/web/app/(commonLayout)/deployments/[appInstanceId]/releases/page.tsx deleted file mode 100644 index 29c928a8a8b..00000000000 --- a/web/app/(commonLayout)/deployments/[appInstanceId]/releases/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { DeploymentReleases } from '@/features/deployments/detail/releases' - -export default function InstanceDetailReleasesPage() { - return -} diff --git a/web/app/(commonLayout)/deployments/__tests__/layout.spec.tsx b/web/app/(commonLayout)/deployments/__tests__/layout.spec.tsx deleted file mode 100644 index f84508352e8..00000000000 --- a/web/app/(commonLayout)/deployments/__tests__/layout.spec.tsx +++ /dev/null @@ -1,23 +0,0 @@ -const mocks = vi.hoisted(() => ({ - notFound: vi.fn(() => { - throw new Error('NEXT_NOT_FOUND') - }), -})) - -vi.mock('@/next/navigation', () => ({ - notFound: () => mocks.notFound(), -})) - -describe('DeploymentsLayout', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - - it('should always trigger notFound', async () => { - const { default: DeploymentsLayout } = await import('../layout') - - expect(() => DeploymentsLayout()).toThrow('NEXT_NOT_FOUND') - - expect(mocks.notFound).toHaveBeenCalledTimes(1) - }) -}) diff --git a/web/app/(commonLayout)/deployments/create/page.tsx b/web/app/(commonLayout)/deployments/create/page.tsx deleted file mode 100644 index 0be7206cc62..00000000000 --- a/web/app/(commonLayout)/deployments/create/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -'use client' - -import { useTranslation } from 'react-i18next' -import { CreateDeploymentGuide } from '@/features/deployments/create-guide' -import useDocumentTitle from '@/hooks/use-document-title' - -export default function CreateDeploymentPage() { - const { t } = useTranslation('deployments') - useDocumentTitle(t(($) => $['documentTitle.create'])) - - return -} diff --git a/web/app/(commonLayout)/deployments/layout.tsx b/web/app/(commonLayout)/deployments/layout.tsx deleted file mode 100644 index 2070a44ff99..00000000000 --- a/web/app/(commonLayout)/deployments/layout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { notFound } from '@/next/navigation' - -export default function DeploymentsLayout() { - notFound() -} diff --git a/web/app/(commonLayout)/deployments/page.tsx b/web/app/(commonLayout)/deployments/page.tsx deleted file mode 100644 index 0dfa2117048..00000000000 --- a/web/app/(commonLayout)/deployments/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -'use client' -import { useTranslation } from 'react-i18next' -import { DeploymentsList } from '@/features/deployments/list' -import useDocumentTitle from '@/hooks/use-document-title' - -export default function DeploymentsPage() { - const { t } = useTranslation('deployments') - useDocumentTitle(t(($) => $['documentTitle.list'])) - return -} diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index 60e56f80f43..c8388ad6282 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -2,7 +2,6 @@ import * as React from 'react' import Zendesk from '@/app/components/base/zendesk' import MaintenanceNotice from '@/app/components/header/maintenance-notice' import MainNavLayout from '@/app/components/main-nav/layout' -import { NextRouteStateBridge } from '@/app/components/next-route-state' import { CommonLayoutGlobalMounts } from './global-mounts' import { ConsoleContextProviders, ConsoleRuntimeProviders } from './providers' @@ -16,15 +15,13 @@ export default async function Layout({ return ( - -
- - - {children} - - -
-
+
+ + + {children} + + +
diff --git a/web/app/components/main-nav/__tests__/index.spec.tsx b/web/app/components/main-nav/__tests__/index.spec.tsx index 57d5f2e5fe1..9bbc932eb25 100644 --- a/web/app/components/main-nav/__tests__/index.spec.tsx +++ b/web/app/components/main-nav/__tests__/index.spec.tsx @@ -720,12 +720,6 @@ describe('MainNav', () => { ).not.toBeInTheDocument() }) - it('hides deployments in primary navigation when app deploy is enabled', () => { - renderMainNav({ branding: { enabled: false }, enable_app_deploy: true }) - - expect(screen.queryByRole('link', { name: /common.menus.deployments/ })).not.toBeInTheDocument() - }) - it('orders the Step-by-step Tour before the account and help actions', async () => { localStorage.setItem(STEP_BY_STEP_TOUR_SHELL_MODE_STORAGE_KEY, 'collapsed') @@ -811,7 +805,6 @@ describe('MainNav', () => { 'href', '/marketplace', ) - expect(screen.queryByRole('link', { name: /common.menus.deployments/ })).not.toBeInTheDocument() expect( screen.queryByRole('button', { name: 'explore.sidebar.webApps' }), ).not.toBeInTheDocument() @@ -831,14 +824,13 @@ describe('MainNav', () => { workspacePermissionKeys: ['app_library.access', 'tool.manage', 'agent.manage'], } - renderMainNav({ branding: { enabled: false }, enable_app_deploy: true }) + renderMainNav({ branding: { enabled: false } }) expect(screen.getByRole('link', { name: /common.mainNav.home/ })).toBeInTheDocument() expect(screen.getByRole('link', { name: /common.menus.apps/ })).toBeInTheDocument() expect(screen.getByRole('link', { name: /Agents/ })).toBeInTheDocument() expect(screen.getByRole('link', { name: /common.menus.datasets/ })).toBeInTheDocument() expect(screen.getByRole('link', { name: /common.mainNav.integrations/ })).toBeInTheDocument() - expect(screen.queryByRole('link', { name: /common.menus.deployments/ })).not.toBeInTheDocument() expect(screen.getByRole('link', { name: /common.mainNav.marketplace/ })).toBeInTheDocument() }) @@ -892,24 +884,6 @@ describe('MainNav', () => { expect(screen.getByRole('link', { name: /common.menus.apps/ })).toHaveAttribute('href', '/apps') }) - it.each(['/deployments', '/deployments/create'])( - 'keeps global navigation on deployment collection route %s', - (pathname) => { - mockPathname = pathname - - renderMainNav({ branding: { enabled: false }, enable_app_deploy: true }) - - expect(screen.queryByTestId('deployment-detail-top')).not.toBeInTheDocument() - expect(screen.queryByTestId('deployment-detail-section')).not.toBeInTheDocument() - expect( - screen.getByRole('button', { name: 'common.mainNav.workspace.openMenu' }), - ).toBeInTheDocument() - expect( - screen.queryByRole('link', { name: /common.menus.deployments/ }), - ).not.toBeInTheDocument() - }, - ) - it.each([ '/datasets/create', '/datasets/create-from-pipeline', diff --git a/web/app/components/main-nav/__tests__/layout.spec.tsx b/web/app/components/main-nav/__tests__/layout.spec.tsx index 4937f4e9270..cf93c353bd4 100644 --- a/web/app/components/main-nav/__tests__/layout.spec.tsx +++ b/web/app/components/main-nav/__tests__/layout.spec.tsx @@ -1,6 +1,5 @@ import type { ReactNode } from 'react' import type { Mock } from 'vitest' -import { useSuspenseQuery } from '@tanstack/react-query' import { fireEvent, screen } from '@testing-library/react' import { useStore as useAppStore } from '@/app/components/app/store' import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag' @@ -11,7 +10,6 @@ import MainNavLayout from '../layout' const mockConsoleState = vi.hoisted(() => ({ current: { isCurrentWorkspaceDatasetOperator: false, - isCurrentWorkspaceEditor: true, }, })) @@ -24,14 +22,6 @@ vi.mock('@/app/components/header/header-wrapper', () => ({
{children}
), })) -vi.mock('@tanstack/react-query', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - useSuspenseQuery: vi.fn(), - } -}) - vi.mock('@/context/workspace-state', async () => { const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture') return createWorkspaceStateModuleMock(() => mockConsoleState.current) @@ -65,13 +55,7 @@ describe('MainNavLayout', () => { ;(usePathname as Mock).mockReturnValue('/apps') mockConsoleState.current = { isCurrentWorkspaceDatasetOperator: false, - isCurrentWorkspaceEditor: true, } - ;(useSuspenseQuery as Mock).mockReturnValue({ - data: { - enable_app_deploy: true, - }, - }) ;(isAgentV2Enabled as Mock).mockReturnValue(true) }) @@ -196,64 +180,29 @@ describe('MainNavLayout', () => { expect(screen.getByRole('main')).toHaveTextContent('new knowledge detail') }) - it.each([ - '/datasets/create', - '/datasets/new/create', - '/datasets/dataset-1/documents/create', - '/deployments/create', - ])('keeps the global main nav on collection and creation route %s', (pathname) => { - ;(usePathname as Mock).mockReturnValue(pathname) + it.each(['/datasets/create', '/datasets/new/create', '/datasets/dataset-1/documents/create'])( + 'keeps the global main nav on collection and creation route %s', + (pathname) => { + ;(usePathname as Mock).mockReturnValue(pathname) - render( - Detail sidebar}> -
content
-
, - ) + render( + Detail sidebar}> +
content
+
, + ) - expect(screen.getByTestId('main-nav')).toBeInTheDocument() - expect(screen.queryByRole('complementary', { name: 'Detail sidebar' })).not.toBeInTheDocument() - }) + expect(screen.getByTestId('main-nav')).toBeInTheDocument() + expect( + screen.queryByRole('complementary', { name: 'Detail sidebar' }), + ).not.toBeInTheDocument() + }, + ) - it.each([ - { - label: 'agent detail route for dataset operators', - pathname: '/agents/agent-1/configure', - consoleState: { - isCurrentWorkspaceDatasetOperator: true, - isCurrentWorkspaceEditor: true, - }, - systemFeatures: { - enable_app_deploy: true, - }, - }, - { - label: 'deployment detail route for non-editor workspaces', - pathname: '/deployments/app-instance-1/overview', - consoleState: { - isCurrentWorkspaceDatasetOperator: false, - isCurrentWorkspaceEditor: false, - }, - systemFeatures: { - enable_app_deploy: true, - }, - }, - { - label: 'deployment detail route when deployment is disabled', - pathname: '/deployments/app-instance-1/overview', - consoleState: { - isCurrentWorkspaceDatasetOperator: false, - isCurrentWorkspaceEditor: true, - }, - systemFeatures: { - enable_app_deploy: false, - }, - }, - ])('keeps the global main nav on $label', ({ pathname, consoleState, systemFeatures }) => { - ;(usePathname as Mock).mockReturnValue(pathname) - mockConsoleState.current = consoleState - ;(useSuspenseQuery as Mock).mockReturnValue({ - data: systemFeatures, - }) + it('keeps the global main nav on agent detail routes for dataset operators', () => { + ;(usePathname as Mock).mockReturnValue('/agents/agent-1/configure') + mockConsoleState.current = { + isCurrentWorkspaceDatasetOperator: true, + } render( Detail sidebar}> diff --git a/web/app/components/main-nav/index.tsx b/web/app/components/main-nav/index.tsx index 4d82690e400..94e49570e54 100644 --- a/web/app/components/main-nav/index.tsx +++ b/web/app/components/main-nav/index.tsx @@ -10,10 +10,7 @@ import Badge from '@/app/components/base/badge' import { DifyLogo } from '@/app/components/base/logo/dify-logo' import EnvNav from '@/app/components/header/env-nav' import StepByStepTourMount from '@/app/components/step-by-step-tour/mount' -import { - isCurrentWorkspaceDatasetOperatorAtom, - isCurrentWorkspaceEditorAtom, -} from '@/context/workspace-state' +import { isCurrentWorkspaceDatasetOperatorAtom } from '@/context/workspace-state' import { userProfileQueryOptions } from '@/features/account-profile/client' import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag' import { useCanManageAgents } from '@/features/agent-v2/permissions' @@ -34,7 +31,6 @@ export function MainNav({ className }: MainNavProps) { const { t } = useTranslation() const pathname = usePathname() const isCurrentWorkspaceDatasetOperator = useAtomValue(isCurrentWorkspaceDatasetOperatorAtom) - const isCurrentWorkspaceEditor = useAtomValue(isCurrentWorkspaceEditorAtom) const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions()) const { data: currentEnv } = useSuspenseQuery({ ...userProfileQueryOptions(), @@ -43,7 +39,6 @@ export function MainNav({ className }: MainNavProps) { const agentV2Enabled = isAgentV2Enabled() const canManageAgents = useCanManageAgents() const showEnvTag = currentEnv === 'TESTING' || currentEnv === 'DEVELOPMENT' - const canUseAppDeploy = isCurrentWorkspaceEditor && systemFeatures.enable_app_deploy const navItems = useMemo( () => @@ -51,7 +46,6 @@ export function MainNav({ className }: MainNavProps) { isMainNavRouteVisible(route, { agentV2Enabled, canManageAgents, - canUseAppDeploy, isCurrentWorkspaceDatasetOperator, marketplaceEnabled: systemFeatures.enable_marketplace, }), @@ -65,7 +59,6 @@ export function MainNav({ className }: MainNavProps) { [ agentV2Enabled, canManageAgents, - canUseAppDeploy, isCurrentWorkspaceDatasetOperator, systemFeatures.enable_marketplace, t, diff --git a/web/app/components/main-nav/layout.tsx b/web/app/components/main-nav/layout.tsx index e6b79fe335b..0c82a31fb57 100644 --- a/web/app/components/main-nav/layout.tsx +++ b/web/app/components/main-nav/layout.tsx @@ -1,18 +1,13 @@ 'use client' import type { ReactNode } from 'react' -import { useSuspenseQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import { useStore as useAppStore } from '@/app/components/app/store' -import { - isCurrentWorkspaceDatasetOperatorAtom, - isCurrentWorkspaceEditorAtom, -} from '@/context/workspace-state' +import { isCurrentWorkspaceDatasetOperatorAtom } from '@/context/workspace-state' import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag' -import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { usePathname } from '@/next/navigation' import { MainNav } from '.' import { shouldHideMainNavigation, shouldUseDetailSidebar } from './routes' @@ -45,12 +40,9 @@ const MainNavLayout = ({ children, detailSidebar }: MainNavLayoutProps) => { const { t } = useTranslation('common') const pathname = usePathname() const isCurrentWorkspaceDatasetOperator = useAtomValue(isCurrentWorkspaceDatasetOperatorAtom) - const isCurrentWorkspaceEditor = useAtomValue(isCurrentWorkspaceEditorAtom) - const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions()) const hideMainNavigation = shouldHideMainNavigation(pathname) const useDetailSidebar = shouldUseDetailSidebar(pathname, { agentV2Enabled: isAgentV2Enabled(), - canUseAppDeploy: isCurrentWorkspaceEditor && systemFeatures.enable_app_deploy, isCurrentWorkspaceDatasetOperator, }) diff --git a/web/app/components/main-nav/routes.ts b/web/app/components/main-nav/routes.ts index 22062086106..7d308a1cdea 100644 --- a/web/app/components/main-nav/routes.ts +++ b/web/app/components/main-nav/routes.ts @@ -4,7 +4,6 @@ type MainNavRouteVisibility = (options: MainNavRouteVisibilityOptions) => boolea const DATASET_COLLECTION_ROUTES = new Set(['create', 'create-from-pipeline', 'connect']) const DATASET_DOCUMENT_CREATION_ROUTES = new Set(['create', 'create-from-pipeline']) -const DEPLOYMENT_COLLECTION_ROUTES = new Set(['create']) export type MainNavRouteConfig = { key: string @@ -19,14 +18,13 @@ export type MainNavRouteConfig = { export type MainNavRouteVisibilityOptions = { agentV2Enabled: boolean canManageAgents: boolean - canUseAppDeploy: boolean isCurrentWorkspaceDatasetOperator: boolean marketplaceEnabled: boolean } export type DetailSidebarVisibilityOptions = Pick< MainNavRouteVisibilityOptions, - 'agentV2Enabled' | 'canUseAppDeploy' | 'isCurrentWorkspaceDatasetOperator' + 'agentV2Enabled' | 'isCurrentWorkspaceDatasetOperator' > const VISIBLE_TO_ALL: MainNavRouteVisibility = () => true @@ -147,14 +145,6 @@ function isAgentDetailPathname(pathname: string) { return section === 'agents' && !!agentId } -function isDeploymentDetailPathname(pathname: string) { - const [section, appInstanceId] = pathname.split('/').filter(Boolean) - - return ( - section === 'deployments' && !!appInstanceId && !DEPLOYMENT_COLLECTION_ROUTES.has(appInstanceId) - ) -} - function isSnippetDetailPathname(pathname: string) { const [section, snippetId] = pathname.split('/').filter(Boolean) @@ -168,7 +158,5 @@ export function shouldUseDetailSidebar(pathname: string, options: DetailSidebarV if (isAppDetailPathname(pathname)) return true - if (options.agentV2Enabled && isAgentDetailPathname(pathname)) return true - - return options.canUseAppDeploy && isDeploymentDetailPathname(pathname) + return options.agentV2Enabled && isAgentDetailPathname(pathname) } diff --git a/web/app/components/next-route-state/atoms.ts b/web/app/components/next-route-state/atoms.ts deleted file mode 100644 index 833380d0b3d..00000000000 --- a/web/app/components/next-route-state/atoms.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { atom } from 'jotai' - -export type NextRouteParams = Record - -type NextRouteState = { - pathname: string - params: NextRouteParams -} - -// Mirrors Next router state. NextRouteStateBridge force-hydrates this atom on -// render so feature atoms can read route state without calling router hooks. -const nextRouteStateAtom = atom({ - pathname: '', - params: {}, -}) - -function normalizedParamEntries(params: NextRouteParams) { - return Object.keys(params) - .sort() - .map((key) => { - const value = params[key] - return [key, Array.isArray(value) ? [...value] : value] as const - }) -} - -function normalizeNextRouteParams(params: NextRouteParams): NextRouteParams { - return Object.fromEntries(normalizedParamEntries(params)) as NextRouteParams -} - -function routeParamsKey(params: NextRouteParams) { - return JSON.stringify(normalizedParamEntries(params)) -} - -export const nextParamsAtom = atom((get) => get(nextRouteStateAtom).params) -export const nextPathnameAtom = atom((get) => get(nextRouteStateAtom).pathname) - -export const setNextRouteStateAtom = atom(null, (get, set, routeState: NextRouteState) => { - const nextParams = normalizeNextRouteParams(routeState.params) - const currentRouteState = get(nextRouteStateAtom) - - if ( - currentRouteState.pathname !== routeState.pathname || - routeParamsKey(currentRouteState.params) !== routeParamsKey(nextParams) - ) { - set(nextRouteStateAtom, { - pathname: routeState.pathname, - params: nextParams, - }) - } -}) diff --git a/web/app/components/next-route-state/index.tsx b/web/app/components/next-route-state/index.tsx deleted file mode 100644 index 0293b653038..00000000000 --- a/web/app/components/next-route-state/index.tsx +++ /dev/null @@ -1,27 +0,0 @@ -'use client' - -import type { ReactNode } from 'react' -import type { NextRouteParams } from './atoms' -import { useHydrateAtoms } from 'jotai/utils' -import { useParams, usePathname } from '@/next/navigation' -import { setNextRouteStateAtom } from './atoms' - -export function NextRouteStateBridge({ children }: { children: ReactNode }) { - const pathname = usePathname() - const params = useParams() - - useHydrateAtoms( - [ - [ - setNextRouteStateAtom, - { - pathname, - params, - }, - ], - ] as const, - { dangerouslyForceHydrate: true }, - ) - - return children -} diff --git a/web/context/workspace-state.ts b/web/context/workspace-state.ts index 5bc5432ae41..8c9460286dd 100644 --- a/web/context/workspace-state.ts +++ b/web/context/workspace-state.ts @@ -32,6 +32,11 @@ export const isCurrentWorkspaceManagerAtom = atom((get) => { return get(workspaceRoleFlagsAtom).isCurrentWorkspaceManager }) +/** + * Retained for future permission checks when RBAC is disabled. + * + * @public + */ export const isCurrentWorkspaceEditorAtom = atom((get) => { return get(workspaceRoleFlagsAtom).isCurrentWorkspaceEditor }) diff --git a/web/features/deployments/README.md b/web/features/deployments/README.md deleted file mode 100644 index c226bfb2fdb..00000000000 --- a/web/features/deployments/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# Deployments - -Deployment app instance, release, runtime target, access, and developer API feature modules. - -## Internal Module Dependency Graph - -The graph shows product module dependencies inside `web/features/deployments`. It omits route-state plumbing and shared support modules. - -```mermaid -flowchart TD - list["list"] - detail["detail"] - createGuide["create-guide"] - createRelease["create-release"] - deployDrawer["deploy-drawer"] - deploymentActions["deployment-actions"] - - detailOverview["detail/overview"] - detailInstances["detail/instances"] - detailAccess["detail/access"] - detailApiTokens["detail/api-tokens"] - detailReleases["detail/releases"] - - accessChannels["detail/access/channels"] - accessPermissions["detail/access/permissions"] - accessSubjectSelector["detail/access/permissions/access-subject-selector"] - apiTokenManagement["detail/api-tokens/api-token-management"] - apiKeys["detail/api-tokens/api-keys"] - apiDocs["detail/api-tokens/docs"] - instanceEnvironmentList["detail/instances/environment-list"] - instanceHeaderActions["detail/instances/header-actions"] - instanceRowActions["detail/instances/row-actions"] - overviewAccessSummary["detail/overview/access-summary"] - overviewEnvironmentStatus["detail/overview/environment-status"] - overviewReleaseSummary["detail/overview/release-summary"] - releaseActions["detail/releases/release-actions"] - releaseHistory["detail/releases/release-history"] - - list --> createGuide - list --> createRelease - list --> deployDrawer - list --> deploymentActions - list --> detail - - detail --> createRelease - detail --> deploymentActions - detail --> detailOverview - detail --> detailInstances - detail --> detailAccess - detail --> detailApiTokens - detail --> detailReleases - detail --> instanceHeaderActions - - detailOverview --> overviewAccessSummary - detailOverview --> overviewEnvironmentStatus - detailOverview --> overviewReleaseSummary - - overviewEnvironmentStatus --> deployDrawer - overviewReleaseSummary --> createRelease - - detailInstances --> detail - detailInstances --> instanceEnvironmentList - detailInstances --> instanceHeaderActions - instanceEnvironmentList --> detailInstances - instanceEnvironmentList --> instanceRowActions - instanceHeaderActions --> deployDrawer - instanceHeaderActions --> detail - instanceRowActions --> deployDrawer - - detailAccess --> accessChannels - detailAccess --> accessPermissions - accessChannels --> detailAccess - accessPermissions --> detailAccess - accessPermissions --> accessSubjectSelector - - detailApiTokens --> apiTokenManagement - apiTokenManagement --> detailApiTokens - apiTokenManagement --> apiKeys - apiTokenManagement --> apiDocs - apiKeys --> detailApiTokens - - detailReleases --> releaseHistory - releaseActions --> deployDrawer - releaseHistory --> detailReleases - releaseHistory --> releaseActions -``` - -## Internal Modules - -| Module | Why this module uses it | -| -------------------- | ------------------------------------------------------------- | -| `list` | Owns the deployment app instance list surface. | -| `detail` | Owns the deployment app instance detail shell and route tabs. | -| `create-guide` | Owns the create deployment guide workflow. | -| `create-release` | Owns release creation entry points and dialog state. | -| `deploy-drawer` | Owns deployment target selection and submit workflow state. | -| `deployment-actions` | Owns app instance edit and delete action surfaces. | - -## External Modules - -None. diff --git a/web/features/deployments/create-guide/README.md b/web/features/deployments/create-guide/README.md deleted file mode 100644 index bdc24c614d1..00000000000 --- a/web/features/deployments/create-guide/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Deployment Create Guide - -Guided deployment creation workflow for selecting a source, defining the initial release, and optionally deploying to an environment. - -## Internal Modules - -| Module | Why this module uses it | -| -------- | ------------------------------------------------------------------------------ | -| `shared` | Reuses shared deployment domain rules, UI primitives, hooks, or local helpers. | - -## External Modules - -| Module | Why this module uses it | -| --------------------------------------------------- | -------------------------------------------------------------------------------- | -| `app/components/app/create-from-dsl-modal/uploader` | Reuses the existing DSL file uploader for the import source step. | -| `app/components/base/app-icon` | Renders app icons consistently in source app options. | -| `app/components/base/skeleton` | Reuses the existing skeleton primitive for source and target loading states. | -| `types/app` | Uses app types and mode enums to narrow selectable source apps to workflow apps. | diff --git a/web/features/deployments/create-guide/index.tsx b/web/features/deployments/create-guide/index.tsx deleted file mode 100644 index 929669a2de2..00000000000 --- a/web/features/deployments/create-guide/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -'use client' - -import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog' -import { useTranslation } from 'react-i18next' -import Link from '@/next/link' -import { useRouter } from '@/next/navigation' -import { CreateDeploymentGuideProvider } from './state/provider' -import { CreateDeploymentGuideShell } from './ui/shell' - -export function CreateDeploymentGuide() { - const { t } = useTranslation('deployments') - const router = useRouter() - - return ( - !open && router.push('/deployments')}> - -
- $['createGuide.nav.back'])} - className="absolute top-3 right-3 z-50 flex h-9 w-9 cursor-pointer items-center justify-center rounded-[10px] bg-components-button-tertiary-bg hover:bg-components-button-tertiary-bg-hover" - > -
-
-
- ) -} diff --git a/web/features/deployments/create-guide/link.tsx b/web/features/deployments/create-guide/link.tsx deleted file mode 100644 index 7fd4dc90f08..00000000000 --- a/web/features/deployments/create-guide/link.tsx +++ /dev/null @@ -1,12 +0,0 @@ -'use client' - -import type { ComponentProps } from 'react' -import Link from '@/next/link' - -const createDeploymentGuideHref = '/deployments/create' - -type CreateDeploymentGuideLinkProps = Omit, 'href'> - -export function CreateDeploymentGuideLink(props: CreateDeploymentGuideLinkProps) { - return -} diff --git a/web/features/deployments/create-guide/state/__tests__/index.spec.ts b/web/features/deployments/create-guide/state/__tests__/index.spec.ts deleted file mode 100644 index 4aa7035d039..00000000000 --- a/web/features/deployments/create-guide/state/__tests__/index.spec.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { createQueryAtomTestStore } from '@/test/query-atom' -import { - dslFileAtom, - effectiveMethodAtom, - instanceNameAtom, - methodAtom, - releaseNameAtom, - selectedAppAtom, - stepAtom, -} from '../primitives' -import { - dslDefaultAppNameAtom, - dslReadErrorAtom, - isReadingDslAtom, - sourceAppsQueryAtom, -} from '../source' -import { - continueFromSourceAtom, - selectDslFileAtom, - selectMethodAtom, - sourceCanGoNextAtom, -} from '../workflow' - -type QueryOptions = { - enabled?: boolean - input?: unknown - queryKey?: readonly unknown[] - retry?: boolean -} - -type InfiniteQueryOptions = QueryOptions & { - input?: (pageParam: number) => unknown -} - -const queryOptionsMocks = vi.hoisted(() => ({ - sourceApps: vi.fn(), -})) - -vi.mock('@/service/client', () => ({ - consoleQuery: { - apps: { - get: { - infiniteOptions: (options: InfiniteQueryOptions) => { - queryOptionsMocks.sourceApps(options) - - return { - ...options, - queryKey: ['sourceApps'], - queryFn: async () => ({ data: [], has_more: false, page: 1 }), - } - }, - }, - }, - enterprise: { - appInstanceService: { - listAppInstances: { - infiniteOptions: (options: InfiniteQueryOptions) => ({ - ...options, - queryKey: ['existingInstanceNames'], - queryFn: async () => ({ appInstances: [], pagination: {} }), - }), - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['instanceNameConflict'], - queryFn: async () => ({ appInstances: [] }), - }), - }, - }, - environmentService: { - listEnvironments: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['environments'], - queryFn: async () => ({ environments: [] }), - }), - }, - }, - releaseService: { - computeDeploymentOptions: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['deploymentOptions'], - queryFn: async () => ({ options: { credentialSlots: [], envVarSlots: [] } }), - }), - }, - precheckRelease: { - queryOptions: (options: QueryOptions) => ({ - ...options, - queryKey: ['precheckRelease'], - queryFn: async () => ({ canCreate: false, unsupportedNodes: [] }), - }), - }, - }, - }, - }, -})) - -function workflowDsl() { - return ['app:', ' mode: workflow', ' name: Imported guide'].join('\n') -} - -describe('create deployment guide state', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - - it('should keep the guide on source app mode when DSL import is disabled', () => { - const { store } = createQueryAtomTestStore() - - store.set(selectMethodAtom, 'importDsl') - - expect(store.get(methodAtom)).toBe('bindApp') - expect(store.get(effectiveMethodAtom)).toBe('bindApp') - }) - - it('should keep source app loading enabled if stale state points to DSL import', () => { - const { store } = createQueryAtomTestStore() - - store.set(methodAtom, 'importDsl') - - store.get(sourceAppsQueryAtom) - - expect(store.get(effectiveMethodAtom)).toBe('bindApp') - expect(queryOptionsMocks.sourceApps).toHaveBeenLastCalledWith( - expect.objectContaining({ enabled: true }), - ) - }) - - it('should continue from source app mode and auto-fill unique release metadata', () => { - const { queryClient, store } = createQueryAtomTestStore() - queryClient.setQueryData(['sourceApps'], { - pages: [ - { - data: [ - { - id: 'source-app-1', - name: 'Customer Service', - mode: 'workflow', - }, - ], - }, - ], - pageParams: [1], - }) - queryClient.setQueryData(['precheckRelease'], { - canCreate: true, - unsupportedNodes: [], - }) - queryClient.setQueryData(['deploymentOptions'], { - options: { - credentialSlots: [], - envVarSlots: [], - }, - }) - queryClient.setQueryData(['existingInstanceNames'], { - pages: [ - { - appInstances: [ - { displayName: 'Customer Service' }, - { displayName: 'Customer Service 1' }, - ], - }, - ], - pageParams: [1], - }) - - expect(store.get(sourceCanGoNextAtom)).toBe(true) - - store.set(continueFromSourceAtom, { - defaultDslAppName: 'Imported DSL', - defaultReleaseName: 'Initial Release', - }) - - expect(store.get(selectedAppAtom)).toMatchObject({ - id: 'source-app-1', - name: 'Customer Service', - }) - expect(store.get(instanceNameAtom)).toMatch(/^Customer Service-[a-z]{4}$/) - expect(store.get(releaseNameAtom)).toBe('Initial Release') - expect(store.get(stepAtom)).toBe('release') - }) - - it('should read selected DSL file content through the file content query', () => { - const { queryClient, store } = createQueryAtomTestStore() - const text = vi.fn().mockResolvedValue(workflowDsl()) - const file = new File([], 'workflow.yml', { type: 'text/yaml' }) - Object.defineProperty(file, 'text', { value: text }) - - store.set(selectDslFileAtom, file) - queryClient.setQueryData( - ['createGuideDslFileContent', 1, file, file.name, file.size, file.lastModified], - workflowDsl(), - ) - - expect(text).not.toHaveBeenCalled() - expect(store.get(dslFileAtom)).toBe(file) - expect(store.get(dslDefaultAppNameAtom)).toBe('Imported guide') - expect(store.get(isReadingDslAtom)).toBe(false) - expect(store.get(dslReadErrorAtom)).toBe(false) - }) -}) diff --git a/web/features/deployments/create-guide/state/environment.ts b/web/features/deployments/create-guide/state/environment.ts deleted file mode 100644 index 91792bc28da..00000000000 --- a/web/features/deployments/create-guide/state/environment.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { Environment } from '@dify/contracts/enterprise/types.gen' - -export function environmentMatchesIdentifier(environment: Environment, identifier: string) { - const normalizedIdentifier = identifier.trim() - if (!normalizedIdentifier) return false - - return environment.id === normalizedIdentifier || environment.displayName === normalizedIdentifier -} diff --git a/web/features/deployments/create-guide/state/primitives.ts b/web/features/deployments/create-guide/state/primitives.ts deleted file mode 100644 index 9b6382371c3..00000000000 --- a/web/features/deployments/create-guide/state/primitives.ts +++ /dev/null @@ -1,37 +0,0 @@ -'use client' - -import type { GuideMethod, GuideStep, WorkflowSourceApp } from './types' -import type { EnvVarValues } from '@/features/deployments/shared/components/env-var-bindings' -import type { RuntimeCredentialBindingSelections } from '@/features/deployments/shared/components/runtime-credential-bindings-utils' -import type { UnsupportedDslNode } from '@/features/deployments/shared/domain/error' -import { atom } from 'jotai' -import { deploymentGuideMethod } from './utils' - -export const stepAtom = atom('source') -export const methodAtom = atom('bindApp') -export const effectiveMethodAtom = atom((get) => deploymentGuideMethod(get(methodAtom))) - -export const sourceSearchTextAtom = atom('') -export const selectedAppAtom = atom(undefined) - -export const dslFileAtom = atom(undefined) -export const dslFileReadVersionAtom = atom(0) - -export const instanceNameAtom = atom('') -export const instanceDescriptionAtom = atom('') -export const releaseNameAtom = atom('') -export const releaseDescriptionAtom = atom('') -export const autoFilledInstanceNameAtom = atom('') -export const autoFilledReleaseNameAtom = atom('') - -export const selectedEnvironmentIdAtom = atom('') -export const manualBindingSelectionsAtom = atom({}) -export const envVarValuesAtom = atom({}) - -export const submissionUnsupportedDslNodesAtom = atom([]) -export const isCreatingDeploymentAtom = atom(false) -export const isCreatingReleaseOnlyAtom = atom(false) - -export const isSubmittingDeploymentGuideAtom = atom( - (get) => get(isCreatingDeploymentAtom) || get(isCreatingReleaseOnlyAtom), -) diff --git a/web/features/deployments/create-guide/state/provider.tsx b/web/features/deployments/create-guide/state/provider.tsx deleted file mode 100644 index 3cb9593f139..00000000000 --- a/web/features/deployments/create-guide/state/provider.tsx +++ /dev/null @@ -1,9 +0,0 @@ -'use client' - -import type { ReactNode } from 'react' -import { ScopeProvider } from 'jotai-scope' -import { createDeploymentGuideScopedAtoms } from './scoped' - -export function CreateDeploymentGuideProvider({ children }: { children: ReactNode }) { - return {children} -} diff --git a/web/features/deployments/create-guide/state/queries.ts b/web/features/deployments/create-guide/state/queries.ts deleted file mode 100644 index 6fa3773179f..00000000000 --- a/web/features/deployments/create-guide/state/queries.ts +++ /dev/null @@ -1,225 +0,0 @@ -'use client' - -import type { Getter } from 'jotai/vanilla' -import { keepPreviousData, skipToken } from '@tanstack/react-query' -import { atom } from 'jotai' -import { atomWithInfiniteQuery, atomWithQuery } from 'jotai-tanstack-query' -import { selectAtom } from 'jotai/utils' -import { encodeDslContent } from '@/features/deployments/shared/domain/dsl' -import { consoleQuery } from '@/service/client' -import { - effectiveMethodAtom, - instanceNameAtom, - submissionUnsupportedDslNodesAtom, -} from './primitives' -import { dslContentAtom, effectiveSelectedAppAtom, sourceReady } from './source' -import { DEPLOYMENT_PAGE_SIZE, getNextPageParamFromPagination } from './utils' - -export const existingInstanceNamesQueryAtom = atomWithInfiniteQuery(() => - consoleQuery.enterprise.appInstanceService.listAppInstances.infiniteOptions({ - input: (pageParam) => ({ - query: { - pageNumber: Number(pageParam), - resultsPerPage: DEPLOYMENT_PAGE_SIZE, - }, - }), - getNextPageParam: (lastPage) => getNextPageParamFromPagination(lastPage.pagination), - initialPageParam: 1, - placeholderData: keepPreviousData, - }), -) - -export const instanceNameConflictQueryAtom = atomWithQuery((get) => { - const submittedInstanceName = get(instanceNameAtom).trim() - - return consoleQuery.enterprise.appInstanceService.listAppInstances.queryOptions({ - input: { - query: { - pageNumber: 1, - resultsPerPage: 1, - displayName: submittedInstanceName, - }, - }, - enabled: Boolean(submittedInstanceName), - }) -}) - -export const deployableEnvironmentsQueryAtom = atomWithQuery((get) => { - return consoleQuery.enterprise.environmentService.listEnvironments.queryOptions({ - input: { - query: { - // The guide offers every deployable environment at once; environment - // count is capped well below the 100-per-page maximum. - pageNumber: 1, - resultsPerPage: 100, - }, - }, - enabled: sourceReady(get), - }) -}) - -export const deployableEnvironmentsDataAtom = selectAtom( - deployableEnvironmentsQueryAtom, - (query) => query.data, -) -export const deployableEnvironmentsIsErrorAtom = selectAtom( - deployableEnvironmentsQueryAtom, - (query) => query.isError, -) -export const deployableEnvironmentsIsLoadingAtom = selectAtom( - deployableEnvironmentsQueryAtom, - (query) => query.isLoading, -) -export const deployableEnvironmentsIsFetchingAtom = selectAtom( - deployableEnvironmentsQueryAtom, - (query) => query.isFetching, -) - -const precheckReleaseQueryAtom = atomWithQuery((get) => { - const method = get(effectiveMethodAtom) - const effectiveSelectedApp = get(effectiveSelectedAppAtom) - const dslContent = get(dslContentAtom) - const encodedDslContent = dslContent.trim() ? encodeDslContent(dslContent) : undefined - const enabled = sourceReady(get) - - // PrecheckRelease takes exactly one source arm (dsl | sourceAppId). - const precheckReleaseQueryOptions = - method === 'importDsl' - ? consoleQuery.enterprise.releaseService.precheckRelease.queryOptions({ - input: encodedDslContent - ? { - body: { - dsl: encodedDslContent, - }, - } - : skipToken, - enabled, - retry: false, - }) - : consoleQuery.enterprise.releaseService.precheckRelease.queryOptions({ - input: effectiveSelectedApp?.id - ? { - body: { - sourceAppId: effectiveSelectedApp.id, - }, - } - : skipToken, - enabled: enabled && Boolean(effectiveSelectedApp?.id), - retry: false, - }) - - return precheckReleaseQueryOptions -}) - -const precheckReleaseDataAtom = selectAtom(precheckReleaseQueryAtom, (query) => query.data) -const precheckReleaseIsSuccessAtom = selectAtom( - precheckReleaseQueryAtom, - (query) => query.isSuccess, -) -const precheckReleaseIsLoadingAtom = selectAtom( - precheckReleaseQueryAtom, - (query) => query.isLoading, -) -const precheckReleaseIsFetchingAtom = selectAtom( - precheckReleaseQueryAtom, - (query) => query.isFetching, -) - -function precheckReleaseReady(get: Getter) { - const precheckRelease = get(precheckReleaseDataAtom) - - return ( - sourceReady(get) && - get(precheckReleaseIsSuccessAtom) && - Boolean(precheckRelease?.canCreate) && - (precheckRelease?.unsupportedNodes.length ?? 0) === 0 && - get(submissionUnsupportedDslNodesAtom).length === 0 - ) -} - -const deploymentOptionsQueryAtom = atomWithQuery((get) => { - const method = get(effectiveMethodAtom) - const effectiveSelectedApp = get(effectiveSelectedAppAtom) - const dslContent = get(dslContentAtom) - const encodedDslContent = dslContent.trim() ? encodeDslContent(dslContent) : undefined - const enabled = precheckReleaseReady(get) - - // ComputeDeploymentOptions takes exactly one source arm (dsl | sourceAppId | releaseId). - const deploymentOptionsQueryOptions = - method === 'importDsl' - ? consoleQuery.enterprise.releaseService.computeDeploymentOptions.queryOptions({ - input: encodedDslContent - ? { - body: { - dsl: encodedDslContent, - }, - } - : skipToken, - enabled, - retry: false, - }) - : consoleQuery.enterprise.releaseService.computeDeploymentOptions.queryOptions({ - input: effectiveSelectedApp?.id - ? { - body: { - sourceAppId: effectiveSelectedApp.id, - }, - } - : skipToken, - enabled: enabled && Boolean(effectiveSelectedApp?.id), - retry: false, - }) - - return deploymentOptionsQueryOptions -}) - -export const deploymentOptionsDataAtom = selectAtom( - deploymentOptionsQueryAtom, - (query) => query.data, -) -export const deploymentOptionsIsErrorAtom = selectAtom( - deploymentOptionsQueryAtom, - (query) => query.isError, -) -export const deploymentOptionsIsLoadingAtom = selectAtom( - deploymentOptionsQueryAtom, - (query) => query.isLoading, -) -export const deploymentOptionsIsFetchingAtom = selectAtom( - deploymentOptionsQueryAtom, - (query) => query.isFetching, -) -const deploymentOptionsIsSuccessAtom = selectAtom( - deploymentOptionsQueryAtom, - (query) => query.isSuccess, -) - -export const unsupportedDslNodesAtom = atom((get) => { - const submissionUnsupportedDslNodes = get(submissionUnsupportedDslNodesAtom) - if (submissionUnsupportedDslNodes.length > 0) return submissionUnsupportedDslNodes - - if (!sourceReady(get)) return [] - - return get(precheckReleaseDataAtom)?.unsupportedNodes ?? [] -}) - -const precheckReleaseReadyAtom = atom((get) => { - return precheckReleaseReady(get) -}) - -export const deploymentOptionsReadyAtom = atom((get) => { - return sourceReady(get) && get(precheckReleaseReadyAtom) && get(deploymentOptionsIsSuccessAtom) -}) - -export const deploymentOptionsContentCheckedAtom = atom((get) => { - const isLoadingOptions = - get(deploymentOptionsIsLoadingAtom) || - (get(deploymentOptionsIsFetchingAtom) && !get(deploymentOptionsDataAtom)) - const isCheckingReleaseContent = - get(precheckReleaseIsLoadingAtom) || - (get(precheckReleaseIsFetchingAtom) && !get(precheckReleaseDataAtom)) - - if (!sourceReady(get) || isCheckingReleaseContent || isLoadingOptions) return false - - return get(precheckReleaseReadyAtom) && get(deploymentOptionsIsSuccessAtom) -}) diff --git a/web/features/deployments/create-guide/state/release.ts b/web/features/deployments/create-guide/state/release.ts deleted file mode 100644 index 0ae13ddf26f..00000000000 --- a/web/features/deployments/create-guide/state/release.ts +++ /dev/null @@ -1,91 +0,0 @@ -'use client' - -import { atom } from 'jotai' -import { - autoFilledInstanceNameAtom, - autoFilledReleaseNameAtom, - envVarValuesAtom, - instanceDescriptionAtom, - instanceNameAtom, - manualBindingSelectionsAtom, - releaseDescriptionAtom, - releaseNameAtom, - selectedEnvironmentIdAtom, - stepAtom, -} from './primitives' -import { - deploymentOptionsContentCheckedAtom, - existingInstanceNamesQueryAtom, - instanceNameConflictQueryAtom, -} from './queries' -import { sourceReady } from './source' - -export const hasInstanceNameConflictAtom = atom((get) => { - const submittedInstanceName = get(instanceNameAtom).trim() - const instanceNameConflictQuery = get(instanceNameConflictQueryAtom) - const existingInstanceNamesQuery = get(existingInstanceNamesQueryAtom) - const existingInstanceNames = - existingInstanceNamesQuery.data?.pages.flatMap((page) => - page.appInstances.flatMap((appInstance) => { - const name = appInstance.displayName.trim() - - return name ? [name] : [] - }), - ) ?? [] - - return Boolean( - submittedInstanceName && - (existingInstanceNames.includes(submittedInstanceName) || - (instanceNameConflictQuery.data?.appInstances.some( - (appInstance) => appInstance.displayName.trim() === submittedInstanceName, - ) ?? - false)), - ) -}) - -export const submittedReleaseReadyAtom = atom((get) => { - return Boolean(sourceReady(get) && get(instanceNameAtom).trim() && get(releaseNameAtom).trim()) -}) - -export const releaseCanGoNextAtom = atom((get) => { - const submittedInstanceName = get(instanceNameAtom).trim() - const instanceNameConflictQuery = get(instanceNameConflictQueryAtom) - - return ( - Boolean(get(submittedReleaseReadyAtom)) && - !get(hasInstanceNameConflictAtom) && - !(Boolean(submittedInstanceName) && instanceNameConflictQuery.isLoading) && - get(deploymentOptionsContentCheckedAtom) - ) -}) - -export const setInstanceNameAtom = atom(null, (_get, set, value: string) => { - set(instanceNameAtom, value) - set(autoFilledInstanceNameAtom, '') - set(stepAtom, 'release') -}) - -export const setInstanceDescriptionAtom = atom(null, (_get, set, value: string) => { - set(instanceDescriptionAtom, value) - set(stepAtom, 'release') -}) - -export const setReleaseNameAtom = atom(null, (_get, set, value: string) => { - set(releaseNameAtom, value) - set(autoFilledReleaseNameAtom, '') - set(stepAtom, 'release') -}) - -export const setReleaseDescriptionAtom = atom(null, (_get, set, value: string) => { - set(releaseDescriptionAtom, value) - set(stepAtom, 'release') -}) - -export const continueFromReleaseAtom = atom(null, (get, set) => { - if (!get(releaseCanGoNextAtom)) return - - set(selectedEnvironmentIdAtom, '') - set(manualBindingSelectionsAtom, {}) - set(envVarValuesAtom, {}) - set(stepAtom, 'target') -}) diff --git a/web/features/deployments/create-guide/state/scoped.ts b/web/features/deployments/create-guide/state/scoped.ts deleted file mode 100644 index 0abe15b295f..00000000000 --- a/web/features/deployments/create-guide/state/scoped.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { - autoFilledInstanceNameAtom, - autoFilledReleaseNameAtom, - dslFileAtom, - dslFileReadVersionAtom, - envVarValuesAtom, - instanceDescriptionAtom, - instanceNameAtom, - isCreatingDeploymentAtom, - isCreatingReleaseOnlyAtom, - manualBindingSelectionsAtom, - methodAtom, - releaseDescriptionAtom, - releaseNameAtom, - selectedAppAtom, - selectedEnvironmentIdAtom, - sourceSearchTextAtom, - stepAtom, - submissionUnsupportedDslNodesAtom, -} from './primitives' - -export const createDeploymentGuideScopedAtoms = [ - stepAtom, - methodAtom, - sourceSearchTextAtom, - selectedAppAtom, - dslFileAtom, - dslFileReadVersionAtom, - instanceNameAtom, - instanceDescriptionAtom, - releaseNameAtom, - releaseDescriptionAtom, - autoFilledInstanceNameAtom, - autoFilledReleaseNameAtom, - selectedEnvironmentIdAtom, - manualBindingSelectionsAtom, - envVarValuesAtom, - submissionUnsupportedDslNodesAtom, - isCreatingDeploymentAtom, - isCreatingReleaseOnlyAtom, -] diff --git a/web/features/deployments/create-guide/state/source.ts b/web/features/deployments/create-guide/state/source.ts deleted file mode 100644 index 2130ff34df7..00000000000 --- a/web/features/deployments/create-guide/state/source.ts +++ /dev/null @@ -1,145 +0,0 @@ -'use client' - -import type { Getter } from 'jotai/vanilla' -import type { WorkflowSourceApp } from './types' -import { keepPreviousData, queryOptions } from '@tanstack/react-query' -import { atom } from 'jotai' -import { atomWithInfiniteQuery, atomWithQuery } from 'jotai-tanstack-query' -import { selectAtom } from 'jotai/utils' -import { dslAppName, isWorkflowDsl } from '@/features/deployments/shared/domain/dsl' -import { consoleQuery } from '@/service/client' -import { AppModeEnum } from '@/types/app' -import { - dslFileAtom, - dslFileReadVersionAtom, - effectiveMethodAtom, - selectedAppAtom, - sourceSearchTextAtom, -} from './primitives' -import { SOURCE_APPS_PAGE_SIZE } from './utils' - -const dslFileContentQueryAtom = atomWithQuery((get) => { - const file = get(dslFileAtom) - const fileReadVersion = get(dslFileReadVersionAtom) - - return queryOptions({ - queryKey: [ - 'createGuideDslFileContent', - fileReadVersion, - file, - file?.name ?? '', - file?.size ?? 0, - file?.lastModified ?? 0, - ], - queryFn: async () => (file ? await file.text() : ''), - enabled: Boolean(file), - retry: false, - }) -}) - -export const dslContentAtom = atom((get) => { - return get(dslFileContentQueryAtom).data ?? '' -}) - -export const isReadingDslAtom = atom((get) => { - const file = get(dslFileAtom) - const dslFileContentQuery = get(dslFileContentQueryAtom) - - return Boolean(file && (dslFileContentQuery.isLoading || dslFileContentQuery.isFetching)) -}) - -export const dslReadErrorAtom = atom((get) => { - return Boolean(get(dslFileAtom) && get(dslFileContentQueryAtom).isError) -}) - -export const dslDefaultAppNameAtom = atom((get) => { - const dslContent = get(dslContentAtom) - - return dslContent ? dslAppName(dslContent) : '' -}) - -export const dslUnsupportedModeAtom = atom((get) => { - const dslContent = get(dslContentAtom) - - return ( - get(effectiveMethodAtom) === 'importDsl' && - Boolean(dslContent.trim()) && - !get(isReadingDslAtom) && - !get(dslReadErrorAtom) && - !isWorkflowDsl(dslContent) - ) -}) - -export const importDslReadyAtom = atom((get) => { - return ( - Boolean(get(dslContentAtom).trim()) && - !get(isReadingDslAtom) && - !get(dslReadErrorAtom) && - !get(dslUnsupportedModeAtom) - ) -}) - -export const sourceAppsQueryAtom = atomWithInfiniteQuery((get) => { - const sourceSearchText = get(sourceSearchTextAtom) - - return consoleQuery.apps.get.infiniteOptions({ - input: (pageParam) => ({ - query: { - page: Number(pageParam), - limit: SOURCE_APPS_PAGE_SIZE, - name: sourceSearchText, - mode: AppModeEnum.WORKFLOW, - }, - }), - getNextPageParam: (lastPage) => (lastPage.has_more ? lastPage.page + 1 : undefined), - initialPageParam: 1, - placeholderData: keepPreviousData, - enabled: get(effectiveMethodAtom) === 'bindApp', - }) -}) - -const sourceAppsDataAtom = selectAtom(sourceAppsQueryAtom, (query) => query.data) -export const sourceAppsErrorAtom = selectAtom(sourceAppsQueryAtom, (query) => query.error) -export const sourceAppsFetchNextPageAtom = selectAtom( - sourceAppsQueryAtom, - (query) => query.fetchNextPage, -) -export const sourceAppsHasNextPageAtom = selectAtom( - sourceAppsQueryAtom, - (query) => query.hasNextPage, -) -export const sourceAppsIsFetchingAtom = selectAtom(sourceAppsQueryAtom, (query) => query.isFetching) -export const sourceAppsIsFetchingNextPageAtom = selectAtom( - sourceAppsQueryAtom, - (query) => query.isFetchingNextPage, -) -export const sourceAppsIsLoadingAtom = selectAtom(sourceAppsQueryAtom, (query) => query.isLoading) -export const sourceAppsIsPlaceholderDataAtom = selectAtom( - sourceAppsQueryAtom, - (query) => query.isPlaceholderData, -) - -export const sourceAppsAtom = atom((get): WorkflowSourceApp[] => { - const apps = get(sourceAppsDataAtom)?.pages.flatMap((page) => page.data) ?? [] - - return apps.flatMap((app) => - app.mode === AppModeEnum.WORKFLOW ? [{ ...app, mode: 'workflow' as const }] : [], - ) -}) - -export const effectiveSelectedAppAtom = atom((get) => { - const selectedApp = get(selectedAppAtom) - if (selectedApp) return selectedApp - - if (get(sourceAppsIsPlaceholderDataAtom)) return undefined - - return get(sourceAppsAtom)[0] -}) - -export function sourceReady(get: Getter) { - const method = get(effectiveMethodAtom) - - return method === 'importDsl' - ? get(importDslReadyAtom) - : Boolean(get(effectiveSelectedAppAtom)?.id) -} diff --git a/web/features/deployments/create-guide/state/submission.ts b/web/features/deployments/create-guide/state/submission.ts deleted file mode 100644 index 8dbbfceed2e..00000000000 --- a/web/features/deployments/create-guide/state/submission.ts +++ /dev/null @@ -1,221 +0,0 @@ -'use client' - -import type { DeployRequest } from '@dify/contracts/enterprise/types.gen' -import { atom } from 'jotai' -import { atomWithMutation } from 'jotai-tanstack-query' -import { selectedDeploymentRuntimeCredentials } from '@/features/deployments/shared/components/runtime-credential-bindings-utils' -import { encodeDslContent, isWorkflowDsl } from '@/features/deployments/shared/domain/dsl' -import { unsupportedDslNodeError } from '@/features/deployments/shared/domain/error' -import { createDeploymentIdempotencyKey } from '@/features/deployments/shared/domain/idempotency' -import { consoleQuery } from '@/service/client' -import { environmentMatchesIdentifier } from './environment' -import { - effectiveMethodAtom, - envVarValuesAtom, - instanceDescriptionAtom, - instanceNameAtom, - isCreatingDeploymentAtom, - isCreatingReleaseOnlyAtom, - isSubmittingDeploymentGuideAtom, - releaseDescriptionAtom, - releaseNameAtom, - selectedEnvironmentIdAtom, - submissionUnsupportedDslNodesAtom, -} from './primitives' -import { deployableEnvironmentsQueryAtom, deploymentOptionsDataAtom } from './queries' -import { submittedReleaseReadyAtom } from './release' -import { dslContentAtom, effectiveSelectedAppAtom } from './source' -import { - canDeployAtom, - canSkipDeploymentAtom, - deployableEnvironmentsAtom, - deploymentTargetBindingSelectionsAtom, - deploymentTargetBindingSlotsAtom, - deploymentTargetEnvVarSlotsAtom, - requiredBindingsReadyAtom, - requiredEnvVarsReadyAtom, -} from './target' -import { envVarInput } from './utils' - -const createAppInstanceMutationAtom = atomWithMutation(() => - consoleQuery.enterprise.appInstanceService.createAppInstance.mutationOptions(), -) - -const createReleaseMutationAtom = atomWithMutation(() => - consoleQuery.enterprise.releaseService.createRelease.mutationOptions(), -) - -const createInitialDeploymentMutationAtom = atomWithMutation(() => - consoleQuery.enterprise.deploymentService.deploy.mutationOptions(), -) - -export class CreateDeploymentGuideSubmissionBlockedError extends Error { - reason: 'unsupportedDslMode' | 'deployFailed' - - constructor(reason: 'unsupportedDslMode' | 'deployFailed') { - super(reason) - this.reason = reason - this.name = 'CreateDeploymentGuideSubmissionBlockedError' - } -} - -export const createDeploymentGuideSubmissionAtom = atom( - null, - async ( - get, - set, - { - deployToEnvironment, - }: { - deployToEnvironment: boolean - }, - ) => { - const method = get(effectiveMethodAtom) - const dslContent = get(dslContentAtom) - const submittedInstanceName = get(instanceNameAtom).trim() - const submittedReleaseName = get(releaseNameAtom).trim() - const submittedReleaseDescription = get(releaseDescriptionAtom).trim() - - if (get(isSubmittingDeploymentGuideAtom) || !get(submittedReleaseReadyAtom)) return undefined - - const effectiveSelectedApp = get(effectiveSelectedAppAtom) - const deployableEnvironmentsQuery = get(deployableEnvironmentsQueryAtom) - const deploymentOptions = get(deploymentOptionsDataAtom)?.options - const envVarSlots = get(deploymentTargetEnvVarSlotsAtom) - const envVarValues = get(envVarValuesAtom) - const bindingSlots = get(deploymentTargetBindingSlotsAtom) - const bindingSelections = get(deploymentTargetBindingSelectionsAtom) - const selectedEnvironmentId = get(selectedEnvironmentIdAtom) - const effectiveSelectedEnvironmentId = - selectedEnvironmentId || get(deployableEnvironmentsAtom)[0]?.id - const selectedEnvironment = effectiveSelectedEnvironmentId - ? get(deployableEnvironmentsAtom).find((env) => - environmentMatchesIdentifier(env, effectiveSelectedEnvironmentId), - ) - : undefined - - if (deployToEnvironment && !selectedEnvironment && !selectedEnvironmentId.trim()) - return undefined - if (method === 'bindApp' && !effectiveSelectedApp?.id) return undefined - if (method === 'importDsl' && !dslContent.trim()) return undefined - if (method === 'importDsl' && !isWorkflowDsl(dslContent)) - throw new CreateDeploymentGuideSubmissionBlockedError('unsupportedDslMode') - - set(submissionUnsupportedDslNodesAtom, []) - - try { - if (!deployToEnvironment) { - if (!get(canSkipDeploymentAtom)) return undefined - - set(isCreatingReleaseOnlyAtom, true) - - try { - const createdAppInstance = await get(createAppInstanceMutationAtom).mutateAsync({ - body: { - displayName: submittedInstanceName, - description: get(instanceDescriptionAtom).trim() || undefined, - }, - }) - const appInstanceId = createdAppInstance.appInstance.id - - if (method === 'importDsl') { - await get(createReleaseMutationAtom).mutateAsync({ - body: { - appInstanceId, - dsl: encodeDslContent(dslContent), - displayName: submittedReleaseName, - description: submittedReleaseDescription || undefined, - createAppInstance: false, - }, - }) - - return appInstanceId - } - - if (!effectiveSelectedApp?.id) return undefined - - await get(createReleaseMutationAtom).mutateAsync({ - body: { - appInstanceId, - sourceAppId: effectiveSelectedApp.id, - displayName: submittedReleaseName, - description: submittedReleaseDescription || undefined, - createAppInstance: false, - }, - }) - - return appInstanceId - } finally { - set(isCreatingReleaseOnlyAtom, false) - } - } - - if (!get(canDeployAtom)) return undefined - - set(isCreatingDeploymentAtom, true) - - try { - const selectedEnvironmentIdentifier = selectedEnvironmentId.trim() - const freshSelectedEnvironment = - selectedEnvironment || - (selectedEnvironmentIdentifier - ? (await deployableEnvironmentsQuery.refetch()).data?.environments.find((environment) => - environmentMatchesIdentifier(environment, selectedEnvironmentIdentifier), - ) - : undefined) - const targetEnvironmentId = freshSelectedEnvironment?.id - if (!targetEnvironmentId) - throw new CreateDeploymentGuideSubmissionBlockedError('deployFailed') - - if (!get(requiredBindingsReadyAtom)) throw new Error('Missing required deployment binding.') - if (!get(requiredEnvVarsReadyAtom)) - throw new Error('Missing required deployment environment variable.') - - const envVars = envVarSlots.flatMap((slot) => envVarInput(slot, envVarValues[slot.key])) - const commonDeploymentRequest = { - newAppInstance: { - displayName: submittedInstanceName, - description: get(instanceDescriptionAtom).trim() || undefined, - }, - environmentId: targetEnvironmentId, - releaseName: submittedReleaseName, - releaseDescription: submittedReleaseDescription || undefined, - credentials: selectedDeploymentRuntimeCredentials(bindingSlots, bindingSelections), - envVars, - idempotencyKey: createDeploymentIdempotencyKey(), - expectedDslDigest: deploymentOptions?.dslDigest, - } satisfies Omit - const deploymentRequest = - method === 'importDsl' - ? { - ...commonDeploymentRequest, - dsl: encodeDslContent(dslContent), - } - : effectiveSelectedApp?.id - ? { - ...commonDeploymentRequest, - sourceAppId: effectiveSelectedApp.id, - } - : undefined - if (!deploymentRequest) return undefined - - const response = await get(createInitialDeploymentMutationAtom).mutateAsync({ - body: deploymentRequest, - }) - - return response.appInstance.id - } finally { - set(isCreatingDeploymentAtom, false) - } - } catch (error) { - const unsupportedError = await unsupportedDslNodeError(error) - if (unsupportedError?.nodes.length) { - set(submissionUnsupportedDslNodesAtom, unsupportedError.nodes) - - return undefined - } - - throw error - } - }, -) diff --git a/web/features/deployments/create-guide/state/target.ts b/web/features/deployments/create-guide/state/target.ts deleted file mode 100644 index fc89b42004c..00000000000 --- a/web/features/deployments/create-guide/state/target.ts +++ /dev/null @@ -1,172 +0,0 @@ -'use client' - -import type { - EnvVarBindingSlot, - EnvVarValueSelection, -} from '@/features/deployments/shared/components/env-var-bindings' -import { atom } from 'jotai' -import { - envVarBindingSlotFromContract, - envVarBindingValueType, -} from '@/features/deployments/shared/components/env-var-bindings-utils' -import { - hasMissingRequiredRuntimeCredentialBinding, - runtimeCredentialSlotKey, - selectedRuntimeCredentialSelections, -} from '@/features/deployments/shared/components/runtime-credential-bindings-utils' -import { dslEnvVarSlots } from '@/features/deployments/shared/domain/dsl' -import { environmentMatchesIdentifier } from './environment' -import { - effectiveMethodAtom, - envVarValuesAtom, - manualBindingSelectionsAtom, - selectedEnvironmentIdAtom, -} from './primitives' -import { - deployableEnvironmentsDataAtom, - deploymentOptionsDataAtom, - deploymentOptionsReadyAtom, -} from './queries' -import { submittedReleaseReadyAtom } from './release' -import { dslContentAtom, sourceReady } from './source' -import { envVarSelectionReady } from './utils' - -export const deployableEnvironmentsAtom = atom((get) => { - const deployableEnvironments = get(deployableEnvironmentsDataAtom) - - return sourceReady(get) ? (deployableEnvironments?.environments ?? []) : [] -}) - -const deployableEnvironmentsReadyAtom = atom((get) => { - return sourceReady(get) && Boolean(get(deployableEnvironmentsDataAtom)) -}) - -export const effectiveSelectedEnvironmentIdAtom = atom((get) => { - return get(selectedEnvironmentIdAtom) || get(deployableEnvironmentsAtom)[0]?.id -}) - -export const deploymentTargetBindingSlotsAtom = atom((get) => { - const deploymentOptions = get(deploymentOptionsDataAtom) - - return sourceReady(get) - ? (deploymentOptions?.options?.credentialSlots?.filter((slot) => - runtimeCredentialSlotKey(slot), - ) ?? []) - : [] -}) - -export const deploymentTargetBindingSelectionsAtom = atom((get) => { - return selectedRuntimeCredentialSelections( - get(deploymentTargetBindingSlotsAtom), - get(manualBindingSelectionsAtom), - ) -}) - -export const requiredBindingsReadyAtom = atom((get) => { - const bindingSelections = get(deploymentTargetBindingSelectionsAtom) - - return get(deploymentTargetBindingSlotsAtom).every( - (slot) => - !hasMissingRequiredRuntimeCredentialBinding( - slot, - bindingSelections[runtimeCredentialSlotKey(slot)], - ), - ) -}) - -export const deploymentTargetEnvVarSlotsAtom = atom((get) => { - const method = get(effectiveMethodAtom) - const deploymentOptions = get(deploymentOptionsDataAtom) - const slots = sourceReady(get) ? deploymentOptions?.options?.envVarSlots : undefined - const dslContent = get(dslContentAtom) - - // Deployment options own the canonical slot list; DSL metadata only enriches import-DSL defaults. - const deploymentOptionEnvVarSlots = - slots?.flatMap((slot): EnvVarBindingSlot[] => { - const bindingSlot = envVarBindingSlotFromContract(slot) - return bindingSlot ? [bindingSlot] : [] - }) ?? [] - const dslEnvVarMetadataSlots = - method === 'importDsl' && dslContent - ? dslEnvVarSlots(dslContent).flatMap((slot) => { - const key = slot.key.trim() - if (!key) return [] - - return [ - { - key, - ...(slot.description ? { description: slot.description } : {}), - ...(slot.defaultValue !== undefined - ? { defaultValue: slot.defaultValue, hasDefaultValue: true } - : {}), - ...(slot.valueType ? { valueType: envVarBindingValueType(slot.valueType) } : {}), - }, - ] - }) - : [] - - if (dslEnvVarMetadataSlots.length === 0) return deploymentOptionEnvVarSlots - - const metadataByKey = new Map(dslEnvVarMetadataSlots.map((slot) => [slot.key, slot] as const)) - - return deploymentOptionEnvVarSlots.map((slot) => { - const metadata = metadataByKey.get(slot.key) - if (!metadata) return slot - - const nextSlot = { ...slot } - - if (!nextSlot.description && metadata.description) nextSlot.description = metadata.description - if (!nextSlot.hasDefaultValue && metadata.defaultValue !== undefined) { - nextSlot.defaultValue = metadata.defaultValue - nextSlot.hasDefaultValue = true - } - if (nextSlot.valueType === 'string' && metadata.valueType) - nextSlot.valueType = metadata.valueType - - return nextSlot - }) -}) - -export const requiredEnvVarsReadyAtom = atom((get) => { - const envVarValues = get(envVarValuesAtom) - - return get(deploymentTargetEnvVarSlotsAtom).every((slot) => - envVarSelectionReady(slot, envVarValues[slot.key]), - ) -}) - -export const canDeployAtom = atom((get) => { - const effectiveSelectedEnvironmentId = get(effectiveSelectedEnvironmentIdAtom) - const selectedEnvironment = effectiveSelectedEnvironmentId - ? get(deployableEnvironmentsAtom).find((env) => - environmentMatchesIdentifier(env, effectiveSelectedEnvironmentId), - ) - : undefined - - return Boolean( - selectedEnvironment?.id && - get(deployableEnvironmentsReadyAtom) && - get(deploymentOptionsReadyAtom) && - get(requiredBindingsReadyAtom) && - get(requiredEnvVarsReadyAtom) && - get(submittedReleaseReadyAtom), - ) -}) - -export const canSkipDeploymentAtom = atom((get) => { - return get(submittedReleaseReadyAtom) && get(deploymentOptionsReadyAtom) -}) - -export const selectBindingAtom = atom(null, (get, set, slot: string, value: string) => { - set(manualBindingSelectionsAtom, { - ...get(manualBindingSelectionsAtom), - [slot]: value, - }) -}) - -export const setEnvVarAtom = atom(null, (get, set, key: string, value: EnvVarValueSelection) => { - set(envVarValuesAtom, { - ...get(envVarValuesAtom), - [key]: value, - }) -}) diff --git a/web/features/deployments/create-guide/state/types.ts b/web/features/deployments/create-guide/state/types.ts deleted file mode 100644 index a1ed1b32670..00000000000 --- a/web/features/deployments/create-guide/state/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { AppPartial, GetAppsData } from '@dify/contracts/api/console/apps/types.gen' - -export type GuideMethod = 'bindApp' | 'importDsl' -export type GuideStep = 'source' | 'release' | 'target' -export type WorkflowSourceApp = AppPartial & { - mode: Extract['mode']>, 'workflow'> -} diff --git a/web/features/deployments/create-guide/state/utils.ts b/web/features/deployments/create-guide/state/utils.ts deleted file mode 100644 index 7ad4d1a2c7b..00000000000 --- a/web/features/deployments/create-guide/state/utils.ts +++ /dev/null @@ -1,107 +0,0 @@ -import type { EnvVarInput, Pagination } from '@dify/contracts/enterprise/types.gen' -import type { GuideMethod } from './types' -import type { - EnvVarBindingSlot, - EnvVarValueSelection, -} from '@/features/deployments/shared/components/env-var-bindings' -import { EnvVarValueSource as ApiEnvVarValueSource } from '@dify/contracts/enterprise/types.gen' -import { isDeploymentDslImportEnabled } from '@/features/deployments/shared/domain/feature-flags' - -export const DEPLOYMENT_PAGE_SIZE = 100 -export const SOURCE_APPS_PAGE_SIZE = 100 - -export function getNextPageParamFromPagination(pagination?: Pagination) { - const currentPage = pagination?.currentPage ?? 1 - const totalPages = pagination?.totalPages ?? 1 - - return currentPage < totalPages ? currentPage + 1 : undefined -} - -export function deploymentGuideMethod(method: GuideMethod): GuideMethod { - return method === 'importDsl' && !isDeploymentDslImportEnabled ? 'bindApp' : method -} - -const RANDOM_SUFFIX_ALPHABET = 'abcdefghijklmnopqrstuvwxyz' -const RANDOM_SUFFIX_LENGTH = 4 -const RANDOM_SUFFIX_FALLBACK_LENGTH = 6 -const RANDOM_SUFFIX_MAX_ATTEMPTS = 16 - -function randomLetterCombination(length: number) { - const randomValues = new Uint8Array(length) - - if (globalThis.crypto) { - globalThis.crypto.getRandomValues(randomValues) - } else { - randomValues.forEach((_, index) => { - randomValues[index] = Math.floor(Math.random() * 256) - }) - } - - return Array.from( - randomValues, - (value) => RANDOM_SUFFIX_ALPHABET[value % RANDOM_SUFFIX_ALPHABET.length], - ).join('') -} - -export function availableInstanceName(sourceName: string, existingNameSet: Set) { - if (!existingNameSet.has(sourceName)) return sourceName - - for (let attempt = 0; attempt < RANDOM_SUFFIX_MAX_ATTEMPTS; attempt++) { - const candidate = `${sourceName}-${randomLetterCombination(RANDOM_SUFFIX_LENGTH)}` - if (!existingNameSet.has(candidate)) return candidate - } - - return `${sourceName}-${randomLetterCombination(RANDOM_SUFFIX_FALLBACK_LENGTH)}` -} - -function envVarValueSource(slot: EnvVarBindingSlot, selection: EnvVarValueSelection | undefined) { - return ( - selection?.valueSource ?? - (slot.hasDefaultValue - ? ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_DSL_DEFAULT - : slot.hasLastValue - ? ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_LAST_DEPLOYMENT - : ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_LITERAL) - ) -} - -export function envVarSelectionReady( - slot: EnvVarBindingSlot, - selection: EnvVarValueSelection | undefined, -) { - const valueSource = envVarValueSource(slot, selection) - - if (valueSource === ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_LAST_DEPLOYMENT) - return Boolean(slot.hasLastValue) - if (valueSource === ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_DSL_DEFAULT) - return Boolean(slot.hasDefaultValue) - if (!selection?.value) return false - - return slot.valueType !== 'number' || !Number.isNaN(Number(selection.value)) -} - -export function envVarInput( - slot: EnvVarBindingSlot, - selection: EnvVarValueSelection | undefined, -): EnvVarInput[] { - const valueSource = envVarValueSource(slot, selection) - - if (valueSource === ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_LAST_DEPLOYMENT) { - return slot.hasLastValue ? [{ key: slot.key, valueSource }] : [] - } - - if (valueSource === ApiEnvVarValueSource.ENV_VAR_VALUE_SOURCE_DSL_DEFAULT) { - return slot.hasDefaultValue ? [{ key: slot.key, valueSource }] : [] - } - - if (!selection?.value || (slot.valueType === 'number' && Number.isNaN(Number(selection.value)))) - return [] - - return [ - { - key: slot.key, - value: selection.value, - valueSource, - }, - ] -} diff --git a/web/features/deployments/create-guide/state/workflow.ts b/web/features/deployments/create-guide/state/workflow.ts deleted file mode 100644 index 71f5ad2324c..00000000000 --- a/web/features/deployments/create-guide/state/workflow.ts +++ /dev/null @@ -1,130 +0,0 @@ -'use client' - -import type { WorkflowSourceApp } from './types' -import { atom } from 'jotai' -import { - autoFilledInstanceNameAtom, - autoFilledReleaseNameAtom, - dslFileAtom, - dslFileReadVersionAtom, - effectiveMethodAtom, - envVarValuesAtom, - instanceNameAtom, - manualBindingSelectionsAtom, - methodAtom, - releaseNameAtom, - selectedAppAtom, - selectedEnvironmentIdAtom, - sourceSearchTextAtom, - stepAtom, - submissionUnsupportedDslNodesAtom, -} from './primitives' -import { deploymentOptionsContentCheckedAtom, existingInstanceNamesQueryAtom } from './queries' -import { dslDefaultAppNameAtom, effectiveSelectedAppAtom, importDslReadyAtom } from './source' -import { availableInstanceName, deploymentGuideMethod } from './utils' - -export const sourceCanGoNextAtom = atom((get) => { - const method = get(effectiveMethodAtom) - const effectiveSelectedApp = get(effectiveSelectedAppAtom) - const importDslReady = method === 'importDsl' && get(importDslReadyAtom) - const bindAppReady = method === 'bindApp' && Boolean(effectiveSelectedApp?.id) - - return (importDslReady || bindAppReady) && get(deploymentOptionsContentCheckedAtom) -}) - -export const setSourceSearchTextAtom = atom(null, (get, set, value: string) => { - if (get(sourceSearchTextAtom) === value) return - - set(sourceSearchTextAtom, value) - set(selectedAppAtom, undefined) - set(selectedEnvironmentIdAtom, '') - set(manualBindingSelectionsAtom, {}) - set(envVarValuesAtom, {}) - set(submissionUnsupportedDslNodesAtom, []) -}) - -export const selectSourceAppAtom = atom(null, (_get, set, app: WorkflowSourceApp) => { - set(selectedAppAtom, app) - set(selectedEnvironmentIdAtom, '') - set(manualBindingSelectionsAtom, {}) - set(envVarValuesAtom, {}) - set(submissionUnsupportedDslNodesAtom, []) -}) - -export const continueFromSourceAtom = atom( - null, - ( - get, - set, - { - defaultDslAppName, - defaultReleaseName, - }: { - defaultDslAppName: string - defaultReleaseName: string - }, - ) => { - if (!get(sourceCanGoNextAtom)) return - - const method = get(effectiveMethodAtom) - const effectiveSelectedApp = get(effectiveSelectedAppAtom) - if (method === 'bindApp' && effectiveSelectedApp) set(selectSourceAppAtom, effectiveSelectedApp) - - const sourceName = - method === 'importDsl' - ? get(dslDefaultAppNameAtom) || defaultDslAppName - : effectiveSelectedApp?.name - const nextInstanceName = sourceName?.trim() - - if (nextInstanceName) { - const currentInstanceName = get(instanceNameAtom).trim() - const autoFilledInstanceName = get(autoFilledInstanceNameAtom) - const existingInstanceNamesQuery = get(existingInstanceNamesQueryAtom) - const existingNameSet = new Set( - existingInstanceNamesQuery.data?.pages.flatMap((page) => - page.appInstances.flatMap((appInstance) => { - const name = appInstance.displayName.trim() - - return name ? [name] : [] - }), - ) ?? [], - ) - - if (!currentInstanceName || currentInstanceName === autoFilledInstanceName) { - const nextAvailableInstanceName = availableInstanceName(nextInstanceName, existingNameSet) - set(instanceNameAtom, nextAvailableInstanceName) - set(autoFilledInstanceNameAtom, nextAvailableInstanceName) - } - } - - const currentReleaseName = get(releaseNameAtom).trim() - const autoFilledReleaseName = get(autoFilledReleaseNameAtom) - if (!currentReleaseName || currentReleaseName === autoFilledReleaseName) { - set(releaseNameAtom, defaultReleaseName) - set(autoFilledReleaseNameAtom, defaultReleaseName) - } - set(stepAtom, 'release') - }, -) - -export const selectDslFileAtom = atom(null, (get, set, dslFile?: File) => { - set(selectedEnvironmentIdAtom, '') - set(manualBindingSelectionsAtom, {}) - set(envVarValuesAtom, {}) - set(submissionUnsupportedDslNodesAtom, []) - - set(dslFileReadVersionAtom, get(dslFileReadVersionAtom) + 1) - set(dslFileAtom, dslFile) -}) - -export const selectMethodAtom = atom( - null, - (_get, set, method: Parameters[0]) => { - set(methodAtom, deploymentGuideMethod(method)) - set(selectedEnvironmentIdAtom, '') - set(manualBindingSelectionsAtom, {}) - set(envVarValuesAtom, {}) - set(submissionUnsupportedDslNodesAtom, []) - set(stepAtom, 'source') - }, -) diff --git a/web/features/deployments/create-guide/ui/__tests__/source-step.spec.tsx b/web/features/deployments/create-guide/ui/__tests__/source-step.spec.tsx deleted file mode 100644 index 17c488f5fe2..00000000000 --- a/web/features/deployments/create-guide/ui/__tests__/source-step.spec.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { act, render, screen } from '@testing-library/react' -import { Provider } from 'jotai' -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { SourceStepContent } from '../source-step' - -const mocks = vi.hoisted(() => { - const sourceAppsQuery = { - data: { pages: [{ data: [] }] }, - error: null, - fetchNextPage: vi.fn(), - hasNextPage: false, - isFetching: false, - isFetchingNextPage: false, - isLoading: false, - isPlaceholderData: false, - } - - return { sourceAppsQuery } -}) - -type ObserverRecord = { - callback: IntersectionObserverCallback - options?: IntersectionObserverInit -} - -const observers: ObserverRecord[] = [] - -function getInfiniteScrollObserver() { - return observers.find(({ options }) => options?.rootMargin === '0px 0px 160px 0px') -} - -vi.mock('@/features/deployments/create-guide/state/primitives', async () => { - const { atom } = await import('jotai') - const methodAtom = atom<'bindApp' | 'importDsl'>('bindApp') - - return { - dslFileAtom: atom(undefined), - effectiveMethodAtom: atom((get) => get(methodAtom)), - methodAtom, - sourceSearchTextAtom: atom(''), - } -}) - -vi.mock('@/features/deployments/create-guide/state/source', async () => { - const { atom } = await import('jotai') - - return { - dslReadErrorAtom: atom(false), - dslUnsupportedModeAtom: atom(false), - effectiveSelectedAppAtom: atom(undefined), - isReadingDslAtom: atom(false), - sourceAppsAtom: atom(() => mocks.sourceAppsQuery.data.pages.flatMap((page) => page.data)), - sourceAppsErrorAtom: atom(() => mocks.sourceAppsQuery.error), - sourceAppsFetchNextPageAtom: atom(() => mocks.sourceAppsQuery.fetchNextPage), - sourceAppsHasNextPageAtom: atom(() => mocks.sourceAppsQuery.hasNextPage), - sourceAppsIsFetchingAtom: atom(() => mocks.sourceAppsQuery.isFetching), - sourceAppsIsFetchingNextPageAtom: atom(() => mocks.sourceAppsQuery.isFetchingNextPage), - sourceAppsIsLoadingAtom: atom(() => mocks.sourceAppsQuery.isLoading), - sourceAppsIsPlaceholderDataAtom: atom(() => mocks.sourceAppsQuery.isPlaceholderData), - sourceAppsQueryAtom: atom(mocks.sourceAppsQuery), - } -}) - -vi.mock('@/features/deployments/create-guide/state/workflow', async () => { - const { atom } = await import('jotai') - const { methodAtom } = await import('@/features/deployments/create-guide/state/primitives') - const emptyActionAtom = atom(null, () => undefined) - - return { - continueFromSourceAtom: emptyActionAtom, - selectDslFileAtom: emptyActionAtom, - selectMethodAtom: atom(null, (_get, set, value: 'bindApp' | 'importDsl') => { - set(methodAtom, value) - }), - selectSourceAppAtom: emptyActionAtom, - setSourceSearchTextAtom: emptyActionAtom, - sourceCanGoNextAtom: atom(false), - } -}) - -vi.mock('@/features/deployments/create-guide/state/queries', async () => { - const { atom } = await import('jotai') - - return { - unsupportedDslNodesAtom: atom([]), - } -}) - -describe('SourceStepContent', () => { - beforeEach(() => { - vi.clearAllMocks() - mocks.sourceAppsQuery.fetchNextPage.mockReset() - observers.length = 0 - vi.stubGlobal( - 'IntersectionObserver', - class MockIntersectionObserver { - readonly root = null - readonly rootMargin = '' - readonly scrollMargin = '' - readonly thresholds: ReadonlyArray = [] - readonly disconnect = vi.fn() - readonly observe = vi.fn() - readonly takeRecords = () => [] - readonly unobserve = vi.fn() - - constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) { - observers.push({ callback, options }) - } - }, - ) - Object.assign(mocks.sourceAppsQuery, { - data: { pages: [{ data: [] }] }, - error: null, - hasNextPage: false, - isFetching: false, - isFetchingNextPage: false, - isLoading: false, - isPlaceholderData: false, - }) - }) - - afterEach(() => { - vi.unstubAllGlobals() - }) - - it('should hide the import DSL option when deployment DSL import is disabled', () => { - render( - - - , - ) - - expect(screen.getByText(/createGuide\.methods\.bindApp\.title/)).toBeInTheDocument() - expect(screen.queryByText(/createGuide\.methods\.importDsl\.title/)).not.toBeInTheDocument() - expect( - screen.queryByText(/createGuide\.methods\.importDsl\.description/), - ).not.toBeInTheDocument() - expect( - screen.getByRole('textbox', { name: /createGuide\.source\.sourceApp/ }), - ).toBeInTheDocument() - }) - - it('should use infinite scroll to load more source apps', () => { - Object.assign(mocks.sourceAppsQuery, { - data: { - pages: [ - { - data: [ - { - id: 'app-1', - name: 'Workflow App', - }, - ], - }, - ], - }, - hasNextPage: true, - }) - - render( - - - , - ) - - act(() => { - getInfiniteScrollObserver()?.callback( - [{ isIntersecting: true } as IntersectionObserverEntry], - {} as IntersectionObserver, - ) - }) - - expect(mocks.sourceAppsQuery.fetchNextPage).toHaveBeenCalledWith({ cancelRefetch: false }) - expect( - screen.queryByRole('button', { name: /createModal\.loadMoreApps/ }), - ).not.toBeInTheDocument() - }) -}) diff --git a/web/features/deployments/create-guide/ui/layout.tsx b/web/features/deployments/create-guide/ui/layout.tsx deleted file mode 100644 index 1e8ffecb3e6..00000000000 --- a/web/features/deployments/create-guide/ui/layout.tsx +++ /dev/null @@ -1,220 +0,0 @@ -'use client' - -import type { ReactNode } from 'react' -import type { GuideStep } from '@/features/deployments/create-guide/state/types' -import { cn } from '@langgenius/dify-ui/cn' -import { - ScrollArea, - ScrollAreaContent, - ScrollAreaScrollbar, - ScrollAreaThumb, - ScrollAreaViewport, -} from '@langgenius/dify-ui/scroll-area' -import { useTranslation } from 'react-i18next' -import { TitleTooltip } from '@/features/deployments/shared/components/title-tooltip' - -const GUIDE_PROGRESS_STEPS: GuideStep[] = ['source', 'release', 'target'] - -function GuideStepIntro({ activeStep }: { activeStep: GuideStep }) { - const { t } = useTranslation('deployments') - - let title: string - let description: string - - if (activeStep === 'source') { - title = t(($) => $['createGuide.source.title']) - description = t(($) => $['createGuide.method.description']) - } else if (activeStep === 'release') { - title = t(($) => $['createGuide.release.title']) - description = t(($) => $['createGuide.release.description']) - } else if (activeStep === 'target') { - title = t(($) => $['createGuide.target.title']) - description = t(($) => $['createGuide.target.description']) - } else { - return null - } - - return ( -
-

{title}

-

{description}

-
- ) -} - -function GuideProgress({ activeStep }: { activeStep: GuideStep }) { - const { t } = useTranslation('deployments') - const activeIndex = GUIDE_PROGRESS_STEPS.indexOf(activeStep) - - return ( -
    - {GUIDE_PROGRESS_STEPS.map((step, index) => { - const isActive = step === activeStep - const isComplete = index < activeIndex - const label = t(($) => $[`createGuide.steps.${step}`]) - - return ( - -
  1. - - {label} -
  2. -
    - ) - })} -
- ) -} - -function GuideProgressSummary({ activeStep }: { activeStep: GuideStep }) { - const { t } = useTranslation('deployments') - const activeIndex = GUIDE_PROGRESS_STEPS.indexOf(activeStep) - const activeStepNumber = activeIndex + 1 - - let activeStepLabel: string - if (activeStep === 'source') activeStepLabel = t(($) => $['createGuide.steps.source']) - else if (activeStep === 'release') activeStepLabel = t(($) => $['createGuide.steps.release']) - else if (activeStep === 'target') activeStepLabel = t(($) => $['createGuide.steps.target']) - else return null - - if (activeIndex < 0) return null - - return ( -
-
- {activeStepLabel} - - {activeStepNumber}/{GUIDE_PROGRESS_STEPS.length} - -
- -
- ) -} - -export function StepShell({ - title, - description, - descriptionClassName, - hideHeader, - className, - children, -}: { - title: string - description: string - descriptionClassName?: string - hideHeader?: boolean - className?: string - children: ReactNode -}) { - return ( -
- {!hideHeader && ( -
-

{title}

-

- {description} -

-
- )} - {children} -
- ) -} - -export function GuideCard({ - children, - actions, - contentScrollable = true, -}: { - children: ReactNode - actions: ReactNode - contentScrollable?: boolean -}) { - return ( -
- {contentScrollable ? ( - - - {children} - - - - - - ) : ( -
{children}
- )} - {actions} -
- ) -} - -export function GuideFrame({ - activeStep, - children, -}: { - activeStep: GuideStep - children: ReactNode -}) { - const { t } = useTranslation('deployments') - - return ( -
-
-
$['createGuide.title'])} - className="flex h-full w-full max-w-210 flex-col px-5 sm:px-8 lg:px-10" - > -
-
-

- {t(($) => $['createGuide.title'])} -

-
- -
-
- -
- -
- {children} -
-
-
- ) -} diff --git a/web/features/deployments/create-guide/ui/release-step.tsx b/web/features/deployments/create-guide/ui/release-step.tsx deleted file mode 100644 index b76b465a3d6..00000000000 --- a/web/features/deployments/create-guide/ui/release-step.tsx +++ /dev/null @@ -1,222 +0,0 @@ -'use client' - -import { Button } from '@langgenius/dify-ui/button' -import { Input } from '@langgenius/dify-ui/input' -import { useAtomValue, useSetAtom } from 'jotai' -import { useTranslation } from 'react-i18next' -import { - effectiveMethodAtom, - instanceDescriptionAtom, - instanceNameAtom, - releaseDescriptionAtom, - releaseNameAtom, - selectedAppAtom, - stepAtom, -} from '@/features/deployments/create-guide/state/primitives' -import { - continueFromReleaseAtom, - hasInstanceNameConflictAtom, - releaseCanGoNextAtom, - setInstanceDescriptionAtom, - setInstanceNameAtom, - setReleaseDescriptionAtom, - setReleaseNameAtom, -} from '@/features/deployments/create-guide/state/release' -import { dslDefaultAppNameAtom } from '@/features/deployments/create-guide/state/source' -import { StepShell } from './layout' - -const releaseTextareaClassName = - 'min-h-16 w-full resize-none appearance-none rounded-md border border-transparent bg-components-input-bg-normal p-2 px-3 system-sm-regular text-components-input-text-filled caret-primary-600 outline-hidden placeholder:text-components-input-text-placeholder hover:border-components-input-border-hover hover:bg-components-input-bg-hover focus:border-components-input-border-active focus:bg-components-input-bg-active focus:shadow-xs' - -export function ReleaseStepContent() { - const { t } = useTranslation('deployments') - - return ( - $['createGuide.release.title'])} - description={t(($) => $['createGuide.release.description'])} - hideHeader - > -
- - -
-
- ) -} - -function DeploymentInfoSection() { - const { t } = useTranslation('deployments') - - return ( -
-

- {t(($) => $['createGuide.release.deployInfo'])} -

- - -
- ) -} - -function InitialReleaseSection() { - const { t } = useTranslation('deployments') - - return ( -
-

- {t(($) => $['createGuide.release.firstVersion'])} -

- - -
- ) -} - -function InstanceNameField() { - const { t } = useTranslation('deployments') - const instanceName = useAtomValue(instanceNameAtom) - const setInstanceName = useSetAtom(setInstanceNameAtom) - const method = useAtomValue(effectiveMethodAtom) - const selectedApp = useAtomValue(selectedAppAtom) - const dslDefaultAppName = useAtomValue(dslDefaultAppNameAtom) - const instanceNamePlaceholder = - method === 'importDsl' - ? dslDefaultAppName || t(($) => $['createGuide.dsl.defaultAppName']) - : selectedApp?.name - const hasInstanceNameConflict = useAtomValue(hasInstanceNameConflictAtom) - const instanceNameError = hasInstanceNameConflict - ? t(($) => $['createGuide.release.instanceNameConflict']) - : undefined - const instanceNameErrorId = 'create-guide-instance-name-error' - - return ( -
- - setInstanceName(event.target.value)} - placeholder={instanceNamePlaceholder} - required - aria-invalid={instanceNameError ? true : undefined} - aria-describedby={instanceNameError ? instanceNameErrorId : undefined} - className="h-9" - /> - {instanceNameError && ( - - )} -
- ) -} - -function InstanceDescriptionField() { - const { t } = useTranslation('deployments') - const instanceDescription = useAtomValue(instanceDescriptionAtom) - const setInstanceDescription = useSetAtom(setInstanceDescriptionAtom) - - return ( -
- - {t(($) => $['createGuide.release.instanceDescription'])} - -