617f571400
## Summary - Converts applicationVariable from a bespoke sync path to a proper SyncableEntity, unifying it with the workspace migration pipeline used by all other manifest-managed entities (agent, skill, frontComponent, webhook, etc.) - Removes the upsertManyApplicationVariableEntities method and its direct-DB-mutation approach in favor of the standard validate → build → run action handler pipeline - Adds universalIdentifier, deletedAt columns and makes applicationId NOT NULL via an instance command migration ## Motivation Before this change, applicationVariable was the only manifest-managed entity that bypassed ApplicationManifestMigrationService.syncMetadataFromManifest(). It used a bespoke service method called directly from syncApplication(), creating two mental models, two validation styles, and two cache invalidation patterns. Now there's one unified pipeline for all manifest entities. ## What changed ### Entity refactor: - ApplicationVariableEntity now extends SyncableEntity (gains universalIdentifier, non-nullable applicationId with CASCADE, soft-delete via deletedAt) ### New flat entity layer (flat-application-variable/): - Type, maps type, editable properties constant, entity-to-flat converter, cache service, module ### New migration pipeline wiring: - Manifest converter (fromApplicationVariableManifestToUniversalFlatApplicationVariable) - Validator service (FlatApplicationVariableValidatorService) - Builder service (WorkspaceMigrationApplicationVariableActionsBuilderService) - Create/Update/Delete action handlers with secret encryption hooks - Registered in orchestrator, builder module, runner module, and all type registries ### Removed bespoke path: - Deleted upsertManyApplicationVariableEntities from ApplicationVariableEntityService - Removed its call from ApplicationSyncService.syncApplication() - Kept update() (operator-set value at runtime) and getDisplayValue() (runtime display) ### Database migration: - Instance command to add columns, backfill universalIdentifier, enforce NOT NULL constraints, and update indexes ## Test plan - npx nx typecheck twenty-server passes (0 errors) - Unit tests pass (application-variable.service.spec.ts, build-env-var.spec.ts) - Install an app with applicationVariables in its manifest → variables appear with correct universalIdentifier - Update app manifest (add/remove/modify a variable) → migration pipeline handles diff correctly - Operator-set value via update endpoint persists correctly with encryption - Uninstall app → variables cascade-deleted - app dev --once on example app syncs without errors
72 lines
1.8 KiB
TypeScript
72 lines
1.8 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { defineFrontComponent } from 'twenty-sdk/define';
|
|
import {
|
|
enqueueSnackbar,
|
|
unmountFrontComponent,
|
|
updateProgress,
|
|
useRecordId,
|
|
} from 'twenty-sdk/front-component';
|
|
import { CoreApiClient } from 'twenty-client-sdk/core';
|
|
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
|
|
|
|
const SendPostCardsEffect = () => {
|
|
const recordId = useRecordId();
|
|
|
|
useEffect(() => {
|
|
const send = async () => {
|
|
try {
|
|
await updateProgress(0.1);
|
|
const client = new CoreApiClient();
|
|
|
|
await updateProgress(0.3);
|
|
|
|
if (recordId) {
|
|
await client.mutation({
|
|
updatePostCard: {
|
|
__args: {
|
|
id: recordId,
|
|
data: { status: 'SENT' },
|
|
},
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
await enqueueSnackbar({
|
|
message: `Postcard sent`,
|
|
variant: 'success',
|
|
});
|
|
}
|
|
|
|
await unmountFrontComponent();
|
|
} catch (error) {
|
|
const message =
|
|
error instanceof Error ? error.message : 'Failed to send postcards';
|
|
|
|
await enqueueSnackbar({ message, variant: 'error' });
|
|
await unmountFrontComponent();
|
|
}
|
|
};
|
|
|
|
send();
|
|
}, [recordId]);
|
|
|
|
return null;
|
|
};
|
|
|
|
export default defineFrontComponent({
|
|
universalIdentifier: 'a3b7c2d1-4e5f-4a8b-9c0d-1e2f3a4b5c6d',
|
|
name: 'Send Post Cards',
|
|
description: 'Sets postcard status to Sent',
|
|
isHeadless: true,
|
|
component: SendPostCardsEffect,
|
|
command: {
|
|
universalIdentifier: 'bd75de13-87a1-4f7a-94e2-92e19e97523c',
|
|
label: 'Send post cards',
|
|
shortLabel: 'Send',
|
|
icon: 'IconSend',
|
|
isPinned: true,
|
|
availabilityType: 'RECORD_SELECTION',
|
|
availabilityObjectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
|
|
},
|
|
});
|