Fix remote object read-only + remove relations (#4921)
- Set `readOnly` boolean in table row context. Preventing updates and deletion - Show page is null for remote objects. No need for complicated design since this is temporary? - Relation creations are now behind a feature flag for remote objects - Refetch objects and views after syncing objects --------- Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
@ -1,10 +1,13 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { ApolloClient, useMutation } from '@apollo/client';
|
import { ApolloClient, useApolloClient, useMutation } from '@apollo/client';
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
|
|
||||||
import { SYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/syncRemoteTable';
|
import { SYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/syncRemoteTable';
|
||||||
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
||||||
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
|
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
|
||||||
|
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
|
||||||
import {
|
import {
|
||||||
RemoteTableInput,
|
RemoteTableInput,
|
||||||
SyncRemoteTableMutation,
|
SyncRemoteTableMutation,
|
||||||
@ -13,6 +16,14 @@ import {
|
|||||||
|
|
||||||
export const useSyncRemoteTable = () => {
|
export const useSyncRemoteTable = () => {
|
||||||
const apolloMetadataClient = useApolloMetadataClient();
|
const apolloMetadataClient = useApolloMetadataClient();
|
||||||
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
|
const { refetch: refetchObjectMetadataItems } =
|
||||||
|
useFindManyObjectMetadataItems();
|
||||||
|
|
||||||
|
const { findManyRecordsQuery: findManyViewsQuery } = useFindManyRecordsQuery({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.View,
|
||||||
|
});
|
||||||
|
|
||||||
const [mutate] = useMutation<
|
const [mutate] = useMutation<
|
||||||
SyncRemoteTableMutation,
|
SyncRemoteTableMutation,
|
||||||
@ -23,15 +34,24 @@ export const useSyncRemoteTable = () => {
|
|||||||
|
|
||||||
const syncRemoteTable = useCallback(
|
const syncRemoteTable = useCallback(
|
||||||
async (input: RemoteTableInput) => {
|
async (input: RemoteTableInput) => {
|
||||||
return await mutate({
|
const remoteTable = await mutate({
|
||||||
variables: {
|
variables: {
|
||||||
input,
|
input,
|
||||||
},
|
},
|
||||||
awaitRefetchQueries: true,
|
awaitRefetchQueries: true,
|
||||||
refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''],
|
refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: we should return the tables with the columns and store in cache instead of refetching
|
||||||
|
await refetchObjectMetadataItems();
|
||||||
|
await apolloClient.query({
|
||||||
|
query: findManyViewsQuery,
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
});
|
||||||
|
|
||||||
|
return remoteTable;
|
||||||
},
|
},
|
||||||
[mutate],
|
[apolloClient, findManyViewsQuery, mutate, refetchObjectMetadataItems],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { getOperationName } from '@apollo/client/utilities';
|
|||||||
import { UNSYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/unsyncRemoteTable';
|
import { UNSYNC_REMOTE_TABLE } from '@/databases/graphql/mutations/unsyncRemoteTable';
|
||||||
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
import { GET_MANY_REMOTE_TABLES } from '@/databases/graphql/queries/findManyRemoteTables';
|
||||||
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
|
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
|
||||||
|
import { useFindManyObjectMetadataItems } from '@/object-metadata/hooks/useFindManyObjectMetadataItems';
|
||||||
import {
|
import {
|
||||||
RemoteTableInput,
|
RemoteTableInput,
|
||||||
UnsyncRemoteTableMutation,
|
UnsyncRemoteTableMutation,
|
||||||
@ -13,6 +14,8 @@ import {
|
|||||||
|
|
||||||
export const useUnsyncRemoteTable = () => {
|
export const useUnsyncRemoteTable = () => {
|
||||||
const apolloMetadataClient = useApolloMetadataClient();
|
const apolloMetadataClient = useApolloMetadataClient();
|
||||||
|
const { refetch: refetchObjectMetadataItems } =
|
||||||
|
useFindManyObjectMetadataItems();
|
||||||
|
|
||||||
const [mutate] = useMutation<
|
const [mutate] = useMutation<
|
||||||
UnsyncRemoteTableMutation,
|
UnsyncRemoteTableMutation,
|
||||||
@ -23,15 +26,19 @@ export const useUnsyncRemoteTable = () => {
|
|||||||
|
|
||||||
const unsyncRemoteTable = useCallback(
|
const unsyncRemoteTable = useCallback(
|
||||||
async (input: RemoteTableInput) => {
|
async (input: RemoteTableInput) => {
|
||||||
return await mutate({
|
const remoteTable = await mutate({
|
||||||
variables: {
|
variables: {
|
||||||
input,
|
input,
|
||||||
},
|
},
|
||||||
awaitRefetchQueries: true,
|
awaitRefetchQueries: true,
|
||||||
refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''],
|
refetchQueries: [getOperationName(GET_MANY_REMOTE_TABLES) ?? ''],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await refetchObjectMetadataItems();
|
||||||
|
|
||||||
|
return remoteTable;
|
||||||
},
|
},
|
||||||
[mutate],
|
[mutate, refetchObjectMetadataItems],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export const useFindManyObjectMetadataItems = ({
|
|||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { data, loading, error } = useQuery<
|
const { data, loading, error, refetch } = useQuery<
|
||||||
ObjectMetadataItemsQuery,
|
ObjectMetadataItemsQuery,
|
||||||
ObjectMetadataItemsQueryVariables
|
ObjectMetadataItemsQueryVariables
|
||||||
>(FIND_MANY_OBJECT_METADATA_ITEMS, {
|
>(FIND_MANY_OBJECT_METADATA_ITEMS, {
|
||||||
@ -59,5 +59,6 @@ export const useFindManyObjectMetadataItems = ({
|
|||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
|
refetch,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -106,7 +106,21 @@ export const useRecordActionBar = ({
|
|||||||
recordIndexId: objectMetadataItem.namePlural,
|
recordIndexId: objectMetadataItem.namePlural,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isRemoteObject = objectMetadataItem.isRemote;
|
||||||
|
|
||||||
const baseActions: ContextMenuEntry[] = useMemo(
|
const baseActions: ContextMenuEntry[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: `${progress === undefined ? `Export` : `Export (${progress}%)`}`,
|
||||||
|
Icon: IconFileExport,
|
||||||
|
accent: 'default',
|
||||||
|
onClick: () => download(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[download, progress],
|
||||||
|
);
|
||||||
|
|
||||||
|
const deletionActions: ContextMenuEntry[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
@ -130,17 +144,9 @@ export const useRecordActionBar = ({
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: `${progress === undefined ? `Export` : `Export (${progress}%)`}`,
|
|
||||||
Icon: IconFileExport,
|
|
||||||
accent: 'default',
|
|
||||||
onClick: () => download(),
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
handleDeleteClick,
|
handleDeleteClick,
|
||||||
download,
|
|
||||||
progress,
|
|
||||||
selectedRecordIds,
|
selectedRecordIds,
|
||||||
isDeleteRecordsModalOpen,
|
isDeleteRecordsModalOpen,
|
||||||
setIsDeleteRecordsModalOpen,
|
setIsDeleteRecordsModalOpen,
|
||||||
@ -160,8 +166,9 @@ export const useRecordActionBar = ({
|
|||||||
return {
|
return {
|
||||||
setContextMenuEntries: useCallback(() => {
|
setContextMenuEntries: useCallback(() => {
|
||||||
setContextMenuEntries([
|
setContextMenuEntries([
|
||||||
|
...(isRemoteObject ? [] : deletionActions),
|
||||||
...baseActions,
|
...baseActions,
|
||||||
...(isFavorite && hasOnlyOneRecordSelected
|
...(!isRemoteObject && isFavorite && hasOnlyOneRecordSelected
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: 'Remove from favorites',
|
label: 'Remove from favorites',
|
||||||
@ -170,7 +177,7 @@ export const useRecordActionBar = ({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(!isFavorite && hasOnlyOneRecordSelected
|
...(!isRemoteObject && !isFavorite && hasOnlyOneRecordSelected
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: 'Add to favorites',
|
label: 'Add to favorites',
|
||||||
@ -182,9 +189,11 @@ export const useRecordActionBar = ({
|
|||||||
]);
|
]);
|
||||||
}, [
|
}, [
|
||||||
baseActions,
|
baseActions,
|
||||||
|
deletionActions,
|
||||||
handleFavoriteButtonClick,
|
handleFavoriteButtonClick,
|
||||||
hasOnlyOneRecordSelected,
|
hasOnlyOneRecordSelected,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
|
isRemoteObject,
|
||||||
setContextMenuEntries,
|
setContextMenuEntries,
|
||||||
]),
|
]),
|
||||||
|
|
||||||
@ -209,12 +218,15 @@ export const useRecordActionBar = ({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
...(isRemoteObject ? [] : deletionActions),
|
||||||
...baseActions,
|
...baseActions,
|
||||||
]);
|
]);
|
||||||
}, [
|
}, [
|
||||||
baseActions,
|
baseActions,
|
||||||
dataExecuteQuickActionOnmentEnabled,
|
dataExecuteQuickActionOnmentEnabled,
|
||||||
|
deletionActions,
|
||||||
handleExecuteQuickActionOnClick,
|
handleExecuteQuickActionOnClick,
|
||||||
|
isRemoteObject,
|
||||||
setActionBarEntriesState,
|
setActionBarEntriesState,
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -47,6 +47,7 @@ type FieldInputProps = {
|
|||||||
onEscape?: FieldInputEvent;
|
onEscape?: FieldInputEvent;
|
||||||
onTab?: FieldInputEvent;
|
onTab?: FieldInputEvent;
|
||||||
onShiftTab?: FieldInputEvent;
|
onShiftTab?: FieldInputEvent;
|
||||||
|
isReadOnly?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FieldInput = ({
|
export const FieldInput = ({
|
||||||
@ -58,6 +59,7 @@ export const FieldInput = ({
|
|||||||
onShiftTab,
|
onShiftTab,
|
||||||
onTab,
|
onTab,
|
||||||
onClickOutside,
|
onClickOutside,
|
||||||
|
isReadOnly,
|
||||||
}: FieldInputProps) => {
|
}: FieldInputProps) => {
|
||||||
const { fieldDefinition } = useContext(FieldContext);
|
const { fieldDefinition } = useContext(FieldContext);
|
||||||
|
|
||||||
@ -136,7 +138,7 @@ export const FieldInput = ({
|
|||||||
onShiftTab={onShiftTab}
|
onShiftTab={onShiftTab}
|
||||||
/>
|
/>
|
||||||
) : isFieldBoolean(fieldDefinition) ? (
|
) : isFieldBoolean(fieldDefinition) ? (
|
||||||
<BooleanFieldInput onSubmit={onSubmit} />
|
<BooleanFieldInput onSubmit={onSubmit} readonly={isReadOnly} />
|
||||||
) : isFieldRating(fieldDefinition) ? (
|
) : isFieldRating(fieldDefinition) ? (
|
||||||
<RatingFieldInput onSubmit={onSubmit} />
|
<RatingFieldInput onSubmit={onSubmit} />
|
||||||
) : isFieldSelect(fieldDefinition) ? (
|
) : isFieldSelect(fieldDefinition) ? (
|
||||||
|
|||||||
@ -92,6 +92,7 @@ export const RecordInlineCell = ({ readonly }: RecordInlineCellProps) => {
|
|||||||
onTab={handleTab}
|
onTab={handleTab}
|
||||||
onShiftTab={handleShiftTab}
|
onShiftTab={handleShiftTab}
|
||||||
onClickOutside={handleClickOutside}
|
onClickOutside={handleClickOutside}
|
||||||
|
isReadOnly={readonly}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
displayModeContent={<FieldDisplay />}
|
displayModeContent={<FieldDisplay />}
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export const RecordTableRow = ({ recordId, rowIndex }: RecordTableRowProps) => {
|
|||||||
objectNameSingular: objectMetadataItem.nameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
}) + recordId,
|
}) + recordId,
|
||||||
isSelected: currentRowSelected,
|
isSelected: currentRowSelected,
|
||||||
|
isReadOnly: objectMetadataItem.isRemote ?? false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<tr
|
<tr
|
||||||
|
|||||||
@ -5,6 +5,7 @@ type RecordTableRowContextProps = {
|
|||||||
recordId: string;
|
recordId: string;
|
||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
|
isReadOnly: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordTableRowContext = createContext<RecordTableRowContextProps>(
|
export const RecordTableRowContext = createContext<RecordTableRowContextProps>(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { FieldDisplay } from '@/object-record/record-field/components/FieldDispl
|
|||||||
import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
import { FieldInput } from '@/object-record/record-field/components/FieldInput';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
|
||||||
|
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
||||||
import { RecordTableCellContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellContainer';
|
import { RecordTableCellContainer } from '@/object-record/record-table/record-table-cell/components/RecordTableCellContainer';
|
||||||
import { useCloseRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCell';
|
import { useCloseRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCell';
|
||||||
@ -21,6 +22,7 @@ export const RecordTableCell = ({
|
|||||||
const { moveLeft, moveRight, moveDown } = useRecordTableMoveFocus();
|
const { moveLeft, moveRight, moveDown } = useRecordTableMoveFocus();
|
||||||
|
|
||||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||||
|
const { isReadOnly } = useContext(RecordTableRowContext);
|
||||||
|
|
||||||
const handleEnter: FieldInputEvent = (persistField) => {
|
const handleEnter: FieldInputEvent = (persistField) => {
|
||||||
upsertRecord(persistField);
|
upsertRecord(persistField);
|
||||||
@ -78,6 +80,7 @@ export const RecordTableCell = ({
|
|||||||
onShiftTab={handleShiftTab}
|
onShiftTab={handleShiftTab}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
onTab={handleTab}
|
onTab={handleTab}
|
||||||
|
isReadOnly={isReadOnly}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
nonEditModeContent={<FieldDisplay />}
|
nonEditModeContent={<FieldDisplay />}
|
||||||
|
|||||||
@ -90,7 +90,7 @@ export const RecordTableCellContainer = ({
|
|||||||
openTableCell();
|
openTableCell();
|
||||||
};
|
};
|
||||||
|
|
||||||
const { isSelected } = useContext(RecordTableRowContext);
|
const { isSelected, isReadOnly } = useContext(RecordTableRowContext);
|
||||||
|
|
||||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
||||||
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
|
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
|
||||||
@ -146,7 +146,8 @@ export const RecordTableCellContainer = ({
|
|||||||
hasSoftFocus &&
|
hasSoftFocus &&
|
||||||
!isCurrentTableCellInEditMode &&
|
!isCurrentTableCellInEditMode &&
|
||||||
!editModeContentOnly &&
|
!editModeContentOnly &&
|
||||||
(!isFirstColumn || !isEmpty);
|
(!isFirstColumn || !isEmpty) &&
|
||||||
|
!isReadOnly;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTd
|
<StyledTd
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export const DEFAULT_CELL_SCOPE: HotkeyScope = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useOpenRecordTableCell = () => {
|
export const useOpenRecordTableCell = () => {
|
||||||
const { pathToShowPage } = useContext(RecordTableRowContext);
|
const { pathToShowPage, isReadOnly } = useContext(RecordTableRowContext);
|
||||||
|
|
||||||
const { setCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
|
const { setCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
@ -46,6 +46,10 @@ export const useOpenRecordTableCell = () => {
|
|||||||
|
|
||||||
const openTableCell = useRecoilCallback(
|
const openTableCell = useRecoilCallback(
|
||||||
() => (options?: { initialValue?: string }) => {
|
() => (options?: { initialValue?: string }) => {
|
||||||
|
if (isReadOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isFirstColumnCell && !isEmpty) {
|
if (isFirstColumnCell && !isEmpty) {
|
||||||
leaveTableFocus();
|
leaveTableFocus();
|
||||||
navigate(pathToShowPage);
|
navigate(pathToShowPage);
|
||||||
@ -70,15 +74,16 @@ export const useOpenRecordTableCell = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
|
isReadOnly,
|
||||||
isFirstColumnCell,
|
isFirstColumnCell,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
leaveTableFocus,
|
|
||||||
navigate,
|
|
||||||
pathToShowPage,
|
|
||||||
setDragSelectionStartEnabled,
|
setDragSelectionStartEnabled,
|
||||||
setCurrentTableCellInEditMode,
|
setCurrentTableCellInEditMode,
|
||||||
initFieldInputDraftValue,
|
initFieldInputDraftValue,
|
||||||
customCellHotkeyScope,
|
customCellHotkeyScope,
|
||||||
|
leaveTableFocus,
|
||||||
|
navigate,
|
||||||
|
pathToShowPage,
|
||||||
setHotkeyScope,
|
setHotkeyScope,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -22,6 +22,7 @@ type SettingsObjectFieldItemTableRowProps = {
|
|||||||
fieldMetadataItem: FieldMetadataItem;
|
fieldMetadataItem: FieldMetadataItem;
|
||||||
identifierType?: Nullable<FieldIdentifierType>;
|
identifierType?: Nullable<FieldIdentifierType>;
|
||||||
variant?: 'field-type' | 'identifier';
|
variant?: 'field-type' | 'identifier';
|
||||||
|
isRemoteObjectField?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StyledObjectFieldTableRow = styled(TableRow)`
|
export const StyledObjectFieldTableRow = styled(TableRow)`
|
||||||
@ -43,6 +44,7 @@ export const SettingsObjectFieldItemTableRow = ({
|
|||||||
fieldMetadataItem,
|
fieldMetadataItem,
|
||||||
identifierType,
|
identifierType,
|
||||||
variant = 'field-type',
|
variant = 'field-type',
|
||||||
|
isRemoteObjectField,
|
||||||
}: SettingsObjectFieldItemTableRowProps) => {
|
}: SettingsObjectFieldItemTableRowProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
@ -76,7 +78,11 @@ export const SettingsObjectFieldItemTableRow = ({
|
|||||||
</StyledNameTableCell>
|
</StyledNameTableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{variant === 'field-type' &&
|
{variant === 'field-type' &&
|
||||||
(fieldMetadataItem.isCustom ? 'Custom' : 'Standard')}
|
(isRemoteObjectField
|
||||||
|
? 'Remote'
|
||||||
|
: fieldMetadataItem.isCustom
|
||||||
|
? 'Custom'
|
||||||
|
: 'Standard')}
|
||||||
{variant === 'identifier' &&
|
{variant === 'identifier' &&
|
||||||
!!identifierType &&
|
!!identifierType &&
|
||||||
(identifierType === 'label' ? 'Record text' : 'Record image')}
|
(identifierType === 'label' ? 'Record text' : 'Record image')}
|
||||||
|
|||||||
@ -4,7 +4,8 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||||
import { SettingsDataModelIsCustomTag } from '@/settings/data-model/objects/SettingsDataModelIsCustomTag';
|
import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/SettingsDataModelObjectTypeTag';
|
||||||
|
import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
|
||||||
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
@ -42,6 +43,7 @@ export const SettingsObjectItemTableRow = ({
|
|||||||
});
|
});
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const Icon = getIcon(objectItem.icon);
|
const Icon = getIcon(objectItem.icon);
|
||||||
|
const objectTypeLabel = getObjectTypeLabel(objectItem);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledObjectTableRow key={objectItem.namePlural} onClick={onClick}>
|
<StyledObjectTableRow key={objectItem.namePlural} onClick={onClick}>
|
||||||
@ -52,7 +54,7 @@ export const SettingsObjectItemTableRow = ({
|
|||||||
{objectItem.labelPlural}
|
{objectItem.labelPlural}
|
||||||
</StyledNameTableCell>
|
</StyledNameTableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<SettingsDataModelIsCustomTag isCustom={objectItem.isCustom} />
|
<SettingsDataModelObjectTypeTag objectTypeLabel={objectTypeLabel} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="right">
|
<TableCell align="right">
|
||||||
{objectItem.fields.filter((field) => !field.isSystem).length}
|
{objectItem.fields.filter((field) => !field.isSystem).length}
|
||||||
|
|||||||
@ -2,8 +2,10 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { IconArchive, IconDotsVertical, IconPencil } from 'twenty-ui';
|
import { IconArchive, IconDotsVertical, IconPencil } from 'twenty-ui';
|
||||||
|
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { SettingsSummaryCard } from '@/settings/components/SettingsSummaryCard';
|
import { SettingsSummaryCard } from '@/settings/components/SettingsSummaryCard';
|
||||||
import { SettingsDataModelIsCustomTag } from '@/settings/data-model/objects/SettingsDataModelIsCustomTag';
|
import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/SettingsDataModelObjectTypeTag';
|
||||||
|
import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
|
||||||
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
||||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
@ -13,14 +15,14 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
|||||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
|
|
||||||
type SettingsObjectSummaryCardProps = {
|
type SettingsObjectSummaryCardProps = {
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
iconKey?: string;
|
iconKey?: string;
|
||||||
isCustom: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
onDeactivate: () => void;
|
onDeactivate: () => void;
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledIsCustomTag = styled(SettingsDataModelIsCustomTag)`
|
const StyledObjectTypeTag = styled(SettingsDataModelObjectTypeTag)`
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: ${({ theme }) => theme.spacing(6)};
|
height: ${({ theme }) => theme.spacing(6)};
|
||||||
`;
|
`;
|
||||||
@ -28,8 +30,8 @@ const StyledIsCustomTag = styled(SettingsDataModelIsCustomTag)`
|
|||||||
const dropdownId = 'settings-object-edit-about-menu-dropdown';
|
const dropdownId = 'settings-object-edit-about-menu-dropdown';
|
||||||
|
|
||||||
export const SettingsObjectSummaryCard = ({
|
export const SettingsObjectSummaryCard = ({
|
||||||
|
objectMetadataItem,
|
||||||
iconKey = '',
|
iconKey = '',
|
||||||
isCustom,
|
|
||||||
name,
|
name,
|
||||||
onDeactivate,
|
onDeactivate,
|
||||||
onEdit,
|
onEdit,
|
||||||
@ -50,6 +52,8 @@ export const SettingsObjectSummaryCard = ({
|
|||||||
closeDropdown();
|
closeDropdown();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const objectTypeLabel = getObjectTypeLabel(objectMetadataItem);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsSummaryCard
|
<SettingsSummaryCard
|
||||||
title={
|
title={
|
||||||
@ -60,7 +64,7 @@ export const SettingsObjectSummaryCard = ({
|
|||||||
}
|
}
|
||||||
rightComponent={
|
rightComponent={
|
||||||
<>
|
<>
|
||||||
<StyledIsCustomTag isCustom={isCustom} />
|
<StyledObjectTypeTag objectTypeLabel={objectTypeLabel} />
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
clickableComponent={
|
clickableComponent={
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
import { Tag } from '@/ui/display/tag/components/Tag';
|
|
||||||
|
|
||||||
type SettingsDataModelIsCustomTagProps = {
|
|
||||||
className?: string;
|
|
||||||
isCustom?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SettingsDataModelIsCustomTag = ({
|
|
||||||
className,
|
|
||||||
isCustom,
|
|
||||||
}: SettingsDataModelIsCustomTagProps) => (
|
|
||||||
<Tag
|
|
||||||
className={className}
|
|
||||||
color={isCustom ? 'orange' : 'blue'}
|
|
||||||
text={isCustom ? 'Custom' : 'Standard'}
|
|
||||||
weight="medium"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
@ -2,15 +2,13 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { SettingsDataModelIsCustomTag } from '@/settings/data-model/objects/SettingsDataModelIsCustomTag';
|
import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/SettingsDataModelObjectTypeTag';
|
||||||
|
import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
|
||||||
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
||||||
|
|
||||||
export type SettingsDataModelObjectSummaryProps = {
|
export type SettingsDataModelObjectSummaryProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
objectMetadataItem: Pick<
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
ObjectMetadataItem,
|
|
||||||
'icon' | 'isCustom' | 'labelPlural'
|
|
||||||
>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledObjectSummary = styled.div`
|
const StyledObjectSummary = styled.div`
|
||||||
@ -35,6 +33,7 @@ export const SettingsDataModelObjectSummary = ({
|
|||||||
|
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const ObjectIcon = getIcon(objectMetadataItem.icon);
|
const ObjectIcon = getIcon(objectMetadataItem.icon);
|
||||||
|
const objectTypeLabel = getObjectTypeLabel(objectMetadataItem);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledObjectSummary className={className}>
|
<StyledObjectSummary className={className}>
|
||||||
@ -42,7 +41,7 @@ export const SettingsDataModelObjectSummary = ({
|
|||||||
<ObjectIcon size={theme.icon.size.sm} stroke={theme.icon.stroke.md} />
|
<ObjectIcon size={theme.icon.size.sm} stroke={theme.icon.stroke.md} />
|
||||||
{objectMetadataItem.labelPlural}
|
{objectMetadataItem.labelPlural}
|
||||||
</StyledObjectName>
|
</StyledObjectName>
|
||||||
<SettingsDataModelIsCustomTag isCustom={objectMetadataItem.isCustom} />
|
<SettingsDataModelObjectTypeTag objectTypeLabel={objectTypeLabel} />
|
||||||
</StyledObjectSummary>
|
</StyledObjectSummary>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { ObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
|
||||||
|
import { Tag } from '@/ui/display/tag/components/Tag';
|
||||||
|
|
||||||
|
type SettingsDataModelObjectTypeTagProps = {
|
||||||
|
objectTypeLabel: ObjectTypeLabel;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsDataModelObjectTypeTag = ({
|
||||||
|
className,
|
||||||
|
objectTypeLabel,
|
||||||
|
}: SettingsDataModelObjectTypeTagProps) => {
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
className={className}
|
||||||
|
color={objectTypeLabel.labelColor}
|
||||||
|
text={objectTypeLabel.labelText}
|
||||||
|
weight="medium"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
|
||||||
|
export type ObjectTypeLabel =
|
||||||
|
| StandardObjectTypeLabel
|
||||||
|
| CustomObjectTypeLabel
|
||||||
|
| RemoteObjectTypeLabel;
|
||||||
|
|
||||||
|
type StandardObjectTypeLabel = {
|
||||||
|
labelText: 'Standard';
|
||||||
|
labelColor: 'blue';
|
||||||
|
};
|
||||||
|
|
||||||
|
type CustomObjectTypeLabel = {
|
||||||
|
labelText: 'Custom';
|
||||||
|
labelColor: 'orange';
|
||||||
|
};
|
||||||
|
|
||||||
|
type RemoteObjectTypeLabel = {
|
||||||
|
labelText: 'Remote';
|
||||||
|
labelColor: 'green';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getObjectTypeLabel = (
|
||||||
|
objectMetadataItem: ObjectMetadataItem,
|
||||||
|
): ObjectTypeLabel =>
|
||||||
|
objectMetadataItem.isCustom
|
||||||
|
? {
|
||||||
|
labelText: 'Custom',
|
||||||
|
labelColor: 'orange',
|
||||||
|
}
|
||||||
|
: objectMetadataItem.isRemote
|
||||||
|
? {
|
||||||
|
labelText: 'Remote',
|
||||||
|
labelColor: 'green',
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
labelText: 'Standard',
|
||||||
|
labelColor: 'blue',
|
||||||
|
};
|
||||||
@ -63,7 +63,7 @@ export const SettingsIntegrationDatabaseTablesListCard = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[connectionId, syncRemoteTable, items, unsyncRemoteTable],
|
[items, syncRemoteTable, connectionId, unsyncRemoteTable],
|
||||||
);
|
);
|
||||||
|
|
||||||
const rowRightComponent = useCallback(
|
const rowRightComponent = useCallback(
|
||||||
|
|||||||
@ -88,6 +88,11 @@ export const RecordShowPage = () => {
|
|||||||
].join(' ')
|
].join(' ')
|
||||||
: `${labelIdentifierFieldValue}`;
|
: `${labelIdentifierFieldValue}`;
|
||||||
|
|
||||||
|
// Temporarily since we don't have relations for remote objects yet
|
||||||
|
if (objectMetadataItem.isRemote) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<PageTitle title={pageName} />
|
<PageTitle title={pageName} />
|
||||||
|
|||||||
@ -102,7 +102,7 @@ export const SettingsObjectDetail = () => {
|
|||||||
<SettingsObjectSummaryCard
|
<SettingsObjectSummaryCard
|
||||||
iconKey={activeObjectMetadataItem.icon ?? undefined}
|
iconKey={activeObjectMetadataItem.icon ?? undefined}
|
||||||
name={activeObjectMetadataItem.labelPlural || ''}
|
name={activeObjectMetadataItem.labelPlural || ''}
|
||||||
isCustom={activeObjectMetadataItem.isCustom}
|
objectMetadataItem={activeObjectMetadataItem}
|
||||||
onDeactivate={handleDisableObject}
|
onDeactivate={handleDisableObject}
|
||||||
onEdit={() => navigate('./edit')}
|
onEdit={() => navigate('./edit')}
|
||||||
/>
|
/>
|
||||||
@ -150,6 +150,7 @@ export const SettingsObjectDetail = () => {
|
|||||||
: 'field-type'
|
: 'field-type'
|
||||||
}
|
}
|
||||||
fieldMetadataItem={activeMetadataField}
|
fieldMetadataItem={activeMetadataField}
|
||||||
|
isRemoteObjectField={activeObjectMetadataItem.isRemote}
|
||||||
ActionIcon={
|
ActionIcon={
|
||||||
<SettingsObjectFieldActiveActionDropdown
|
<SettingsObjectFieldActiveActionDropdown
|
||||||
isCustomField={!!activeMetadataField.isCustom}
|
isCustomField={!!activeMetadataField.isCustom}
|
||||||
|
|||||||
@ -140,6 +140,7 @@ export const SettingsObjectNewFieldStep1 = () => {
|
|||||||
<SettingsObjectFieldItemTableRow
|
<SettingsObjectFieldItemTableRow
|
||||||
key={activeMetadataField.id}
|
key={activeMetadataField.id}
|
||||||
fieldMetadataItem={activeMetadataField}
|
fieldMetadataItem={activeMetadataField}
|
||||||
|
isRemoteObjectField={activeObjectMetadataItem.isRemote}
|
||||||
ActionIcon={
|
ActionIcon={
|
||||||
isLabelIdentifierField({
|
isLabelIdentifierField({
|
||||||
fieldMetadataItem: activeMetadataField,
|
fieldMetadataItem: activeMetadataField,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export enum FeatureFlagKeys {
|
|||||||
IsAirtableIntegrationEnabled = 'IS_AIRTABLE_INTEGRATION_ENABLED',
|
IsAirtableIntegrationEnabled = 'IS_AIRTABLE_INTEGRATION_ENABLED',
|
||||||
IsPostgreSQLIntegrationEnabled = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
IsPostgreSQLIntegrationEnabled = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
||||||
IsMultiSelectEnabled = 'IS_MULTI_SELECT_ENABLED',
|
IsMultiSelectEnabled = 'IS_MULTI_SELECT_ENABLED',
|
||||||
|
IsRelationForRemoteObjectsEnabled = 'IS_RELATION_FOR_REMOTE_OBJECTS_ENABLED',
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity({ name: 'featureFlag', schema: 'core' })
|
@Entity({ name: 'featureFlag', schema: 'core' })
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NestjsQueryGraphQLModule,
|
NestjsQueryGraphQLModule,
|
||||||
@ -15,6 +16,8 @@ import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
|||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metadata/object-metadata.resolver';
|
import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metadata/object-metadata.resolver';
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
|
|
||||||
import { ObjectMetadataService } from './object-metadata.service';
|
import { ObjectMetadataService } from './object-metadata.service';
|
||||||
import { ObjectMetadataEntity } from './object-metadata.entity';
|
import { ObjectMetadataEntity } from './object-metadata.entity';
|
||||||
@ -32,9 +35,11 @@ import { ObjectMetadataDTO } from './dtos/object-metadata.dto';
|
|||||||
[ObjectMetadataEntity, FieldMetadataEntity, RelationMetadataEntity],
|
[ObjectMetadataEntity, FieldMetadataEntity, RelationMetadataEntity],
|
||||||
'metadata',
|
'metadata',
|
||||||
),
|
),
|
||||||
|
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
WorkspaceMigrationModule,
|
WorkspaceMigrationModule,
|
||||||
WorkspaceMigrationRunnerModule,
|
WorkspaceMigrationRunnerModule,
|
||||||
|
FeatureFlagModule,
|
||||||
],
|
],
|
||||||
services: [ObjectMetadataService],
|
services: [ObjectMetadataService],
|
||||||
resolvers: [
|
resolvers: [
|
||||||
|
|||||||
@ -7,7 +7,12 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
|
|
||||||
import console from 'console';
|
import console from 'console';
|
||||||
|
|
||||||
import { FindManyOptions, FindOneOptions, Repository } from 'typeorm';
|
import {
|
||||||
|
DataSource,
|
||||||
|
FindManyOptions,
|
||||||
|
FindOneOptions,
|
||||||
|
Repository,
|
||||||
|
} from 'typeorm';
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
|
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
|
||||||
|
|
||||||
@ -46,9 +51,14 @@ import {
|
|||||||
createForeignKeyDeterministicUuid,
|
createForeignKeyDeterministicUuid,
|
||||||
createRelationDeterministicUuid,
|
createRelationDeterministicUuid,
|
||||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
|
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
|
||||||
import { buildWorkspaceMigrationsForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-workspace-migrations-for-custom-object.util';
|
import { createWorkspaceMigrationsForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-custom-object.util';
|
||||||
import { buildWorkspaceMigrationsForRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/build-workspace-migrations-for-remote-object.util';
|
import { createWorkspaceMigrationsForRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object.util';
|
||||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
|
import {
|
||||||
|
FeatureFlagEntity,
|
||||||
|
FeatureFlagKeys,
|
||||||
|
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
|
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from './object-metadata.entity';
|
import { ObjectMetadataEntity } from './object-metadata.entity';
|
||||||
|
|
||||||
@ -70,6 +80,8 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
private readonly typeORMService: TypeORMService,
|
private readonly typeORMService: TypeORMService,
|
||||||
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
||||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||||
|
@InjectRepository(FeatureFlagEntity, 'core')
|
||||||
|
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
|
||||||
) {
|
) {
|
||||||
super(objectMetadataRepository);
|
super(objectMetadataRepository);
|
||||||
}
|
}
|
||||||
@ -322,27 +334,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
[],
|
[],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { eventObjectMetadata } = await this.createEventRelation(
|
|
||||||
objectMetadataInput.workspaceId,
|
|
||||||
createdObjectMetadata,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { activityTargetObjectMetadata } =
|
|
||||||
await this.createActivityTargetRelation(
|
|
||||||
objectMetadataInput.workspaceId,
|
|
||||||
createdObjectMetadata,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { favoriteObjectMetadata } = await this.createFavoriteRelation(
|
|
||||||
objectMetadataInput.workspaceId,
|
|
||||||
createdObjectMetadata,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { attachmentObjectMetadata } = await this.createAttachmentRelation(
|
|
||||||
objectMetadataInput.workspaceId,
|
|
||||||
createdObjectMetadata,
|
|
||||||
);
|
|
||||||
|
|
||||||
const dataSourceMetadata =
|
const dataSourceMetadata =
|
||||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||||
createdObjectMetadata.workspaceId,
|
createdObjectMetadata.workspaceId,
|
||||||
@ -351,27 +342,12 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
const workspaceDataSource =
|
const workspaceDataSource =
|
||||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||||
|
|
||||||
await this.workspaceMigrationService.createCustomMigration(
|
await this.createObjectRelationsMetadataAndMigrations(
|
||||||
generateMigrationName(`create-${createdObjectMetadata.nameSingular}`),
|
objectMetadataInput,
|
||||||
createdObjectMetadata.workspaceId,
|
createdObjectMetadata,
|
||||||
isCustom
|
lastDataSourceMetadata,
|
||||||
? buildWorkspaceMigrationsForCustomObject(
|
workspaceDataSource,
|
||||||
createdObjectMetadata,
|
objectMetadataInput.isRemote,
|
||||||
activityTargetObjectMetadata,
|
|
||||||
attachmentObjectMetadata,
|
|
||||||
eventObjectMetadata,
|
|
||||||
favoriteObjectMetadata,
|
|
||||||
)
|
|
||||||
: await buildWorkspaceMigrationsForRemoteObject(
|
|
||||||
createdObjectMetadata,
|
|
||||||
activityTargetObjectMetadata,
|
|
||||||
attachmentObjectMetadata,
|
|
||||||
eventObjectMetadata,
|
|
||||||
favoriteObjectMetadata,
|
|
||||||
lastDataSourceMetadata.schema,
|
|
||||||
objectMetadataInput.remoteTablePrimaryKeyColumnType ?? 'uuid',
|
|
||||||
workspaceDataSource,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
||||||
@ -483,6 +459,67 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
await this.objectMetadataRepository.delete({ workspaceId });
|
await this.objectMetadataRepository.delete({ workspaceId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async createObjectRelationsMetadataAndMigrations(
|
||||||
|
objectMetadataInput: CreateObjectInput,
|
||||||
|
createdObjectMetadata: ObjectMetadataEntity,
|
||||||
|
lastDataSourceMetadata: DataSourceEntity,
|
||||||
|
workspaceDataSource: DataSource | undefined,
|
||||||
|
isRemoteObject: boolean = false,
|
||||||
|
) {
|
||||||
|
const isRelationEnabledForRemoteObjects =
|
||||||
|
await this.isRelationEnabledForRemoteObjects(
|
||||||
|
objectMetadataInput.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isRemoteObject && !isRelationEnabledForRemoteObjects) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { eventObjectMetadata } = await this.createEventRelation(
|
||||||
|
objectMetadataInput.workspaceId,
|
||||||
|
createdObjectMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { activityTargetObjectMetadata } =
|
||||||
|
await this.createActivityTargetRelation(
|
||||||
|
objectMetadataInput.workspaceId,
|
||||||
|
createdObjectMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { favoriteObjectMetadata } = await this.createFavoriteRelation(
|
||||||
|
objectMetadataInput.workspaceId,
|
||||||
|
createdObjectMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { attachmentObjectMetadata } = await this.createAttachmentRelation(
|
||||||
|
objectMetadataInput.workspaceId,
|
||||||
|
createdObjectMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.workspaceMigrationService.createCustomMigration(
|
||||||
|
generateMigrationName(`create-${createdObjectMetadata.nameSingular}`),
|
||||||
|
createdObjectMetadata.workspaceId,
|
||||||
|
isRemoteObject
|
||||||
|
? await createWorkspaceMigrationsForRemoteObject(
|
||||||
|
createdObjectMetadata,
|
||||||
|
activityTargetObjectMetadata,
|
||||||
|
attachmentObjectMetadata,
|
||||||
|
eventObjectMetadata,
|
||||||
|
favoriteObjectMetadata,
|
||||||
|
lastDataSourceMetadata.schema,
|
||||||
|
objectMetadataInput.remoteTablePrimaryKeyColumnType ?? 'uuid',
|
||||||
|
workspaceDataSource,
|
||||||
|
)
|
||||||
|
: createWorkspaceMigrationsForCustomObject(
|
||||||
|
createdObjectMetadata,
|
||||||
|
activityTargetObjectMetadata,
|
||||||
|
attachmentObjectMetadata,
|
||||||
|
eventObjectMetadata,
|
||||||
|
favoriteObjectMetadata,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async createActivityTargetRelation(
|
private async createActivityTargetRelation(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
createdObjectMetadata: ObjectMetadataEntity,
|
createdObjectMetadata: ObjectMetadataEntity,
|
||||||
@ -855,4 +892,14 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
|
|
||||||
return { favoriteObjectMetadata };
|
return { favoriteObjectMetadata };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async isRelationEnabledForRemoteObjects(workspaceId: string) {
|
||||||
|
const featureFlag = await this.featureFlagRepository.findOneBy({
|
||||||
|
workspaceId,
|
||||||
|
key: FeatureFlagKeys.IsRelationForRemoteObjectsEnabled,
|
||||||
|
value: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return featureFlag && featureFlag.value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
|
|
||||||
export const buildWorkspaceMigrationsForCustomObject = (
|
export const createWorkspaceMigrationsForCustomObject = (
|
||||||
createdObjectMetadata: ObjectMetadataEntity,
|
createdObjectMetadata: ObjectMetadataEntity,
|
||||||
activityTargetObjectMetadata: ObjectMetadataEntity,
|
activityTargetObjectMetadata: ObjectMetadataEntity,
|
||||||
attachmentObjectMetadata: ObjectMetadataEntity,
|
attachmentObjectMetadata: ObjectMetadataEntity,
|
||||||
@ -47,7 +47,7 @@ const buildCommentForRemoteObjectForeignKey = async (
|
|||||||
return `@graphql(${JSON.stringify(parsedComment)})`;
|
return `@graphql(${JSON.stringify(parsedComment)})`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildWorkspaceMigrationsForRemoteObject = async (
|
export const createWorkspaceMigrationsForRemoteObject = async (
|
||||||
createdObjectMetadata: ObjectMetadataEntity,
|
createdObjectMetadata: ObjectMetadataEntity,
|
||||||
activityTargetObjectMetadata: ObjectMetadataEntity,
|
activityTargetObjectMetadata: ObjectMetadataEntity,
|
||||||
attachmentObjectMetadata: ObjectMetadataEntity,
|
attachmentObjectMetadata: ObjectMetadataEntity,
|
||||||
@ -74,19 +74,6 @@ export const buildWorkspaceMigrationsForRemoteObject = async (
|
|||||||
} satisfies WorkspaceMigrationColumnCreate,
|
} satisfies WorkspaceMigrationColumnCreate,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: computeObjectTargetTable(activityTargetObjectMetadata),
|
|
||||||
action: WorkspaceMigrationTableActionType.ALTER,
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
action: WorkspaceMigrationColumnActionType.CREATE,
|
|
||||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
|
||||||
isForeignKey: true,
|
|
||||||
}),
|
|
||||||
columnType: remoteTablePrimaryKeyColumnType,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: computeObjectTargetTable(activityTargetObjectMetadata),
|
name: computeObjectTargetTable(activityTargetObjectMetadata),
|
||||||
action: WorkspaceMigrationTableActionType.ALTER,
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
@ -117,19 +104,6 @@ export const buildWorkspaceMigrationsForRemoteObject = async (
|
|||||||
} satisfies WorkspaceMigrationColumnCreate,
|
} satisfies WorkspaceMigrationColumnCreate,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: computeObjectTargetTable(attachmentObjectMetadata),
|
|
||||||
action: WorkspaceMigrationTableActionType.ALTER,
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
action: WorkspaceMigrationColumnActionType.CREATE,
|
|
||||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
|
||||||
isForeignKey: true,
|
|
||||||
}),
|
|
||||||
columnType: remoteTablePrimaryKeyColumnType,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: computeObjectTargetTable(attachmentObjectMetadata),
|
name: computeObjectTargetTable(attachmentObjectMetadata),
|
||||||
action: WorkspaceMigrationTableActionType.ALTER,
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
@ -160,19 +134,6 @@ export const buildWorkspaceMigrationsForRemoteObject = async (
|
|||||||
} satisfies WorkspaceMigrationColumnCreate,
|
} satisfies WorkspaceMigrationColumnCreate,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: computeObjectTargetTable(eventObjectMetadata),
|
|
||||||
action: WorkspaceMigrationTableActionType.ALTER,
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
action: WorkspaceMigrationColumnActionType.CREATE,
|
|
||||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
|
||||||
isForeignKey: true,
|
|
||||||
}),
|
|
||||||
columnType: remoteTablePrimaryKeyColumnType,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: computeObjectTargetTable(eventObjectMetadata),
|
name: computeObjectTargetTable(eventObjectMetadata),
|
||||||
action: WorkspaceMigrationTableActionType.ALTER,
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
@ -203,19 +164,6 @@ export const buildWorkspaceMigrationsForRemoteObject = async (
|
|||||||
} satisfies WorkspaceMigrationColumnCreate,
|
} satisfies WorkspaceMigrationColumnCreate,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: computeObjectTargetTable(favoriteObjectMetadata),
|
|
||||||
action: WorkspaceMigrationTableActionType.ALTER,
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
action: WorkspaceMigrationColumnActionType.CREATE,
|
|
||||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
|
||||||
isForeignKey: true,
|
|
||||||
}),
|
|
||||||
columnType: remoteTablePrimaryKeyColumnType,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: computeObjectTargetTable(favoriteObjectMetadata),
|
name: computeObjectTargetTable(favoriteObjectMetadata),
|
||||||
action: WorkspaceMigrationTableActionType.ALTER,
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
@ -197,7 +197,7 @@ export class RemoteTableService {
|
|||||||
description: 'Remote table',
|
description: 'Remote table',
|
||||||
dataSourceId: dataSourceMetatada.id,
|
dataSourceId: dataSourceMetatada.id,
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
icon: 'IconUser',
|
icon: 'IconPlug',
|
||||||
isRemote: true,
|
isRemote: true,
|
||||||
remoteTablePrimaryKeyColumnType: remoteTableIdColumn.udtName,
|
remoteTablePrimaryKeyColumnType: remoteTableIdColumn.udtName,
|
||||||
} satisfies CreateObjectInput);
|
} satisfies CreateObjectInput);
|
||||||
@ -213,7 +213,7 @@ export class RemoteTableService {
|
|||||||
objectMetadataId: objectMetadata.id,
|
objectMetadataId: objectMetadata.id,
|
||||||
isRemoteCreation: true,
|
isRemoteCreation: true,
|
||||||
isNullable: true,
|
isNullable: true,
|
||||||
icon: 'IconUser',
|
icon: 'IconPlug',
|
||||||
} satisfies CreateFieldInput);
|
} satisfies CreateFieldInput);
|
||||||
|
|
||||||
if (column.columnName === 'id') {
|
if (column.columnName === 'id') {
|
||||||
|
|||||||
@ -59,6 +59,7 @@ export class AddStandardIdCommand extends CommandRunner {
|
|||||||
IS_AIRTABLE_INTEGRATION_ENABLED: true,
|
IS_AIRTABLE_INTEGRATION_ENABLED: true,
|
||||||
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
|
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
|
||||||
IS_MULTI_SELECT_ENABLED: false,
|
IS_MULTI_SELECT_ENABLED: false,
|
||||||
|
IS_RELATION_FOR_REMOTE_OBJECTS_ENABLED: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const standardFieldMetadataCollection = this.standardFieldFactory.create(
|
const standardFieldMetadataCollection = this.standardFieldFactory.create(
|
||||||
@ -74,6 +75,7 @@ export class AddStandardIdCommand extends CommandRunner {
|
|||||||
IS_AIRTABLE_INTEGRATION_ENABLED: true,
|
IS_AIRTABLE_INTEGRATION_ENABLED: true,
|
||||||
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
|
IS_POSTGRESQL_INTEGRATION_ENABLED: true,
|
||||||
IS_MULTI_SELECT_ENABLED: false,
|
IS_MULTI_SELECT_ENABLED: false,
|
||||||
|
IS_RELATION_FOR_REMOTE_OBJECTS_ENABLED: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user