refactor(app-marketplace): rename featured to vetted (#22674)
## What Renames the application-registration "featured" flag to "vetted" across the backend, frontend, GraphQL schema/DTOs, and the marketplace UI. "Vetted" better describes what the flag actually does today: it marks an app as reviewed and approved by the Twenty team (a trust signal), rather than "featured" which reads as spotlighting/promotion. The admin toggle description was already "Mark this app as reviewed and approved". ## How - Renamed `isFeatured` -> `isVetted` on the `ApplicationRegistration` entity, DTOs (`MarketplaceApp`, `MarketplaceAppDetail`, `UpdateApplicationRegistrationPayload`), services, GraphQL fragments, and the settings/admin UI (labels: "Featured" -> "Vetted", "Featured only" -> "Vetted only", etc.). - Renamed the `MARKETPLACE_FEATURED_APPLICATIONS` constant/file to `MARKETPLACE_VETTED_APPLICATIONS`. - Regenerated GraphQL client artifacts (`generated-metadata`, `generated-admin`, `twenty-client-sdk`). ### Database The `isFeatured` column is renamed in place to `isVetted` via a single 2.20 fast instance command (`ALTER TABLE ... RENAME COLUMN`). No new column, no data-copy backfill. - Since all 2.19 commands (including the existing `isFeatured` backfill) complete before any 2.20 command runs, the rename carries over the values that backfill set. - The entity uses `@WasRenamedInUpgrade` so the upgrade-aware layer queries the old column name until the rename step runs during an upgrade. ## Testing - `nx typecheck` and `nx lint:diff-with-main` pass for twenty-server and twenty-front. - Ran `database:reset` on a fresh dev DB: the 2.19 `isFeatured` backfill runs first, then the 2.20 rename; the column ends up as `isVetted` (and `isFeatured` no longer exists), values preserved. - Booted the server: the `@WasRenamedInUpgrade` decorator validates against the upgrade sequence, and GraphQL introspection confirms all four types expose `isVetted` and none expose `isFeatured`. - Ran the three `graphql:generate` configs and the SDK metadata client generator so the committed generated files match the generator output (field ordering included). ## Notes - The `api-breaking-changes` check flags the removal of the `isFeatured` GraphQL field — that is expected and inherent to this rename. - Translation catalogs (`locales/`) are intentionally not touched here since they are managed via Crowdin; new English strings render via Lingui's default-message fallback until translated.
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
export const MARKETPLACE_FEATURED_APPLICATIONS: {
|
||||
export const MARKETPLACE_VETTED_APPLICATIONS: {
|
||||
universalIdentifier: string;
|
||||
position?: number;
|
||||
}[] = [
|
||||
+1
-1
@@ -43,7 +43,7 @@ export class MarketplaceAppDetailDTO {
|
||||
|
||||
@IsBoolean()
|
||||
@Field(() => Boolean)
|
||||
isFeatured: boolean;
|
||||
isVetted: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
+1
-1
@@ -45,5 +45,5 @@ export class MarketplaceAppDTO {
|
||||
|
||||
@IsBoolean()
|
||||
@Field(() => Boolean)
|
||||
isFeatured: boolean;
|
||||
isVetted: boolean;
|
||||
}
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ export class MarketplaceCatalogCacheProviderService extends CoreEntityCacheProvi
|
||||
category: catalogCard.category ?? '',
|
||||
logo: catalogCard.logoUrl ?? undefined,
|
||||
sourcePackage: catalogCard.sourcePackage ?? undefined,
|
||||
isFeatured: catalogCard.isFeatured,
|
||||
isVetted: catalogCard.isVetted,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -23,10 +23,10 @@ export class MarketplacePublicResolver {
|
||||
@Query(() => [MarketplaceAppDTO], { name: 'publicMarketplaceApps' })
|
||||
@UseGuards(PublicEndpointGuard, NoPermissionGuard)
|
||||
async findManyPublicMarketplaceApps(
|
||||
@Args('isFeatured', { type: () => Boolean, defaultValue: true })
|
||||
isFeatured: boolean,
|
||||
@Args('isVetted', { type: () => Boolean, defaultValue: true })
|
||||
isVetted: boolean,
|
||||
): Promise<MarketplaceAppDTO[]> {
|
||||
return this.marketplaceQueryService.findManyMarketplaceApps(isFeatured);
|
||||
return this.marketplaceQueryService.findManyMarketplaceApps(isVetted);
|
||||
}
|
||||
|
||||
@Query(() => MarketplaceAppDetailDTO, { name: 'publicMarketplaceAppDetail' })
|
||||
|
||||
+4
-4
@@ -23,7 +23,7 @@ export class MarketplaceQueryService {
|
||||
) {}
|
||||
|
||||
async findManyMarketplaceApps(
|
||||
isFeatured?: boolean,
|
||||
isVetted?: boolean,
|
||||
): Promise<MarketplaceAppDTO[]> {
|
||||
const appsByUniversalIdentifier =
|
||||
(await this.coreEntityCacheService.get(
|
||||
@@ -33,11 +33,11 @@ export class MarketplaceQueryService {
|
||||
|
||||
const apps = Object.values(appsByUniversalIdentifier);
|
||||
|
||||
if (!isDefined(isFeatured)) {
|
||||
if (!isDefined(isVetted)) {
|
||||
return apps;
|
||||
}
|
||||
|
||||
return apps.filter((app) => app.isFeatured === isFeatured);
|
||||
return apps.filter((app) => app.isVetted === isVetted);
|
||||
}
|
||||
|
||||
async findMarketplaceAppDetail(
|
||||
@@ -78,7 +78,7 @@ export class MarketplaceQueryService {
|
||||
sourcePackage: registration.sourcePackage ?? undefined,
|
||||
latestAvailableVersion: registration.latestAvailableVersion ?? undefined,
|
||||
isListed: registration.isListed,
|
||||
isFeatured: registration.isFeatured,
|
||||
isVetted: registration.isVetted,
|
||||
description:
|
||||
registration.description ??
|
||||
registration.manifest?.application?.description ??
|
||||
|
||||
+10
-2
@@ -23,6 +23,7 @@ import { ApplicationRegistrationVariableEntity } from 'src/engine/core-modules/a
|
||||
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { WasRenamedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-renamed-in-upgrade.decorator';
|
||||
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@@ -122,8 +123,15 @@ export class ApplicationRegistrationEntity {
|
||||
isListed: boolean;
|
||||
|
||||
@Field(() => Boolean)
|
||||
@Column({ name: 'isFeatured', type: 'boolean', default: false })
|
||||
isFeatured: boolean;
|
||||
@Column({ name: 'isVetted', type: 'boolean', default: false })
|
||||
@WasRenamedInUpgrade([
|
||||
{
|
||||
previousName: 'isFeatured',
|
||||
upgradeCommandName:
|
||||
'2.20.0_RenameIsFeaturedToIsVettedOnApplicationRegistrationFastInstanceCommand_1783527064000',
|
||||
},
|
||||
])
|
||||
isVetted: boolean;
|
||||
|
||||
// Auto-installed on every new workspace; existing workspaces are
|
||||
// backfilled by the `install-pre-installed-apps` CLI command.
|
||||
|
||||
+11
-13
@@ -34,7 +34,7 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service';
|
||||
import { CoreEntityCacheService } from 'src/engine/core-entity-cache/services/core-entity-cache.service';
|
||||
import { MARKETPLACE_CATALOG_CACHE_ENTITY_ID } from 'src/engine/core-modules/application/application-marketplace/constants/marketplace-apps-cache.constant';
|
||||
import { MARKETPLACE_FEATURED_APPLICATIONS } from 'src/engine/core-modules/application/application-marketplace/constants/marketplace-featured-applications.constant';
|
||||
import { MARKETPLACE_VETTED_APPLICATIONS } from 'src/engine/core-modules/application/application-marketplace/constants/marketplace-vetted-applications.constant';
|
||||
|
||||
const BCRYPT_SALT_ROUNDS = 10;
|
||||
|
||||
@@ -53,7 +53,7 @@ const APPLICATION_REGISTRATION_WITHOUT_MANIFEST_SELECT: (keyof ApplicationRegist
|
||||
'tarballFileId',
|
||||
'latestAvailableVersion',
|
||||
'isListed',
|
||||
'isFeatured',
|
||||
'isVetted',
|
||||
'isPreInstalled',
|
||||
'logo',
|
||||
'description',
|
||||
@@ -74,7 +74,7 @@ export type ApplicationRegistrationCatalogCard = {
|
||||
universalIdentifier: string;
|
||||
name: string;
|
||||
sourcePackage: string | null;
|
||||
isFeatured: boolean;
|
||||
isVetted: boolean;
|
||||
description: string | null;
|
||||
author: string | null;
|
||||
category: string | null;
|
||||
@@ -297,7 +297,7 @@ export class ApplicationRegistrationService {
|
||||
if (isDefined(update.isListed)) updateData.isListed = update.isListed;
|
||||
if (isDefined(update.isPreInstalled))
|
||||
updateData.isPreInstalled = update.isPreInstalled;
|
||||
if (isDefined(update.isFeatured)) updateData.isFeatured = update.isFeatured;
|
||||
if (isDefined(update.isVetted)) updateData.isVetted = update.isVetted;
|
||||
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
await this.applicationRegistrationRepository.update(id, updateData);
|
||||
@@ -407,13 +407,11 @@ export class ApplicationRegistrationService {
|
||||
params.universalIdentifier,
|
||||
);
|
||||
|
||||
const featuredIdentifiers = new Set(
|
||||
MARKETPLACE_FEATURED_APPLICATIONS.map(
|
||||
(entry) => entry.universalIdentifier,
|
||||
),
|
||||
const vettedIdentifiers = new Set(
|
||||
MARKETPLACE_VETTED_APPLICATIONS.map((entry) => entry.universalIdentifier),
|
||||
);
|
||||
|
||||
const isFeatured = featuredIdentifiers.has(params.universalIdentifier);
|
||||
const isVetted = vettedIdentifiers.has(params.universalIdentifier);
|
||||
|
||||
if (isDefined(existing)) {
|
||||
await this.applicationRegistrationRepository.save({
|
||||
@@ -422,7 +420,7 @@ export class ApplicationRegistrationService {
|
||||
sourceType: params.sourceType,
|
||||
sourcePackage: params.sourcePackage,
|
||||
latestAvailableVersion: params.latestAvailableVersion,
|
||||
isFeatured,
|
||||
isVetted,
|
||||
manifest: params.manifest,
|
||||
...fromManifestApplicationToDisplayFields(params.manifest?.application),
|
||||
});
|
||||
@@ -434,7 +432,7 @@ export class ApplicationRegistrationService {
|
||||
sourcePackage: params.sourcePackage,
|
||||
latestAvailableVersion: params.latestAvailableVersion,
|
||||
isListed: true,
|
||||
isFeatured,
|
||||
isVetted,
|
||||
manifest: params.manifest,
|
||||
...fromManifestApplicationToDisplayFields(params.manifest?.application),
|
||||
oAuthClientId: v4(),
|
||||
@@ -505,7 +503,7 @@ export class ApplicationRegistrationService {
|
||||
'universalIdentifier',
|
||||
'name',
|
||||
'sourcePackage',
|
||||
'isFeatured',
|
||||
'isVetted',
|
||||
'logo',
|
||||
'description',
|
||||
'author',
|
||||
@@ -522,7 +520,7 @@ export class ApplicationRegistrationService {
|
||||
universalIdentifier: registration.universalIdentifier,
|
||||
name: registration.name,
|
||||
sourcePackage: registration.sourcePackage,
|
||||
isFeatured: registration.isFeatured,
|
||||
isVetted: registration.isVetted,
|
||||
description: registration.description,
|
||||
author: registration.author,
|
||||
category: registration.category,
|
||||
|
||||
+2
-2
@@ -160,7 +160,7 @@ export class ApplicationTarballService {
|
||||
...fromManifestApplicationToDisplayFields(manifest.application),
|
||||
latestAvailableVersion: packageJson?.version ?? null,
|
||||
isListed: false,
|
||||
isFeatured: false,
|
||||
isVetted: false,
|
||||
oAuthClientId: v4(),
|
||||
oAuthRedirectUris: [],
|
||||
oAuthScopes: [],
|
||||
@@ -198,7 +198,7 @@ export class ApplicationTarballService {
|
||||
...fromManifestApplicationToDisplayFields(manifest.application),
|
||||
latestAvailableVersion: packageJson?.version ?? null,
|
||||
isListed: false,
|
||||
isFeatured: false,
|
||||
isVetted: false,
|
||||
ownerWorkspaceId: params.ownerWorkspaceId,
|
||||
} as QueryDeepPartialEntity<ApplicationRegistrationEntity>);
|
||||
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ export class UpdateApplicationRegistrationPayload {
|
||||
@Field(() => Boolean, { nullable: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isFeatured?: boolean;
|
||||
isVetted?: boolean;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
|
||||
Reference in New Issue
Block a user