Fix Apps UI: replace 'Managed' label with actual app name and unify app icons (#19897)
## Summary
- The Data Model table was labeling core Twenty objects (e.g. Person,
Company) as **Managed** even though they are part of the standard
application. This PR teaches the frontend to resolve an `applicationId`
back to its real application name (`Standard`, `Custom`, or any
installed app), and removes the misleading **Managed** label entirely.
- Introduces a single, consistent way to render an "app badge" across
the settings UI:
- new `Avatar` variant `type="app"` (rounded 4px corners + 1px
deterministic border derived from `placeholderColorSeed`)
- new `AppChip` component (icon + name) backed by a new
`useApplicationChipData` hook
- new `useApplicationsByIdMap` hook + `CurrentApplicationContext` so the
chip can render **This app** when shown inside the matching app's detail
page
- Reuses these primitives on:
- the application detail page header (`SettingsApplicationDetailTitle`)
- the Installed / My apps tables (`SettingsApplicationTableRow`)
- the NPM packages list (`SettingsApplicationsDeveloperTab`)
- Backend: exposes a minimal `installedApplications { id name
universalIdentifier }` field on `Workspace` (resolved from the workspace
cache, soft-deleted entries filtered out) so the frontend can resolve
`applicationId` -> name without N+1 fetches.
- Cleanup: deletes `getItemTagInfo` and inlines its tiny
responsibilities into the components that need them, matching the
`RecordChip` pattern.
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/application-package/utils/get-default-application-package-fields.util';
|
||||
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/application-package/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
|
||||
import { ApplicationVariableEntity } from 'src/engine/core-modules/application/application-variable/application-variable.entity';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
|
||||
@@ -138,6 +139,20 @@ export class ApplicationService {
|
||||
});
|
||||
}
|
||||
|
||||
async findManyInstalledFlatApplications(
|
||||
workspaceId: string,
|
||||
): Promise<FlatApplication[]> {
|
||||
const { flatApplicationMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
return Object.values(flatApplicationMaps.byId).filter(
|
||||
(flatApplication): flatApplication is FlatApplication =>
|
||||
isDefined(flatApplication) && !isDefined(flatApplication.deletedAt),
|
||||
);
|
||||
}
|
||||
|
||||
async findOneApplication({
|
||||
id,
|
||||
universalIdentifier,
|
||||
|
||||
@@ -258,6 +258,18 @@ export class WorkspaceResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@ResolveField(() => [ApplicationDTO])
|
||||
async installedApplications(
|
||||
@Parent() workspace: WorkspaceEntity,
|
||||
): Promise<ApplicationDTO[]> {
|
||||
const flatApplications =
|
||||
await this.applicationService.findManyInstalledFlatApplications(
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
return flatApplications.map(fromFlatApplicationToApplicationDto);
|
||||
}
|
||||
|
||||
@ResolveField(() => BillingSubscriptionEntity, { nullable: true })
|
||||
async currentBillingSubscription(
|
||||
@Parent() workspace: WorkspaceEntity,
|
||||
|
||||
Reference in New Issue
Block a user