Setup relations for remote objects (#5149)
New strategy: - add settings field on FieldMetadata. Contains a boolean isIdField and for numbers, a precision - if idField, the graphql scalar returned will be a GraphQL id. This will allow the app to work even for ids that are not uuid - remove globals dateScalar and numberScalar modes. These were not used - set limit as Integer - check manually in query runner mutations that we send a valid id Todo left: - remove WorkspaceBuildSchemaOptions since this is not used anymore. Will do in another PR --------- Co-authored-by: Thomas Trompette <thomast@twenty.com> Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
co-authored by
Thomas Trompette
Weiko
parent
dc576d0818
commit
224c8d361b
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -861,6 +861,7 @@ export type Field = {
|
||||
name: Scalars['String'];
|
||||
options?: Maybe<Scalars['JSON']>;
|
||||
relationDefinition?: Maybe<RelationDefinition>;
|
||||
settings?: Maybe<Scalars['JSON']>;
|
||||
toRelationMetadata?: Maybe<Relation>;
|
||||
type: FieldMetadataType;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
|
||||
@@ -51,7 +51,7 @@ const mocks: MockedResponse[] = [
|
||||
$filter: ActivityTargetFilterInput
|
||||
$orderBy: ActivityTargetOrderByInput
|
||||
$lastCursor: String
|
||||
$limit: Float
|
||||
$limit: Int
|
||||
) {
|
||||
activityTargets(
|
||||
filter: $filter
|
||||
@@ -105,7 +105,7 @@ const mocks: MockedResponse[] = [
|
||||
$filter: ActivityFilterInput
|
||||
$orderBy: ActivityOrderByInput
|
||||
$lastCursor: String
|
||||
$limit: Float
|
||||
$limit: Int
|
||||
) {
|
||||
activities(
|
||||
filter: $filter
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ const mocks: MockedResponse[] = [
|
||||
$filter: ActivityTargetFilterInput
|
||||
$orderBy: ActivityTargetOrderByInput
|
||||
$lastCursor: String
|
||||
$limit: Float
|
||||
$limit: Int
|
||||
) {
|
||||
activityTargets(
|
||||
filter: $filter
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ const mocks: MockedResponse[] = [
|
||||
{
|
||||
request: {
|
||||
query: gql`
|
||||
query FindOneWorkspaceMember($objectRecordId: UUID!) {
|
||||
query FindOneWorkspaceMember($objectRecordId: ID!) {
|
||||
workspaceMember(filter: { id: { eq: $objectRecordId } }) {
|
||||
__typename
|
||||
colorScheme
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ const mocks: MockedResponse[] = [
|
||||
request: {
|
||||
query: gql`
|
||||
mutation UpdateOneActivity(
|
||||
$idToUpdate: UUID!
|
||||
$idToUpdate: ID!
|
||||
$input: ActivityUpdateInput!
|
||||
) {
|
||||
updateActivity(id: $idToUpdate, data: $input) {
|
||||
|
||||
@@ -177,7 +177,7 @@ export const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: gql`
|
||||
mutation DeleteOneFavorite($idToDelete: UUID!) {
|
||||
mutation DeleteOneFavorite($idToDelete: ID!) {
|
||||
deleteFavorite(id: $idToDelete) {
|
||||
id
|
||||
}
|
||||
@@ -197,7 +197,7 @@ export const mocks = [
|
||||
request: {
|
||||
query: gql`
|
||||
mutation UpdateOneFavorite(
|
||||
$idToUpdate: UUID!
|
||||
$idToUpdate: ID!
|
||||
$input: FavoriteUpdateInput!
|
||||
) {
|
||||
updateFavorite(id: $idToUpdate, data: $input) {
|
||||
|
||||
@@ -48,6 +48,7 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
||||
nameSingular
|
||||
namePlural
|
||||
isSystem
|
||||
isRemote
|
||||
}
|
||||
toFieldMetadataId
|
||||
}
|
||||
@@ -60,6 +61,7 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
||||
nameSingular
|
||||
namePlural
|
||||
isSystem
|
||||
isRemote
|
||||
}
|
||||
fromFieldMetadataId
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ export const findManyViewsQuery = gql`
|
||||
$filter: ViewFilterInput
|
||||
$orderBy: ViewOrderByInput
|
||||
$lastCursor: String
|
||||
$limit: Float
|
||||
$limit: Int
|
||||
) {
|
||||
views(
|
||||
filter: $filter
|
||||
|
||||
@@ -21,6 +21,7 @@ export type FieldMetadataItem = Omit<
|
||||
| 'toRelationMetadata'
|
||||
| 'defaultValue'
|
||||
| 'options'
|
||||
| 'settings'
|
||||
| 'relationDefinition'
|
||||
> & {
|
||||
__typename?: string;
|
||||
@@ -28,7 +29,7 @@ export type FieldMetadataItem = Omit<
|
||||
| (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & {
|
||||
toObjectMetadata: Pick<
|
||||
Relation['toObjectMetadata'],
|
||||
'id' | 'nameSingular' | 'namePlural' | 'isSystem'
|
||||
'id' | 'nameSingular' | 'namePlural' | 'isSystem' | 'isRemote'
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
@@ -36,7 +37,7 @@ export type FieldMetadataItem = Omit<
|
||||
| (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & {
|
||||
fromObjectMetadata: Pick<
|
||||
Relation['fromObjectMetadata'],
|
||||
'id' | 'nameSingular' | 'namePlural' | 'isSystem'
|
||||
'id' | 'nameSingular' | 'namePlural' | 'isSystem' | 'isRemote'
|
||||
>;
|
||||
})
|
||||
| null;
|
||||
|
||||
+8
-3
@@ -2,10 +2,15 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const isObjectMetadataAvailableForRelation = (
|
||||
objectMetadataItem: Pick<ObjectMetadataItem, 'isSystem' | 'nameSingular'>,
|
||||
objectMetadataItem: Pick<
|
||||
ObjectMetadataItem,
|
||||
'isSystem' | 'nameSingular' | 'isRemote'
|
||||
>,
|
||||
) => {
|
||||
return (
|
||||
!objectMetadataItem.isSystem ||
|
||||
objectMetadataItem.nameSingular === CoreObjectNameSingular.WorkspaceMember
|
||||
(!objectMetadataItem.isSystem ||
|
||||
objectMetadataItem.nameSingular ===
|
||||
CoreObjectNameSingular.WorkspaceMember) &&
|
||||
!objectMetadataItem.isRemote
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
import { EntityChip, EntityChipVariant } from 'twenty-ui';
|
||||
|
||||
import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapToObjectRecordIdentifier';
|
||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
|
||||
|
||||
@@ -25,16 +23,8 @@ export const RecordChip = ({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
// Will only exists if the chip is inside a record table.
|
||||
// This is temporary until we have the show page for remote objects.
|
||||
const { isReadOnly } = useContext(RecordTableRowContext);
|
||||
|
||||
const objectRecordIdentifier = mapToObjectRecordIdentifier(record);
|
||||
|
||||
const linkToEntity = isReadOnly
|
||||
? undefined
|
||||
: objectRecordIdentifier.linkToShowPage;
|
||||
|
||||
return (
|
||||
<EntityChip
|
||||
entityId={record.id}
|
||||
@@ -43,7 +33,7 @@ export const RecordChip = ({
|
||||
avatarUrl={
|
||||
getImageAbsoluteURIOrBase64(objectRecordIdentifier.avatarUrl) || ''
|
||||
}
|
||||
linkToEntity={linkToEntity}
|
||||
linkToEntity={objectRecordIdentifier.linkToShowPage}
|
||||
maxWidth={maxWidth}
|
||||
className={className}
|
||||
variant={variant}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const query = gql`
|
||||
mutation DeleteOnePerson($idToDelete: UUID!) {
|
||||
mutation DeleteOnePerson($idToDelete: ID!) {
|
||||
deletePerson(id: $idToDelete) {
|
||||
id
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { gql } from '@apollo/client';
|
||||
export { responseData } from './useUpdateOneRecord';
|
||||
|
||||
export const query = gql`
|
||||
mutation ExecuteQuickActionOnOnePerson($idToExecuteQuickActionOn: UUID!) {
|
||||
mutation ExecuteQuickActionOnOnePerson($idToExecuteQuickActionOn: ID!) {
|
||||
executeQuickActionOnPerson(id: $idToExecuteQuickActionOn) {
|
||||
__typename
|
||||
xLink {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ export const query = gql`
|
||||
$filter: PersonFilterInput
|
||||
$orderBy: PersonOrderByInput
|
||||
$lastCursor: String
|
||||
$limit: Float
|
||||
$limit: Int
|
||||
) {
|
||||
people(
|
||||
filter: $filter
|
||||
|
||||
@@ -3,7 +3,7 @@ import { gql } from '@apollo/client';
|
||||
import { responseData as person } from './useUpdateOneRecord';
|
||||
|
||||
export const query = gql`
|
||||
query FindOnePerson($objectRecordId: UUID!) {
|
||||
query FindOnePerson($objectRecordId: ID!) {
|
||||
person(filter: { id: { eq: $objectRecordId } }) {
|
||||
__typename
|
||||
xLink {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const query = gql`
|
||||
mutation UpdateOnePerson($idToUpdate: UUID!, $input: PersonUpdateInput!) {
|
||||
mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) {
|
||||
updatePerson(id: $idToUpdate, data: $input) {
|
||||
__typename
|
||||
xLink {
|
||||
|
||||
@@ -26,7 +26,7 @@ export const useDeleteOneRecordMutation = ({
|
||||
);
|
||||
|
||||
const deleteOneRecordMutation = gql`
|
||||
mutation DeleteOne${capitalizedObjectName}($idToDelete: UUID!) {
|
||||
mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) {
|
||||
${mutationResponseField}(id: $idToDelete) {
|
||||
id
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ export const useExecuteQuickActionOnOneRecordMutation = ({
|
||||
});
|
||||
|
||||
const executeQuickActionOnOneRecordMutation = gql`
|
||||
mutation ExecuteQuickActionOnOne${capitalizedObjectName}($idToExecuteQuickActionOn: UUID!) {
|
||||
mutation ExecuteQuickActionOnOne${capitalizedObjectName}($idToExecuteQuickActionOn: ID!) {
|
||||
${graphQLFieldForExecuteQuickActionOnOneRecordMutation}(id: $idToExecuteQuickActionOn) ${mapObjectMetadataToGraphQLQuery(
|
||||
{
|
||||
objectMetadataItems,
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export const useFindDuplicateRecordsQuery = ({
|
||||
const findDuplicateRecordsQuery = gql`
|
||||
query FindDuplicate${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}($id: UUID) {
|
||||
)}($id: ID!) {
|
||||
${getFindDuplicateRecordsQueryResponseField(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}(id: $id) {
|
||||
|
||||
@@ -22,7 +22,7 @@ export const useFindOneRecordQuery = ({
|
||||
const findOneRecordQuery = gql`
|
||||
query FindOne${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}($objectRecordId: UUID!) {
|
||||
)}($objectRecordId: ID!) {
|
||||
${objectMetadataItem.nameSingular}(filter: {
|
||||
id: {
|
||||
eq: $objectRecordId
|
||||
@@ -32,7 +32,7 @@ export const useFindOneRecordQuery = ({
|
||||
objectMetadataItem,
|
||||
depth,
|
||||
})}
|
||||
}
|
||||
},
|
||||
`;
|
||||
|
||||
return {
|
||||
|
||||
@@ -35,7 +35,7 @@ export const useUpdateOneRecordMutation = ({
|
||||
);
|
||||
|
||||
const updateOneRecordMutation = gql`
|
||||
mutation UpdateOne${capitalizedObjectName}($idToUpdate: UUID!, $input: ${capitalizedObjectName}UpdateInput!) {
|
||||
mutation UpdateOne${capitalizedObjectName}($idToUpdate: ID!, $input: ${capitalizedObjectName}UpdateInput!) {
|
||||
${mutationResponseField}(id: $idToUpdate, data: $input) ${mapObjectMetadataToGraphQLQuery(
|
||||
{
|
||||
objectMetadataItems,
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({
|
||||
const limitPerMetadataItemArray = capitalizedObjectNameSingulars
|
||||
.map(
|
||||
(capitalizedObjectNameSingular) =>
|
||||
`$limit${capitalizedObjectNameSingular}: Float`,
|
||||
`$limit${capitalizedObjectNameSingular}: Int`,
|
||||
)
|
||||
.join(', ');
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
|
||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||
|
||||
const query = gql`
|
||||
mutation UpdateOnePerson($idToUpdate: UUID!, $input: PersonUpdateInput!) {
|
||||
mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) {
|
||||
updatePerson(id: $idToUpdate, data: $input) {
|
||||
__typename
|
||||
xLink {
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ const mocks: MockedResponse[] = [
|
||||
request: {
|
||||
query: gql`
|
||||
mutation UpdateOneCompany(
|
||||
$idToUpdate: UUID!
|
||||
$idToUpdate: ID!
|
||||
$input: CompanyUpdateInput!
|
||||
) {
|
||||
updateCompany(id: $idToUpdate, data: $input) {
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import { isFieldDateValue } from '@/object-record/record-field/types/guards/isFi
|
||||
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
||||
import { isFieldFullNameValue } from '@/object-record/record-field/types/guards/isFieldFullNameValue';
|
||||
import { isFieldMultiSelect } from '@/object-record/record-field/types/guards/isFieldMultiSelect';
|
||||
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue';
|
||||
import { isFieldMultiSelectValue } from '@/object-record/record-field/types/guards/isFieldMultiSelectValue.ts';
|
||||
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
|
||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ const query = gql`
|
||||
$filterNameSingular: NameSingularFilterInput
|
||||
$orderByNameSingular: NameSingularOrderByInput
|
||||
$lastCursorNameSingular: String
|
||||
$limitNameSingular: Float
|
||||
$limitNameSingular: Int
|
||||
) {
|
||||
namePlural(
|
||||
filter: $filterNameSingular
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ query FindMany${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}FilterInput, $orderBy: ${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}OrderByInput, $lastCursor: String, $limit: Float) {
|
||||
)}OrderByInput, $lastCursor: String, $limit: Int) {
|
||||
${
|
||||
objectMetadataItem.namePlural
|
||||
}(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ export const query = gql`
|
||||
$filter: PersonFilterInput
|
||||
$orderBy: PersonOrderByInput
|
||||
$lastCursor: String
|
||||
$limit: Float = 60
|
||||
$limit: Int = 60
|
||||
) {
|
||||
people(
|
||||
filter: $filter
|
||||
|
||||
+6
-1
@@ -65,7 +65,12 @@ export const ShowPageRightContainer = ({
|
||||
const { activeTabIdState } = useTabList(TAB_LIST_COMPONENT_ID);
|
||||
const activeTabId = useRecoilValue(activeTabIdState);
|
||||
|
||||
const shouldDisplayCalendarTab = useIsFeatureEnabled('IS_CALENDAR_ENABLED');
|
||||
const shouldDisplayCalendarTab =
|
||||
useIsFeatureEnabled('IS_CALENDAR_ENABLED') &&
|
||||
(targetableObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Company ||
|
||||
targetableObject.targetObjectNameSingular ===
|
||||
CoreObjectNameSingular.Person);
|
||||
const shouldDisplayLogTab = useIsFeatureEnabled('IS_EVENT_OBJECT_ENABLED');
|
||||
|
||||
const shouldDisplayEmailsTab =
|
||||
|
||||
@@ -96,11 +96,6 @@ export const RecordShowPage = () => {
|
||||
? `${pageName} - ${capitalize(objectNameSingular)}`
|
||||
: capitalize(objectNameSingular);
|
||||
|
||||
// Temporarily since we don't have relations for remote objects yet
|
||||
if (objectMetadataItem.isRemote) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageTitle title={pageTitle} />
|
||||
|
||||
@@ -88,6 +88,8 @@ export const SettingsObjectDetail = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const shouldDisplayAddFieldButton = !activeObjectMetadataItem.isRemote;
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||
<SettingsPageContainer>
|
||||
@@ -207,21 +209,23 @@ export const SettingsObjectDetail = () => {
|
||||
</TableSection>
|
||||
)}
|
||||
</Table>
|
||||
<StyledDiv>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="Add Field"
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
disabledMetadataFields.length
|
||||
? './new-field/step-1'
|
||||
: './new-field/step-2',
|
||||
)
|
||||
}
|
||||
/>
|
||||
</StyledDiv>
|
||||
{shouldDisplayAddFieldButton && (
|
||||
<StyledDiv>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="Add Field"
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
disabledMetadataFields.length
|
||||
? './new-field/step-1'
|
||||
: './new-field/step-2',
|
||||
)
|
||||
}
|
||||
/>
|
||||
</StyledDiv>
|
||||
)}
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
|
||||
@@ -9,6 +9,7 @@ export const mockedClientConfig: ClientConfig = {
|
||||
google: true,
|
||||
password: true,
|
||||
magicLink: false,
|
||||
microsoft: false,
|
||||
__typename: 'AuthProviders',
|
||||
},
|
||||
telemetry: {
|
||||
|
||||
@@ -63,6 +63,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||
nameSingular: 'person',
|
||||
namePlural: 'people',
|
||||
isSystem: false,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: 'c756f6ff-8c00-4fe5-a923-c6cfc7b1ac4a',
|
||||
},
|
||||
@@ -91,6 +92,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||
nameSingular: 'opportunity',
|
||||
namePlural: 'opportunities',
|
||||
isSystem: false,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '00468e2a-a601-4635-ae9c-a9bb826cc860',
|
||||
},
|
||||
@@ -119,6 +121,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||
nameSingular: 'activityTarget',
|
||||
namePlural: 'activityTargets',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: 'bba19feb-c248-487b-92d7-98df54c51e44',
|
||||
},
|
||||
@@ -221,6 +224,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||
nameSingular: 'attachment',
|
||||
namePlural: 'attachments',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '0880dac5-37d2-43a6-b143-722126d4923f',
|
||||
},
|
||||
@@ -331,6 +335,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||
nameSingular: 'workspaceMember',
|
||||
namePlural: 'workspaceMembers',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
fromFieldMetadataId: '0f3e456f-3bb4-4261-a436-95246dc0e159',
|
||||
},
|
||||
@@ -378,6 +383,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||
nameSingular: 'favorite',
|
||||
namePlural: 'favorites',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '8fd8965b-bd4e-4a9b-90e9-c75652dadda1',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user