diff --git a/app/web/src/api/sdf/dal/schema.ts b/app/web/src/api/sdf/dal/schema.ts index c84e428920..35a2689b7b 100644 --- a/app/web/src/api/sdf/dal/schema.ts +++ b/app/web/src/api/sdf/dal/schema.ts @@ -65,6 +65,7 @@ export interface SchemaVariant { inputSockets: InputSocket[]; outputSockets: OutputSocket[]; props: Prop[]; + canCreateNewComponents: boolean; } export const outputSocketsAndPropsFor = (schemaVariant: SchemaVariant) => { diff --git a/app/web/src/components/AssetCard.vue b/app/web/src/components/AssetCard.vue index 753c39b698..67ceb7042f 100644 --- a/app/web/src/components/AssetCard.vue +++ b/app/web/src/components/AssetCard.vue @@ -49,11 +49,7 @@ />
(() => { const unlockedExists = assetStore.variantList.some( diff --git a/app/web/src/components/AssetDetailsPanel.vue b/app/web/src/components/AssetDetailsPanel.vue index 7d1598d6c5..fe710a5c64 100644 --- a/app/web/src/components/AssetDetailsPanel.vue +++ b/app/web/src/components/AssetDetailsPanel.vue @@ -10,7 +10,7 @@ class="flex flex-row items-center justify-around gap-xs p-xs border-b dark:border-neutral-600" > (); -const ffStore = useFeatureFlagsStore(); const assetStore = useAssetStore(); const loadAssetReqStatus = assetStore.getRequestStatus( "LOAD_SCHEMA_VARIANT", @@ -256,7 +254,12 @@ const saveAssetReqStatus = assetStore.getRequestStatus( ); const executeAsset = async () => { if (editingAsset.value) { - await assetStore.REGENERATE_VARIANT(editingAsset.value.schemaVariantId); + const resp = await assetStore.REGENERATE_VARIANT( + editingAsset.value.schemaVariantId, + ); + if (resp.result.success) { + assetStore.setSchemaVariantSelection(resp.result.data.schemaVariantId); + } } }; @@ -272,7 +275,7 @@ const cloneAsset = async (name: string) => { ); if (result.result.success) { cloneAssetModalRef.value?.modal?.close(); - await assetStore.setSchemaVariantSelection(result.result.data.id, true); + assetStore.setSchemaVariantSelection(result.result.data.id, true); } } }; diff --git a/app/web/src/components/AssetEditor.vue b/app/web/src/components/AssetEditor.vue index 5c3ead37fc..90c52725e9 100644 --- a/app/web/src/components/AssetEditor.vue +++ b/app/web/src/components/AssetEditor.vue @@ -18,12 +18,9 @@ : `asset-${assetId}` " v-model="editingAsset" + :disabled="selectedAsset.isLocked" + :recordId="selectedAsset.schemaVariantId" :typescript="editorTs || ''" - :disabled=" - isReadOnly || - (useFeatureFlagsStore().IMMUTABLE_SCHEMA_VARIANTS && - selectedAsset.isLocked) - " @change="onChange" /> @@ -48,7 +45,6 @@ import { useAssetStore } from "@/store/asset.store"; import { useFuncStore } from "@/store/func/funcs.store"; import { useChangeSetsStore } from "@/store/change_sets.store"; import { editor_ts, loadEditorTs } from "@/utils/load_editor_ts"; -import { useFeatureFlagsStore } from "@/store/feature_flags.store"; import CodeEditor from "./CodeEditor.vue"; import AssetEditorHeader from "./AssetEditorHeader.vue"; @@ -71,10 +67,6 @@ const selectedAssetFuncCode = computed(() => { return funcStore.funcCodeById[fId]?.code; }); -const isReadOnly = computed(() => { - return false; -}); - const editingAsset = ref(selectedAssetFuncCode.value ?? ""); const loadAssetReqStatus = assetStore.getRequestStatus( @@ -100,7 +92,7 @@ watch( }, ); -const onChange = () => { +const onChange = (_: string, code: string) => { if ( !selectedAsset.value || selectedAssetFuncCode.value === editingAsset.value || @@ -115,7 +107,7 @@ const onChange = () => { { ...selectedAsset.value, }, - editingAsset.value, + code, ); }; diff --git a/app/web/src/components/AssetFuncAttachDropdown.vue b/app/web/src/components/AssetFuncAttachDropdown.vue index 0849d08e2a..22d08219c2 100644 --- a/app/web/src/components/AssetFuncAttachDropdown.vue +++ b/app/web/src/components/AssetFuncAttachDropdown.vue @@ -9,11 +9,16 @@ @click="onClick" > - + New function Existing @@ -26,8 +31,11 @@ import { PropType, ref } from "vue"; import { ApiRequestStatus } from "@si/vue-lib/pinia"; import { DropdownMenu, DropdownMenuItem } from "@si/vue-lib/design-system"; +import { useAssetStore } from "@/store/asset.store"; import IconButton from "./IconButton.vue"; +const assetStore = useAssetStore(); + defineProps({ requestStatus: { type: Object as PropType }, }); diff --git a/app/web/src/components/AssetFuncAttachModal.vue b/app/web/src/components/AssetFuncAttachModal.vue index e336c68f23..ed8ab0b0c5 100644 --- a/app/web/src/components/AssetFuncAttachModal.vue +++ b/app/web/src/components/AssetFuncAttachModal.vue @@ -119,6 +119,7 @@
diff --git a/app/web/src/components/CodeEditor.vue b/app/web/src/components/CodeEditor.vue index 074cd5816c..1250080750 100644 --- a/app/web/src/components/CodeEditor.vue +++ b/app/web/src/components/CodeEditor.vue @@ -70,6 +70,7 @@ import { const props = defineProps({ id: String, modelValue: { type: String, required: true }, + recordId: { type: String, required: true }, disabled: { type: Boolean }, json: Boolean, typescript: { type: String }, @@ -81,24 +82,25 @@ const props = defineProps({ const emit = defineEmits<{ "update:modelValue": [v: string]; blur: [v: string]; - change: [v: string]; + change: [id: string, v: string]; close: []; }>(); watch( () => props.modelValue, () => { - if (!props.id && yText) { - view.update([ - view.state.update({ - changes: { - from: 0, - to: view.state.doc.length, - insert: props.modelValue, - }, - }), - ]); - } + // always up the code editor with the new text that comes from the prop + // Note: props are read only, and will change when the selected func/variant changes + // this does not change as a result of a user typing + view.update([ + view.state.update({ + changes: { + from: 0, + to: view.state.doc.length, + insert: props.modelValue, + }, + }), + ]); }, ); @@ -130,7 +132,7 @@ function onEditorValueUpdated(update: ViewUpdate) { if (!update.docChanged) return; emit("update:modelValue", update.state.doc.toString()); - emit("change", view.state.doc.toString()); + emit("change", props.recordId, view.state.doc.toString()); const serializedState = update.view.state.toJSON({ history: historyField }); if (props.id && serializedState.history) { @@ -296,7 +298,7 @@ let wsProvider: WebsocketProvider | undefined; let yText: Y.Text | undefined; onBeforeUnmount(() => { if (view) { - emit("change", view.state.doc.toString()); + emit("change", props.recordId, view.state.doc.toString()); emit("blur", view.state.doc.toString()); wsProvider?.destroy(); } @@ -384,7 +386,7 @@ const mountEditor = async () => { }); view.contentDOM.onblur = () => { - emit("change", view.state.doc.toString()); + emit("change", props.recordId, view.state.doc.toString()); emit("blur", view.state.doc.toString()); }; }; diff --git a/app/web/src/components/FuncEditor/ActionDetails.vue b/app/web/src/components/FuncEditor/ActionDetails.vue index 78256d8fad..782101a470 100644 --- a/app/web/src/components/FuncEditor/ActionDetails.vue +++ b/app/web/src/components/FuncEditor/ActionDetails.vue @@ -5,7 +5,7 @@ id="create" v-model="isCreate" title="This action creates a resource" - :disabled="disabled" + :disabled="disabled || func?.isLocked" @update:model-value="setCreate" /> @@ -14,7 +14,7 @@ id="refresh" v-model="isRefresh" title="This action refreshes a resource" - :disabled="disabled" + :disabled="disabled || func?.isLocked" @update:model-value="setRefresh" /> @@ -23,7 +23,7 @@ id="delete" v-model="isDelete" title="This action deletes a resource" - :disabled="disabled" + :disabled="disabled || func?.isLocked" @update:model-value="setDelete" /> @@ -57,6 +57,10 @@ const isCreate = ref(false); const isDelete = ref(false); const isRefresh = ref(false); +const func = computed(() => { + return funcStore.funcsById[props.funcId]; +}); + const binding = computed(() => { const bindings = funcStore.actionBindings[props.funcId]; const binding = bindings diff --git a/app/web/src/components/FuncEditor/AttributeBindings.vue b/app/web/src/components/FuncEditor/AttributeBindings.vue index e850f600aa..ca85e4d4a1 100644 --- a/app/web/src/components/FuncEditor/AttributeBindings.vue +++ b/app/web/src/components/FuncEditor/AttributeBindings.vue @@ -5,7 +5,7 @@ class="w-full flex p-xs gap-2xs border-b dark:border-neutral-600" >
{ return binding; }); +const variant = computed(() => { + return componentStore.schemaVariantsById[ + binding.value?.schemaVariantId || "" + ]; +}); + const getPropPathFrom = ( schemaVariantId: SchemaVariantId | null, propId: PropId, @@ -166,6 +176,7 @@ const getSocketNameFrom = ( interface ExtendedBinding extends Attribute { outputDescription: string; attributePrototypeId: AttributePrototypeId; + schemaVariant: SchemaVariant | undefined; } const bindings = computed(() => { let b; @@ -175,6 +186,8 @@ const bindings = computed(() => { } b = ((b as ExtendedBinding[]) || []).filter(nonNullable); return b.map((_b) => { + _b.schemaVariant = + componentStore.schemaVariantsById[_b.schemaVariantId || ""]; if (_b.outputSocketId) { _b.outputDescription = getSocketNameFrom(_b.schemaVariantId, _b.outputSocketId) || "N/A"; diff --git a/app/web/src/components/FuncEditor/AttributeBindingsModal.vue b/app/web/src/components/FuncEditor/AttributeBindingsModal.vue index 88fd4ba642..7303af0406 100644 --- a/app/web/src/components/FuncEditor/AttributeBindingsModal.vue +++ b/app/web/src/components/FuncEditor/AttributeBindingsModal.vue @@ -15,7 +15,7 @@ @@ -67,7 +67,8 @@ import { useComponentsStore } from "@/store/components.store"; import { nilId } from "@/utils/nilId"; const componentsStore = useComponentsStore(); -const { schemaVariantOptions } = storeToRefs(componentsStore); +const { schemaVariantOptionsUnlocked, schemaVariantOptions } = + storeToRefs(componentsStore); const funcStore = useFuncStore(); diff --git a/app/web/src/components/FuncEditor/CodeGenerationDetails.vue b/app/web/src/components/FuncEditor/CodeGenerationDetails.vue index 5567bcf91b..4db01fff13 100644 --- a/app/web/src/components/FuncEditor/CodeGenerationDetails.vue +++ b/app/web/src/components/FuncEditor/CodeGenerationDetails.vue @@ -1,6 +1,6 @@ @@ -17,6 +17,10 @@ const props = defineProps<{ funcId: string; }>(); +const func = computed(() => { + return funcStore.funcsById[props.funcId]; +}); + const binding = computed(() => { const bindings = funcStore.codegenBindings[props.funcId]; const binding = bindings diff --git a/app/web/src/components/FuncEditor/FuncDetails.vue b/app/web/src/components/FuncEditor/FuncDetails.vue index 3ee29c0947..c40581ee1a 100644 --- a/app/web/src/components/FuncEditor/FuncDetails.vue +++ b/app/web/src/components/FuncEditor/FuncDetails.vue @@ -6,45 +6,51 @@ secondaryText="Select a function from the list on the left panel to view its details here." />
- +
This function is connected to other - {{ - selectedFuncSummary.kind === FuncKind.Attribute - ? "attributes" - : "assets" - }}. + {{ editingFunc.kind === FuncKind.Attribute ? "attributes" : "assets" }}. -
@@ -110,109 +110,115 @@ class="flex flex-col absolute inset-0 overflow-y-auto overflow-x-hidden border-t border-neutral-200 dark:border-neutral-600" > - +
@@ -244,16 +250,10 @@ import { VormInput, } from "@si/vue-lib/design-system"; import clsx from "clsx"; -import { - FuncKind, - FuncId, - FuncBinding, - FuncBindingKind, -} from "@/api/sdf/dal/func"; +import { FuncKind, FuncId, FuncBindingKind } from "@/api/sdf/dal/func"; import { useFuncStore } from "@/store/func/funcs.store"; import { useAssetStore } from "@/store/asset.store"; import AuthenticationDetails from "@/components/FuncEditor/AuthenticationDetails.vue"; -import { useFeatureFlagsStore } from "@/store/feature_flags.store"; import FuncArguments from "./FuncArguments.vue"; import ActionDetails from "./ActionDetails.vue"; import AttributeBindings from "./AttributeBindings.vue"; @@ -275,7 +275,6 @@ const funcDetailsTabGroupRef = ref(); const funcStore = useFuncStore(); const assetStore = useAssetStore(); -const ffStore = useFeatureFlagsStore(); const emit = defineEmits<{ (e: "expandPanel"): void; @@ -321,29 +320,22 @@ const updateFunc = () => { if (editingFunc.value) funcStore.UPDATE_FUNC(editingFunc.value); }; -// THIS FEELS UNNECESSARY NOW -const isConnectedToOtherAssetTypes = computed(() => { - if ( - selectedFuncSummary.value && - selectedFuncSummary.value.bindings.length > 0 - ) { - return selectedFuncSummary.value.bindings - .map((b: FuncBinding) => { - switch (b.bindingKind) { - case FuncBindingKind.Qualification: - case FuncBindingKind.CodeGeneration: - return !!b.schemaVariantId; - case FuncBindingKind.Action: - return !!b.schemaVariantId; - case FuncBindingKind.Attribute: - return !!b.attributePrototypeId; - default: - return false; - } - }) - .some(Boolean); +const unlock = async () => { + if (editingFunc.value?.funcId) { + const resp = await funcStore.CREATE_UNLOCKED_COPY(editingFunc.value.funcId); + if (resp.result.success) { + await assetStore.setFuncSelection(resp.result.data.summary.funcId); + } } - return false; +}; + +const isConnectedToOtherSchemas = computed(() => { + if (!editingFunc.value) return false; + + if (editingFunc.value.bindings.length === 1) return false; + + // TODO this is wrong for attribute funcs, since they can have multiple bindings on the same variant + return true; }); const execFuncReqStatus = funcStore.getRequestStatus( @@ -368,21 +360,24 @@ const detachFunc = async () => { const isDeleting = ref(false); const deleteFunc = async () => { if (!funcId.value) return; - await funcStore.DELETE_FUNC(funcId.value); + await funcStore.DELETE_UNLOCKED_FUNC(funcId.value); + assetStore.setFuncSelection(undefined); }; +/* dont think we need this anymore const hasAssociations = computed(() => { - if (selectedFuncSummary.value?.bindings.length === 0) return true; + if (editingFunc.value?.bindings.length === 0) return true; return false; }); +*/ // The parent component can allow the test panel to be enabled, but we need to dynamically enable // it based on the func kind. const enableTestPanel = computed(() => { return ( props.allowTestPanel && - selectedFuncSummary.value && - selectedFuncSummary.value.bindings + editingFunc.value && + editingFunc.value.bindings .map((b) => b.bindingKind) .filter((kind) => [ diff --git a/app/web/src/components/FuncEditor/FuncEditor.vue b/app/web/src/components/FuncEditor/FuncEditor.vue index c41188969a..57e6cc25dc 100644 --- a/app/web/src/components/FuncEditor/FuncEditor.vue +++ b/app/web/src/components/FuncEditor/FuncEditor.vue @@ -27,6 +27,8 @@ : undefined " v-model="editingFunc" + :disabled="selectedFuncSummary?.isLocked" + :recordId="selectedFuncSummary?.funcId || ''" :typescript="selectedFuncCode?.types" @change="updateFuncCode" @close="emit('close')" @@ -75,7 +77,7 @@ const loadFuncDetailsReq = funcStore.getRequestStatus( ); watch( - selectedFuncCode, + () => selectedFuncCode.value?.funcId, () => { if (!selectedFuncCode.value) { return; @@ -101,12 +103,13 @@ watch( updatedHead.value = false; }, ); -const updateFuncCode = (code: string) => { +const updateFuncCode = (funcId: string, code: string) => { if (updatedHead.value) return; + if (!funcId) return; // protecting empty string, should never happen updatedHead.value = changeSetsStore.selectedChangeSetId === changeSetsStore.headChangeSetId; - funcStore.updateFuncCode(props.funcId, code); + funcStore.updateFuncCode(funcId, code); }; const emit = defineEmits<{ diff --git a/app/web/src/components/FuncEditor/LeafInputs.vue b/app/web/src/components/FuncEditor/LeafInputs.vue index f10f4fa49a..e92e8a1b9e 100644 --- a/app/web/src/components/FuncEditor/LeafInputs.vue +++ b/app/web/src/components/FuncEditor/LeafInputs.vue @@ -4,16 +4,36 @@ Function inputs
- + Code - + Deleted At - + Domain - + Resource - +
@@ -18,6 +18,10 @@ const props = defineProps<{ disabled?: boolean; }>(); +const func = computed(() => { + return funcStore.funcsById[props.funcId]; +}); + const binding = computed(() => { const bindings = funcStore.qualificationBindings[props.funcId]; const binding = bindings diff --git a/app/web/src/store/asset.store.ts b/app/web/src/store/asset.store.ts index b4035c4139..80c3725704 100644 --- a/app/web/src/store/asset.store.ts +++ b/app/web/src/store/asset.store.ts @@ -287,10 +287,9 @@ export const useAssetStore = () => { enqueueVariantSave(schemaVariant: SchemaVariant, code: string) { this.editingFuncLatestCode[schemaVariant.schemaVariantId] = code; - if (changeSetsStore.headSelected) - return this.SAVE_SCHEMA_VARIANT(schemaVariant, code); - - this.variantsById[schemaVariant.schemaVariantId] = schemaVariant; + // don't see how this should ever happen + /* if (changeSetsStore.headSelected) + return this.SAVE_SCHEMA_VARIANT(schemaVariant, code); */ if (!assetSaveDebouncer) { assetSaveDebouncer = keyedDebouncer((id: SchemaVariantId) => { @@ -364,7 +363,9 @@ export const useAssetStore = () => { if (!code) throw new Error(`${schemaVariantId} Code does not exist`); - return new ApiRequest({ + return new ApiRequest<{ + schemaVariantId: SchemaVariantId; + }>({ method: "post", url: "/variant/regenerate_variant", keyRequestStatusBy: schemaVariantId, diff --git a/app/web/src/store/components.store.ts b/app/web/src/store/components.store.ts index 8601b3c6de..c1f9a6c307 100644 --- a/app/web/src/store/components.store.ts +++ b/app/web/src/store/components.store.ts @@ -443,6 +443,16 @@ export const useComponentsStore = (forceChangeSetId?: ChangeSetId) => { selectedComponentResource(): Resource | undefined { return this.componentResourceById[this.selectedComponentId || 0]; }, + schemaVariantOptionsUnlocked: ( + state, + ): { label: string; value: string }[] => { + return Object.values(state.schemaVariantsById) + .filter((v) => !v.isLocked) + .map((sv) => ({ + label: sv.displayName || sv.schemaName, + value: sv.schemaVariantId, + })); + }, schemaVariantOptions: (state): { label: string; value: string }[] => { return Object.values(state.schemaVariantsById).map((sv) => ({ label: sv.displayName || sv.schemaName, @@ -530,7 +540,9 @@ export const useComponentsStore = (forceChangeSetId?: ChangeSetId) => { if (!variants) return null; return { displayName: category, - schemaVariants: variants, + schemaVariants: variants.filter( + (v) => v.canCreateNewComponents, + ), }; }) .filter(nonNullable); diff --git a/app/web/src/store/feature_flags.store.ts b/app/web/src/store/feature_flags.store.ts index e2565d9d52..54ff68f09e 100644 --- a/app/web/src/store/feature_flags.store.ts +++ b/app/web/src/store/feature_flags.store.ts @@ -7,7 +7,6 @@ import { posthog } from "@/utils/posthog"; const FLAG_MAPPING = { // STORE_FLAG_NAME: "posthogFlagName", MODULES_TAB: "modules_tab", - IMMUTABLE_SCHEMA_VARIANTS: "immutable_schema_variants", }; type FeatureFlags = keyof typeof FLAG_MAPPING; diff --git a/app/web/src/store/func/funcs.store.ts b/app/web/src/store/func/funcs.store.ts index 2068bdb454..08569b2829 100644 --- a/app/web/src/store/func/funcs.store.ts +++ b/app/web/src/store/func/funcs.store.ts @@ -49,6 +49,7 @@ export type FuncExecutionLog = { export interface DeleteFuncResponse { success: boolean; + name: string; } export const useFuncStore = () => { @@ -200,17 +201,22 @@ export const useFuncStore = () => { }, }); }, - // TODO: jobelenus, doesn't exist yet... - async DELETE_FUNC(funcId: FuncId) { - return new ApiRequest({ + async CREATE_UNLOCKED_COPY(funcId: FuncId) { + return new ApiRequest<{ summary: FuncSummary; code: FuncCode }>({ method: "post", - url: "func/delete_func", - params: { - id: funcId, - ...visibility, + url: `${API_PREFIX}/${funcId}/create_unlocked_copy`, + onSuccess: (response) => { + this.funcsById[response.summary.funcId] = response.summary; + this.funcCodeById[response.code.funcId] = response.code; }, }); }, + async DELETE_UNLOCKED_FUNC(funcId: FuncId) { + return new ApiRequest({ + method: "post", + url: `${API_PREFIX}/${funcId}/delete`, + }); + }, async UPDATE_FUNC(func: FuncSummary) { if (changeSetsStore.creatingChangeSet) throw new Error("race, wait until the change set is created"); @@ -492,6 +498,24 @@ export const useFuncStore = () => { } }, }, + { + eventType: "FuncCreated", + callback: (data) => { + if (data.changeSetId !== selectedChangeSetId) return; + this.funcsById[data.funcSummary.funcId] = data.funcSummary; + const bindings = processBindings(data.funcSummary); + this.actionBindings[data.funcSummary.funcId] = + bindings.actionBindings; + this.attributeBindings[data.funcSummary.funcId] = + bindings.attributeBindings; + this.authenticationBindings[data.funcSummary.funcId] = + bindings.authenticationBindings; + this.qualificationBindings[data.funcSummary.funcId] = + bindings.qualificationBindings; + this.codegenBindings[data.funcSummary.funcId] = + bindings.codegenBindings; + }, + }, { eventType: "FuncUpdated", callback: (data) => { diff --git a/lib/sdf-server/src/server/service/variant/regenerate_variant.rs b/lib/sdf-server/src/server/service/variant/regenerate_variant.rs index a4f32c2c0f..5561ecd099 100644 --- a/lib/sdf-server/src/server/service/variant/regenerate_variant.rs +++ b/lib/sdf-server/src/server/service/variant/regenerate_variant.rs @@ -3,8 +3,8 @@ use axum::{response::IntoResponse, Json}; use serde::{Deserialize, Serialize}; use dal::schema::variant::authoring::VariantAuthoringClient; -use dal::Visibility; use dal::{ChangeSet, WsEvent}; +use dal::{SchemaVariantId, Visibility}; use crate::server::extract::{AccessBuilder, HandlerContext, PosthogClient}; use crate::server::tracking::track; @@ -12,7 +12,7 @@ use crate::service::variant::SchemaVariantResult; #[derive(Deserialize, Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] -pub struct UpdateVariantRequest { +pub struct RegenerateVariantRequest { // We need to get the updated data here, to ensure we create the prop the user is seeing pub variant: si_frontend_types::SchemaVariant, pub code: String, @@ -20,16 +20,22 @@ pub struct UpdateVariantRequest { pub visibility: Visibility, } +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct RegenerateVariantResponse { + pub schema_variant_id: SchemaVariantId, +} + pub async fn regenerate_variant( HandlerContext(builder): HandlerContext, AccessBuilder(request_ctx): AccessBuilder, PosthogClient(posthog_client): PosthogClient, OriginalUri(original_uri): OriginalUri, - Json(UpdateVariantRequest { + Json(RegenerateVariantRequest { variant, code, visibility, - }): Json, + }): Json, ) -> SchemaVariantResult { let mut ctx = builder.build(request_ctx.build(visibility)).await?; @@ -77,5 +83,10 @@ pub async fn regenerate_variant( if let Some(force_change_set_id) = force_change_set_id { response = response.header("force_change_set_id", force_change_set_id.to_string()); } - Ok(response.body(axum::body::Empty::new())?) + + Ok( + response.body(serde_json::to_string(&RegenerateVariantResponse { + schema_variant_id: updated_schema_variant_id, + })?)?, + ) }