1518 extensibility front add an application section in settings (#15056)
Protected by IS_APPLICATION_ENABLED featureFlag Add `Application` section in settings <img width="301" height="137" alt="image" src="https://github.com/user-attachments/assets/ee53bdd2-36f6-45c6-8646-17b1e08abf00" /> A `settings/applications` route listing all installed applications <img width="661" height="428" alt="image" src="https://github.com/user-attachments/assets/69d534c4-4e9e-452a-a3d9-ded0223bb457" /> Introduce a new Tag for application managed items <img width="885" height="759" alt="image" src="https://github.com/user-attachments/assets/19767be5-61e5-4bd2-a51d-54ed9bfb1923" /> A `settings/applications/<application_id>` details setting page listing all objects, serverlessFunctions and agents created by the application: <img width="917" height="778" alt="image" src="https://github.com/user-attachments/assets/7fc056a6-1d73-4242-b2eb-6f8955d8597d" /> A `settings/applications/<application_id>/<serverless_function_id>` <img width="905" height="652" alt="image" src="https://github.com/user-attachments/assets/56ca0021-26bf-42cb-9abf-34879f16050a" /> Add trigger tab in serverless function details (readonly for now) <img width="899" height="724" alt="image" src="https://github.com/user-attachments/assets/5eeefa35-f2a4-4fd8-a640-7b5c5891f226" /> Set object, serverless and agent setting detail pages readonly for managed items <img width="1075" height="859" alt="image" src="https://github.com/user-attachments/assets/57c73d69-4980-47a2-b752-8dc5ab494530" /> <img width="648" height="582" alt="image" src="https://github.com/user-attachments/assets/5ad5f3f7-3bc3-4e40-870a-4981c6492524" /> <img width="982" height="692" alt="image" src="https://github.com/user-attachments/assets/7ad756c4-5d33-4a0a-9eb8-416c040362b9" /> <img width="1077" height="647" alt="image" src="https://github.com/user-attachments/assets/e086b9f5-4062-4d10-82a9-4023de3cad3f" />
This commit is contained in:
+5
-3
@@ -20,10 +20,12 @@ type SettingsObjectFieldActiveActionDropdownProps = {
|
||||
onEdit: () => void;
|
||||
onSetAsLabelIdentifier?: () => void;
|
||||
fieldMetadataItemId: string;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export const SettingsObjectFieldActiveActionDropdown = ({
|
||||
isCustomField,
|
||||
readonly = false,
|
||||
onDeactivate,
|
||||
onEdit,
|
||||
onSetAsLabelIdentifier,
|
||||
@@ -62,18 +64,18 @@ export const SettingsObjectFieldActiveActionDropdown = ({
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={isCustomField ? 'Edit' : 'View'}
|
||||
text={isCustomField && !readonly ? 'Edit' : 'View'}
|
||||
LeftIcon={isCustomField ? IconPencil : IconEye}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
{isDefined(onSetAsLabelIdentifier) && (
|
||||
{isDefined(onSetAsLabelIdentifier) && !readonly && (
|
||||
<MenuItem
|
||||
text="Set as record text"
|
||||
LeftIcon={IconTextSize}
|
||||
onClick={handleSetAsLabelIdentifier}
|
||||
/>
|
||||
)}
|
||||
{isDefined(onDeactivate) && (
|
||||
{isDefined(onDeactivate) && !readonly && (
|
||||
<MenuItem
|
||||
text="Deactivate"
|
||||
LeftIcon={IconArchive}
|
||||
|
||||
+11
-7
@@ -22,10 +22,12 @@ type SettingsObjectFieldInactiveActionDropdownProps = {
|
||||
onEdit: () => void;
|
||||
onDelete: () => void;
|
||||
fieldMetadataItemId: string;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export const SettingsObjectFieldInactiveActionDropdown = ({
|
||||
onActivate,
|
||||
readonly = false,
|
||||
fieldMetadataItemId,
|
||||
onDelete,
|
||||
onEdit,
|
||||
@@ -66,16 +68,18 @@ export const SettingsObjectFieldInactiveActionDropdown = ({
|
||||
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={isCustomField ? t`Edit` : t`View`}
|
||||
text={isCustomField && !readonly ? t`Edit` : t`View`}
|
||||
LeftIcon={isCustomField ? IconPencil : IconEye}
|
||||
onClick={handleEdit}
|
||||
/>
|
||||
<MenuItem
|
||||
text={t`Activate`}
|
||||
LeftIcon={IconArchiveOff}
|
||||
onClick={handleActivate}
|
||||
/>
|
||||
{isDeletable && (
|
||||
{!readonly && (
|
||||
<MenuItem
|
||||
text={t`Activate`}
|
||||
LeftIcon={IconArchiveOff}
|
||||
onClick={handleActivate}
|
||||
/>
|
||||
)}
|
||||
{isDeletable && !readonly && (
|
||||
<MenuItem
|
||||
text={t`Delete`}
|
||||
accent="danger"
|
||||
|
||||
+16
-1
@@ -30,6 +30,7 @@ import { type SettingsObjectDetailTableItem } from '~/pages/settings/data-model/
|
||||
|
||||
import { RELATION_TYPES } from '../../constants/RelationTypes';
|
||||
import { SettingsObjectFieldDataType } from './SettingsObjectFieldDataType';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
|
||||
type SettingsObjectFieldItemTableRowProps = {
|
||||
settingsObjectDetailTableItem: SettingsObjectDetailTableItem;
|
||||
@@ -65,6 +66,10 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
const { fieldMetadataItem, identifierType, objectMetadataItem } =
|
||||
settingsObjectDetailTableItem;
|
||||
|
||||
const readonly = isObjectMetadataReadOnly({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const isRemoteObjectField = objectMetadataItem.isRemote;
|
||||
|
||||
const variant = objectMetadataItem.isCustom ? 'identifier' : 'field-type';
|
||||
@@ -118,6 +123,10 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
const handleDisableField = async (
|
||||
activeFieldMetadatItem: FieldMetadataItem,
|
||||
) => {
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
await deactivateMetadataField(
|
||||
activeFieldMetadatItem.id,
|
||||
objectMetadataItem.id,
|
||||
@@ -146,13 +155,17 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
|
||||
const handleSetLabelIdentifierField = (
|
||||
activeFieldMetadatItem: FieldMetadataItem,
|
||||
) =>
|
||||
) => {
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
updateOneObjectMetadataItem({
|
||||
idToUpdate: objectMetadataItem.id,
|
||||
updatePayload: {
|
||||
labelIdentifierFieldMetadataId: activeFieldMetadatItem.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const [, setActiveSettingsObjectFields] = useRecoilState(
|
||||
settingsObjectFieldsFamilyState({
|
||||
@@ -254,6 +267,7 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
mode === 'view' ? (
|
||||
<SettingsObjectFieldActiveActionDropdown
|
||||
isCustomField={fieldMetadataItem.isCustom === true}
|
||||
readonly={readonly}
|
||||
fieldMetadataItemId={fieldMetadataItem.id}
|
||||
onEdit={() =>
|
||||
navigate(SettingsPath.ObjectFieldEdit, {
|
||||
@@ -284,6 +298,7 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
) : mode === 'view' ? (
|
||||
<SettingsObjectFieldInactiveActionDropdown
|
||||
isCustomField={fieldMetadataItem.isCustom === true}
|
||||
readonly={readonly}
|
||||
fieldMetadataItemId={fieldMetadataItem.id}
|
||||
onEdit={() =>
|
||||
navigate(SettingsPath.ObjectFieldEdit, {
|
||||
|
||||
+2
-4
@@ -3,11 +3,10 @@ import styled from '@emotion/styled';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag';
|
||||
import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
|
||||
|
||||
export type SettingsObjectMetadataItemTableRowProps = {
|
||||
action: ReactNode;
|
||||
@@ -46,7 +45,6 @@ export const SettingsObjectMetadataItemTableRow = ({
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
const Icon = getIcon(objectMetadataItem.icon);
|
||||
const objectTypeLabel = getObjectTypeLabel(objectMetadataItem);
|
||||
|
||||
return (
|
||||
<StyledObjectTableRow key={objectMetadataItem.namePlural} to={link}>
|
||||
@@ -63,7 +61,7 @@ export const SettingsObjectMetadataItemTableRow = ({
|
||||
</StyledNameLabel>
|
||||
</StyledNameTableCell>
|
||||
<TableCell>
|
||||
<SettingsDataModelObjectTypeTag objectTypeLabel={objectTypeLabel} />
|
||||
<SettingsItemTypeTag item={objectMetadataItem} />
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{objectMetadataItem.fields.filter((field) => !field.isSystem).length}
|
||||
|
||||
+15
-5
@@ -14,6 +14,7 @@ import { SettingsPath } from 'twenty-shared/types';
|
||||
import { ZodError } from 'zod';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { updatedObjectNamePluralState } from '~/pages/settings/data-model/states/updatedObjectNamePluralState';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
|
||||
type SettingsUpdateDataModelObjectAboutFormProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
@@ -22,6 +23,7 @@ type SettingsUpdateDataModelObjectAboutFormProps = {
|
||||
export const SettingsUpdateDataModelObjectAboutForm = ({
|
||||
objectMetadataItem,
|
||||
}: SettingsUpdateDataModelObjectAboutFormProps) => {
|
||||
const readonly = isObjectMetadataReadOnly({ objectMetadataItem });
|
||||
const navigate = useNavigateSettings();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const setUpdatedObjectNamePlural = useSetRecoilState(
|
||||
@@ -54,6 +56,10 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
|
||||
const handleSave = async (
|
||||
formValues: SettingsDataModelObjectAboutFormValues,
|
||||
) => {
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(Object.keys(formConfig.formState.dirtyFields).length > 0)) {
|
||||
return;
|
||||
}
|
||||
@@ -71,10 +77,10 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
|
||||
description,
|
||||
icon: icon ?? undefined,
|
||||
isLabelSyncedWithName: formValues.isLabelSyncedWithName,
|
||||
labelPlural: updatedObject.data?.updateOneObject.labelPlural,
|
||||
labelSingular: updatedObject.data?.updateOneObject.labelSingular,
|
||||
namePlural: updatedObject.data?.updateOneObject.namePlural,
|
||||
nameSingular: updatedObject.data?.updateOneObject.nameSingular,
|
||||
labelPlural: updatedObject?.data?.updateOneObject.labelPlural,
|
||||
labelSingular: updatedObject?.data?.updateOneObject.labelSingular,
|
||||
namePlural: updatedObject?.data?.updateOneObject.namePlural,
|
||||
nameSingular: updatedObject?.data?.updateOneObject.nameSingular,
|
||||
});
|
||||
} else {
|
||||
formConfig.reset(undefined, { keepValues: true });
|
||||
@@ -91,6 +97,10 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
|
||||
const updateObjectMetadata = async (
|
||||
formValues: SettingsDataModelObjectAboutFormValues,
|
||||
) => {
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatePayload = { ...formValues };
|
||||
|
||||
if (!objectMetadataItem.isCustom) {
|
||||
@@ -139,7 +149,7 @@ export const SettingsUpdateDataModelObjectAboutForm = ({
|
||||
<FormProvider {...formConfig}>
|
||||
<SettingsDataModelObjectAboutForm
|
||||
onNewDirtyField={() => formConfig.handleSubmit(handleSave)()}
|
||||
disableEdition={!objectMetadataItem.isCustom}
|
||||
disableEdition={!objectMetadataItem.isCustom || readonly}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
</FormProvider>
|
||||
|
||||
+5
-2
@@ -9,6 +9,7 @@ import { H2Title, IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
display: flex;
|
||||
@@ -21,7 +22,9 @@ type ObjectFieldsProps = {
|
||||
};
|
||||
|
||||
export const ObjectFields = ({ objectMetadataItem }: ObjectFieldsProps) => {
|
||||
const shouldDisplayAddFieldButton = !objectMetadataItem.isRemote;
|
||||
const readonly = isObjectMetadataReadOnly({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const { t } = useLingui();
|
||||
const objectLabelSingular = objectMetadataItem.labelSingular;
|
||||
@@ -36,7 +39,7 @@ export const ObjectFields = ({ objectMetadataItem }: ObjectFieldsProps) => {
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
mode="view"
|
||||
/>
|
||||
{shouldDisplayAddFieldButton && (
|
||||
{!readonly && (
|
||||
<StyledDiv>
|
||||
<UndecoratedLink
|
||||
to={getSettingsPath(SettingsPath.ObjectNewFieldSelect, {
|
||||
|
||||
+18
-11
@@ -10,6 +10,7 @@ import { H2Title, IconArchive } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
|
||||
type ObjectSettingsProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
@@ -27,6 +28,7 @@ const StyledFormSection = styled(Section)`
|
||||
|
||||
export const ObjectSettings = ({ objectMetadataItem }: ObjectSettingsProps) => {
|
||||
const { t } = useLingui();
|
||||
const readonly = isObjectMetadataReadOnly({ objectMetadataItem });
|
||||
const navigate = useNavigateSettings();
|
||||
const { updateOneObjectMetadataItem } = useUpdateOneObjectMetadataItem();
|
||||
const handleDisable = async () => {
|
||||
@@ -59,17 +61,22 @@ export const ObjectSettings = ({ objectMetadataItem }: ObjectSettingsProps) => {
|
||||
/>
|
||||
</Section>
|
||||
</StyledFormSection>
|
||||
<StyledFormSection>
|
||||
<Section>
|
||||
<H2Title title={t`Danger zone`} description={t`Deactivate object`} />
|
||||
<Button
|
||||
Icon={IconArchive}
|
||||
title={t`Deactivate`}
|
||||
size="small"
|
||||
onClick={handleDisable}
|
||||
/>
|
||||
</Section>
|
||||
</StyledFormSection>
|
||||
{!readonly && (
|
||||
<StyledFormSection>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Danger zone`}
|
||||
description={t`Deactivate object`}
|
||||
/>
|
||||
<Button
|
||||
Icon={IconArchive}
|
||||
title={t`Deactivate`}
|
||||
size="small"
|
||||
onClick={handleDisable}
|
||||
/>
|
||||
</Section>
|
||||
</StyledFormSection>
|
||||
)}
|
||||
</StyledContentContainer>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user