Fix application icons (#20142)
fixes application chip (icon Name) in all setting tables ## After <img width="1200" height="896" alt="image" src="https://github.com/user-attachments/assets/bd377f47-1d52-4142-b904-f2ce90c1db78" /> <img width="1200" height="917" alt="image" src="https://github.com/user-attachments/assets/f49cc742-f11e-47e3-86ed-34beffe493c7" /> <img width="1234" height="878" alt="image" src="https://github.com/user-attachments/assets/2ab459de-5f9d-4d39-9490-eec4ed9ee432" /> <img width="1239" height="845" alt="image" src="https://github.com/user-attachments/assets/3c1bf258-285a-47b9-a60d-05ba1564334d" /> <img width="1183" height="907" alt="image" src="https://github.com/user-attachments/assets/715b2470-2d88-48e3-88ac-d3daf3451717" /> <img width="1300" height="912" alt="image" src="https://github.com/user-attachments/assets/d7c829fa-bf1d-4f19-82de-a8bf29e22bfa" />
This commit is contained in:
+8
-2
@@ -1,4 +1,4 @@
|
||||
import { ApplicationDisplay } from '@/applications/components/ApplicationDisplay';
|
||||
import { AppChip } from '@/applications/components/AppChip';
|
||||
import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
@@ -173,7 +173,13 @@ const SettingsAdminAppsTableRow = ({
|
||||
minWidth="0"
|
||||
overflow="hidden"
|
||||
>
|
||||
<ApplicationDisplay application={registration} />
|
||||
<AppChip
|
||||
size="md"
|
||||
fallbackApplicationData={{
|
||||
logo: registration.logoUrl,
|
||||
name: registration.name,
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell overflow="hidden" align="right">
|
||||
{getFormattedSource(registration)}
|
||||
|
||||
+10
@@ -25,8 +25,10 @@ describe('useComputeObjectAndFieldsContentForApplication', () => {
|
||||
it('should return object rows for installed application objects', () => {
|
||||
const installedApplication = {
|
||||
id: APP_ID,
|
||||
universalIdentifier: APP_ID,
|
||||
objects: [{ id: personObject.id }],
|
||||
name: 'Test App',
|
||||
logo: null,
|
||||
canBeUninstalled: true,
|
||||
availablePackages: {},
|
||||
applicationVariables: [],
|
||||
@@ -53,8 +55,10 @@ describe('useComputeObjectAndFieldsContentForApplication', () => {
|
||||
it('should return empty object rows when application has no objects', () => {
|
||||
const installedApplication = {
|
||||
id: APP_ID,
|
||||
universalIdentifier: APP_ID,
|
||||
objects: [],
|
||||
name: 'Test App',
|
||||
logo: null,
|
||||
canBeUninstalled: true,
|
||||
availablePackages: {},
|
||||
applicationVariables: [],
|
||||
@@ -79,8 +83,10 @@ describe('useComputeObjectAndFieldsContentForApplication', () => {
|
||||
|
||||
const installedApplication = {
|
||||
id: fieldBelongingToApp.applicationId ?? APP_ID,
|
||||
universalIdentifier: fieldBelongingToApp.applicationId ?? APP_ID,
|
||||
objects: [{ id: personObject.id }],
|
||||
name: 'Test App',
|
||||
logo: null,
|
||||
canBeUninstalled: true,
|
||||
availablePackages: {},
|
||||
applicationVariables: [],
|
||||
@@ -106,8 +112,10 @@ describe('useComputeObjectAndFieldsContentForApplication', () => {
|
||||
it('should exclude deny-listed objects from field rows', () => {
|
||||
const installedApplication = {
|
||||
id: APP_ID,
|
||||
universalIdentifier: APP_ID,
|
||||
objects: [],
|
||||
name: 'Test App',
|
||||
logo: null,
|
||||
canBeUninstalled: true,
|
||||
availablePackages: {},
|
||||
applicationVariables: [],
|
||||
@@ -261,8 +269,10 @@ describe('useComputeObjectAndFieldsContentForApplication', () => {
|
||||
it('should use installed application data when both sources are provided', () => {
|
||||
const installedApplication = {
|
||||
id: APP_ID,
|
||||
universalIdentifier: APP_ID,
|
||||
objects: [{ id: personObject.id }],
|
||||
name: 'Test App',
|
||||
logo: null,
|
||||
canBeUninstalled: true,
|
||||
availablePackages: {},
|
||||
applicationVariables: [],
|
||||
|
||||
+1
@@ -67,6 +67,7 @@ export const useComputeObjectAndFieldsContentForApplication = ({
|
||||
.map((field) => ({
|
||||
key: `${item.id}-${field.id}`,
|
||||
name: field.label,
|
||||
applicationId: item?.applicationId ?? undefined,
|
||||
icon: field.icon ?? undefined,
|
||||
secondary: t`on ${item.labelSingular}`,
|
||||
link: getSettingsPath(SettingsPath.ObjectFieldEdit, {
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { type ApplicationDisplayData } from '@/applications/types/applicationDisplayData.type';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { getInstalledApplicationObjectAndFieldRows } from '@/settings/applications/utils/getInstalledApplicationObjectAndFieldRows';
|
||||
import { getManifestObjectAndFieldRows } from '@/settings/applications/utils/getManifestObjectAndFieldRows';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useMemo } from 'react';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type Application } from '~/generated-metadata/graphql';
|
||||
|
||||
type InstalledApplicationForObjectRows = Omit<
|
||||
Application,
|
||||
'objects' | 'frontComponents'
|
||||
> & {
|
||||
objects: { id: string }[];
|
||||
};
|
||||
|
||||
export const useObjectAndFieldRows = ({
|
||||
installedApplication,
|
||||
manifestContent,
|
||||
applicationInfo,
|
||||
}: {
|
||||
applicationId: string;
|
||||
installedApplication?: InstalledApplicationForObjectRows;
|
||||
manifestContent?: Manifest;
|
||||
applicationInfo?: ApplicationDisplayData;
|
||||
}) => {
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
const installedApplications = currentWorkspace?.installedApplications;
|
||||
|
||||
return useMemo(() => {
|
||||
if (isDefined(installedApplication)) {
|
||||
return getInstalledApplicationObjectAndFieldRows({
|
||||
installedApplication,
|
||||
objectMetadataItems,
|
||||
installedApplications,
|
||||
});
|
||||
}
|
||||
|
||||
return getManifestObjectAndFieldRows({
|
||||
manifestContent,
|
||||
objectMetadataItems,
|
||||
applicationInfo,
|
||||
});
|
||||
}, [
|
||||
installedApplication,
|
||||
manifestContent,
|
||||
objectMetadataItems,
|
||||
installedApplications,
|
||||
applicationInfo,
|
||||
]);
|
||||
};
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
import { type ApplicationDisplayData } from '@/applications/types/applicationDisplayData.type';
|
||||
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { type Application } from '~/generated-metadata/graphql';
|
||||
import { type ApplicationDataTableRow } from '~/pages/settings/applications/types/applicationDataTableRow';
|
||||
|
||||
const FIELD_GROUP_DENY_LIST = ['timelineActivity', 'favorite'];
|
||||
|
||||
type InstalledApplication = Omit<Application, 'objects' | 'frontComponents'> & {
|
||||
objects: { id: string }[];
|
||||
};
|
||||
|
||||
export const getInstalledApplicationObjectAndFieldRows = ({
|
||||
installedApplication,
|
||||
objectMetadataItems,
|
||||
installedApplications,
|
||||
}: {
|
||||
installedApplication: InstalledApplication;
|
||||
objectMetadataItems: EnrichedObjectMetadataItem[];
|
||||
installedApplications?: ApplicationDisplayData[];
|
||||
}): {
|
||||
objectRows: ApplicationDataTableRow[];
|
||||
fieldGroupRows: ApplicationDataTableRow[];
|
||||
} => {
|
||||
const installedObjectIds = installedApplication.objects.map(
|
||||
(object) => object.id,
|
||||
);
|
||||
|
||||
const objectRows: ApplicationDataTableRow[] =
|
||||
installedApplication.objects.length === 0
|
||||
? []
|
||||
: objectMetadataItems
|
||||
.filter((item) => installedObjectIds.includes(item.id))
|
||||
.map((item) => ({
|
||||
key: item.nameSingular,
|
||||
labelPlural: item.labelPlural,
|
||||
icon: item.icon ?? undefined,
|
||||
fieldsCount: item.fields.filter((f) => !isHiddenSystemField(f))
|
||||
.length,
|
||||
link: getSettingsPath(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural: item.namePlural,
|
||||
}),
|
||||
application: {
|
||||
id: installedApplication.id,
|
||||
logo: installedApplication.logo,
|
||||
name: installedApplication.name,
|
||||
universalIdentifier: installedApplication.universalIdentifier,
|
||||
},
|
||||
}))
|
||||
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
|
||||
|
||||
const fieldGroupRows: ApplicationDataTableRow[] = objectMetadataItems
|
||||
.filter((item) => {
|
||||
if (installedObjectIds.includes(item.id)) return false;
|
||||
if (FIELD_GROUP_DENY_LIST.includes(item.nameSingular)) return false;
|
||||
|
||||
return item.fields.some(
|
||||
(field) => field.applicationId === installedApplication.id,
|
||||
);
|
||||
})
|
||||
.map((item) => ({
|
||||
key: item.nameSingular,
|
||||
labelPlural: item.labelPlural,
|
||||
icon: item.icon ?? undefined,
|
||||
fieldsCount: item.fields.filter(
|
||||
(field) => field.applicationId === installedApplication.id,
|
||||
).length,
|
||||
link: getSettingsPath(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural: item.namePlural,
|
||||
}),
|
||||
application:
|
||||
installedApplications?.find((app) => app.id === item.applicationId) ??
|
||||
{},
|
||||
}));
|
||||
|
||||
return { objectRows, fieldGroupRows };
|
||||
};
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import { type ApplicationDisplayData } from '@/applications/types/applicationDisplayData.type';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type ApplicationDataTableRow } from '~/pages/settings/applications/types/applicationDataTableRow';
|
||||
import { findObjectNameByUniversalIdentifier } from '~/pages/settings/applications/utils/findObjectNameByUniversalIdentifier';
|
||||
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
|
||||
|
||||
const FIELD_GROUP_DENY_LIST = ['timelineActivity', 'favorite'];
|
||||
|
||||
export const getManifestObjectAndFieldRows = ({
|
||||
manifestContent,
|
||||
objectMetadataItems,
|
||||
applicationInfo,
|
||||
}: {
|
||||
manifestContent?: Manifest;
|
||||
objectMetadataItems: EnrichedObjectMetadataItem[];
|
||||
applicationInfo?: ApplicationDisplayData;
|
||||
}): {
|
||||
objectRows: ApplicationDataTableRow[];
|
||||
fieldGroupRows: ApplicationDataTableRow[];
|
||||
} => {
|
||||
const manifestObjects = manifestContent?.objects ?? [];
|
||||
const manifestFields = manifestContent?.fields ?? [];
|
||||
|
||||
const objectRows: ApplicationDataTableRow[] = manifestObjects
|
||||
.map((appObject) => ({
|
||||
key: appObject.nameSingular,
|
||||
labelPlural: appObject.labelPlural,
|
||||
icon: appObject.icon ?? undefined,
|
||||
fieldsCount: appObject.fields.filter((f) => !isHiddenSystemField(f))
|
||||
.length,
|
||||
application: applicationInfo ?? {},
|
||||
}))
|
||||
.sort((a, b) => a.labelPlural.localeCompare(b.labelPlural));
|
||||
|
||||
if (manifestFields.length === 0) {
|
||||
return { objectRows, fieldGroupRows: [] };
|
||||
}
|
||||
|
||||
const manifestObjectUids = new Set(
|
||||
manifestObjects.map((obj) => obj.universalIdentifier),
|
||||
);
|
||||
|
||||
const groupMap = new Map<
|
||||
string,
|
||||
{ objectUniversalIdentifier: string; count: number }
|
||||
>();
|
||||
|
||||
for (const field of manifestFields) {
|
||||
const objectUid = field.objectUniversalIdentifier;
|
||||
|
||||
if (manifestObjectUids.has(objectUid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existing = groupMap.get(objectUid);
|
||||
|
||||
if (isDefined(existing)) {
|
||||
existing.count++;
|
||||
} else {
|
||||
groupMap.set(objectUid, {
|
||||
objectUniversalIdentifier: objectUid,
|
||||
count: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const fieldGroupRows: ApplicationDataTableRow[] = Array.from(
|
||||
groupMap.values(),
|
||||
)
|
||||
.map((group) => {
|
||||
const standardObjectName = findObjectNameByUniversalIdentifier(
|
||||
group.objectUniversalIdentifier,
|
||||
);
|
||||
|
||||
const objectMetadataItem = isDefined(standardObjectName)
|
||||
? objectMetadataItems.find(
|
||||
(item) => item.nameSingular === standardObjectName,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (FIELD_GROUP_DENY_LIST.includes(objectMetadataItem.nameSingular)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
key: objectMetadataItem.nameSingular,
|
||||
labelPlural: objectMetadataItem.labelPlural,
|
||||
icon: objectMetadataItem.icon ?? undefined,
|
||||
fieldsCount: group.count,
|
||||
application: applicationInfo ?? {},
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
return { objectRows, fieldGroupRows };
|
||||
};
|
||||
Reference in New Issue
Block a user