feat(applications): restore the application custom settings tab (#23256)
## Summary Restores the application **custom settings tab** feature that was removed in #22156. This reverts that removal so applications can again expose a custom settings tab via a front component. ## Changes - Restore the `SettingsApplicationCustomTab` component and its tab entry/rendering in `SettingsApplicationDetails`. - `ApplicationManifestMigrationService` syncs `settingsCustomTabFrontComponent` from application manifests again (`syncDefaultRoleAndSettingsCustomTab`), resolving the front component from `settingsCustomTabFrontComponentUniversalIdentifier`. - Remove the deprecation annotations added by #22156: - `ApplicationDTO.settingsCustomTabFrontComponentId` (drop GraphQL `@deprecated`) - `ApplicationManifest.settingsCustomTabFrontComponentUniversalIdentifier` - the `settingsCustomTabFrontComponentId` column comment on `ApplicationEntity` - Regenerate the corresponding GraphQL schema/types to drop the `@deprecated` reason. The DB column was never dropped, so no schema migration is required. --- _Generated by [Claude Code](https://claude.ai/code/session_01A6aoLa5kZjba9C3uwo6nay)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23256?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
+33
-10
@@ -249,7 +249,7 @@ export class ApplicationManifestMigrationService {
|
||||
);
|
||||
|
||||
if (!dryRun) {
|
||||
await this.syncDefaultRole({
|
||||
await this.syncDefaultRoleAndSettingsFrontComponent({
|
||||
manifest,
|
||||
workspaceId,
|
||||
ownerFlatApplication,
|
||||
@@ -262,7 +262,7 @@ export class ApplicationManifestMigrationService {
|
||||
};
|
||||
}
|
||||
|
||||
private async syncDefaultRole({
|
||||
private async syncDefaultRoleAndSettingsFrontComponent({
|
||||
manifest,
|
||||
workspaceId,
|
||||
ownerFlatApplication,
|
||||
@@ -271,10 +271,13 @@ export class ApplicationManifestMigrationService {
|
||||
workspaceId: string;
|
||||
ownerFlatApplication: FlatApplication;
|
||||
}) {
|
||||
const { flatRoleMaps: refreshedFlatRoleMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatRoleMaps',
|
||||
]);
|
||||
const {
|
||||
flatRoleMaps: refreshedFlatRoleMaps,
|
||||
flatFrontComponentMaps: refreshedFlatFrontComponentMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatRoleMaps',
|
||||
'flatFrontComponentMaps',
|
||||
]);
|
||||
|
||||
let defaultRoleId: string | null = null;
|
||||
|
||||
@@ -299,11 +302,31 @@ export class ApplicationManifestMigrationService {
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(defaultRoleId)) {
|
||||
await this.applicationService.update(ownerFlatApplication.id, {
|
||||
workspaceId,
|
||||
defaultRoleId,
|
||||
let settingsCustomTabFrontComponentId: string | null = null;
|
||||
|
||||
const settingsFrontComponentUniversalIdentifier =
|
||||
manifest.application.settingsFrontComponent?.universalIdentifier;
|
||||
|
||||
if (isDefined(settingsFrontComponentUniversalIdentifier)) {
|
||||
const flatFrontComponent = findFlatEntityByUniversalIdentifier({
|
||||
flatEntityMaps: refreshedFlatFrontComponentMaps,
|
||||
universalIdentifier: settingsFrontComponentUniversalIdentifier,
|
||||
});
|
||||
|
||||
if (!isDefined(flatFrontComponent)) {
|
||||
throw new ApplicationException(
|
||||
`Failed to resolve front component for settings front component universalIdentifier ${settingsFrontComponentUniversalIdentifier}`,
|
||||
ApplicationExceptionCode.ENTITY_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
settingsCustomTabFrontComponentId = flatFrontComponent.id;
|
||||
}
|
||||
|
||||
await this.applicationService.update(ownerFlatApplication.id, {
|
||||
workspaceId,
|
||||
settingsCustomTabFrontComponentId,
|
||||
...(isDefined(defaultRoleId) ? { defaultRoleId } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -5,10 +5,12 @@ import { type UniversalFlatFrontComponent } from 'src/engine/workspace-manager/w
|
||||
export const fromFrontComponentManifestToUniversalFlatFrontComponent = ({
|
||||
frontComponentManifest,
|
||||
applicationUniversalIdentifier,
|
||||
isSettingsFrontComponent,
|
||||
now,
|
||||
}: {
|
||||
frontComponentManifest: FrontComponentManifest;
|
||||
applicationUniversalIdentifier: string;
|
||||
isSettingsFrontComponent: boolean;
|
||||
now: string;
|
||||
}): UniversalFlatFrontComponent => {
|
||||
return {
|
||||
@@ -20,7 +22,10 @@ export const fromFrontComponentManifestToUniversalFlatFrontComponent = ({
|
||||
builtComponentPath: frontComponentManifest.builtComponentPath,
|
||||
componentName: frontComponentManifest.componentName,
|
||||
builtComponentChecksum: frontComponentManifest.builtComponentChecksum,
|
||||
isHeadless: frontComponentManifest.isHeadless ?? false,
|
||||
// A settings front component always renders visible UI.
|
||||
isHeadless: isSettingsFrontComponent
|
||||
? false
|
||||
: (frontComponentManifest.isHeadless ?? false),
|
||||
usesSdkClient: frontComponentManifest.usesSdkClient ?? false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
+6
@@ -215,12 +215,18 @@ export class ComputeApplicationManifestAllUniversalFlatEntityMapsService {
|
||||
});
|
||||
}
|
||||
|
||||
const settingsFrontComponentUniversalIdentifier =
|
||||
manifest.application.settingsFrontComponent?.universalIdentifier;
|
||||
|
||||
for (const frontComponentManifest of manifest.frontComponents) {
|
||||
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
|
||||
universalFlatEntity:
|
||||
fromFrontComponentManifestToUniversalFlatFrontComponent({
|
||||
frontComponentManifest,
|
||||
applicationUniversalIdentifier,
|
||||
isSettingsFrontComponent:
|
||||
frontComponentManifest.universalIdentifier ===
|
||||
settingsFrontComponentUniversalIdentifier,
|
||||
now,
|
||||
}),
|
||||
universalFlatEntityMapsToMutate:
|
||||
|
||||
@@ -113,11 +113,6 @@ export class ApplicationEntity extends WorkspaceRelatedEntity {
|
||||
@Field(() => RoleDTO, { nullable: true })
|
||||
defaultRole: RoleDTO | null;
|
||||
|
||||
/**
|
||||
* @deprecated Custom settings tabs are no longer supported. The column is
|
||||
* kept (not dropped) so existing installations upgrade cleanly, but the
|
||||
* value is no longer read or synced from manifests.
|
||||
*/
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
settingsCustomTabFrontComponentId: string | null;
|
||||
|
||||
|
||||
@@ -95,17 +95,9 @@ export class ApplicationDTO {
|
||||
@Field({ nullable: true })
|
||||
defaultRoleId?: string;
|
||||
|
||||
/**
|
||||
* @deprecated Custom settings tabs are no longer supported. Kept for
|
||||
* backward compatibility with existing installations; the value is ignored.
|
||||
*/
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
@Field(() => UUIDScalarType, {
|
||||
nullable: true,
|
||||
deprecationReason:
|
||||
'Custom settings tabs are no longer supported. This field is ignored.',
|
||||
})
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
settingsCustomTabFrontComponentId?: string;
|
||||
|
||||
@IsOptional()
|
||||
|
||||
Reference in New Issue
Block a user