From dcac3ec6cff9c9e5a157302cd39ba44773e462f1 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 11 Sep 2026 23:38:40 +0530 Subject: [PATCH 01/10] feat: read-only tests and rollback on prod, two-stage gate on dev Prod had no tests at all: a release was marked last-known-good straight after deploy. Now main runs only the readonly allowlist against prod and promotes on pass or rolls back on failure; dev runs smoke, then functional only if smoke passed. api_base_url is explicit per branch -- run-smoke's api job otherwise falls back to the dev API. --- .github/workflows/deploy-Dataspace.yml | 71 ++++++++++++++++---------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/.github/workflows/deploy-Dataspace.yml b/.github/workflows/deploy-Dataspace.yml index 6d769c6a..132d87a2 100644 --- a/.github/workflows/deploy-Dataspace.yml +++ b/.github/workflows/deploy-Dataspace.yml @@ -182,21 +182,20 @@ jobs: # Production has no smoke-test job today, so its only quality gate is the # boot health check above. Mark this release good immediately so a FUTURE # deploy knows what to roll back to if it fails. - - name: Mark release as last-known-good (prod only) - if: github.ref_name == 'main' - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ vars.EC2_HOST }} - username: ${{ secrets.EC2_USERNAME }} - key: ${{ secrets.EC2_PRIVATE_KEY }} - script: echo "${{ steps.meta.outputs.release }}" > /home/ubuntu/DataExchange/releases/.last_good - smoke-tests: needs: build-and-deploy - if: github.ref_name == 'dev' uses: CivicDataLab/CivicDataSpace-test/.github/workflows/run-smoke.yml@CI + with: + # main: only the readonly allowlist ever runs against prod. + # dev: stage 1 of 2 -- functional-tests runs only if this passes. + suite: ${{ github.ref_name == 'main' && 'readonly' || 'smoke' }} + # Explicit: run-smoke's api job falls back to the dev API when this is + # unset, which would test dev's backend while gating a prod deploy. + api_base_url: ${{ github.ref_name == 'main' && 'https://api.datakeep.civicdays.in' || 'https://dev.api.civicdataspace.in' }} secrets: - HOME_URL_DEV: ${{ secrets.HOME_URL_DEV }} + # Literal, not a secret, so a missing value can't silently fall back to + # the dev site through the `&& ||` idiom. + HOME_URL_DEV: ${{ github.ref_name == 'main' && 'https://civicdataspace.in' || secrets.HOME_URL_DEV }} TEST_EMAIL_1: ${{ secrets.TEST_EMAIL_1 }} TEST_PASSWORD_1: ${{ secrets.TEST_PASSWORD_1 }} TEST_EMAIL_2: ${{ secrets.TEST_EMAIL_2 }} @@ -206,18 +205,36 @@ jobs: # and the job fails its preflight. KEYCLOAK_CLIENT_SECRET: ${{ secrets.KEYCLOAK_CLIENT_SECRET }} - # dev only: smoke tests are the real quality gate here. Only once they pass - # does this release become the thing a future rollback would target. - promote-dev: - needs: [build-and-deploy, smoke-tests] - if: github.ref_name == 'dev' && needs.smoke-tests.result == 'success' + functional-tests: + needs: smoke-tests + # dev only, stage 2: runs only once smoke has passed. Functional tests + # write (datasets, use cases), so they never run against prod. + if: github.ref_name != 'main' + uses: CivicDataLab/CivicDataSpace-test/.github/workflows/run-smoke.yml@CI + with: + suite: functional + api_base_url: https://dev.api.civicdataspace.in + secrets: + HOME_URL_DEV: ${{ secrets.HOME_URL_DEV }} + TEST_EMAIL_1: ${{ secrets.TEST_EMAIL_1 }} + TEST_PASSWORD_1: ${{ secrets.TEST_PASSWORD_1 }} + TEST_EMAIL_2: ${{ secrets.TEST_EMAIL_2 }} + TEST_PASSWORD_2: ${{ secrets.TEST_PASSWORD_2 }} + KEYCLOAK_CLIENT_SECRET: ${{ secrets.KEYCLOAK_CLIENT_SECRET }} + + # Only once the tests pass does this release become the thing a future + # rollback would target -- on prod too, which used to be marked + # last-known-good straight after deploy with no tests at all. + promote: + needs: [build-and-deploy, smoke-tests, functional-tests] + # Not success(): on main functional-tests is skipped by design, and a + # skipped need would skip this job too. Nothing failed + smoke passed. + if: ${{ !failure() && !cancelled() && needs.smoke-tests.result == 'success' }} runs-on: ubuntu-latest - # vars.EC2_HOST is an environment-scoped variable (Settings -> Environments - # -> development), only visible to jobs that declare `environment:`. - # Without this, appleboy/ssh-action fails with "missing server host" - # (found via a live run - build-and-deploy/smoke-tests both succeeded but - # this job still failed, which is why `if:` here is already gated to dev). - environment: development + # vars.EC2_HOST is an environment-scoped variable, only visible to jobs + # that declare `environment:` -- without it appleboy/ssh-action fails + # with "missing server host". + environment: ${{ github.ref_name == 'main' && 'production' || 'development' }} steps: - name: Mark this release as last-known-good uses: appleboy/ssh-action@v1.0.3 @@ -227,13 +244,13 @@ jobs: key: ${{ secrets.EC2_PRIVATE_KEY }} script: echo "${{ needs.build-and-deploy.outputs.release }}" > /home/ubuntu/DataExchange/releases/.last_good - rollback-dev: - needs: [build-and-deploy, smoke-tests] - if: github.ref_name == 'dev' && needs.smoke-tests.result == 'failure' + rollback: + needs: [build-and-deploy, smoke-tests, functional-tests] + if: failure() && needs.build-and-deploy.result == 'success' runs-on: ubuntu-latest - # Same environment-scoped vars.EC2_HOST issue as promote-dev - see the + # Same environment-scoped vars.EC2_HOST issue as promote - see the # comment there. - environment: development + environment: ${{ github.ref_name == 'main' && 'production' || 'development' }} steps: - name: Revert to last known-good release uses: appleboy/ssh-action@v1.0.3 From 1d8bcd25717934c0c9381cbfaf9af7d5fb252ff5 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 11 Sep 2026 23:38:40 +0530 Subject: [PATCH 02/10] feat: full-suite PR gate against dev for PRs into main --- .github/workflows/pr-gate.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/pr-gate.yml diff --git a/.github/workflows/pr-gate.yml b/.github/workflows/pr-gate.yml new file mode 100644 index 00000000..d11ea871 --- /dev/null +++ b/.github/workflows/pr-gate.yml @@ -0,0 +1,27 @@ +# Full test suite against dev for every PR into main. Required by branch +# protection on main, so a PR merges (and prod deploys) only once it's green. +# dev already runs the PR's code (it was merged to dev first). +name: PR Gate + +on: + pull_request: + branches: [main] + +concurrency: + group: pr-gate-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + full-suite: + name: Full Suite (dev) + uses: CivicDataLab/CivicDataSpace-test/.github/workflows/run-smoke.yml@CI + with: + suite: full + api_base_url: https://dev.api.civicdataspace.in + secrets: + HOME_URL_DEV: ${{ secrets.HOME_URL_DEV }} + TEST_EMAIL_1: ${{ secrets.TEST_EMAIL_1 }} + TEST_PASSWORD_1: ${{ secrets.TEST_PASSWORD_1 }} + TEST_EMAIL_2: ${{ secrets.TEST_EMAIL_2 }} + TEST_PASSWORD_2: ${{ secrets.TEST_PASSWORD_2 }} + KEYCLOAK_CLIENT_SECRET: ${{ secrets.KEYCLOAK_CLIENT_SECRET }} From 9f5379375d364bf71235e7a8d504234da8be16e8 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 15 Sep 2026 11:35:12 +0530 Subject: [PATCH 03/10] feat: run provider functional report-only while it is flaky against dev Provider functional tests fail at shifting points against dev (autosave races, slow create flows), so gating on them would roll back dev deploys spuriously. The gating functional stage and the PR gate now skip provider; separate provider jobs run with only_provider and report, without being a dependency of rollback/promote or a required check. --- .github/workflows/deploy-Dataspace.yml | 21 +++++++++++++++++++++ .github/workflows/pr-gate.yml | 16 ++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/.github/workflows/deploy-Dataspace.yml b/.github/workflows/deploy-Dataspace.yml index 132d87a2..3372b293 100644 --- a/.github/workflows/deploy-Dataspace.yml +++ b/.github/workflows/deploy-Dataspace.yml @@ -214,6 +214,9 @@ jobs: with: suite: functional api_base_url: https://dev.api.civicdataspace.in + # Provider functional is flaky against dev, so it runs report-only in + # provider-functional-report instead of gating the deploy. + skip_provider: true secrets: HOME_URL_DEV: ${{ secrets.HOME_URL_DEV }} TEST_EMAIL_1: ${{ secrets.TEST_EMAIL_1 }} @@ -222,6 +225,24 @@ jobs: TEST_PASSWORD_2: ${{ secrets.TEST_PASSWORD_2 }} KEYCLOAK_CLIENT_SECRET: ${{ secrets.KEYCLOAK_CLIENT_SECRET }} + provider-functional-report: + name: Provider Functional (report-only) + needs: smoke-tests + # Report-only: promote and rollback do not list this job in needs, so its + # result never rolls back or blocks a dev deploy. Promote it to a gate + # once the provider suite is stable against dev. + if: github.ref_name != 'main' + uses: CivicDataLab/CivicDataSpace-test/.github/workflows/run-smoke.yml@CI + with: + suite: functional + only_provider: true + secrets: + HOME_URL_DEV: ${{ secrets.HOME_URL_DEV }} + TEST_EMAIL_1: ${{ secrets.TEST_EMAIL_1 }} + TEST_PASSWORD_1: ${{ secrets.TEST_PASSWORD_1 }} + TEST_EMAIL_2: ${{ secrets.TEST_EMAIL_2 }} + TEST_PASSWORD_2: ${{ secrets.TEST_PASSWORD_2 }} + # Only once the tests pass does this release become the thing a future # rollback would target -- on prod too, which used to be marked # last-known-good straight after deploy with no tests at all. diff --git a/.github/workflows/pr-gate.yml b/.github/workflows/pr-gate.yml index d11ea871..06ed599e 100644 --- a/.github/workflows/pr-gate.yml +++ b/.github/workflows/pr-gate.yml @@ -17,6 +17,9 @@ jobs: uses: CivicDataLab/CivicDataSpace-test/.github/workflows/run-smoke.yml@CI with: suite: full + # Provider is flaky against dev: it reports in provider-report below, which + # branch protection does not require. + skip_provider: true api_base_url: https://dev.api.civicdataspace.in secrets: HOME_URL_DEV: ${{ secrets.HOME_URL_DEV }} @@ -25,3 +28,16 @@ jobs: TEST_EMAIL_2: ${{ secrets.TEST_EMAIL_2 }} TEST_PASSWORD_2: ${{ secrets.TEST_PASSWORD_2 }} KEYCLOAK_CLIENT_SECRET: ${{ secrets.KEYCLOAK_CLIENT_SECRET }} + + provider-report: + name: Provider Full Suite (report-only) + uses: CivicDataLab/CivicDataSpace-test/.github/workflows/run-smoke.yml@CI + with: + suite: full + only_provider: true + secrets: + HOME_URL_DEV: ${{ secrets.HOME_URL_DEV }} + TEST_EMAIL_1: ${{ secrets.TEST_EMAIL_1 }} + TEST_PASSWORD_1: ${{ secrets.TEST_PASSWORD_1 }} + TEST_EMAIL_2: ${{ secrets.TEST_EMAIL_2 }} + TEST_PASSWORD_2: ${{ secrets.TEST_PASSWORD_2 }} From 7403543334960c7b70a3139335128762cc29f468 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 15 Sep 2026 12:25:58 +0530 Subject: [PATCH 04/10] fix: wait for every test before finalizing or rolling back Finalize and rollback did not list the report-only provider job in needs, so a deploy asked for finalize approval while provider tests were still running. Both now wait for it, and condition on the gating jobs' results explicitly instead of failure()/!failure(), which would count the report-only job's result. --- .github/workflows/deploy-Dataspace.yml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-Dataspace.yml b/.github/workflows/deploy-Dataspace.yml index 3372b293..59e773d1 100644 --- a/.github/workflows/deploy-Dataspace.yml +++ b/.github/workflows/deploy-Dataspace.yml @@ -247,10 +247,15 @@ jobs: # rollback would target -- on prod too, which used to be marked # last-known-good straight after deploy with no tests at all. promote: - needs: [build-and-deploy, smoke-tests, functional-tests] - # Not success(): on main functional-tests is skipped by design, and a - # skipped need would skip this job too. Nothing failed + smoke passed. - if: ${{ !failure() && !cancelled() && needs.smoke-tests.result == 'success' }} + # provider-functional-report is listed so this waits for every test to + # finish, but its result is deliberately not checked (report-only). + needs: [build-and-deploy, smoke-tests, functional-tests, provider-functional-report] + # Explicit gating results, not success()/!failure(): on main the functional + # jobs are skipped by design, and the provider job must not block. + if: >- + ${{ !cancelled() && needs.build-and-deploy.result == 'success' && + needs.smoke-tests.result == 'success' && + (needs.functional-tests.result == 'success' || needs.functional-tests.result == 'skipped') }} runs-on: ubuntu-latest # vars.EC2_HOST is an environment-scoped variable, only visible to jobs # that declare `environment:` -- without it appleboy/ssh-action fails @@ -266,8 +271,13 @@ jobs: script: echo "${{ needs.build-and-deploy.outputs.release }}" > /home/ubuntu/DataExchange/releases/.last_good rollback: - needs: [build-and-deploy, smoke-tests, functional-tests] - if: failure() && needs.build-and-deploy.result == 'success' + # Waits for the report-only provider job too, without checking its result. + needs: [build-and-deploy, smoke-tests, functional-tests, provider-functional-report] + # Explicit gating results, not failure(): failure() would also count the + # report-only provider job. + if: >- + ${{ !cancelled() && needs.build-and-deploy.result == 'success' && + (needs.smoke-tests.result == 'failure' || needs.functional-tests.result == 'failure') }} runs-on: ubuntu-latest # Same environment-scoped vars.EC2_HOST issue as promote - see the # comment there. From 1348e396d81bd212a6bdfe2068bfda5ddf177bb9 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 15 Sep 2026 14:25:18 +0530 Subject: [PATCH 05/10] fix: run prod finalize/rollback in production-ops, without a second approval production requires a reviewer, and every job referencing it asked again: approve the deploy, then approve finalize or rollback -- so a failing prod deploy stayed live until someone clicked. production-ops holds the same host and SSH secrets with no reviewer and a main-only branch policy; the human approval stays on the deploy job. --- .github/workflows/deploy-Dataspace.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-Dataspace.yml b/.github/workflows/deploy-Dataspace.yml index 59e773d1..f90c4924 100644 --- a/.github/workflows/deploy-Dataspace.yml +++ b/.github/workflows/deploy-Dataspace.yml @@ -260,7 +260,9 @@ jobs: # vars.EC2_HOST is an environment-scoped variable, only visible to jobs # that declare `environment:` -- without it appleboy/ssh-action fails # with "missing server host". - environment: ${{ github.ref_name == 'main' && 'production' || 'development' }} + # production-ops: same prod host/SSH secrets, no required reviewer, main-only. + # The human approval is on the deploy job; finalize/rollback must not wait. + environment: ${{ github.ref_name == 'main' && 'production-ops' || 'development' }} steps: - name: Mark this release as last-known-good uses: appleboy/ssh-action@v1.0.3 @@ -281,7 +283,9 @@ jobs: runs-on: ubuntu-latest # Same environment-scoped vars.EC2_HOST issue as promote - see the # comment there. - environment: ${{ github.ref_name == 'main' && 'production' || 'development' }} + # production-ops: same prod host/SSH secrets, no required reviewer, main-only. + # The human approval is on the deploy job; finalize/rollback must not wait. + environment: ${{ github.ref_name == 'main' && 'production-ops' || 'development' }} steps: - name: Revert to last known-good release uses: appleboy/ssh-action@v1.0.3 From 71fe86d890441abd41c5f1d59f6d25703af5d09a Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 15 Sep 2026 14:39:21 +0530 Subject: [PATCH 06/10] Revert "fix: run prod finalize/rollback in production-ops, without a second approval" This reverts commit 1348e396d81bd212a6bdfe2068bfda5ddf177bb9. --- .github/workflows/deploy-Dataspace.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-Dataspace.yml b/.github/workflows/deploy-Dataspace.yml index f90c4924..59e773d1 100644 --- a/.github/workflows/deploy-Dataspace.yml +++ b/.github/workflows/deploy-Dataspace.yml @@ -260,9 +260,7 @@ jobs: # vars.EC2_HOST is an environment-scoped variable, only visible to jobs # that declare `environment:` -- without it appleboy/ssh-action fails # with "missing server host". - # production-ops: same prod host/SSH secrets, no required reviewer, main-only. - # The human approval is on the deploy job; finalize/rollback must not wait. - environment: ${{ github.ref_name == 'main' && 'production-ops' || 'development' }} + environment: ${{ github.ref_name == 'main' && 'production' || 'development' }} steps: - name: Mark this release as last-known-good uses: appleboy/ssh-action@v1.0.3 @@ -283,9 +281,7 @@ jobs: runs-on: ubuntu-latest # Same environment-scoped vars.EC2_HOST issue as promote - see the # comment there. - # production-ops: same prod host/SSH secrets, no required reviewer, main-only. - # The human approval is on the deploy job; finalize/rollback must not wait. - environment: ${{ github.ref_name == 'main' && 'production-ops' || 'development' }} + environment: ${{ github.ref_name == 'main' && 'production' || 'development' }} steps: - name: Revert to last known-good release uses: appleboy/ssh-action@v1.0.3 From f6db7b179aaa8c3d97d6054ee868a5b810307c54 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 15 Sep 2026 18:43:38 +0530 Subject: [PATCH 07/10] fix: give report-only provider jobs the Keycloak secret (#462) * fix: pass KEYCLOAK_CLIENT_SECRET to the report-only provider job org_add_permission needs a Keycloak token to check canAdd. A called workflow only sees secrets its caller passes, and the report-only job passed none, so every org-create test skipped on both workers -- the provider report looked green while never exercising org flows. * fix: pass KEYCLOAK_CLIENT_SECRET to the report-only provider-report job Same gap as the deploy workflow's report job: without it every org-create test skips on both workers. --- .github/workflows/deploy-Dataspace.yml | 4 ++++ .github/workflows/pr-gate.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/deploy-Dataspace.yml b/.github/workflows/deploy-Dataspace.yml index 59e773d1..d58d89b6 100644 --- a/.github/workflows/deploy-Dataspace.yml +++ b/.github/workflows/deploy-Dataspace.yml @@ -236,12 +236,16 @@ jobs: with: suite: functional only_provider: true + api_base_url: https://dev.api.civicdataspace.in secrets: HOME_URL_DEV: ${{ secrets.HOME_URL_DEV }} TEST_EMAIL_1: ${{ secrets.TEST_EMAIL_1 }} TEST_PASSWORD_1: ${{ secrets.TEST_PASSWORD_1 }} TEST_EMAIL_2: ${{ secrets.TEST_EMAIL_2 }} TEST_PASSWORD_2: ${{ secrets.TEST_PASSWORD_2 }} + # org_add_permission needs a token to check canAdd; without this every + # org-create test skips on both workers and the report is hollow. + KEYCLOAK_CLIENT_SECRET: ${{ secrets.KEYCLOAK_CLIENT_SECRET }} # Only once the tests pass does this release become the thing a future # rollback would target -- on prod too, which used to be marked diff --git a/.github/workflows/pr-gate.yml b/.github/workflows/pr-gate.yml index 06ed599e..f68c08a0 100644 --- a/.github/workflows/pr-gate.yml +++ b/.github/workflows/pr-gate.yml @@ -35,9 +35,13 @@ jobs: with: suite: full only_provider: true + api_base_url: https://dev.api.civicdataspace.in secrets: HOME_URL_DEV: ${{ secrets.HOME_URL_DEV }} TEST_EMAIL_1: ${{ secrets.TEST_EMAIL_1 }} TEST_PASSWORD_1: ${{ secrets.TEST_PASSWORD_1 }} TEST_EMAIL_2: ${{ secrets.TEST_EMAIL_2 }} TEST_PASSWORD_2: ${{ secrets.TEST_PASSWORD_2 }} + # org_add_permission needs a token to check canAdd; without this every + # org-create test skips on both workers and the report is hollow. + KEYCLOAK_CLIENT_SECRET: ${{ secrets.KEYCLOAK_CLIENT_SECRET }} From b3307dcf83140e3c0509781d23c58b375eaf76f9 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 16 Sep 2026 10:19:36 +0530 Subject: [PATCH 08/10] fix: build prompt metadata dropdowns from codegen enums The four __type introspection queries fired together on form load; when the backend was slow the browser aborted them and staleTime: Infinity cached the failure, leaving Task Type / Domain / Target Languages / Target Model Types empty until a reload. Enum values are fixed at build time, so read them from the generated enums instead. --- .../[id]/edit/components/EditMetadata.tsx | 134 ++++-------------- lib/enumValues.ts | 8 ++ 2 files changed, 37 insertions(+), 105 deletions(-) create mode 100644 lib/enumValues.ts diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/components/EditMetadata.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/components/EditMetadata.tsx index e258be5a..097f05b6 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/components/EditMetadata.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/components/EditMetadata.tsx @@ -5,6 +5,10 @@ import { useParams } from 'next/navigation'; import { graphql } from '@/gql'; import { MetadataModels, + PromptDomain, + PromptTaskType, + TargetLanguage, + TargetModelType, UpdateMetadataInput, UpdatePromptMetadataInput, } from '@/gql/generated/graphql'; @@ -21,6 +25,7 @@ import { } from 'opub-ui'; import { GraphQL } from '@/lib/api'; +import { enumValues } from '@/lib/enumValues'; import { RichTextEditor } from '@/components/RichTextEditor'; import DatasetLoading from '../../../components/loading-dataset'; import { useDatasetEditStatus } from '../context'; @@ -113,54 +118,6 @@ const metadataQueryDoc = graphql(` } `); -// Introspection query to get PromptTaskType enum values from schema -const promptTaskTypeEnumQuery = graphql(` - query PromptTaskTypeEnum { - __type(name: "PromptTaskType") { - enumValues { - name - description - } - } - } -`); - -// Introspection query to get PromptDomain enum values from schema -const promptDomainEnumQuery = graphql(` - query PromptDomainEnum { - __type(name: "PromptDomain") { - enumValues { - name - description - } - } - } -`); - -// Introspection query to get TargetLanguage enum values from schema -const targetLanguageEnumQuery = graphql(` - query TargetLanguageEnum { - __type(name: "TargetLanguage") { - enumValues { - name - description - } - } - } -`); - -// Introspection query to get TargetModelType enum values from schema -const targetModelTypeEnumQuery = graphql(` - query TargetModelTypeEnum { - __type(name: "TargetModelType") { - enumValues { - name - description - } - } - } -`); - // Mutation to update prompt-specific metadata const updatePromptMetadataMutationDoc = graphql(` mutation UpdatePromptMetadata($updateInput: UpdatePromptMetadataInput!) { @@ -362,31 +319,6 @@ export function EditMetadata({ id }: { id: string }) { ) ); - // Fetch PromptTaskType enum values from GraphQL schema - const getPromptTaskTypeEnum = useQuery( - ['prompt_task_type_enum'], - () => GraphQL(promptTaskTypeEnumQuery), - { staleTime: Infinity } - ); - - const getPromptDomainEnum = useQuery( - ['prompt_domain_enum'], - () => GraphQL(promptDomainEnumQuery), - { staleTime: Infinity } - ); - - const getTargetLanguageEnum = useQuery( - ['target_language_enum'], - () => GraphQL(targetLanguageEnumQuery), - { staleTime: Infinity } - ); - - const getTargetModelTypeEnum = useQuery( - ['target_model_type_enum'], - () => GraphQL(targetModelTypeEnumQuery), - { staleTime: Infinity } - ); - const [isTagsListUpdated, setIsTagsListUpdated] = useState(false); // State for prompt metadata fields @@ -975,14 +907,12 @@ export function EditMetadata({ id }: { id: string }) { label="Task Type" displaySelected list={ - getPromptTaskTypeEnum.data?.__type?.enumValues?.map( - (enumValue) => ({ - label: enumValue.name - .replace(/_/g, ' ') - .replace(/\b\w/g, (c: string) => c.toUpperCase()), - value: enumValue.name, - }) - ) || [] + enumValues(PromptTaskType).map((name) => ({ + label: name + .replace(/_/g, ' ') + .replace(/\b\w/g, (c: string) => c.toUpperCase()), + value: name, + })) } selectedValue={ promptMetadataState.taskType @@ -1010,14 +940,12 @@ export function EditMetadata({ id }: { id: string }) { label="Domain" displaySelected list={ - getPromptDomainEnum.data?.__type?.enumValues?.map( - (enumValue) => ({ - label: enumValue.name - .replace(/_/g, ' ') - .replace(/\b\w/g, (c: string) => c.toUpperCase()), - value: enumValue.name, - }) - ) || [] + enumValues(PromptDomain).map((name) => ({ + label: name + .replace(/_/g, ' ') + .replace(/\b\w/g, (c: string) => c.toUpperCase()), + value: name, + })) } selectedValue={ promptMetadataState.domain @@ -1046,14 +974,12 @@ export function EditMetadata({ id }: { id: string }) { displaySelected creatable list={ - getTargetLanguageEnum.data?.__type?.enumValues?.map( - (enumValue) => ({ - label: enumValue.name - .replace(/_/g, ' ') - .replace(/\b\w/g, (c: string) => c.toUpperCase()), - value: enumValue.name, - }) - ) || [] + enumValues(TargetLanguage).map((name) => ({ + label: name + .replace(/_/g, ' ') + .replace(/\b\w/g, (c: string) => c.toUpperCase()), + value: name, + })) } selectedValue={ promptMetadataState.targetLanguages?.map( @@ -1078,14 +1004,12 @@ export function EditMetadata({ id }: { id: string }) { displaySelected creatable list={ - getTargetModelTypeEnum.data?.__type?.enumValues?.map( - (enumValue) => ({ - label: enumValue.name - .replace(/_/g, ' ') - .replace(/\b\w/g, (c: string) => c.toUpperCase()), - value: enumValue.name, - }) - ) || [] + enumValues(TargetModelType).map((name) => ({ + label: name + .replace(/_/g, ' ') + .replace(/\b\w/g, (c: string) => c.toUpperCase()), + value: name, + })) } selectedValue={ promptMetadataState.targetModelTypes?.map( diff --git a/lib/enumValues.ts b/lib/enumValues.ts new file mode 100644 index 00000000..c12fd03c --- /dev/null +++ b/lib/enumValues.ts @@ -0,0 +1,8 @@ +/** + * Schema enums come from codegen, not from a runtime `__type` introspection + * query: the values are fixed at build time, and four introspection queries + * fired together on form load could be aborted by the browser, leaving the + * dropdowns permanently empty because the failure was cached. + */ +export const enumValues = (schemaEnum: Record): string[] => + Object.values(schemaEnum); From 72222d88dd54a9ea435e549ed4bddd084afa7b47 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 16 Sep 2026 10:19:42 +0530 Subject: [PATCH 09/10] fix: build the Prompt Format dropdown from the codegen enum Same abort-and-cache failure as the metadata form; also drops a debug effect that logged the whole enum payload to the console. --- .../resources/components/EditResource.tsx | 45 ++++--------------- 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/resources/components/EditResource.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/resources/components/EditResource.tsx index 28a15474..2ba88ffc 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/resources/components/EditResource.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/resources/components/EditResource.tsx @@ -3,6 +3,7 @@ import { useParams } from 'next/navigation'; import { graphql } from '@/gql'; import { CreateFileResourceInput, + PromptFormat, SchemaUpdateInput, UpdateFileResourceInput, UpdatePromptResourceInput, @@ -21,6 +22,7 @@ import { } from 'opub-ui'; import { GraphQL } from '@/lib/api'; +import { enumValues } from '@/lib/enumValues'; import { Loading } from '@/components/loading'; import PdfPreview from '../../../../../../../../(user)/components/PdfPreview'; import { useDatasetEditStatus } from '../../context'; @@ -93,18 +95,6 @@ const resourceDetails = graphql(` } `); -// Introspection query to get PromptFormat enum values from schema -const promptFormatEnumQuery = graphql(` - query PromptFormatEnumResource { - __type(name: "PromptFormat") { - enumValues { - name - description - } - } - } -`); - // Mutation to update prompt resource metadata const updatePromptResourceMutationDoc = graphql(` mutation UpdatePromptResource($updateInput: UpdatePromptResourceInput!) { @@ -407,23 +397,6 @@ export const EditResource = ({ } } - // Fetch PromptFormat enum values from GraphQL schema - const getPromptFormatEnum = useQuery( - ['prompt_format_enum_resource'], - () => GraphQL(promptFormatEnumQuery), - { staleTime: Infinity, enabled: isPromptDataset } - ); - - // Debug: Log enum data when it changes - React.useEffect(() => { - if (getPromptFormatEnum.data) { - console.log( - 'PromptFormat enum raw data:', - JSON.stringify(getPromptFormatEnum.data, null, 2) - ); - } - }, [getPromptFormatEnum.data]); - // Mutation for updating prompt resource metadata const updatePromptResourceMutation = useMutation( (data: { updateInput: UpdatePromptResourceInput }) => @@ -636,14 +609,12 @@ export const EditResource = ({ label="Prompt Format" displaySelected list={ - getPromptFormatEnum.data?.__type?.enumValues?.map( - (enumValue) => ({ - label: enumValue.name - .replace(/_/g, ' ') - .replace(/\b\w/g, (c: string) => c.toUpperCase()), - value: enumValue.name, - }) - ) || [] + enumValues(PromptFormat).map((name) => ({ + label: name + .replace(/_/g, ' ') + .replace(/\b\w/g, (c: string) => c.toUpperCase()), + value: name, + })) } selectedValue={promptFormat ? promptFormat : ''} onChange={(value) => { From 0b45273a19cccfa43e257f0f24a3c5d2cd4636ab Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 16 Sep 2026 10:19:42 +0530 Subject: [PATCH 10/10] fix: build the AI model domain dropdown from the codegen enum --- .../aimodels/edit/[id]/details/page.tsx | 35 +++++-------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/aimodels/edit/[id]/details/page.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/aimodels/edit/[id]/details/page.tsx index 579dc0fa..f4fa25f0 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/aimodels/edit/[id]/details/page.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/aimodels/edit/[id]/details/page.tsx @@ -18,6 +18,7 @@ import { } from 'opub-ui'; import { GraphQL } from '@/lib/api'; +import { enumValues } from '@/lib/enumValues'; import RichTextEditor from '@/components/RichTextEditor/RichTextEditor'; import { useEditStatus } from '../../context'; @@ -170,17 +171,6 @@ const geographiesListQueryDoc = graphql(` } `); -const promptDomainEnumValuesQueryDoc = graphql(` - query PromptDomainEnum { - __type(name: "PromptDomain") { - enumValues { - name - description - } - } - } -`); - const FetchAIModelDetails = graphql(` query AIModelDetails($filters: AIModelFilter) { aiModels(filters: $filters) { @@ -297,11 +287,6 @@ export default function AIModelDetailsPage() { ) ); - const getPromptDomainEnumValues = - useQuery([`prompt_domain_enum_values_query`], () => - GraphQL(promptDomainEnumValuesQueryDoc, {}) - ); - const AIModelData = useQuery( [ `fetch_AIModelDetails`, @@ -477,16 +462,14 @@ export default function AIModelDetailsPage() { const domainOptions = [ { label: 'Click to select from dropdown', value: '' }, - ...( - getPromptDomainEnumValues.data?.__type?.enumValues?.map((item: { name: string }) => ({ - label: item.name - .toLowerCase() - .split('_') - .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' '), - value: item.name, - })) || [] - ), + ...enumValues(PromptDomain).map((name) => ({ + label: name + .toLowerCase() + .split('_') + .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '), + value: name, + })), ]; const modelTypeOptions = [