Rework relations (#3431)

* Rework relations

* Fix tests
This commit is contained in:
Charles Bochet
2024-01-15 12:07:23 +01:00
committed by GitHub
parent 8c96acc2a3
commit 16a24c5f0c
60 changed files with 392 additions and 463 deletions

View File

@ -20,7 +20,7 @@ const documents = {
"\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: ID!\n $updatePayload: UpdateObjectInput!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.UpdateOneObjectMetadataItemDocument, "\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: ID!\n $updatePayload: UpdateObjectInput!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.UpdateOneObjectMetadataItemDocument,
"\n mutation DeleteOneObjectMetadataItem($idToDelete: ID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.DeleteOneObjectMetadataItemDocument, "\n mutation DeleteOneObjectMetadataItem($idToDelete: ID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.DeleteOneObjectMetadataItemDocument,
"\n mutation DeleteOneFieldMetadataItem($idToDelete: ID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n": types.DeleteOneFieldMetadataItemDocument, "\n mutation DeleteOneFieldMetadataItem($idToDelete: ID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n": types.DeleteOneFieldMetadataItemDocument,
"\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n toFieldMetadataId\n }\n toRelationMetadata {\n id\n relationType\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromFieldMetadataId\n }\n defaultValue\n options\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n": types.ObjectMetadataItemsDocument, "\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n isSystem\n }\n toFieldMetadataId\n }\n toRelationMetadata {\n id\n relationType\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n isSystem\n }\n fromFieldMetadataId\n }\n defaultValue\n options\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n": types.ObjectMetadataItemsDocument,
}; };
/** /**
@ -68,7 +68,7 @@ export function graphql(source: "\n mutation DeleteOneFieldMetadataItem($idToDe
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function graphql(source: "\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n toFieldMetadataId\n }\n toRelationMetadata {\n id\n relationType\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromFieldMetadataId\n }\n defaultValue\n options\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"): (typeof documents)["\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n toFieldMetadataId\n }\n toRelationMetadata {\n id\n relationType\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n }\n fromFieldMetadataId\n }\n defaultValue\n options\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"]; export function graphql(source: "\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n isSystem\n }\n toFieldMetadataId\n }\n toRelationMetadata {\n id\n relationType\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n isSystem\n }\n fromFieldMetadataId\n }\n defaultValue\n options\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"): (typeof documents)["\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n isSystem\n }\n toFieldMetadataId\n }\n toRelationMetadata {\n id\n relationType\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n isSystem\n }\n fromFieldMetadataId\n }\n defaultValue\n options\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n totalCount\n }\n }\n"];
export function graphql(source: string) { export function graphql(source: string) {
return (documents as any)[source] ?? {}; return (documents as any)[source] ?? {};

File diff suppressed because one or more lines are too long

View File

@ -46,6 +46,7 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
dataSourceId dataSourceId
nameSingular nameSingular
namePlural namePlural
isSystem
} }
toFieldMetadataId toFieldMetadataId
} }
@ -57,6 +58,7 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
dataSourceId dataSourceId
nameSingular nameSingular
namePlural namePlural
isSystem
} }
fromFieldMetadataId fromFieldMetadataId
} }

View File

@ -54,6 +54,26 @@ export const useMapFieldMetadataToGraphQLQuery = () => {
) )
.join('\n')} .join('\n')}
}`; }`;
} else if (
fieldType === 'RELATION' &&
field.toRelationMetadata?.relationType === 'ONE_TO_ONE'
) {
const relationMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id ===
(field.toRelationMetadata as any)?.fromObjectMetadata?.id,
);
return `${field.name}
{
id
${(relationMetadataItem?.fields ?? [])
.filter((field) => field.type !== 'RELATION')
.map((field) =>
mapFieldMetadataToGraphQLQuery(field, maxDepthForRelations - 1),
)
.join('\n')}
}`;
} else if ( } else if (
fieldType === 'RELATION' && fieldType === 'RELATION' &&
field.fromRelationMetadata?.relationType === 'ONE_TO_MANY' field.fromRelationMetadata?.relationType === 'ONE_TO_MANY'

View File

@ -9,7 +9,7 @@ export type FieldMetadataItem = Omit<
| (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & { | (Pick<Relation, 'id' | 'toFieldMetadataId' | 'relationType'> & {
toObjectMetadata: Pick< toObjectMetadata: Pick<
Relation['toObjectMetadata'], Relation['toObjectMetadata'],
'id' | 'nameSingular' | 'namePlural' 'id' | 'nameSingular' | 'namePlural' | 'isSystem'
>; >;
}) })
| null; | null;
@ -17,7 +17,7 @@ export type FieldMetadataItem = Omit<
| (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & { | (Pick<Relation, 'id' | 'fromFieldMetadataId' | 'relationType'> & {
fromObjectMetadata: Pick< fromObjectMetadata: Pick<
Relation['fromObjectMetadata'], Relation['fromObjectMetadata'],
'id' | 'nameSingular' | 'namePlural' 'id' | 'nameSingular' | 'namePlural' | 'isSystem'
>; >;
}) })
| null; | null;

View File

@ -1,5 +0,0 @@
export enum StandardObjectNameSingular {
Company = 'company',
Person = 'person',
Opportunity = 'opportunity',
}

View File

@ -0,0 +1,11 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const isObjectMetadataAvailableForRelation = (
objectMetadataItem: Pick<ObjectMetadataItem, 'isSystem' | 'nameSingular'>,
) => {
return (
!objectMetadataItem.isSystem ||
objectMetadataItem.nameSingular === CoreObjectNameSingular.WorkspaceMember
);
};

View File

@ -1,11 +0,0 @@
import { StandardObjectNameSingular } from '@/object-metadata/types/StandardObjectNameSingular';
export const isStandardObject = (objectNameSingular: string) => {
const standardObjectNames = [
StandardObjectNameSingular.Company,
StandardObjectNameSingular.Person,
StandardObjectNameSingular.Opportunity,
] as string[];
return standardObjectNames.includes(objectNameSingular);
};

View File

@ -1,9 +1,10 @@
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { useFavorites } from '@/favorites/hooks/useFavorites'; import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType'; import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType';
import { parseFieldType } from '@/object-metadata/utils/parseFieldType'; import { parseFieldType } from '@/object-metadata/utils/parseFieldType';
import { import {
@ -62,7 +63,7 @@ export const RecordShowPage = () => {
const { favorites, createFavorite, deleteFavorite } = useFavorites(); const { favorites, createFavorite, deleteFavorite } = useFavorites();
const [, setEntityFields] = useRecoilState( const setEntityFields = useSetRecoilState(
entityFieldsFamilyState(objectRecordId ?? ''), entityFieldsFamilyState(objectRecordId ?? ''),
); );
@ -274,8 +275,21 @@ export const RecordShowPage = () => {
)} )}
</PropertyBox> </PropertyBox>
{isRelationFieldCardEnabled && {isRelationFieldCardEnabled &&
relationFieldMetadataItems.map( relationFieldMetadataItems
(fieldMetadataItem, index) => ( .filter((item) => {
const relationObjectMetadataItem =
item.toRelationMetadata
? item.toRelationMetadata.fromObjectMetadata
: item.fromRelationMetadata?.toObjectMetadata;
if (!relationObjectMetadataItem) {
return false;
}
return isObjectMetadataAvailableForRelation(
relationObjectMetadataItem,
);
})
.map((fieldMetadataItem, index) => (
<FieldContext.Provider <FieldContext.Provider
key={record.id + fieldMetadataItem.id} key={record.id + fieldMetadataItem.id}
value={{ value={{
@ -294,8 +308,7 @@ export const RecordShowPage = () => {
> >
<RecordRelationFieldCardSection /> <RecordRelationFieldCardSection />
</FieldContext.Provider> </FieldContext.Provider>
), ))}
)}
</> </>
)} )}
</ShowPageLeftContainer> </ShowPageLeftContainer>

View File

@ -6,7 +6,9 @@ import { useRelationField } from '../../hooks/useRelationField';
export const RelationFieldDisplay = () => { export const RelationFieldDisplay = () => {
const { fieldValue, fieldDefinition } = useRelationField(); const { fieldValue, fieldDefinition } = useRelationField();
const { identifiersMapper } = useRelationPicker(); const { identifiersMapper } = useRelationPicker({
relationPickerScopeId: 'relation-picker',
});
if (!fieldValue || !fieldDefinition || !identifiersMapper) { if (!fieldValue || !fieldDefinition || !identifiersMapper) {
return <></>; return <></>;

View File

@ -19,7 +19,9 @@ export const useChipField = () => {
const record = useRecoilValue<any | null>(entityFieldsFamilyState(entityId)); const record = useRecoilValue<any | null>(entityFieldsFamilyState(entityId));
const { identifiersMapper } = useRelationPicker(); const { identifiersMapper } = useRelationPicker({
relationPickerScopeId: 'relation-picker',
});
return { return {
basePathToShowPage, basePathToShowPage,

View File

@ -19,6 +19,7 @@ import { useUpsertRecordFromState } from '@/object-record/hooks/useUpsertRecordF
import { RecordRelationFieldCardContent } from '@/object-record/record-relation-card/components/RecordRelationFieldCardContent'; import { RecordRelationFieldCardContent } from '@/object-record/record-relation-card/components/RecordRelationFieldCardContent';
import { SingleEntitySelectMenuItemsWithSearch } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch'; import { SingleEntitySelectMenuItemsWithSearch } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch';
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker'; import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { IconForbid, IconPlus } from '@/ui/display/icon'; import { IconForbid, IconPlus } from '@/ui/display/icon';
@ -153,12 +154,10 @@ export const RecordRelationFieldCardSection = () => {
const { closeDropdown, isDropdownOpen } = useDropdown(dropdownId); const { closeDropdown, isDropdownOpen } = useDropdown(dropdownId);
const { const { relationPickerSearchFilter, setRelationPickerSearchFilter } =
identifiersMapper, useRelationPicker({ relationPickerScopeId: dropdownId });
relationPickerSearchFilter,
searchQuery, const { identifiersMapper, searchQuery } = useRelationPicker();
setRelationPickerSearchFilter,
} = useRelationPicker();
const entities = useFilteredSearchEntityQuery({ const entities = useFilteredSearchEntityQuery({
filters: [ filters: [
@ -225,53 +224,55 @@ export const RecordRelationFieldCardSection = () => {
return ( return (
<Section> <Section>
<StyledHeader isDropdownOpen={isDropdownOpen}> <RelationPickerScope relationPickerScopeId={dropdownId}>
<StyledTitle> <StyledHeader isDropdownOpen={isDropdownOpen}>
<StyledTitleLabel>{fieldDefinition.label}</StyledTitleLabel> <StyledTitle>
{parseFieldRelationType(relationFieldMetadataItem) === <StyledTitleLabel>{fieldDefinition.label}</StyledTitleLabel>
'TO_ONE_OBJECT' && ( {parseFieldRelationType(relationFieldMetadataItem) ===
<StyledLink to={filterLinkHref}> 'TO_ONE_OBJECT' && (
All ({relationRecords.length}) <StyledLink to={filterLinkHref}>
</StyledLink> All ({relationRecords.length})
)} </StyledLink>
</StyledTitle> )}
<DropdownScope dropdownScopeId={dropdownId}> </StyledTitle>
<StyledAddDropdown <DropdownScope dropdownScopeId={dropdownId}>
dropdownId={dropdownId} <StyledAddDropdown
dropdownPlacement="right-start" dropdownId={dropdownId}
onClose={handleCloseRelationPickerDropdown} dropdownPlacement="right-start"
clickableComponent={ onClose={handleCloseRelationPickerDropdown}
<LightIconButton clickableComponent={
className="displayOnHover" <LightIconButton
Icon={IconPlus} className="displayOnHover"
accent="tertiary" Icon={IconPlus}
/> accent="tertiary"
} />
dropdownComponents={ }
<SingleEntitySelectMenuItemsWithSearch dropdownComponents={
EmptyIcon={IconForbid} <SingleEntitySelectMenuItemsWithSearch
entitiesToSelect={entities.entitiesToSelect} EmptyIcon={IconForbid}
loading={entities.loading} entitiesToSelect={entities.entitiesToSelect}
onEntitySelected={handleRelationPickerEntitySelected} loading={entities.loading}
/> onEntitySelected={handleRelationPickerEntitySelected}
} />
dropdownHotkeyScope={{ }
scope: dropdownId, dropdownHotkeyScope={{
}} scope: dropdownId,
/> }}
</DropdownScope>
</StyledHeader>
{!!relationRecords.length && (
<Card>
{relationRecords.slice(0, 5).map((relationRecord, index) => (
<RecordRelationFieldCardContent
key={`${relationRecord.id}${relationLabelIdentifierFieldMetadata?.id}`}
divider={index < relationRecords.length - 1}
relationRecord={relationRecord}
/> />
))} </DropdownScope>
</Card> </StyledHeader>
)} {!!relationRecords.length && (
<Card>
{relationRecords.slice(0, 5).map((relationRecord, index) => (
<RecordRelationFieldCardContent
key={`${relationRecord.id}${relationLabelIdentifierFieldMetadata?.id}`}
divider={index < relationRecords.length - 1}
relationRecord={relationRecord}
/>
))}
</Card>
)}
</RelationPickerScope>
</Section> </Section>
); );
}; };

View File

@ -4,6 +4,7 @@ import { expect, userEvent, within } from '@storybook/test';
import { IconUserCircle } from '@/ui/display/icon'; import { IconUserCircle } from '@/ui/display/icon';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator'; import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator';
import { RelationPickerDecorator } from '~/testing/decorators/RelationPickerDecorator';
import { mockedPeopleData } from '~/testing/mock-data/people'; import { mockedPeopleData } from '~/testing/mock-data/people';
import { sleep } from '~/testing/sleep'; import { sleep } from '~/testing/sleep';
@ -19,7 +20,11 @@ const entities = mockedPeopleData.map<EntityForSelect>((person) => ({
const meta: Meta<typeof SingleEntitySelect> = { const meta: Meta<typeof SingleEntitySelect> = {
title: 'UI/Input/RelationPicker/SingleEntitySelect', title: 'UI/Input/RelationPicker/SingleEntitySelect',
component: SingleEntitySelect, component: SingleEntitySelect,
decorators: [ComponentDecorator, ComponentWithRecoilScopeDecorator], decorators: [
ComponentDecorator,
ComponentWithRecoilScopeDecorator,
RelationPickerDecorator,
],
argTypes: { argTypes: {
selectedEntity: { selectedEntity: {
options: entities.map(({ name }) => name), options: entities.map(({ name }) => name),

View File

@ -7,9 +7,7 @@ export const useEntitySelectSearch = () => {
setRelationPickerPreselectedId, setRelationPickerPreselectedId,
relationPickerSearchFilter, relationPickerSearchFilter,
setRelationPickerSearchFilter, setRelationPickerSearchFilter,
} = useRelationPicker({ } = useRelationPicker();
relationPickerScopeId: 'relation-picker',
});
const debouncedSetSearchFilter = debounce( const debouncedSetSearchFilter = debounce(
setRelationPickerSearchFilter, setRelationPickerSearchFilter,

View File

@ -1,6 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation';
import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel'; import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel';
import { useIcons } from '@/ui/display/icon/hooks/useIcons'; import { useIcons } from '@/ui/display/icon/hooks/useIcons';
import { IconPicker } from '@/ui/input/components/IconPicker'; import { IconPicker } from '@/ui/input/components/IconPicker';
@ -74,13 +75,13 @@ export const SettingsObjectFieldRelationForm = ({
fullWidth fullWidth
disabled={disableRelationEdition} disabled={disableRelationEdition}
value={values.type} value={values.type}
options={Object.entries(relationTypes).map( options={Object.entries(relationTypes)
([value, { label, Icon }]) => ({ .filter(([value]) => 'ONE_TO_ONE' !== value)
.map(([value, { label, Icon }]) => ({
label, label,
value: value as RelationType, value: value as RelationType,
Icon, Icon,
}), }))}
)}
onChange={(value) => onChange({ type: value })} onChange={(value) => onChange({ type: value })}
/> />
<Select <Select
@ -90,7 +91,9 @@ export const SettingsObjectFieldRelationForm = ({
disabled={disableRelationEdition} disabled={disableRelationEdition}
value={values.objectMetadataId} value={values.objectMetadataId}
options={objectMetadataItems options={objectMetadataItems
.filter((objectMetadataItem) => !objectMetadataItem.isSystem) .filter((objectMetadataItem) =>
isObjectMetadataAvailableForRelation(objectMetadataItem),
)
.map((objectMetadataItem) => ({ .map((objectMetadataItem) => ({
label: objectMetadataItem.labelPlural, label: objectMetadataItem.labelPlural,
value: objectMetadataItem.id, value: objectMetadataItem.id,

View File

@ -7,7 +7,7 @@ import { Notes } from '@/activities/notes/components/Notes';
import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks'; import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks';
import { Timeline } from '@/activities/timeline/components/Timeline'; import { Timeline } from '@/activities/timeline/components/Timeline';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { isStandardObject } from '@/object-metadata/utils/isStandardObject'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { import {
IconCheckbox, IconCheckbox,
IconMail, IconMail,
@ -41,7 +41,7 @@ const StyledTabListContainer = styled.div`
const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list'; const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list';
type ShowPageRightContainerProps = { type ShowPageRightContainerProps = {
targetableObject?: ActivityTargetableObject; targetableObject: ActivityTargetableObject;
timeline?: boolean; timeline?: boolean;
tasks?: boolean; tasks?: boolean;
notes?: boolean; notes?: boolean;
@ -60,11 +60,10 @@ export const ShowPageRightContainer = ({
const { activeTabIdState } = useTabList(TAB_LIST_COMPONENT_ID); const { activeTabIdState } = useTabList(TAB_LIST_COMPONENT_ID);
const activeTabId = useRecoilValue(activeTabIdState()); const activeTabId = useRecoilValue(activeTabIdState());
if (!targetableObject) return <></>; const { objectMetadataItem: targetableObjectMetadataItem } =
useObjectMetadataItem({
const targetableObjectIsStandardObject = isStandardObject( objectNameSingular: targetableObject.targetObjectNameSingular,
targetableObject.targetObjectNameSingular, });
);
const TASK_TABS = [ const TASK_TABS = [
{ {
@ -90,14 +89,14 @@ export const ShowPageRightContainer = ({
title: 'Files', title: 'Files',
Icon: IconPaperclip, Icon: IconPaperclip,
hide: !notes, hide: !notes,
disabled: !targetableObjectIsStandardObject, disabled: targetableObjectMetadataItem.isCustom,
}, },
{ {
id: 'emails', id: 'emails',
title: 'Emails', title: 'Emails',
Icon: IconMail, Icon: IconMail,
hide: !emails, hide: !emails,
disabled: !isMessagingEnabled || !targetableObjectIsStandardObject, disabled: !isMessagingEnabled || targetableObjectMetadataItem.isCustom,
}, },
]; ];

View File

@ -58,6 +58,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
id: 'fcccc985-5edf-405c-aa2b-80c82b230f35', id: 'fcccc985-5edf-405c-aa2b-80c82b230f35',
nameSingular: 'person', nameSingular: 'person',
namePlural: 'people', namePlural: 'people',
isSystem: false,
}, },
toFieldMetadataId: 'c756f6ff-8c00-4fe5-a923-c6cfc7b1ac4a', toFieldMetadataId: 'c756f6ff-8c00-4fe5-a923-c6cfc7b1ac4a',
}, },
@ -85,6 +86,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
id: '169e5b21-dc95-44a8-acd0-5e9447dd0784', id: '169e5b21-dc95-44a8-acd0-5e9447dd0784',
nameSingular: 'opportunity', nameSingular: 'opportunity',
namePlural: 'opportunities', namePlural: 'opportunities',
isSystem: false,
}, },
toFieldMetadataId: '00468e2a-a601-4635-ae9c-a9bb826cc860', toFieldMetadataId: '00468e2a-a601-4635-ae9c-a9bb826cc860',
}, },
@ -112,6 +114,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
id: 'b87c6cac-a8e7-4156-a525-30ec536acd75', id: 'b87c6cac-a8e7-4156-a525-30ec536acd75',
nameSingular: 'activityTarget', nameSingular: 'activityTarget',
namePlural: 'activityTargets', namePlural: 'activityTargets',
isSystem: true,
}, },
toFieldMetadataId: 'bba19feb-c248-487b-92d7-98df54c51e44', toFieldMetadataId: 'bba19feb-c248-487b-92d7-98df54c51e44',
}, },
@ -213,6 +216,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
id: '77240b4b-6bcf-454d-a102-19bbba181716', id: '77240b4b-6bcf-454d-a102-19bbba181716',
nameSingular: 'attachment', nameSingular: 'attachment',
namePlural: 'attachments', namePlural: 'attachments',
isSystem: true,
}, },
toFieldMetadataId: '0880dac5-37d2-43a6-b143-722126d4923f', toFieldMetadataId: '0880dac5-37d2-43a6-b143-722126d4923f',
}, },
@ -322,6 +326,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
id: '92c306ce-ad06-4712-99d2-5d0daf13c95f', id: '92c306ce-ad06-4712-99d2-5d0daf13c95f',
nameSingular: 'workspaceMember', nameSingular: 'workspaceMember',
namePlural: 'workspaceMembers', namePlural: 'workspaceMembers',
isSystem: true,
}, },
fromFieldMetadataId: '0f3e456f-3bb4-4261-a436-95246dc0e159', fromFieldMetadataId: '0f3e456f-3bb4-4261-a436-95246dc0e159',
}, },
@ -368,6 +373,7 @@ export const mockObjectMetadataItem: ObjectMetadataItem = {
id: '1415392e-0ecb-462e-aa67-001e424e6a37', id: '1415392e-0ecb-462e-aa67-001e424e6a37',
nameSingular: 'favorite', nameSingular: 'favorite',
namePlural: 'favorites', namePlural: 'favorites',
isSystem: true,
}, },
toFieldMetadataId: '8fd8965b-bd4e-4a9b-90e9-c75652dadda1', toFieldMetadataId: '8fd8965b-bd4e-4a9b-90e9-c75652dadda1',
}, },

View File

@ -35,7 +35,7 @@ describe('FilterInputFactory', () => {
}; };
expect(() => service.create(request, objectMetadata)).toThrow( expect(() => service.create(request, objectMetadata)).toThrow(
"field 'wrongField' does not exist in 'testingObject' object", "field 'wrongField' does not exist in 'objectName' object",
); );
}); });

View File

@ -112,7 +112,7 @@ describe('OrderByInputFactory', () => {
}; };
expect(() => service.create(request, objectMetadata)).toThrow( expect(() => service.create(request, objectMetadata)).toThrow(
"field 'wrongField' does not exist in 'testingObject' object", "field 'wrongField' does not exist in 'objectName' object",
); );
}); });
}); });

View File

@ -1,6 +1,7 @@
import { BadRequestException } from '@nestjs/common'; import { BadRequestException } from '@nestjs/common';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
export const getFieldType = ( export const getFieldType = (
objectMetadataItem, objectMetadataItem,
@ -28,7 +29,9 @@ export const checkFields = (objectMetadataItem, fieldNames): void => {
.includes(fieldName) .includes(fieldName)
) { ) {
throw new BadRequestException( throw new BadRequestException(
`field '${fieldName}' does not exist in '${objectMetadataItem.targetTableName}' object`, `field '${fieldName}' does not exist in '${computeObjectTargetTable(
objectMetadataItem,
)}' object`,
); );
} }
} }

View File

@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { Record as IRecord } from 'src/workspace/workspace-query-builder/interfaces/record.interface'; import { Record as IRecord } from 'src/workspace/workspace-query-builder/interfaces/record.interface';
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
import { isWorkEmail } from 'src/utils/is-work-email'; import { isWorkEmail } from 'src/utils/is-work-email';
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util'; import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
@ -17,7 +18,19 @@ export class QuickActionsService {
private readonly intelligenceService: IntelligenceService, private readonly intelligenceService: IntelligenceService,
) {} ) {}
async createCompanyFromPerson(id: string, workspaceId: string) { async createCompanyFromPerson(
id: string,
workspaceId: string,
objectMetadataItemCollection: ObjectMetadataInterface[],
) {
const personObjectMetadata = objectMetadataItemCollection.find(
(item) => item.nameSingular === 'person',
);
if (!personObjectMetadata) {
return;
}
const personRequest = const personRequest =
await this.workspaceQueryRunnunerService.executeAndParse<IRecord>( await this.workspaceQueryRunnunerService.executeAndParse<IRecord>(
`query { `query {
@ -32,7 +45,7 @@ export class QuickActionsService {
} }
} }
`, `,
'person', personObjectMetadata,
'', '',
workspaceId, workspaceId,
); );
@ -47,6 +60,14 @@ export class QuickActionsService {
const companyName = capitalize(companyDomainName.split('.')[0]); const companyName = capitalize(companyDomainName.split('.')[0]);
let relatedCompanyId = uuidv4(); let relatedCompanyId = uuidv4();
const companyObjectMetadata = objectMetadataItemCollection.find(
(item) => item.nameSingular === 'company',
);
if (!companyObjectMetadata) {
return;
}
const existingCompany = const existingCompany =
await this.workspaceQueryRunnunerService.executeAndParse<IRecord>( await this.workspaceQueryRunnunerService.executeAndParse<IRecord>(
`query {companyCollection(filter: {domainName: {eq: "${companyDomainName}"}}) { `query {companyCollection(filter: {domainName: {eq: "${companyDomainName}"}}) {
@ -58,7 +79,7 @@ export class QuickActionsService {
} }
} }
`, `,
'company', companyObjectMetadata,
'', '',
workspaceId, workspaceId,
); );
@ -105,7 +126,11 @@ export class QuickActionsService {
} }
} }
async executeQuickActionOnCompany(id: string, workspaceId: string) { async executeQuickActionOnCompany(
id: string,
workspaceId: string,
objectMetadataItem: ObjectMetadataInterface,
) {
const companyQuery = `query { const companyQuery = `query {
companyCollection(filter: {id: {eq: "${id}"}}) { companyCollection(filter: {id: {eq: "${id}"}}) {
edges { edges {
@ -123,7 +148,7 @@ export class QuickActionsService {
const companyRequest = const companyRequest =
await this.workspaceQueryRunnunerService.executeAndParse<IRecord>( await this.workspaceQueryRunnunerService.executeAndParse<IRecord>(
companyQuery, companyQuery,
'company', objectMetadataItem,
'', '',
workspaceId, workspaceId,
); );

View File

@ -39,8 +39,6 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
): Promise<DataSource | undefined> { ): Promise<DataSource | undefined> {
const isMultiDatasourceEnabled = false; const isMultiDatasourceEnabled = false;
console.log('Data sources number', this.dataSources.size);
if (isMultiDatasourceEnabled) { if (isMultiDatasourceEnabled) {
// Wait for a bit before trying again if another initialization is in progress // Wait for a bit before trying again if another initialization is in progress
while (this.isDatasourceInitializing.get(dataSource.id)) { while (this.isDatasourceInitializing.get(dataSource.id)) {

View File

@ -71,6 +71,9 @@ export const currencyObjectDefinition = {
fields: currencyFields(), fields: currencyFields(),
fromRelations: [], fromRelations: [],
toRelations: [], toRelations: [],
isActive: true,
isSystem: true,
isCustom: false,
} satisfies ObjectMetadataInterface; } satisfies ObjectMetadataInterface;
export type CurrencyMetadata = { export type CurrencyMetadata = {

View File

@ -71,6 +71,9 @@ export const fullNameObjectDefinition = {
fields: fullNameFields(), fields: fullNameFields(),
fromRelations: [], fromRelations: [],
toRelations: [], toRelations: [],
isActive: true,
isSystem: true,
isCustom: false,
} satisfies ObjectMetadataInterface; } satisfies ObjectMetadataInterface;
export type FullNameMetadata = { export type FullNameMetadata = {

View File

@ -71,6 +71,9 @@ export const linkObjectDefinition = {
fields: linkFields(), fields: linkFields(),
fromRelations: [], fromRelations: [],
toRelations: [], toRelations: [],
isActive: true,
isSystem: true,
isCustom: false,
} satisfies ObjectMetadataInterface; } satisfies ObjectMetadataInterface;
export type LinkMetadata = { export type LinkMetadata = {

View File

@ -23,6 +23,7 @@ import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceService } from 'src/metadata/data-source/data-source.service'; import { DataSourceService } from 'src/metadata/data-source/data-source.service';
import { UpdateFieldInput } from 'src/metadata/field-metadata/dtos/update-field.input'; import { UpdateFieldInput } from 'src/metadata/field-metadata/dtos/update-field.input';
import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory'; import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { FieldMetadataEntity } from './field-metadata.entity'; import { FieldMetadataEntity } from './field-metadata.entity';
@ -92,7 +93,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
fieldMetadataInput.workspaceId, fieldMetadataInput.workspaceId,
[ [
{ {
name: objectMetadata.targetTableName, name: computeObjectTargetTable(objectMetadata),
action: 'alter', action: 'alter',
columns: this.workspaceMigrationFactory.createColumnActions( columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE, WorkspaceMigrationColumnActionType.CREATE,
@ -201,7 +202,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
existingFieldMetadata.workspaceId, existingFieldMetadata.workspaceId,
[ [
{ {
name: objectMetadata.targetTableName, name: computeObjectTargetTable(objectMetadata),
action: 'alter', action: 'alter',
columns: this.workspaceMigrationFactory.createColumnActions( columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.ALTER, WorkspaceMigrationColumnActionType.ALTER,

View File

@ -12,4 +12,7 @@ export interface ObjectMetadataInterface {
fromRelations: RelationMetadataInterface[]; fromRelations: RelationMetadataInterface[];
toRelations: RelationMetadataInterface[]; toRelations: RelationMetadataInterface[];
fields: FieldMetadataInterface[]; fields: FieldMetadataInterface[];
isSystem: boolean;
isCustom: boolean;
isActive: boolean;
} }

View File

@ -21,7 +21,7 @@ import {
RelationMetadataEntity, RelationMetadataEntity,
RelationMetadataType, RelationMetadataType,
} from 'src/metadata/relation-metadata/relation-metadata.entity'; } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { createCustomColumnName } from 'src/metadata/utils/create-custom-column-name.util'; import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { ObjectMetadataEntity } from './object-metadata.entity'; import { ObjectMetadataEntity } from './object-metadata.entity';
@ -67,7 +67,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
const createdObjectMetadata = await super.createOne({ const createdObjectMetadata = await super.createOne({
...objectMetadataInput, ...objectMetadataInput,
dataSourceId: lastDataSourceMetadata.id, dataSourceId: lastDataSourceMetadata.id,
targetTableName: createCustomColumnName(objectMetadataInput.nameSingular), targetTableName: 'DEPRECATED',
isActive: true, isActive: true,
isCustom: true, isCustom: true,
isSystem: false, isSystem: false,
@ -156,62 +156,74 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
createdObjectMetadata.workspaceId, createdObjectMetadata.workspaceId,
[ [
{ {
name: createdObjectMetadata.targetTableName, name: computeObjectTargetTable(createdObjectMetadata),
action: 'create', action: 'create',
} satisfies WorkspaceMigrationTableAction, } satisfies WorkspaceMigrationTableAction,
// Add activity target relation // Add activity target relation
{ {
name: activityTargetObjectMetadata.targetTableName, name: computeObjectTargetTable(activityTargetObjectMetadata),
action: 'alter', action: 'alter',
columns: [ columns: [
{ {
action: WorkspaceMigrationColumnActionType.CREATE, action: WorkspaceMigrationColumnActionType.CREATE,
columnName: `${createdObjectMetadata.targetTableName}Id`, columnName: `${computeObjectTargetTable(
createdObjectMetadata,
)}Id`,
columnType: 'uuid', columnType: 'uuid',
isNullable: true, isNullable: true,
} satisfies WorkspaceMigrationColumnCreate, } satisfies WorkspaceMigrationColumnCreate,
], ],
}, },
{ {
name: activityTargetObjectMetadata.targetTableName, name: computeObjectTargetTable(activityTargetObjectMetadata),
action: 'alter', action: 'alter',
columns: [ columns: [
{ {
action: WorkspaceMigrationColumnActionType.RELATION, action: WorkspaceMigrationColumnActionType.RELATION,
columnName: `${createdObjectMetadata.targetTableName}Id`, columnName: `${computeObjectTargetTable(
referencedTableName: createdObjectMetadata.targetTableName, createdObjectMetadata,
)}Id`,
referencedTableName: computeObjectTargetTable(
createdObjectMetadata,
),
referencedTableColumnName: 'id', referencedTableColumnName: 'id',
}, },
], ],
}, },
// Add favorite relation // Add favorite relation
{ {
name: favoriteObjectMetadata.targetTableName, name: computeObjectTargetTable(favoriteObjectMetadata),
action: 'alter', action: 'alter',
columns: [ columns: [
{ {
action: WorkspaceMigrationColumnActionType.CREATE, action: WorkspaceMigrationColumnActionType.CREATE,
columnName: `${createdObjectMetadata.targetTableName}Id`, columnName: `${computeObjectTargetTable(
createdObjectMetadata,
)}Id`,
columnType: 'uuid', columnType: 'uuid',
isNullable: true, isNullable: true,
} satisfies WorkspaceMigrationColumnCreate, } satisfies WorkspaceMigrationColumnCreate,
], ],
}, },
{ {
name: favoriteObjectMetadata.targetTableName, name: computeObjectTargetTable(favoriteObjectMetadata),
action: 'alter', action: 'alter',
columns: [ columns: [
{ {
action: WorkspaceMigrationColumnActionType.RELATION, action: WorkspaceMigrationColumnActionType.RELATION,
columnName: `${createdObjectMetadata.targetTableName}Id`, columnName: `${computeObjectTargetTable(
referencedTableName: createdObjectMetadata.targetTableName, createdObjectMetadata,
)}Id`,
referencedTableName: computeObjectTargetTable(
createdObjectMetadata,
),
referencedTableColumnName: 'id', referencedTableColumnName: 'id',
}, },
], ],
}, },
// This is temporary until we implement mainIdentifier // This is temporary until we implement mainIdentifier
{ {
name: createdObjectMetadata.targetTableName, name: computeObjectTargetTable(createdObjectMetadata),
action: 'alter', action: 'alter',
columns: [ columns: [
{ {
@ -384,7 +396,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
name: `${createdObjectMetadata.nameSingular}Id`, name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
targetColumnMap: { targetColumnMap: {
value: `${createdObjectMetadata.targetTableName}Id`, value: `${computeObjectTargetTable(createdObjectMetadata)}Id`,
}, },
description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`, description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined, icon: undefined,
@ -474,7 +486,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
name: `${createdObjectMetadata.nameSingular}Id`, name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
targetColumnMap: { targetColumnMap: {
value: `${createdObjectMetadata.targetTableName}Id`, value: `${computeObjectTargetTable(createdObjectMetadata)}Id`,
}, },
description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`, description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined, icon: undefined,

View File

@ -18,6 +18,7 @@ import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.en
import { WorkspaceMigrationColumnActionType } from 'src/metadata/workspace-migration/workspace-migration.entity'; import { WorkspaceMigrationColumnActionType } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { createCustomColumnName } from 'src/metadata/utils/create-custom-column-name.util'; import { createCustomColumnName } from 'src/metadata/utils/create-custom-column-name.util';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { import {
RelationMetadataEntity, RelationMetadataEntity,
@ -201,8 +202,9 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
[ [
// Create the column // Create the column
{ {
name: objectMetadataMap[relationMetadataInput.toObjectMetadataId] name: computeObjectTargetTable(
.targetTableName, objectMetadataMap[relationMetadataInput.toObjectMetadataId],
),
action: 'alter', action: 'alter',
columns: [ columns: [
{ {
@ -215,16 +217,17 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
}, },
// Create the foreignKey // Create the foreignKey
{ {
name: objectMetadataMap[relationMetadataInput.toObjectMetadataId] name: computeObjectTargetTable(
.targetTableName, objectMetadataMap[relationMetadataInput.toObjectMetadataId],
),
action: 'alter', action: 'alter',
columns: [ columns: [
{ {
action: WorkspaceMigrationColumnActionType.RELATION, action: WorkspaceMigrationColumnActionType.RELATION,
columnName: foreignKeyColumnName, columnName: foreignKeyColumnName,
referencedTableName: referencedTableName: computeObjectTargetTable(
objectMetadataMap[relationMetadataInput.fromObjectMetadataId] objectMetadataMap[relationMetadataInput.fromObjectMetadataId],
.targetTableName, ),
referencedTableColumnName: 'id', referencedTableColumnName: 'id',
isUnique: isUnique:
relationMetadataInput.relationType === relationMetadataInput.relationType ===

View File

@ -20,6 +20,7 @@ import {
} from 'src/core/feature-flag/feature-flag.entity'; } from 'src/core/feature-flag/feature-flag.entity';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import DeleteInactiveWorkspaceEmail from 'src/workspace/cron/clean-inactive-workspaces/delete-inactive-workspaces.email'; import DeleteInactiveWorkspaceEmail from 'src/workspace/cron/clean-inactive-workspaces/delete-inactive-workspaces.email';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
const MILLISECONDS_IN_ONE_DAY = 1000 * 3600 * 24; const MILLISECONDS_IN_ONE_DAY = 1000 * 3600 * 24;
@ -54,7 +55,7 @@ export class CleanInactiveWorkspaceJob implements MessageQueueJob<undefined> {
(objectMetadata) => (objectMetadata) =>
objectMetadata.workspaceId === dataSource.workspaceId, objectMetadata.workspaceId === dataSource.workspaceId,
) )
.map((objectMetadata) => objectMetadata.targetTableName); .map((objectMetadata) => computeObjectTargetTable(objectMetadata));
const workspaceDataSource = const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSource); await this.typeORMService.connectToDataSource(dataSource);

View File

@ -0,0 +1,9 @@
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
export const computeObjectTargetTable = (
objectMetadata: ObjectMetadataInterface,
) => {
const prefix = objectMetadata.isCustom ? '_' : '';
return `${prefix}${objectMetadata.nameSingular}`;
};

View File

@ -9,6 +9,7 @@ import { WorkspaceHealthOptions } from 'src/workspace/workspace-health/interface
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { validName } from 'src/workspace/workspace-health/utils/valid-name.util'; import { validName } from 'src/workspace/workspace-health/utils/valid-name.util';
import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
@Injectable() @Injectable()
export class ObjectMetadataHealthService { export class ObjectMetadataHealthService {
@ -54,14 +55,17 @@ export class ObjectMetadataHealthService {
// Check if the table exist in database // Check if the table exist in database
const tableExist = await mainDataSource.query( const tableExist = await mainDataSource.query(
`SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = '${schemaName}' AND table_name = '${objectMetadata.targetTableName}')`, `SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = '${schemaName}'
AND table_name = '${computeObjectTargetTable(objectMetadata)}')`,
); );
if (!tableExist) { if (!tableExist) {
issues.push({ issues.push({
type: WorkspaceHealthIssueType.MISSING_TABLE, type: WorkspaceHealthIssueType.MISSING_TABLE,
objectMetadata, objectMetadata,
message: `Table ${objectMetadata.targetTableName} not found in schema ${schemaName}`, message: `Table ${computeObjectTargetTable(
objectMetadata,
)} not found in schema ${schemaName}`,
}); });
return issues; return issues;
@ -80,30 +84,13 @@ export class ObjectMetadataHealthService {
): WorkspaceHealthIssue[] { ): WorkspaceHealthIssue[] {
const issues: WorkspaceHealthIssue[] = []; const issues: WorkspaceHealthIssue[] = [];
if (
objectMetadata.isCustom &&
!objectMetadata.targetTableName.startsWith('_')
) {
issues.push({
type: WorkspaceHealthIssueType.TABLE_NAME_SHOULD_BE_CUSTOM,
objectMetadata,
message: `Table ${objectMetadata.targetTableName} is marked as custom but doesn't start with "_"`,
});
}
if (!objectMetadata.targetTableName) {
issues.push({
type: WorkspaceHealthIssueType.TABLE_TARGET_TABLE_NAME_NOT_VALID,
objectMetadata,
message: `Table ${objectMetadata.targetTableName} doesn't have a valid target table name`,
});
}
if (!objectMetadata.dataSourceId) { if (!objectMetadata.dataSourceId) {
issues.push({ issues.push({
type: WorkspaceHealthIssueType.TABLE_DATA_SOURCE_ID_NOT_VALID, type: WorkspaceHealthIssueType.TABLE_DATA_SOURCE_ID_NOT_VALID,
objectMetadata, objectMetadata,
message: `Table ${objectMetadata.targetTableName} doesn't have a data source`, message: `Table ${computeObjectTargetTable(
objectMetadata,
)} doesn't have a data source`,
}); });
} }
@ -118,7 +105,9 @@ export class ObjectMetadataHealthService {
issues.push({ issues.push({
type: WorkspaceHealthIssueType.TABLE_NAME_NOT_VALID, type: WorkspaceHealthIssueType.TABLE_NAME_NOT_VALID,
objectMetadata, objectMetadata,
message: `Table ${objectMetadata.targetTableName} doesn't have a valid name or label`, message: `Table ${computeObjectTargetTable(
objectMetadata,
)} doesn't have a valid name or label`,
}); });
} }

View File

@ -12,6 +12,7 @@ import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metad
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service'; import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
import { ObjectMetadataHealthService } from 'src/workspace/workspace-health/services/object-metadata-health.service'; import { ObjectMetadataHealthService } from 'src/workspace/workspace-health/services/object-metadata-health.service';
import { FieldMetadataHealthService } from 'src/workspace/workspace-health/services/field-metadata-health.service'; import { FieldMetadataHealthService } from 'src/workspace/workspace-health/services/field-metadata-health.service';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
@Injectable() @Injectable()
export class WorkspaceHealthService { export class WorkspaceHealthService {
@ -68,7 +69,7 @@ export class WorkspaceHealthService {
// Check fields metadata health // Check fields metadata health
const fieldIssues = await this.fieldMetadataHealthService.healthCheck( const fieldIssues = await this.fieldMetadataHealthService.healthCheck(
schemaName, schemaName,
objectMetadata.targetTableName, computeObjectTargetTable(objectMetadata),
objectMetadata.fields, objectMetadata.fields,
options, options,
); );

View File

@ -1,237 +0,0 @@
// import { GraphQLResolveInfo } from 'graphql';
// import { FieldMetadataTargetColumnMap } from 'src/metadata/field-metadata/interfaces/field-metadata-target-column-map.interface';
// import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
// import {
// PGGraphQLQueryBuilder,
// PGGraphQLQueryBuilderOptions,
// } from 'src/tenant/resolver-builder/pg-graphql/pg-graphql-query-builder';
// const testUUID = '123e4567-e89b-12d3-a456-426614174001';
// const normalizeWhitespace = (str) => str.replace(/\s+/g, '');
// // Mocking dependencies
// jest.mock('uuid', () => ({
// v4: jest.fn(() => testUUID),
// }));
// jest.mock('graphql-fields', () =>
// jest.fn(() => ({
// name: true,
// age: true,
// complexField: {
// subField1: true,
// subField2: true,
// },
// })),
// );
// describe('PGGraphQLQueryBuilder', () => {
// let queryBuilder;
// let mockOptions: PGGraphQLQueryBuilderOptions;
// beforeEach(() => {
// const fieldMetadataCollection = [
// {
// name: 'name',
// targetColumnMap: {
// value: 'column_name',
// } as FieldMetadataTargetColumnMap,
// },
// {
// name: 'age',
// targetColumnMap: {
// value: 'column_age',
// } as FieldMetadataTargetColumnMap,
// },
// {
// name: 'complexField',
// targetColumnMap: {
// subField1: 'column_subField1',
// subField2: 'column_subField2',
// } as FieldMetadataTargetColumnMap,
// },
// ] as FieldMetadata[];
// mockOptions = {
// targetTableName: 'TestTable',
// info: {} as GraphQLResolveInfo,
// fieldMetadataCollection,
// };
// queryBuilder = new PGGraphQLQueryBuilder(mockOptions);
// });
// test('findMany generates correct query with no arguments', () => {
// const query = queryBuilder.findMany();
// expect(normalizeWhitespace(query)).toBe(
// normalizeWhitespace(`
// query {
// TestTableCollection {
// name: column_name
// age: column_age
// ___complexField_subField1: column_subField1
// ___complexField_subField2: column_subField2
// }
// }
// `),
// );
// });
// test('findMany generates correct query with filter parameters', () => {
// const args = {
// filter: {
// name: { eq: 'Alice' },
// age: { gt: 20 },
// },
// };
// const query = queryBuilder.findMany(args);
// expect(normalizeWhitespace(query)).toBe(
// normalizeWhitespace(`
// query {
// TestTableCollection(filter: { column_name: { eq: "Alice" }, column_age: { gt: 20 } }) {
// name: column_name
// age: column_age
// ___complexField_subField1: column_subField1
// ___complexField_subField2: column_subField2
// }
// }
// `),
// );
// });
// test('findMany generates correct query with combined pagination parameters', () => {
// const args = {
// first: 5,
// after: 'someCursor',
// before: 'anotherCursor',
// last: 3,
// };
// const query = queryBuilder.findMany(args);
// expect(normalizeWhitespace(query)).toBe(
// normalizeWhitespace(`
// query {
// TestTableCollection(
// first: 5,
// after: "someCursor",
// before: "anotherCursor",
// last: 3
// ) {
// name: column_name
// age: column_age
// ___complexField_subField1: column_subField1
// ___complexField_subField2: column_subField2
// }
// }
// `),
// );
// });
// test('findOne generates correct query with ID filter', () => {
// const args = { filter: { id: { eq: testUUID } } };
// const query = queryBuilder.findOne(args);
// expect(normalizeWhitespace(query)).toBe(
// normalizeWhitespace(`
// query {
// TestTableCollection(filter: { id: { eq: "${testUUID}" } }) {
// edges {
// node {
// name: column_name
// age: column_age
// ___complexField_subField1: column_subField1
// ___complexField_subField2: column_subField2
// }
// }
// }
// }
// `),
// );
// });
// test('createMany generates correct mutation with complex and nested fields', () => {
// const args = {
// data: [
// {
// name: 'Alice',
// age: 30,
// complexField: {
// subField1: 'data1',
// subField2: 'data2',
// },
// },
// ],
// };
// const query = queryBuilder.createMany(args);
// expect(normalizeWhitespace(query)).toBe(
// normalizeWhitespace(`
// mutation {
// insertIntoTestTableCollection(objects: [{
// id: "${testUUID}",
// column_name: "Alice",
// column_age: 30,
// column_subField1: "data1",
// column_subField2: "data2"
// }]) {
// affectedCount
// records {
// name: column_name
// age: column_age
// ___complexField_subField1: column_subField1
// ___complexField_subField2: column_subField2
// }
// }
// }
// `),
// );
// });
// test('updateOne generates correct mutation with complex and nested fields', () => {
// const args = {
// id: '1',
// data: {
// name: 'Bob',
// age: 40,
// complexField: {
// subField1: 'newData1',
// subField2: 'newData2',
// },
// },
// };
// const query = queryBuilder.updateOne(args);
// expect(normalizeWhitespace(query)).toBe(
// normalizeWhitespace(`
// mutation {
// updateTestTableCollection(
// set: {
// column_name: "Bob",
// column_age: 40,
// column_subField1: "newData1",
// column_subField2: "newData2"
// },
// filter: { id: { eq: "1" } }
// ) {
// affectedCount
// records {
// name: column_name
// age: column_age
// ___complexField_subField1: column_subField1
// ___complexField_subField2: column_subField2
// }
// }
// }
// `),
// );
// });
// });
it('should pass', () => {
expect(true).toBe(true);
});

View File

@ -7,6 +7,7 @@ import { Record as IRecord } from 'src/workspace/workspace-query-builder/interfa
import { CreateManyResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { CreateManyResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util'; import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { FieldsStringFactory } from './fields-string.factory'; import { FieldsStringFactory } from './fields-string.factory';
import { ArgsAliasFactory } from './args-alias.factory'; import { ArgsAliasFactory } from './args-alias.factory';
@ -36,9 +37,9 @@ export class CreateManyQueryFactory {
return ` return `
mutation { mutation {
insertInto${ insertInto${computeObjectTargetTable(
options.targetTableName options.objectMetadataItem,
}Collection(objects: ${stringifyWithoutKeyQuote( )}Collection(objects: ${stringifyWithoutKeyQuote(
computedArgs.data.map((datum) => ({ computedArgs.data.map((datum) => ({
id: uuidv4(), id: uuidv4(),
...datum, ...datum,

View File

@ -4,6 +4,7 @@ import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-buil
import { DeleteManyResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { DeleteManyResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util'; import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { FieldsStringFactory } from './fields-string.factory'; import { FieldsStringFactory } from './fields-string.factory';
@ -23,9 +24,9 @@ export class DeleteManyQueryFactory {
return ` return `
mutation { mutation {
deleteFrom${ deleteFrom${computeObjectTargetTable(
options.targetTableName options.objectMetadataItem,
}Collection(filter: ${stringifyWithoutKeyQuote( )}Collection(filter: ${stringifyWithoutKeyQuote(
args.filter, args.filter,
)}, atMost: 30) { )}, atMost: 30) {
affectedCount affectedCount

View File

@ -3,6 +3,8 @@ import { Injectable, Logger } from '@nestjs/common';
import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-builder/interfaces/workspace-query-builder-options.interface'; import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-builder/interfaces/workspace-query-builder-options.interface';
import { DeleteOneResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { DeleteOneResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { FieldsStringFactory } from './fields-string.factory'; import { FieldsStringFactory } from './fields-string.factory';
@Injectable() @Injectable()
@ -23,7 +25,9 @@ export class DeleteOneQueryFactory {
return ` return `
mutation { mutation {
deleteFrom${options.targetTableName}Collection(filter: { id: { eq: "${args.id}" } }) { deleteFrom${computeObjectTargetTable(
options.objectMetadataItem,
)}Collection(filter: { id: { eq: "${args.id}" } }) {
affectedCount affectedCount
records { records {
${fieldsString} ${fieldsString}

View File

@ -9,6 +9,7 @@ import { FindManyResolverArgs } from 'src/workspace/workspace-resolver-builder/i
import { ArgsStringFactory } from './args-string.factory'; import { ArgsStringFactory } from './args-string.factory';
import { FieldsStringFactory } from './fields-string.factory'; import { FieldsStringFactory } from './fields-string.factory';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
@Injectable() @Injectable()
export class FindManyQueryFactory { export class FindManyQueryFactory {
@ -38,7 +39,7 @@ export class FindManyQueryFactory {
return ` return `
query { query {
${options.targetTableName}Collection${ ${computeObjectTargetTable(options.objectMetadataItem)}Collection${
argsString ? `(${argsString})` : '' argsString ? `(${argsString})` : ''
} { } {
${fieldsString} ${fieldsString}

View File

@ -4,6 +4,8 @@ import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-buil
import { RecordFilter } from 'src/workspace/workspace-query-builder/interfaces/record.interface'; import { RecordFilter } from 'src/workspace/workspace-query-builder/interfaces/record.interface';
import { FindOneResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { FindOneResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { ArgsStringFactory } from './args-string.factory'; import { ArgsStringFactory } from './args-string.factory';
import { FieldsStringFactory } from './fields-string.factory'; import { FieldsStringFactory } from './fields-string.factory';
@ -32,7 +34,7 @@ export class FindOneQueryFactory {
return ` return `
query { query {
${options.targetTableName}Collection${ ${computeObjectTargetTable(options.objectMetadataItem)}Collection${
argsString ? `(${argsString})` : '' argsString ? `(${argsString})` : ''
} { } {
edges { edges {

View File

@ -13,6 +13,7 @@ import {
} from 'src/workspace/utils/deduce-relation-direction.util'; } from 'src/workspace/utils/deduce-relation-direction.util';
import { getFieldArgumentsByKey } from 'src/workspace/workspace-query-builder/utils/get-field-arguments-by-key.util'; import { getFieldArgumentsByKey } from 'src/workspace/workspace-query-builder/utils/get-field-arguments-by-key.util';
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service'; import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { FieldsStringFactory } from './fields-string.factory'; import { FieldsStringFactory } from './fields-string.factory';
import { ArgsStringFactory } from './args-string.factory'; import { ArgsStringFactory } from './args-string.factory';
@ -109,25 +110,27 @@ export class RelationFieldAliasFactory {
); );
return ` return `
${fieldKey}: ${referencedObjectMetadata.targetTableName}Collection${ ${fieldKey}: ${computeObjectTargetTable(
argsString ? `(${argsString})` : '' referencedObjectMetadata,
} { )}Collection${argsString ? `(${argsString})` : ''} {
${fieldsString} ${fieldsString}
} }
`; `;
} }
let relationAlias = fieldMetadata.isCustom let relationAlias = fieldMetadata.isCustom
? `${fieldKey}: ${referencedObjectMetadata.targetTableName}` ? `${fieldKey}: _${fieldMetadata.name}`
: fieldKey; : fieldKey;
// For one to one relations, pg_graphql use the targetTableName on the side that is not storing the foreign key // For one to one relations, pg_graphql use the target TableName on the side that is not storing the foreign key
// so we need to alias it to the field key // so we need to alias it to the field key
if ( if (
relationMetadata.relationType === RelationMetadataType.ONE_TO_ONE && relationMetadata.relationType === RelationMetadataType.ONE_TO_ONE &&
relationDirection === RelationDirection.FROM relationDirection === RelationDirection.FROM
) { ) {
relationAlias = `${fieldKey}: ${referencedObjectMetadata.targetTableName}`; relationAlias = `${fieldKey}: ${computeObjectTargetTable(
referencedObjectMetadata,
)}`;
} }
const fieldsString = const fieldsString =
await this.fieldsStringFactory.createFieldsStringRecursive( await this.fieldsStringFactory.createFieldsStringRecursive(

View File

@ -10,6 +10,7 @@ import { UpdateManyResolverArgs } from 'src/workspace/workspace-resolver-builder
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util'; import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
import { FieldsStringFactory } from 'src/workspace/workspace-query-builder/factories/fields-string.factory'; import { FieldsStringFactory } from 'src/workspace/workspace-query-builder/factories/fields-string.factory';
import { ArgsAliasFactory } from 'src/workspace/workspace-query-builder/factories/args-alias.factory'; import { ArgsAliasFactory } from 'src/workspace/workspace-query-builder/factories/args-alias.factory';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
@Injectable() @Injectable()
export class UpdateManyQueryFactory { export class UpdateManyQueryFactory {
@ -43,7 +44,7 @@ export class UpdateManyQueryFactory {
return ` return `
mutation { mutation {
update${options.targetTableName}Collection( update${computeObjectTargetTable(options.objectMetadataItem)}Collection(
set: ${stringifyWithoutKeyQuote(argsData)}, set: ${stringifyWithoutKeyQuote(argsData)},
filter: ${stringifyWithoutKeyQuote(args.filter)}, filter: ${stringifyWithoutKeyQuote(args.filter)},
) { ) {

View File

@ -5,6 +5,7 @@ import { Record as IRecord } from 'src/workspace/workspace-query-builder/interfa
import { UpdateOneResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { UpdateOneResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util'; import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { FieldsStringFactory } from './fields-string.factory'; import { FieldsStringFactory } from './fields-string.factory';
import { ArgsAliasFactory } from './args-alias.factory'; import { ArgsAliasFactory } from './args-alias.factory';
@ -39,9 +40,9 @@ export class UpdateOneQueryFactory {
return ` return `
mutation { mutation {
update${ update${computeObjectTargetTable(
options.targetTableName options.objectMetadataItem,
}Collection(set: ${stringifyWithoutKeyQuote( )}Collection(set: ${stringifyWithoutKeyQuote(
argsData, argsData,
)}, filter: { id: { eq: "${computedArgs.id}" } }) { )}, filter: { id: { eq: "${computedArgs.id}" } }) {
affectedCount affectedCount

View File

@ -4,7 +4,7 @@ import { FieldMetadataInterface } from 'src/metadata/field-metadata/interfaces/f
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface'; import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
export interface WorkspaceQueryBuilderOptions { export interface WorkspaceQueryBuilderOptions {
targetTableName: string; objectMetadataItem: ObjectMetadataInterface;
info: GraphQLResolveInfo; info: GraphQLResolveInfo;
fieldMetadataCollection: FieldMetadataInterface[]; fieldMetadataCollection: FieldMetadataInterface[];
objectMetadataCollection: ObjectMetadataInterface[]; objectMetadataCollection: ObjectMetadataInterface[];

View File

@ -4,9 +4,9 @@ import { FieldMetadataInterface } from 'src/metadata/field-metadata/interfaces/f
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface'; import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
export interface WorkspaceQueryRunnerOptions { export interface WorkspaceQueryRunnerOptions {
targetTableName: string;
workspaceId: string; workspaceId: string;
info: GraphQLResolveInfo; info: GraphQLResolveInfo;
objectMetadataItem: ObjectMetadataInterface;
fieldMetadataCollection: FieldMetadataInterface[]; fieldMetadataCollection: FieldMetadataInterface[];
objectMetadataCollection: ObjectMetadataInterface[]; objectMetadataCollection: ObjectMetadataInterface[];
} }

View File

@ -1,6 +1,7 @@
import { Inject, Injectable, Logger } from '@nestjs/common'; import { Inject, Injectable, Logger } from '@nestjs/common';
import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface'; import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface';
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service'; import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service'; import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
@ -20,7 +21,7 @@ export enum CallWebhookJobsJobOperation {
export type CallWebhookJobsJobData = { export type CallWebhookJobsJobData = {
workspaceId: string; workspaceId: string;
objectNameSingular: string; objectMetadataItem: ObjectMetadataInterface;
recordData: any; recordData: any;
operation: CallWebhookJobsJobOperation; operation: CallWebhookJobsJobOperation;
}; };
@ -43,7 +44,7 @@ export class CallWebhookJobsJob
const objectMetadataItem = const objectMetadataItem =
await this.objectMetadataService.findOneOrFailWithinWorkspace( await this.objectMetadataService.findOneOrFailWithinWorkspace(
data.workspaceId, data.workspaceId,
{ where: { nameSingular: data.objectNameSingular } }, { where: { nameSingular: data.objectMetadataItem.nameSingular } },
); );
const dataSourceMetadata = const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(

View File

@ -39,6 +39,8 @@ export const parseResult = (obj: any): any => {
if (obj.hasOwnProperty(key)) { if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key] !== null) { if (typeof obj[key] === 'object' && obj[key] !== null) {
result[key] = parseResult(obj[key]); result[key] = parseResult(obj[key]);
} else if (key === '__typename') {
result[key] = obj[key].replace(/^_*/, '');
} else if (isSpecialKey(key)) { } else if (isSpecialKey(key)) {
handleSpecialKey(result, key, obj[key]); handleSpecialKey(result, key, obj[key]);
} else { } else {

View File

@ -22,6 +22,7 @@ import {
UpdateManyResolverArgs, UpdateManyResolverArgs,
UpdateOneResolverArgs, UpdateOneResolverArgs,
} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
import { WorkspaceQueryBuilderFactory } from 'src/workspace/workspace-query-builder/workspace-query-builder.factory'; import { WorkspaceQueryBuilderFactory } from 'src/workspace/workspace-query-builder/workspace-query-builder.factory';
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service'; import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
@ -35,6 +36,7 @@ import {
import { parseResult } from 'src/workspace/workspace-query-runner/utils/parse-result.util'; import { parseResult } from 'src/workspace/workspace-query-runner/utils/parse-result.util';
import { ExceptionHandlerService } from 'src/integrations/exception-handler/exception-handler.service'; import { ExceptionHandlerService } from 'src/integrations/exception-handler/exception-handler.service';
import { handleExceptionAndConvertToGraphQLError } from 'src/filters/utils/global-exception-handler.util'; import { handleExceptionAndConvertToGraphQLError } from 'src/filters/utils/global-exception-handler.util';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-optionts.interface'; import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-optionts.interface';
import { import {
@ -63,7 +65,7 @@ export class WorkspaceQueryRunnerService {
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<Record> | undefined> { ): Promise<IConnection<Record> | undefined> {
try { try {
const { workspaceId, targetTableName } = options; const { workspaceId, objectMetadataItem } = options;
const start = performance.now(); const start = performance.now();
const query = await this.workspaceQueryBuilderFactory.findMany( const query = await this.workspaceQueryBuilderFactory.findMany(
@ -76,7 +78,11 @@ export class WorkspaceQueryRunnerService {
console.log(`query time: ${end - start} ms`); console.log(`query time: ${end - start} ms`);
return this.parseResult<IConnection<Record>>(result, targetTableName, ''); return this.parseResult<IConnection<Record>>(
result,
objectMetadataItem,
'',
);
} catch (exception) { } catch (exception) {
const error = handleExceptionAndConvertToGraphQLError( const error = handleExceptionAndConvertToGraphQLError(
exception, exception,
@ -98,7 +104,7 @@ export class WorkspaceQueryRunnerService {
if (!args.filter || Object.keys(args.filter).length === 0) { if (!args.filter || Object.keys(args.filter).length === 0) {
throw new BadRequestException('Missing filter argument'); throw new BadRequestException('Missing filter argument');
} }
const { workspaceId, targetTableName } = options; const { workspaceId, objectMetadataItem } = options;
const query = await this.workspaceQueryBuilderFactory.findOne( const query = await this.workspaceQueryBuilderFactory.findOne(
args, args,
options, options,
@ -106,7 +112,7 @@ export class WorkspaceQueryRunnerService {
const result = await this.execute(query, workspaceId); const result = await this.execute(query, workspaceId);
const parsedResult = this.parseResult<IConnection<Record>>( const parsedResult = this.parseResult<IConnection<Record>>(
result, result,
targetTableName, objectMetadataItem,
'', '',
); );
@ -126,16 +132,17 @@ export class WorkspaceQueryRunnerService {
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<Record[] | undefined> { ): Promise<Record[] | undefined> {
try { try {
const { workspaceId, targetTableName } = options; const { workspaceId, objectMetadataItem } = options;
const query = await this.workspaceQueryBuilderFactory.createMany( const query = await this.workspaceQueryBuilderFactory.createMany(
args, args,
options, options,
); );
const result = await this.execute(query, workspaceId); const result = await this.execute(query, workspaceId);
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>( const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
result, result,
targetTableName, objectMetadataItem,
'insertInto', 'insertInto',
)?.records; )?.records;
@ -170,7 +177,7 @@ export class WorkspaceQueryRunnerService {
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<Record | undefined> { ): Promise<Record | undefined> {
try { try {
const { workspaceId, targetTableName } = options; const { workspaceId, objectMetadataItem } = options;
const query = await this.workspaceQueryBuilderFactory.updateOne( const query = await this.workspaceQueryBuilderFactory.updateOne(
args, args,
options, options,
@ -179,7 +186,7 @@ export class WorkspaceQueryRunnerService {
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>( const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
result, result,
targetTableName, objectMetadataItem,
'update', 'update',
)?.records; )?.records;
@ -205,7 +212,7 @@ export class WorkspaceQueryRunnerService {
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<Record | undefined> { ): Promise<Record | undefined> {
try { try {
const { workspaceId, targetTableName } = options; const { workspaceId, objectMetadataItem } = options;
const query = await this.workspaceQueryBuilderFactory.deleteOne( const query = await this.workspaceQueryBuilderFactory.deleteOne(
args, args,
options, options,
@ -214,7 +221,7 @@ export class WorkspaceQueryRunnerService {
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>( const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
result, result,
targetTableName, objectMetadataItem,
'deleteFrom', 'deleteFrom',
)?.records; )?.records;
@ -240,7 +247,7 @@ export class WorkspaceQueryRunnerService {
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<Record[] | undefined> { ): Promise<Record[] | undefined> {
try { try {
const { workspaceId, targetTableName } = options; const { workspaceId, objectMetadataItem } = options;
const query = await this.workspaceQueryBuilderFactory.updateMany( const query = await this.workspaceQueryBuilderFactory.updateMany(
args, args,
options, options,
@ -249,7 +256,7 @@ export class WorkspaceQueryRunnerService {
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>( const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
result, result,
targetTableName, objectMetadataItem,
'update', 'update',
)?.records; )?.records;
@ -278,7 +285,7 @@ export class WorkspaceQueryRunnerService {
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<Record[] | undefined> { ): Promise<Record[] | undefined> {
try { try {
const { workspaceId, targetTableName } = options; const { workspaceId, objectMetadataItem } = options;
const query = await this.workspaceQueryBuilderFactory.deleteMany( const query = await this.workspaceQueryBuilderFactory.deleteMany(
args, args,
options, options,
@ -287,7 +294,7 @@ export class WorkspaceQueryRunnerService {
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>( const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
result, result,
targetTableName, objectMetadataItem,
'deleteFrom', 'deleteFrom',
)?.records; )?.records;
@ -334,18 +341,20 @@ export class WorkspaceQueryRunnerService {
private parseResult<Result>( private parseResult<Result>(
graphqlResult: PGGraphQLResult | undefined, graphqlResult: PGGraphQLResult | undefined,
targetTableName: string, objectMetadataItem: ObjectMetadataInterface,
command: string, command: string,
): Result { ): Result {
const entityKey = `${command}${targetTableName}Collection`; const entityKey = `${command}${computeObjectTargetTable(
objectMetadataItem,
)}Collection`;
const result = graphqlResult?.[0]?.resolve?.data?.[entityKey]; const result = graphqlResult?.[0]?.resolve?.data?.[entityKey];
const errors = graphqlResult?.[0]?.resolve?.errors; const errors = graphqlResult?.[0]?.resolve?.errors;
if (!result) { if (!result) {
throw new InternalServerErrorException( throw new InternalServerErrorException(
`GraphQL errors on ${command}${targetTableName}: ${JSON.stringify( `GraphQL errors on ${command}${
errors, objectMetadataItem.nameSingular
)}`, }: ${JSON.stringify(errors)}`,
); );
} }
@ -354,13 +363,13 @@ export class WorkspaceQueryRunnerService {
async executeAndParse<Result>( async executeAndParse<Result>(
query: string, query: string,
targetTableName: string, objectMetadataItem: ObjectMetadataInterface,
command: string, command: string,
workspaceId: string, workspaceId: string,
): Promise<Result> { ): Promise<Result> {
const result = await this.execute(query, workspaceId); const result = await this.execute(query, workspaceId);
return this.parseResult(result, targetTableName, command); return this.parseResult(result, objectMetadataItem, command);
} }
async triggerWebhooks<Record>( async triggerWebhooks<Record>(
@ -378,7 +387,7 @@ export class WorkspaceQueryRunnerService {
recordData: jobData, recordData: jobData,
workspaceId: options.workspaceId, workspaceId: options.workspaceId,
operation, operation,
objectNameSingular: options.targetTableName, objectMetadataItem: options.objectMetadataItem,
}, },
{ retryLimit: 3 }, { retryLimit: 3 },
); );

View File

@ -26,7 +26,7 @@ export class CreateManyResolverFactory
return (_source, args, context, info) => { return (_source, args, context, info) => {
return this.workspaceQueryRunnerService.createMany(args, { return this.workspaceQueryRunnerService.createMany(args, {
targetTableName: internalContext.targetTableName, objectMetadataItem: internalContext.objectMetadataItem,
workspaceId: internalContext.workspaceId, workspaceId: internalContext.workspaceId,
info, info,
fieldMetadataCollection: internalContext.fieldMetadataCollection, fieldMetadataCollection: internalContext.fieldMetadataCollection,

View File

@ -26,7 +26,7 @@ export class CreateOneResolverFactory
return (_source, args, context, info) => { return (_source, args, context, info) => {
return this.workspaceQueryRunnerService.createOne(args, { return this.workspaceQueryRunnerService.createOne(args, {
targetTableName: internalContext.targetTableName, objectMetadataItem: internalContext.objectMetadataItem,
workspaceId: internalContext.workspaceId, workspaceId: internalContext.workspaceId,
info, info,
fieldMetadataCollection: internalContext.fieldMetadataCollection, fieldMetadataCollection: internalContext.fieldMetadataCollection,

View File

@ -26,7 +26,7 @@ export class DeleteManyResolverFactory
return (_source, args, context, info) => { return (_source, args, context, info) => {
return this.workspaceQueryRunnerService.deleteMany(args, { return this.workspaceQueryRunnerService.deleteMany(args, {
targetTableName: internalContext.targetTableName, objectMetadataItem: internalContext.objectMetadataItem,
workspaceId: internalContext.workspaceId, workspaceId: internalContext.workspaceId,
info, info,
fieldMetadataCollection: internalContext.fieldMetadataCollection, fieldMetadataCollection: internalContext.fieldMetadataCollection,

View File

@ -26,7 +26,7 @@ export class DeleteOneResolverFactory
return (_source, args, context, info) => { return (_source, args, context, info) => {
return this.workspaceQueryRunnerService.deleteOne(args, { return this.workspaceQueryRunnerService.deleteOne(args, {
targetTableName: internalContext.targetTableName, objectMetadataItem: internalContext.objectMetadataItem,
workspaceId: internalContext.workspaceId, workspaceId: internalContext.workspaceId,
info, info,
fieldMetadataCollection: internalContext.fieldMetadataCollection, fieldMetadataCollection: internalContext.fieldMetadataCollection,

View File

@ -32,7 +32,7 @@ export class ExecuteQuickActionOnOneResolverFactory
return (_source, args, context, info) => { return (_source, args, context, info) => {
return this.executeQuickActionOnOne(args, { return this.executeQuickActionOnOne(args, {
targetTableName: internalContext.targetTableName, objectMetadataItem: internalContext.objectMetadataItem,
workspaceId: internalContext.workspaceId, workspaceId: internalContext.workspaceId,
info, info,
fieldMetadataCollection: internalContext.fieldMetadataCollection, fieldMetadataCollection: internalContext.fieldMetadataCollection,
@ -45,11 +45,12 @@ export class ExecuteQuickActionOnOneResolverFactory
args: DeleteOneResolverArgs, args: DeleteOneResolverArgs,
options: WorkspaceQueryRunnerOptions, options: WorkspaceQueryRunnerOptions,
): Promise<Record | undefined> { ): Promise<Record | undefined> {
switch (options.targetTableName) { switch (options.objectMetadataItem.nameSingular) {
case 'company': { case 'company': {
await this.quickActionsService.executeQuickActionOnCompany( await this.quickActionsService.executeQuickActionOnCompany(
args.id, args.id,
options.workspaceId, options.workspaceId,
options.objectMetadataItem,
); );
break; break;
} }
@ -57,6 +58,7 @@ export class ExecuteQuickActionOnOneResolverFactory
await this.quickActionsService.createCompanyFromPerson( await this.quickActionsService.createCompanyFromPerson(
args.id, args.id,
options.workspaceId, options.workspaceId,
options.objectMetadataCollection,
); );
break; break;
} }

View File

@ -26,7 +26,7 @@ export class FindManyResolverFactory
return (_source, args, context, info) => { return (_source, args, context, info) => {
return this.workspaceQueryRunnerService.findMany(args, { return this.workspaceQueryRunnerService.findMany(args, {
targetTableName: internalContext.targetTableName, objectMetadataItem: internalContext.objectMetadataItem,
workspaceId: internalContext.workspaceId, workspaceId: internalContext.workspaceId,
info, info,
fieldMetadataCollection: internalContext.fieldMetadataCollection, fieldMetadataCollection: internalContext.fieldMetadataCollection,

View File

@ -26,7 +26,7 @@ export class FindOneResolverFactory
return (_source, args, context, info) => { return (_source, args, context, info) => {
return this.workspaceQueryRunnerService.findOne(args, { return this.workspaceQueryRunnerService.findOne(args, {
targetTableName: internalContext.targetTableName, objectMetadataItem: internalContext.objectMetadataItem,
workspaceId: internalContext.workspaceId, workspaceId: internalContext.workspaceId,
info, info,
fieldMetadataCollection: internalContext.fieldMetadataCollection, fieldMetadataCollection: internalContext.fieldMetadataCollection,

View File

@ -26,7 +26,7 @@ export class UpdateManyResolverFactory
return (_source, args, context, info) => { return (_source, args, context, info) => {
return this.workspaceQueryRunnerService.updateMany(args, { return this.workspaceQueryRunnerService.updateMany(args, {
targetTableName: internalContext.targetTableName, objectMetadataItem: internalContext.objectMetadataItem,
workspaceId: internalContext.workspaceId, workspaceId: internalContext.workspaceId,
info, info,
fieldMetadataCollection: internalContext.fieldMetadataCollection, fieldMetadataCollection: internalContext.fieldMetadataCollection,

View File

@ -26,7 +26,7 @@ export class UpdateOneResolverFactory
return (_source, args, context, info) => { return (_source, args, context, info) => {
return this.workspaceQueryRunnerService.updateOne(args, { return this.workspaceQueryRunnerService.updateOne(args, {
targetTableName: internalContext.targetTableName, objectMetadataItem: internalContext.objectMetadataItem,
workspaceId: internalContext.workspaceId, workspaceId: internalContext.workspaceId,
info, info,
fieldMetadataCollection: internalContext.fieldMetadataCollection, fieldMetadataCollection: internalContext.fieldMetadataCollection,

View File

@ -79,7 +79,7 @@ export class WorkspaceResolverFactory {
resolvers.Query[resolverName] = resolverFactory.create({ resolvers.Query[resolverName] = resolverFactory.create({
workspaceId, workspaceId,
targetTableName: objectMetadata.targetTableName, objectMetadataItem: objectMetadata,
fieldMetadataCollection: objectMetadata.fields, fieldMetadataCollection: objectMetadata.fields,
objectMetadataCollection: objectMetadataCollection, objectMetadataCollection: objectMetadataCollection,
}); });
@ -102,7 +102,7 @@ export class WorkspaceResolverFactory {
resolvers.Mutation[resolverName] = resolverFactory.create({ resolvers.Mutation[resolverName] = resolverFactory.create({
workspaceId, workspaceId,
targetTableName: objectMetadata.targetTableName, objectMetadataItem: objectMetadata,
fieldMetadataCollection: objectMetadata.fields, fieldMetadataCollection: objectMetadata.fields,
objectMetadataCollection: objectMetadataCollection, objectMetadataCollection: objectMetadataCollection,
}); });

View File

@ -3,7 +3,7 @@ import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/
export interface WorkspaceSchemaBuilderContext { export interface WorkspaceSchemaBuilderContext {
workspaceId: string; workspaceId: string;
targetTableName: string; objectMetadataItem: ObjectMetadataInterface;
fieldMetadataCollection: FieldMetadataInterface[]; fieldMetadataCollection: FieldMetadataInterface[];
objectMetadataCollection: ObjectMetadataInterface[]; objectMetadataCollection: ObjectMetadataInterface[];
} }

View File

@ -16,7 +16,7 @@ export function ObjectMetadata(
{ {
nameSingular: objectName, nameSingular: objectName,
...params, ...params,
targetTableName: objectName, targetTableName: 'DEPRECATED',
isSystem, isSystem,
isCustom: false, isCustom: false,
description: params.description, description: params.description,

View File

@ -37,6 +37,7 @@ import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/work
import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service'; import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service';
import { ReflectiveMetadataFactory } from 'src/workspace/workspace-sync-metadata/reflective-metadata.factory'; import { ReflectiveMetadataFactory } from 'src/workspace/workspace-sync-metadata/reflective-metadata.factory';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity'; import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
@Injectable() @Injectable()
export class WorkspaceSyncMetadataService { export class WorkspaceSyncMetadataService {
@ -391,7 +392,7 @@ export class WorkspaceSyncMetadataService {
objectsToCreate.map((object) => { objectsToCreate.map((object) => {
const migrations = [ const migrations = [
{ {
name: object.targetTableName, name: computeObjectTargetTable(object),
action: 'create', action: 'create',
} satisfies WorkspaceMigrationTableAction, } satisfies WorkspaceMigrationTableAction,
...Object.values(object.fields) ...Object.values(object.fields)
@ -399,7 +400,7 @@ export class WorkspaceSyncMetadataService {
.map( .map(
(field) => (field) =>
({ ({
name: object.targetTableName, name: computeObjectTargetTable(object),
action: 'alter', action: 'alter',
columns: this.workspaceMigrationFactory.createColumnActions( columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE, WorkspaceMigrationColumnActionType.CREATE,
@ -433,7 +434,9 @@ export class WorkspaceSyncMetadataService {
fieldsToCreate.map((field) => { fieldsToCreate.map((field) => {
const migrations = [ const migrations = [
{ {
name: objectsInDbById[field.objectMetadataId].targetTableName, name: computeObjectTargetTable(
objectsInDbById[field.objectMetadataId],
),
action: 'alter', action: 'alter',
columns: this.workspaceMigrationFactory.createColumnActions( columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE, WorkspaceMigrationColumnActionType.CREATE,
@ -454,7 +457,9 @@ export class WorkspaceSyncMetadataService {
fieldsToDelete.map((field) => { fieldsToDelete.map((field) => {
const migrations = [ const migrations = [
{ {
name: objectsInDbById[field.objectMetadataId].targetTableName, name: computeObjectTargetTable(
objectsInDbById[field.objectMetadataId],
),
action: 'alter', action: 'alter',
columns: [ columns: [
{ {
@ -519,13 +524,14 @@ export class WorkspaceSyncMetadataService {
const migrations = [ const migrations = [
{ {
name: toObjectMetadata.targetTableName, name: computeObjectTargetTable(toObjectMetadata),
action: 'alter', action: 'alter',
columns: [ columns: [
{ {
action: WorkspaceMigrationColumnActionType.RELATION, action: WorkspaceMigrationColumnActionType.RELATION,
columnName: `${camelCase(toFieldMetadata.name)}Id`, columnName: `${camelCase(toFieldMetadata.name)}Id`,
referencedTableName: fromObjectMetadata.targetTableName, referencedTableName:
computeObjectTargetTable(fromObjectMetadata),
referencedTableColumnName: 'id', referencedTableColumnName: 'id',
isUnique: isUnique:
relation.relationType === RelationMetadataType.ONE_TO_ONE, relation.relationType === RelationMetadataType.ONE_TO_ONE,