feat: new relation sync-metadata, twenty-orm, create/update (#10217)
Fix https://github.com/twentyhq/core-team-issues/issues/330#issue-2827026606 and https://github.com/twentyhq/core-team-issues/issues/327#issue-2827001814 What this PR does when `isNewRelationEnabled` is set to `true`: - [x] Drop the creation of the foreign key as a `FieldMetadata` - [x] Stop creating `RelationMetadata` - [x] Properly fill `FieldMetadata` of type `RELATION` during the sync command - [x] Use new relation settings in TwentyORM - [x] Properly create `FieldMetadata` relations when we create a new object - [x] Handle `database:reset` with new relations --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
@ -14,7 +14,8 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
|
|||||||
it('should return fieldName if simpleValue', async () => {
|
it('should return fieldName if simpleValue', async () => {
|
||||||
const res = mapFieldMetadataToGraphQLQuery({
|
const res = mapFieldMetadataToGraphQLQuery({
|
||||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||||
field: personObjectMetadataItem.fields.find(
|
gqlField: 'id',
|
||||||
|
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||||
(field) => field.name === 'id',
|
(field) => field.name === 'id',
|
||||||
)!,
|
)!,
|
||||||
});
|
});
|
||||||
@ -23,7 +24,8 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
|
|||||||
it('should return fieldName if composite', async () => {
|
it('should return fieldName if composite', async () => {
|
||||||
const res = mapFieldMetadataToGraphQLQuery({
|
const res = mapFieldMetadataToGraphQLQuery({
|
||||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||||
field: personObjectMetadataItem.fields.find(
|
gqlField: 'name',
|
||||||
|
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||||
(field) => field.name === 'name',
|
(field) => field.name === 'name',
|
||||||
)!,
|
)!,
|
||||||
});
|
});
|
||||||
@ -39,7 +41,8 @@ describe('mapFieldMetadataToGraphQLQuery', () => {
|
|||||||
it('should return non relation subFields if relation', async () => {
|
it('should return non relation subFields if relation', async () => {
|
||||||
const res = mapFieldMetadataToGraphQLQuery({
|
const res = mapFieldMetadataToGraphQLQuery({
|
||||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||||
field: personObjectMetadataItem.fields.find(
|
gqlField: 'company',
|
||||||
|
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||||
(field) => field.name === 'company',
|
(field) => field.name === 'company',
|
||||||
)!,
|
)!,
|
||||||
});
|
});
|
||||||
@ -115,7 +118,8 @@ idealCustomerProfile
|
|||||||
id: true,
|
id: true,
|
||||||
idealCustomerProfile: true,
|
idealCustomerProfile: true,
|
||||||
},
|
},
|
||||||
field: personObjectMetadataItem.fields.find(
|
gqlField: 'company',
|
||||||
|
fieldMetadata: personObjectMetadataItem.fields.find(
|
||||||
(field) => field.name === 'company',
|
(field) => field.name === 'company',
|
||||||
)!,
|
)!,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,14 +5,16 @@ describe('shouldFieldBeQueried', () => {
|
|||||||
describe('if recordGqlFields is absent, we query all except relations', () => {
|
describe('if recordGqlFields is absent, we query all except relations', () => {
|
||||||
it('should be queried if the field is not a relation', () => {
|
it('should be queried if the field is not a relation', () => {
|
||||||
const res = shouldFieldBeQueried({
|
const res = shouldFieldBeQueried({
|
||||||
field: { name: 'fieldName', type: FieldMetadataType.BOOLEAN },
|
gqlField: 'fieldName',
|
||||||
|
fieldMetadata: { name: 'fieldName', type: FieldMetadataType.BOOLEAN },
|
||||||
});
|
});
|
||||||
expect(res).toBe(true);
|
expect(res).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be queried if the field is a relation', () => {
|
it('should not be queried if the field is a relation', () => {
|
||||||
const res = shouldFieldBeQueried({
|
const res = shouldFieldBeQueried({
|
||||||
field: { name: 'fieldName', type: FieldMetadataType.RELATION },
|
gqlField: 'fieldName',
|
||||||
|
fieldMetadata: { name: 'fieldName', type: FieldMetadataType.RELATION },
|
||||||
});
|
});
|
||||||
expect(res).toBe(false);
|
expect(res).toBe(false);
|
||||||
});
|
});
|
||||||
@ -21,8 +23,9 @@ describe('shouldFieldBeQueried', () => {
|
|||||||
describe('if recordGqlFields is present, we respect it', () => {
|
describe('if recordGqlFields is present, we respect it', () => {
|
||||||
it('should be queried if true', () => {
|
it('should be queried if true', () => {
|
||||||
const res = shouldFieldBeQueried({
|
const res = shouldFieldBeQueried({
|
||||||
|
gqlField: 'fieldName',
|
||||||
|
fieldMetadata: { name: 'fieldName', type: FieldMetadataType.RELATION },
|
||||||
recordGqlFields: { fieldName: true },
|
recordGqlFields: { fieldName: true },
|
||||||
field: { name: 'fieldName', type: FieldMetadataType.RELATION },
|
|
||||||
});
|
});
|
||||||
expect(res).toBe(true);
|
expect(res).toBe(true);
|
||||||
});
|
});
|
||||||
@ -30,23 +33,26 @@ describe('shouldFieldBeQueried', () => {
|
|||||||
it('should be queried if object', () => {
|
it('should be queried if object', () => {
|
||||||
const res = shouldFieldBeQueried({
|
const res = shouldFieldBeQueried({
|
||||||
recordGqlFields: { fieldName: { subFieldName: false } },
|
recordGqlFields: { fieldName: { subFieldName: false } },
|
||||||
field: { name: 'fieldName', type: FieldMetadataType.RELATION },
|
fieldMetadata: { name: 'fieldName', type: FieldMetadataType.RELATION },
|
||||||
|
gqlField: 'fieldName',
|
||||||
});
|
});
|
||||||
expect(res).toBe(true);
|
expect(res).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be queried if false', () => {
|
it('should not be queried if false', () => {
|
||||||
const res = shouldFieldBeQueried({
|
const res = shouldFieldBeQueried({
|
||||||
|
gqlField: 'fieldName',
|
||||||
|
fieldMetadata: { name: 'fieldName', type: FieldMetadataType.RELATION },
|
||||||
recordGqlFields: { fieldName: false },
|
recordGqlFields: { fieldName: false },
|
||||||
field: { name: 'fieldName', type: FieldMetadataType.RELATION },
|
|
||||||
});
|
});
|
||||||
expect(res).toBe(false);
|
expect(res).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be queried if absent', () => {
|
it('should not be queried if absent', () => {
|
||||||
const res = shouldFieldBeQueried({
|
const res = shouldFieldBeQueried({
|
||||||
|
gqlField: 'fieldName',
|
||||||
|
fieldMetadata: { name: 'fieldName', type: FieldMetadataType.RELATION },
|
||||||
recordGqlFields: { otherFieldName: false },
|
recordGqlFields: { otherFieldName: false },
|
||||||
field: { name: 'fieldName', type: FieldMetadataType.RELATION },
|
|
||||||
});
|
});
|
||||||
expect(res).toBe(false);
|
expect(res).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,40 +12,50 @@ import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
|||||||
|
|
||||||
type MapFieldMetadataToGraphQLQueryArgs = {
|
type MapFieldMetadataToGraphQLQueryArgs = {
|
||||||
objectMetadataItems: ObjectMetadataItem[];
|
objectMetadataItems: ObjectMetadataItem[];
|
||||||
field: Pick<FieldMetadataItem, 'name' | 'type' | 'relationDefinition'>;
|
gqlField: string;
|
||||||
|
fieldMetadata: Pick<
|
||||||
|
FieldMetadataItem,
|
||||||
|
'name' | 'type' | 'relationDefinition' | 'settings'
|
||||||
|
>;
|
||||||
relationRecordGqlFields?: RecordGqlFields;
|
relationRecordGqlFields?: RecordGqlFields;
|
||||||
computeReferences?: boolean;
|
computeReferences?: boolean;
|
||||||
};
|
};
|
||||||
// TODO: change ObjectMetadataItems mock before refactoring with relationDefinition computed field
|
// TODO: change ObjectMetadataItems mock before refactoring with relationDefinition computed field
|
||||||
export const mapFieldMetadataToGraphQLQuery = ({
|
export const mapFieldMetadataToGraphQLQuery = ({
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
field,
|
gqlField,
|
||||||
|
fieldMetadata,
|
||||||
relationRecordGqlFields,
|
relationRecordGqlFields,
|
||||||
computeReferences = false,
|
computeReferences = false,
|
||||||
}: MapFieldMetadataToGraphQLQueryArgs): string => {
|
}: MapFieldMetadataToGraphQLQueryArgs): string => {
|
||||||
const fieldType = field.type;
|
const fieldType = fieldMetadata.type;
|
||||||
|
|
||||||
const fieldIsNonCompositeField = isNonCompositeField(fieldType);
|
const fieldIsNonCompositeField = isNonCompositeField(fieldType);
|
||||||
|
|
||||||
if (fieldIsNonCompositeField) {
|
if (fieldIsNonCompositeField) {
|
||||||
return field.name;
|
return gqlField;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
fieldType === FieldMetadataType.RELATION &&
|
fieldType === FieldMetadataType.RELATION &&
|
||||||
field.relationDefinition?.direction === RelationDefinitionType.MANY_TO_ONE
|
fieldMetadata.relationDefinition?.direction ===
|
||||||
|
RelationDefinitionType.MANY_TO_ONE
|
||||||
) {
|
) {
|
||||||
const relationMetadataItem = objectMetadataItems.find(
|
const relationMetadataItem = objectMetadataItems.find(
|
||||||
(objectMetadataItem) =>
|
(objectMetadataItem) =>
|
||||||
objectMetadataItem.id ===
|
objectMetadataItem.id ===
|
||||||
field.relationDefinition?.targetObjectMetadata.id,
|
fieldMetadata.relationDefinition?.targetObjectMetadata.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isUndefined(relationMetadataItem)) {
|
if (isUndefined(relationMetadataItem)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${field.name}
|
if (gqlField === fieldMetadata.settings?.joinColumnName) {
|
||||||
|
return `${gqlField}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${gqlField}
|
||||||
${mapObjectMetadataToGraphQLQuery({
|
${mapObjectMetadataToGraphQLQuery({
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem: relationMetadataItem,
|
objectMetadataItem: relationMetadataItem,
|
||||||
@ -57,19 +67,20 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
fieldType === FieldMetadataType.RELATION &&
|
fieldType === FieldMetadataType.RELATION &&
|
||||||
field.relationDefinition?.direction === RelationDefinitionType.ONE_TO_MANY
|
fieldMetadata.relationDefinition?.direction ===
|
||||||
|
RelationDefinitionType.ONE_TO_MANY
|
||||||
) {
|
) {
|
||||||
const relationMetadataItem = objectMetadataItems.find(
|
const relationMetadataItem = objectMetadataItems.find(
|
||||||
(objectMetadataItem) =>
|
(objectMetadataItem) =>
|
||||||
objectMetadataItem.id ===
|
objectMetadataItem.id ===
|
||||||
field.relationDefinition?.targetObjectMetadata.id,
|
fieldMetadata.relationDefinition?.targetObjectMetadata.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isUndefined(relationMetadataItem)) {
|
if (isUndefined(relationMetadataItem)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${field.name}
|
return `${gqlField}
|
||||||
{
|
{
|
||||||
edges {
|
edges {
|
||||||
node ${mapObjectMetadataToGraphQLQuery({
|
node ${mapObjectMetadataToGraphQLQuery({
|
||||||
@ -84,7 +95,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType === FieldMetadataType.LINKS) {
|
if (fieldType === FieldMetadataType.LINKS) {
|
||||||
return `${field.name}
|
return `${gqlField}
|
||||||
{
|
{
|
||||||
primaryLinkUrl
|
primaryLinkUrl
|
||||||
primaryLinkLabel
|
primaryLinkLabel
|
||||||
@ -93,7 +104,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType === FieldMetadataType.CURRENCY) {
|
if (fieldType === FieldMetadataType.CURRENCY) {
|
||||||
return `${field.name}
|
return `${gqlField}
|
||||||
{
|
{
|
||||||
amountMicros
|
amountMicros
|
||||||
currencyCode
|
currencyCode
|
||||||
@ -102,7 +113,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType === FieldMetadataType.FULL_NAME) {
|
if (fieldType === FieldMetadataType.FULL_NAME) {
|
||||||
return `${field.name}
|
return `${gqlField}
|
||||||
{
|
{
|
||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
@ -110,7 +121,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType === FieldMetadataType.ADDRESS) {
|
if (fieldType === FieldMetadataType.ADDRESS) {
|
||||||
return `${field.name}
|
return `${gqlField}
|
||||||
{
|
{
|
||||||
addressStreet1
|
addressStreet1
|
||||||
addressStreet2
|
addressStreet2
|
||||||
@ -124,7 +135,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType === FieldMetadataType.ACTOR) {
|
if (fieldType === FieldMetadataType.ACTOR) {
|
||||||
return `${field.name}
|
return `${gqlField}
|
||||||
{
|
{
|
||||||
source
|
source
|
||||||
workspaceMemberId
|
workspaceMemberId
|
||||||
@ -134,7 +145,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType === FieldMetadataType.EMAILS) {
|
if (fieldType === FieldMetadataType.EMAILS) {
|
||||||
return `${field.name}
|
return `${gqlField}
|
||||||
{
|
{
|
||||||
primaryEmail
|
primaryEmail
|
||||||
additionalEmails
|
additionalEmails
|
||||||
@ -142,7 +153,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType === FieldMetadataType.PHONES) {
|
if (fieldType === FieldMetadataType.PHONES) {
|
||||||
return `${field.name}
|
return `${gqlField}
|
||||||
{
|
{
|
||||||
primaryPhoneNumber
|
primaryPhoneNumber
|
||||||
primaryPhoneCountryCode
|
primaryPhoneCountryCode
|
||||||
@ -152,7 +163,7 @@ ${mapObjectMetadataToGraphQLQuery({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fieldType === FieldMetadataType.RICH_TEXT_V2) {
|
if (fieldType === FieldMetadataType.RICH_TEXT_V2) {
|
||||||
return `${field.name}
|
return `${gqlField}
|
||||||
{
|
{
|
||||||
blocknote
|
blocknote
|
||||||
markdown
|
markdown
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapField
|
|||||||
import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried';
|
import { shouldFieldBeQueried } from '@/object-metadata/utils/shouldFieldBeQueried';
|
||||||
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
import { RecordGqlFields } from '@/object-record/graphql/types/RecordGqlFields';
|
||||||
import { isRecordGqlFieldsNode } from '@/object-record/graphql/utils/isRecordGraphlFieldsNode';
|
import { isRecordGqlFieldsNode } from '@/object-record/graphql/utils/isRecordGraphlFieldsNode';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
type MapObjectMetadataToGraphQLQueryArgs = {
|
type MapObjectMetadataToGraphQLQueryArgs = {
|
||||||
objectMetadataItems: ObjectMetadataItem[];
|
objectMetadataItems: ObjectMetadataItem[];
|
||||||
@ -18,16 +20,40 @@ export const mapObjectMetadataToGraphQLQuery = ({
|
|||||||
computeReferences = false,
|
computeReferences = false,
|
||||||
isRootLevel = true,
|
isRootLevel = true,
|
||||||
}: MapObjectMetadataToGraphQLQueryArgs): string => {
|
}: MapObjectMetadataToGraphQLQueryArgs): string => {
|
||||||
const fieldsThatShouldBeQueried =
|
const manyToOneRelationFields = objectMetadataItem?.fields
|
||||||
objectMetadataItem?.fields
|
.filter((field) => field.isActive)
|
||||||
.filter((field) => field.isActive)
|
.filter((field) => field.type === FieldMetadataType.RELATION)
|
||||||
.sort((fieldA, fieldB) => fieldA.name.localeCompare(fieldB.name))
|
.filter((field) => isDefined(field.settings?.joinColumnName));
|
||||||
.filter((field) =>
|
|
||||||
|
const manyToOneRelationGqlFieldWithFieldMetadata =
|
||||||
|
manyToOneRelationFields.map((field) => ({
|
||||||
|
gqlField: field.settings?.joinColumnName,
|
||||||
|
fieldMetadata: field,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const gqlFieldWithFieldMetadataThatCouldBeQueried = [
|
||||||
|
...objectMetadataItem.fields
|
||||||
|
.filter((fieldMetadata) => fieldMetadata.isActive)
|
||||||
|
.map((fieldMetadata) => ({
|
||||||
|
gqlField: fieldMetadata.name,
|
||||||
|
fieldMetadata,
|
||||||
|
})),
|
||||||
|
...manyToOneRelationGqlFieldWithFieldMetadata,
|
||||||
|
].sort((gqlFieldWithFieldMetadataA, gqlFieldWithFieldMetadataB) =>
|
||||||
|
gqlFieldWithFieldMetadataA.gqlField.localeCompare(
|
||||||
|
gqlFieldWithFieldMetadataB.gqlField,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const gqlFieldWithFieldMetadataThatSouldBeQueried =
|
||||||
|
gqlFieldWithFieldMetadataThatCouldBeQueried.filter(
|
||||||
|
(gqlFieldWithFieldMetadata) =>
|
||||||
shouldFieldBeQueried({
|
shouldFieldBeQueried({
|
||||||
field,
|
gqlField: gqlFieldWithFieldMetadata.gqlField,
|
||||||
|
fieldMetadata: gqlFieldWithFieldMetadata.fieldMetadata,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
}),
|
}),
|
||||||
) ?? [];
|
);
|
||||||
|
|
||||||
if (!isRootLevel && computeReferences) {
|
if (!isRootLevel && computeReferences) {
|
||||||
return `{
|
return `{
|
||||||
@ -37,9 +63,10 @@ export const mapObjectMetadataToGraphQLQuery = ({
|
|||||||
|
|
||||||
return `{
|
return `{
|
||||||
__typename
|
__typename
|
||||||
${fieldsThatShouldBeQueried
|
${gqlFieldWithFieldMetadataThatSouldBeQueried
|
||||||
.map((field) => {
|
.map((gqlFieldWithFieldMetadata) => {
|
||||||
const currentRecordGqlFields = recordGqlFields?.[field.name];
|
const currentRecordGqlFields =
|
||||||
|
recordGqlFields?.[gqlFieldWithFieldMetadata.gqlField];
|
||||||
const relationRecordGqlFields = isRecordGqlFieldsNode(
|
const relationRecordGqlFields = isRecordGqlFieldsNode(
|
||||||
currentRecordGqlFields,
|
currentRecordGqlFields,
|
||||||
)
|
)
|
||||||
@ -47,7 +74,8 @@ ${fieldsThatShouldBeQueried
|
|||||||
: undefined;
|
: undefined;
|
||||||
return mapFieldMetadataToGraphQLQuery({
|
return mapFieldMetadataToGraphQLQuery({
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
field,
|
gqlField: gqlFieldWithFieldMetadata.gqlField,
|
||||||
|
fieldMetadata: gqlFieldWithFieldMetadata.fieldMetadata,
|
||||||
relationRecordGqlFields,
|
relationRecordGqlFields,
|
||||||
computeReferences,
|
computeReferences,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,27 +3,30 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
|||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { FieldMetadataItem } from '../types/FieldMetadataItem';
|
||||||
|
|
||||||
export const shouldFieldBeQueried = ({
|
export const shouldFieldBeQueried = ({
|
||||||
field,
|
gqlField,
|
||||||
|
fieldMetadata,
|
||||||
recordGqlFields,
|
recordGqlFields,
|
||||||
}: {
|
}: {
|
||||||
field: Pick<FieldMetadataItem, 'name' | 'type'>;
|
gqlField: string;
|
||||||
|
fieldMetadata: Pick<FieldMetadataItem, 'name' | 'type'>;
|
||||||
objectRecord?: ObjectRecord;
|
objectRecord?: ObjectRecord;
|
||||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||||
}): any => {
|
}): any => {
|
||||||
if (
|
if (
|
||||||
isUndefinedOrNull(recordGqlFields) &&
|
isUndefinedOrNull(recordGqlFields) &&
|
||||||
field.type !== FieldMetadataType.RELATION
|
fieldMetadata.type !== FieldMetadataType.RELATION
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isDefined(recordGqlFields) &&
|
isDefined(recordGqlFields) &&
|
||||||
isDefined(recordGqlFields[field.name]) &&
|
isDefined(recordGqlFields[gqlField]) &&
|
||||||
recordGqlFields[field.name] !== false
|
recordGqlFields[gqlField] !== false
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,12 +7,12 @@ import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getR
|
|||||||
import { getRefName } from '@/object-record/cache/utils/getRefName';
|
import { getRefName } from '@/object-record/cache/utils/getRefName';
|
||||||
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import {
|
import {
|
||||||
FieldMetadataType,
|
FieldMetadataType,
|
||||||
RelationDefinitionType,
|
RelationDefinitionType,
|
||||||
} from '~/generated-metadata/graphql';
|
} from '~/generated-metadata/graphql';
|
||||||
import { pascalCase } from '~/utils/string/pascalCase';
|
import { pascalCase } from '~/utils/string/pascalCase';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
export const getRecordNodeFromRecord = <T extends ObjectRecord>({
|
export const getRecordNodeFromRecord = <T extends ObjectRecord>({
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
@ -51,9 +51,11 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const field = objectMetadataItem.fields.find(
|
const field =
|
||||||
(field) => field.name === fieldName,
|
objectMetadataItem.fields.find((field) => field.name === fieldName) ??
|
||||||
);
|
objectMetadataItem.fields.find(
|
||||||
|
(field) => field.settings?.joinColumnName === fieldName,
|
||||||
|
);
|
||||||
|
|
||||||
if (isUndefined(field)) {
|
if (isUndefined(field)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -94,6 +96,12 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
|
|||||||
|
|
||||||
switch (field.type) {
|
switch (field.type) {
|
||||||
case FieldMetadataType.RELATION: {
|
case FieldMetadataType.RELATION: {
|
||||||
|
const isJoinColumn = field.settings?.joinColumnName === fieldName;
|
||||||
|
|
||||||
|
if (isJoinColumn) {
|
||||||
|
return [fieldName, value];
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isUndefined(
|
isUndefined(
|
||||||
field.relationDefinition?.targetObjectMetadata.nameSingular,
|
field.relationDefinition?.targetObjectMetadata.nameSingular,
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObje
|
|||||||
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||||
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
export const updateRecordFromCache = <T extends ObjectRecord>({
|
export const updateRecordFromCache = <T extends ObjectRecord>({
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
export type GenerateDepthOneRecordGqlFields = {
|
export type GenerateDepthOneRecordGqlFields = {
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
@ -10,6 +11,11 @@ export const generateDepthOneRecordGqlFields = ({
|
|||||||
objectMetadataItem.fields.reduce<Record<string, true>>((acc, field) => {
|
objectMetadataItem.fields.reduce<Record<string, true>>((acc, field) => {
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
|
...(isDefined(field.settings?.joinColumnName)
|
||||||
|
? {
|
||||||
|
[field.settings.joinColumnName]: true,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
[field.name]: true,
|
[field.name]: true,
|
||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
|
|||||||
@ -18,8 +18,8 @@ import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/g
|
|||||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||||
import { isNull } from '@sniptt/guards';
|
import { isNull } from '@sniptt/guards';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
|
||||||
|
|
||||||
type useUpdateOneRecordProps = {
|
type useUpdateOneRecordProps = {
|
||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
@ -91,6 +91,7 @@ export const useUpdateOneRecord = <
|
|||||||
id: idToUpdate,
|
id: idToUpdate,
|
||||||
__typename: getObjectTypename(objectMetadataItem.nameSingular),
|
__typename: getObjectTypename(objectMetadataItem.nameSingular),
|
||||||
};
|
};
|
||||||
|
|
||||||
const optimisticRecordWithConnection =
|
const optimisticRecordWithConnection =
|
||||||
getRecordNodeFromRecord<ObjectRecord>({
|
getRecordNodeFromRecord<ObjectRecord>({
|
||||||
record: computedOptimisticRecord,
|
record: computedOptimisticRecord,
|
||||||
@ -110,6 +111,7 @@ export const useUpdateOneRecord = <
|
|||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
record: optimisticRecordInput,
|
record: optimisticRecordInput,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateRecordFromCache({
|
updateRecordFromCache({
|
||||||
objectMetadataItems,
|
objectMetadataItems,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
|
|||||||
@ -38,9 +38,9 @@ import { isMatchingRichTextV2Filter } from '@/object-record/record-filter/utils/
|
|||||||
import { isMatchingSelectFilter } from '@/object-record/record-filter/utils/isMatchingSelectFilter';
|
import { isMatchingSelectFilter } from '@/object-record/record-filter/utils/isMatchingSelectFilter';
|
||||||
import { isMatchingStringFilter } from '@/object-record/record-filter/utils/isMatchingStringFilter';
|
import { isMatchingStringFilter } from '@/object-record/record-filter/utils/isMatchingStringFilter';
|
||||||
import { isMatchingUUIDFilter } from '@/object-record/record-filter/utils/isMatchingUUIDFilter';
|
import { isMatchingUUIDFilter } from '@/object-record/record-filter/utils/isMatchingUUIDFilter';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { isEmptyObject } from '~/utils/isEmptyObject';
|
import { isEmptyObject } from '~/utils/isEmptyObject';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
const isLeafFilter = (
|
const isLeafFilter = (
|
||||||
filter: RecordGqlOperationFilter,
|
filter: RecordGqlOperationFilter,
|
||||||
@ -167,9 +167,13 @@ export const isRecordMatchingFilter = ({
|
|||||||
|
|
||||||
if (isEmptyObject(filterValue)) return true;
|
if (isEmptyObject(filterValue)) return true;
|
||||||
|
|
||||||
const objectMetadataField = objectMetadataItem.fields.find(
|
const objectMetadataField =
|
||||||
(field) => field.name === filterKey,
|
objectMetadataItem.fields.find((field) => field.name === filterKey) ??
|
||||||
);
|
objectMetadataItem.fields.find(
|
||||||
|
(field) =>
|
||||||
|
field.type === FieldMetadataType.RELATION &&
|
||||||
|
field.settings?.joinColumnName === filterKey,
|
||||||
|
);
|
||||||
|
|
||||||
if (!isDefined(objectMetadataField)) {
|
if (!isDefined(objectMetadataField)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -359,6 +363,16 @@ export const isRecordMatchingFilter = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
case FieldMetadataType.RELATION: {
|
case FieldMetadataType.RELATION: {
|
||||||
|
const isJoinColumn =
|
||||||
|
objectMetadataField.settings?.joinColumnName === filterKey;
|
||||||
|
|
||||||
|
if (isJoinColumn) {
|
||||||
|
return isMatchingUUIDFilter({
|
||||||
|
uuidFilter: filterValue as UUIDFilter,
|
||||||
|
value: record[filterKey],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Not implemented yet, use UUID filter instead on the corredponding "${filterKey}Id" field`,
|
`Not implemented yet, use UUID filter instead on the corredponding "${filterKey}Id" field`,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -14,9 +14,9 @@ import { isFieldUuid } from '@/object-record/record-field/types/guards/isFieldUu
|
|||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { buildOptimisticActorFieldValueFromCurrentWorkspaceMember } from '@/object-record/utils/buildOptimisticActorFieldValueFromCurrentWorkspaceMember';
|
import { buildOptimisticActorFieldValueFromCurrentWorkspaceMember } from '@/object-record/utils/buildOptimisticActorFieldValueFromCurrentWorkspaceMember';
|
||||||
import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName';
|
import { getForeignKeyNameFromRelationFieldName } from '@/object-record/utils/getForeignKeyNameFromRelationFieldName';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||||
import { FieldMetadataType } from '~/generated/graphql';
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
type ComputeOptimisticCacheRecordInputArgs = {
|
type ComputeOptimisticCacheRecordInputArgs = {
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
@ -32,11 +32,23 @@ export const computeOptimisticRecordFromInput = ({
|
|||||||
}: ComputeOptimisticCacheRecordInputArgs) => {
|
}: ComputeOptimisticCacheRecordInputArgs) => {
|
||||||
const unknownRecordInputFields = Object.keys(recordInput).filter(
|
const unknownRecordInputFields = Object.keys(recordInput).filter(
|
||||||
(recordKey) => {
|
(recordKey) => {
|
||||||
const isUnknownMetadataItemField =
|
const correspondingFieldMetadataItem = objectMetadataItem.fields.find(
|
||||||
objectMetadataItem.fields.find((field) => field.name === recordKey) ===
|
(field) => field.name === recordKey,
|
||||||
undefined;
|
);
|
||||||
|
|
||||||
|
const potentialJoinColumnNameFieldMetadataItem =
|
||||||
|
objectMetadataItem.fields.find(
|
||||||
|
(field) =>
|
||||||
|
field.type === FieldMetadataType.RELATION &&
|
||||||
|
field.settings?.joinColumnName === recordKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isUnknownField =
|
||||||
|
!isDefined(correspondingFieldMetadataItem) &&
|
||||||
|
!isDefined(potentialJoinColumnNameFieldMetadataItem);
|
||||||
|
|
||||||
const isTypenameField = recordKey === GRAPHQL_TYPENAME_KEY;
|
const isTypenameField = recordKey === GRAPHQL_TYPENAME_KEY;
|
||||||
return isUnknownMetadataItemField && !isTypenameField;
|
return isUnknownField && !isTypenameField;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (unknownRecordInputFields.length > 0) {
|
if (unknownRecordInputFields.length > 0) {
|
||||||
@ -122,6 +134,7 @@ export const computeOptimisticRecordFromInput = ({
|
|||||||
const relationFieldIdName = getForeignKeyNameFromRelationFieldName(
|
const relationFieldIdName = getForeignKeyNameFromRelationFieldName(
|
||||||
fieldMetadataItem.name,
|
fieldMetadataItem.name,
|
||||||
);
|
);
|
||||||
|
|
||||||
const recordInputFieldIdValue: string | null | undefined =
|
const recordInputFieldIdValue: string | null | undefined =
|
||||||
recordInput[relationFieldIdName];
|
recordInput[relationFieldIdName];
|
||||||
|
|
||||||
@ -132,7 +145,11 @@ export const computeOptimisticRecordFromInput = ({
|
|||||||
const relationIdFieldMetadataItem = objectMetadataItem.fields.find(
|
const relationIdFieldMetadataItem = objectMetadataItem.fields.find(
|
||||||
(field) => field.name === relationFieldIdName,
|
(field) => field.name === relationFieldIdName,
|
||||||
);
|
);
|
||||||
if (!isDefined(relationIdFieldMetadataItem)) {
|
|
||||||
|
if (
|
||||||
|
!isDefined(relationIdFieldMetadataItem) &&
|
||||||
|
!isDefined(fieldMetadataItem.settings?.joinColumnName)
|
||||||
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Should never occur, encountered unknown relationId within relations definitions',
|
'Should never occur, encountered unknown relationId within relations definitions',
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
import { RelationDefinitionType } from '~/generated-metadata/graphql';
|
||||||
import { FieldMetadataType } from '~/generated/graphql';
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
export const sanitizeRecordInput = ({
|
export const sanitizeRecordInput = ({
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
@ -17,14 +17,26 @@ export const sanitizeRecordInput = ({
|
|||||||
const fieldMetadataItem = objectMetadataItem.fields.find(
|
const fieldMetadataItem = objectMetadataItem.fields.find(
|
||||||
(field) => field.name === fieldName,
|
(field) => field.name === fieldName,
|
||||||
);
|
);
|
||||||
|
const potentialJoinColumnNameFieldMetadataItem =
|
||||||
|
objectMetadataItem.fields.find(
|
||||||
|
(field) =>
|
||||||
|
field.type === FieldMetadataType.RELATION &&
|
||||||
|
field.settings?.joinColumnName === fieldName,
|
||||||
|
);
|
||||||
|
|
||||||
if (!fieldMetadataItem) return undefined;
|
if (
|
||||||
|
!isDefined(fieldMetadataItem) &&
|
||||||
|
!isDefined(potentialJoinColumnNameFieldMetadataItem)
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (!fieldMetadataItem.isNullable && fieldValue == null) {
|
if (fieldMetadataItem?.isNullable === false && fieldValue == null) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
isDefined(fieldMetadataItem) &&
|
||||||
fieldMetadataItem.type === FieldMetadataType.RELATION &&
|
fieldMetadataItem.type === FieldMetadataType.RELATION &&
|
||||||
fieldMetadataItem.relationDefinition?.direction ===
|
fieldMetadataItem.relationDefinition?.direction ===
|
||||||
RelationDefinitionType.MANY_TO_ONE
|
RelationDefinitionType.MANY_TO_ONE
|
||||||
@ -42,6 +54,7 @@ export const sanitizeRecordInput = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
isDefined(fieldMetadataItem) &&
|
||||||
fieldMetadataItem.type === FieldMetadataType.RELATION &&
|
fieldMetadataItem.type === FieldMetadataType.RELATION &&
|
||||||
fieldMetadataItem.relationDefinition?.direction ===
|
fieldMetadataItem.relationDefinition?.direction ===
|
||||||
RelationDefinitionType.ONE_TO_MANY
|
RelationDefinitionType.ONE_TO_MANY
|
||||||
|
|||||||
@ -14,9 +14,9 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
|
|||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
|
||||||
import { H2Title } from 'twenty-ui/display';
|
import { H2Title } from 'twenty-ui/display';
|
||||||
import { Section } from 'twenty-ui/layout';
|
import { Section } from 'twenty-ui/layout';
|
||||||
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
|
|
||||||
export const SettingsAccounts = () => {
|
export const SettingsAccounts = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
|
|||||||
@ -4,6 +4,7 @@ PG_DATABASE_URL=postgres://postgres:postgres@localhost:5432/default
|
|||||||
REDIS_URL=redis://localhost:6379
|
REDIS_URL=redis://localhost:6379
|
||||||
APP_SECRET=replace_me_with_a_random_string
|
APP_SECRET=replace_me_with_a_random_string
|
||||||
SIGN_IN_PREFILLED=true
|
SIGN_IN_PREFILLED=true
|
||||||
|
SYNC_METADATA_INDEX_ENABLED=false
|
||||||
|
|
||||||
FRONTEND_URL=http://localhost:3001
|
FRONTEND_URL=http://localhost:3001
|
||||||
|
|
||||||
|
|||||||
@ -18,10 +18,10 @@ import {
|
|||||||
RelationDirection,
|
RelationDirection,
|
||||||
deduceRelationDirection,
|
deduceRelationDirection,
|
||||||
} from 'src/engine/utils/deduce-relation-direction.util';
|
} from 'src/engine/utils/deduce-relation-direction.util';
|
||||||
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
@Command({
|
@Command({
|
||||||
name: 'upgrade:0-51:migrate-relations-to-field-metadata',
|
name: 'upgrade:0-52:migrate-relations-to-field-metadata',
|
||||||
description: 'Migrate relations to field metadata',
|
description: 'Migrate relations to field metadata',
|
||||||
})
|
})
|
||||||
export class MigrateRelationsToFieldMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
export class MigrateRelationsToFieldMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||||
@ -64,13 +64,13 @@ export class MigrateRelationsToFieldMetadataCommand extends ActiveOrSuspendedWor
|
|||||||
|
|
||||||
const joinColumnFieldMetadataCollection = fieldMetadataCollection.filter(
|
const joinColumnFieldMetadataCollection = fieldMetadataCollection.filter(
|
||||||
(fieldMetadata) =>
|
(fieldMetadata) =>
|
||||||
isFieldMetadataOfType(fieldMetadata, FieldMetadataType.UUID),
|
isFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.UUID),
|
||||||
// TODO: Fix this, it's working in other places but not here
|
// TODO: Fix this, it's working in other places but not here
|
||||||
) as FieldMetadataEntity<FieldMetadataType.UUID>[];
|
) as FieldMetadataEntity<FieldMetadataType.UUID>[];
|
||||||
|
|
||||||
const fieldMetadataToUpdateCollection = fieldMetadataCollection
|
const fieldMetadataToUpdateCollection = fieldMetadataCollection
|
||||||
.filter((fieldMetadata) =>
|
.filter((fieldMetadata) =>
|
||||||
isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION),
|
isFieldMetadataEntityOfType(fieldMetadata, FieldMetadataType.RELATION),
|
||||||
)
|
)
|
||||||
.map((fieldMetadata) =>
|
.map((fieldMetadata) =>
|
||||||
this.updateRelationFieldMetadata(
|
this.updateRelationFieldMetadata(
|
||||||
@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
|||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { BackfillWorkflowNextStepIdsCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-backfill-workflow-next-step-ids.command';
|
import { BackfillWorkflowNextStepIdsCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-backfill-workflow-next-step-ids.command';
|
||||||
|
import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-migrate-relations-to-field-metadata.command';
|
||||||
import { UpgradeDateAndDateTimeFieldsSettingsJsonCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-upgrade-settings-field';
|
import { UpgradeDateAndDateTimeFieldsSettingsJsonCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-upgrade-settings-field';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
@ -20,10 +21,12 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works
|
|||||||
providers: [
|
providers: [
|
||||||
BackfillWorkflowNextStepIdsCommand,
|
BackfillWorkflowNextStepIdsCommand,
|
||||||
UpgradeDateAndDateTimeFieldsSettingsJsonCommand,
|
UpgradeDateAndDateTimeFieldsSettingsJsonCommand,
|
||||||
|
MigrateRelationsToFieldMetadataCommand,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
BackfillWorkflowNextStepIdsCommand,
|
BackfillWorkflowNextStepIdsCommand,
|
||||||
UpgradeDateAndDateTimeFieldsSettingsJsonCommand,
|
UpgradeDateAndDateTimeFieldsSettingsJsonCommand,
|
||||||
|
MigrateRelationsToFieldMetadataCommand,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class V0_52_UpgradeVersionCommandModule {}
|
export class V0_52_UpgradeVersionCommandModule {}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { UpdateDefaultViewRecordOpeningOnWorkflowObjectsCommand } from 'src/data
|
|||||||
import { InitializePermissionsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-initialize-permissions.command';
|
import { InitializePermissionsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-initialize-permissions.command';
|
||||||
import { UpdateViewAggregateOperationsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-update-view-aggregate-operations.command';
|
import { UpdateViewAggregateOperationsCommand } from 'src/database/commands/upgrade-version-command/0-44/0-44-update-view-aggregate-operations.command';
|
||||||
import { UpgradeCreatedByEnumCommand } from 'src/database/commands/upgrade-version-command/0-51/0-51-update-workflow-trigger-type-enum.command';
|
import { UpgradeCreatedByEnumCommand } from 'src/database/commands/upgrade-version-command/0-51/0-51-update-workflow-trigger-type-enum.command';
|
||||||
|
import { MigrateRelationsToFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-migrate-relations-to-field-metadata.command';
|
||||||
import { UpgradeDateAndDateTimeFieldsSettingsJsonCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-upgrade-settings-field';
|
import { UpgradeDateAndDateTimeFieldsSettingsJsonCommand } from 'src/database/commands/upgrade-version-command/0-52/0-52-upgrade-settings-field';
|
||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
@ -58,6 +59,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
|||||||
|
|
||||||
// 0.52 Commands
|
// 0.52 Commands
|
||||||
protected readonly upgradeDateAndDateTimeFieldsSettingsJsonCommand: UpgradeDateAndDateTimeFieldsSettingsJsonCommand,
|
protected readonly upgradeDateAndDateTimeFieldsSettingsJsonCommand: UpgradeDateAndDateTimeFieldsSettingsJsonCommand,
|
||||||
|
protected readonly migrateRelationsToFieldMetadataCommand: MigrateRelationsToFieldMetadataCommand,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
workspaceRepository,
|
workspaceRepository,
|
||||||
@ -99,6 +101,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
|||||||
const _commands_052: VersionCommands = {
|
const _commands_052: VersionCommands = {
|
||||||
beforeSyncMetadata: [
|
beforeSyncMetadata: [
|
||||||
this.upgradeDateAndDateTimeFieldsSettingsJsonCommand,
|
this.upgradeDateAndDateTimeFieldsSettingsJsonCommand,
|
||||||
|
this.migrateRelationsToFieldMetadataCommand,
|
||||||
],
|
],
|
||||||
afterSyncMetadata: [],
|
afterSyncMetadata: [],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -63,7 +63,7 @@ export const seedFeatureFlags = async (
|
|||||||
{
|
{
|
||||||
key: FeatureFlagKey.IsNewRelationEnabled,
|
key: FeatureFlagKey.IsNewRelationEnabled,
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
value: false,
|
value: true,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.execute();
|
.execute();
|
||||||
|
|||||||
@ -29,6 +29,7 @@ export const mockPersonObjectMetadata = (
|
|||||||
fields: [],
|
fields: [],
|
||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
fieldsById: {},
|
fieldsById: {},
|
||||||
|
fieldsByJoinColumnName: {},
|
||||||
fieldsByName: {
|
fieldsByName: {
|
||||||
name: {
|
name: {
|
||||||
id: '',
|
id: '',
|
||||||
|
|||||||
@ -14,15 +14,19 @@ import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.pars
|
|||||||
|
|
||||||
export class GraphqlQueryFilterConditionParser {
|
export class GraphqlQueryFilterConditionParser {
|
||||||
private fieldMetadataMapByName: FieldMetadataMap;
|
private fieldMetadataMapByName: FieldMetadataMap;
|
||||||
|
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;
|
||||||
private queryFilterFieldParser: GraphqlQueryFilterFieldParser;
|
private queryFilterFieldParser: GraphqlQueryFilterFieldParser;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
fieldMetadataMapByName: FieldMetadataMap,
|
fieldMetadataMapByName: FieldMetadataMap,
|
||||||
|
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
|
||||||
featureFlagsMap: FeatureFlagMap,
|
featureFlagsMap: FeatureFlagMap,
|
||||||
) {
|
) {
|
||||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
||||||
|
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
|
||||||
this.queryFilterFieldParser = new GraphqlQueryFilterFieldParser(
|
this.queryFilterFieldParser = new GraphqlQueryFilterFieldParser(
|
||||||
this.fieldMetadataMapByName,
|
this.fieldMetadataMapByName,
|
||||||
|
this.fieldMetadataMapByJoinColumnName,
|
||||||
featureFlagsMap,
|
featureFlagsMap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { WhereExpressionBuilder } from 'typeorm';
|
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
import { WhereExpressionBuilder } from 'typeorm';
|
||||||
|
|
||||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
@ -18,13 +18,16 @@ const ARRAY_OPERATORS = ['in', 'contains', 'notContains'];
|
|||||||
|
|
||||||
export class GraphqlQueryFilterFieldParser {
|
export class GraphqlQueryFilterFieldParser {
|
||||||
private fieldMetadataMapByName: FieldMetadataMap;
|
private fieldMetadataMapByName: FieldMetadataMap;
|
||||||
|
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;
|
||||||
private featureFlagsMap: FeatureFlagMap;
|
private featureFlagsMap: FeatureFlagMap;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
fieldMetadataMapByName: FieldMetadataMap,
|
fieldMetadataMapByName: FieldMetadataMap,
|
||||||
|
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
|
||||||
featureFlagsMap: FeatureFlagMap,
|
featureFlagsMap: FeatureFlagMap,
|
||||||
) {
|
) {
|
||||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
||||||
|
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
|
||||||
this.featureFlagsMap = featureFlagsMap;
|
this.featureFlagsMap = featureFlagsMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +38,9 @@ export class GraphqlQueryFilterFieldParser {
|
|||||||
filterValue: any,
|
filterValue: any,
|
||||||
isFirst = false,
|
isFirst = false,
|
||||||
): void {
|
): void {
|
||||||
const fieldMetadata = this.fieldMetadataMapByName[`${key}`];
|
const fieldMetadata =
|
||||||
|
this.fieldMetadataMapByName[`${key}`] ||
|
||||||
|
this.fieldMetadataMapByJoinColumnName[`${key}`];
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
throw new Error(`Field metadata not found for field: ${key}`);
|
throw new Error(`Field metadata not found for field: ${key}`);
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modu
|
|||||||
|
|
||||||
export class GraphqlQueryParser {
|
export class GraphqlQueryParser {
|
||||||
private fieldMetadataMapByName: FieldMetadataMap;
|
private fieldMetadataMapByName: FieldMetadataMap;
|
||||||
|
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;
|
||||||
private objectMetadataMaps: ObjectMetadataMaps;
|
private objectMetadataMaps: ObjectMetadataMaps;
|
||||||
private filterConditionParser: GraphqlQueryFilterConditionParser;
|
private filterConditionParser: GraphqlQueryFilterConditionParser;
|
||||||
private orderFieldParser: GraphqlQueryOrderFieldParser;
|
private orderFieldParser: GraphqlQueryOrderFieldParser;
|
||||||
@ -31,14 +32,17 @@ export class GraphqlQueryParser {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
fieldMetadataMapByName: FieldMetadataMap,
|
fieldMetadataMapByName: FieldMetadataMap,
|
||||||
|
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
|
||||||
objectMetadataMaps: ObjectMetadataMaps,
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
featureFlagsMap: FeatureFlagMap,
|
featureFlagsMap: FeatureFlagMap,
|
||||||
) {
|
) {
|
||||||
this.objectMetadataMaps = objectMetadataMaps;
|
this.objectMetadataMaps = objectMetadataMaps;
|
||||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
||||||
|
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
|
||||||
this.featureFlagsMap = featureFlagsMap;
|
this.featureFlagsMap = featureFlagsMap;
|
||||||
this.filterConditionParser = new GraphqlQueryFilterConditionParser(
|
this.filterConditionParser = new GraphqlQueryFilterConditionParser(
|
||||||
this.fieldMetadataMapByName,
|
this.fieldMetadataMapByName,
|
||||||
|
this.fieldMetadataMapByJoinColumnName,
|
||||||
featureFlagsMap,
|
featureFlagsMap,
|
||||||
);
|
);
|
||||||
this.orderFieldParser = new GraphqlQueryOrderFieldParser(
|
this.orderFieldParser = new GraphqlQueryOrderFieldParser(
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-met
|
|||||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProcessNestedRelationsV2Helper {
|
export class ProcessNestedRelationsV2Helper {
|
||||||
@ -103,7 +103,10 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
parentObjectMetadataItem.fieldsByName[sourceFieldName];
|
parentObjectMetadataItem.fieldsByName[sourceFieldName];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isFieldMetadataOfType(sourceFieldMetadata, FieldMetadataType.RELATION)
|
!isFieldMetadataInterfaceOfType(
|
||||||
|
sourceFieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
// TODO: Maybe we should throw an error here ?
|
// TODO: Maybe we should throw an error here ?
|
||||||
return;
|
return;
|
||||||
@ -149,7 +152,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
? `"${targetRelationName}Id"`
|
? `"${targetRelationName}Id"`
|
||||||
: 'id',
|
: 'id',
|
||||||
ids: relationIds,
|
ids: relationIds,
|
||||||
limit,
|
limit: limit * parentObjectRecords.length,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
targetObjectMetadata,
|
targetObjectMetadata,
|
||||||
aggregate,
|
aggregate,
|
||||||
@ -313,6 +316,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
result,
|
result,
|
||||||
targetObjectMetadata,
|
targetObjectMetadata,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
return { relationResults, relationAggregatedFieldsResult };
|
return { relationResults, relationAggregatedFieldsResult };
|
||||||
|
|||||||
@ -453,6 +453,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
result,
|
result,
|
||||||
referenceObjectMetadata,
|
referenceObjectMetadata,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
return { relationResults, relationAggregatedFieldsResult };
|
return { relationResults, relationAggregatedFieldsResult };
|
||||||
|
|||||||
@ -130,6 +130,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
|
|
||||||
const graphqlQueryParser = new GraphqlQueryParser(
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
objectMetadataItemWithFieldMaps.fieldsByName,
|
||||||
|
objectMetadataItemWithFieldMaps.fieldsByJoinColumnName,
|
||||||
options.objectMetadataMaps,
|
options.objectMetadataMaps,
|
||||||
featureFlagsMap,
|
featureFlagsMap,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -44,6 +44,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectRecords,
|
objectRecords,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitCreateEvents(
|
this.apiEventEmitterService.emitCreateEvents(
|
||||||
@ -301,6 +302,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectRecords: InsertResult,
|
objectRecords: InsertResult,
|
||||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps: ObjectMetadataMaps,
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
|
featureFlagsMap: Record<FeatureFlagKey, boolean>,
|
||||||
): Promise<ObjectRecord[]> {
|
): Promise<ObjectRecord[]> {
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
@ -317,6 +319,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
nonFormattedUpsertedRecords,
|
nonFormattedUpsertedRecords,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -53,6 +53,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
nonFormattedUpsertedRecords,
|
nonFormattedUpsertedRecords,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitCreateEvents(
|
this.apiEventEmitterService.emitCreateEvents(
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
|||||||
nonFormattedDeletedObjectRecords.raw,
|
nonFormattedDeletedObjectRecords.raw,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDeletedEvents(
|
this.apiEventEmitterService.emitDeletedEvents(
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
nonFormattedDeletedObjectRecords.raw,
|
nonFormattedDeletedObjectRecords.raw,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDeletedEvents(
|
this.apiEventEmitterService.emitDeletedEvents(
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
|||||||
nonFormattedDeletedObjectRecords.raw,
|
nonFormattedDeletedObjectRecords.raw,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDestroyEvents(
|
this.apiEventEmitterService.emitDestroyEvents(
|
||||||
|
|||||||
@ -52,6 +52,7 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
|||||||
nonFormattedDeletedObjectRecords.raw,
|
nonFormattedDeletedObjectRecords.raw,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDestroyEvents(
|
this.apiEventEmitterService.emitDestroyEvents(
|
||||||
|
|||||||
@ -64,6 +64,7 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
|
|||||||
|
|
||||||
const graphqlQueryParser = new GraphqlQueryParser(
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
objectMetadataItemWithFieldsMaps?.fieldsByName,
|
objectMetadataItemWithFieldsMaps?.fieldsByName,
|
||||||
|
objectMetadataItemWithFieldsMaps?.fieldsByJoinColumnName,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
featureFlagsMap,
|
featureFlagsMap,
|
||||||
);
|
);
|
||||||
@ -85,6 +86,7 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
|
|||||||
nonFormattedObjectRecords,
|
nonFormattedObjectRecords,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
} else if (executionArgs.args.data && !isEmpty(executionArgs.args.data)) {
|
} else if (executionArgs.args.data && !isEmpty(executionArgs.args.data)) {
|
||||||
objectRecords = formatData(
|
objectRecords = formatData(
|
||||||
@ -131,6 +133,7 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
|
|||||||
nonFormattedDuplicates,
|
nonFormattedDuplicates,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
return typeORMObjectRecordsParser.createConnection({
|
return typeORMObjectRecordsParser.createConnection({
|
||||||
|
|||||||
@ -130,6 +130,7 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
|
|||||||
nonFormattedObjectRecords,
|
nonFormattedObjectRecords,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { hasNextPage, hasPreviousPage } = getPaginationInfo(
|
const { hasNextPage, hasPreviousPage } = getPaginationInfo(
|
||||||
|
|||||||
@ -59,6 +59,7 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver
|
|||||||
nonFormattedObjectRecord,
|
nonFormattedObjectRecord,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!objectRecord) {
|
if (!objectRecord) {
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
|||||||
nonFormattedRestoredObjectRecords.raw,
|
nonFormattedRestoredObjectRecords.raw,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitRestoreEvents(
|
this.apiEventEmitterService.emitRestoreEvents(
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
|
|||||||
nonFormattedRestoredObjectRecords.raw,
|
nonFormattedRestoredObjectRecords.raw,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitRestoreEvents(
|
this.apiEventEmitterService.emitRestoreEvents(
|
||||||
|
|||||||
@ -55,6 +55,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
existingRecords,
|
existingRecords,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isEmpty(formattedExistingRecords)) {
|
if (isEmpty(formattedExistingRecords)) {
|
||||||
@ -89,6 +90,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
nonFormattedUpdatedObjectRecords.raw,
|
nonFormattedUpdatedObjectRecords.raw,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitUpdateEvents(
|
this.apiEventEmitterService.emitUpdateEvents(
|
||||||
|
|||||||
@ -55,6 +55,7 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
existingRecords,
|
existingRecords,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isEmpty(formattedExistingRecords)) {
|
if (isEmpty(formattedExistingRecords)) {
|
||||||
@ -74,6 +75,7 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
nonFormattedUpdatedObjectRecords.raw,
|
nonFormattedUpdatedObjectRecords.raw,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
);
|
);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitUpdateEvents(
|
this.apiEventEmitterService.emitUpdateEvents(
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/work
|
|||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
// TODO: find a way to prevent conflict between handlers executing logic on object relations
|
// TODO: find a way to prevent conflict between handlers executing logic on object relations
|
||||||
// And this factory that is also executing logic on object relations
|
// And this factory that is also executing logic on object relations
|
||||||
@ -153,7 +153,10 @@ export class QueryResultGettersFactory {
|
|||||||
)
|
)
|
||||||
.filter(isDefined)
|
.filter(isDefined)
|
||||||
.filter((fieldMetadata) =>
|
.filter((fieldMetadata) =>
|
||||||
isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION),
|
isFieldMetadataInterfaceOfType(
|
||||||
|
fieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const relationFieldsProcessedMap = {} as Record<
|
const relationFieldsProcessedMap = {} as Record<
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-b
|
|||||||
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
|
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
|
||||||
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
||||||
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
|
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
|
||||||
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
import { ArgsFactory } from './args.factory';
|
import { ArgsFactory } from './args.factory';
|
||||||
|
|
||||||
@ -108,7 +108,12 @@ export class ExtendObjectTypeDefinitionV2Factory {
|
|||||||
|
|
||||||
for (const fieldMetadata of objectMetadata.fields) {
|
for (const fieldMetadata of objectMetadata.fields) {
|
||||||
// Ignore non-relation fields as they are already defined
|
// Ignore non-relation fields as they are already defined
|
||||||
if (!isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION)) {
|
if (
|
||||||
|
!isFieldMetadataInterfaceOfType(
|
||||||
|
fieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
)
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@ export class InputTypeDefinitionFactory {
|
|||||||
objectMetadata: ObjectMetadataInterface,
|
objectMetadata: ObjectMetadataInterface,
|
||||||
kind: InputTypeDefinitionKind,
|
kind: InputTypeDefinitionKind,
|
||||||
options: WorkspaceBuildSchemaOptions,
|
options: WorkspaceBuildSchemaOptions,
|
||||||
|
isNewRelationEnabled = false,
|
||||||
): InputTypeDefinition {
|
): InputTypeDefinition {
|
||||||
const inputType = new GraphQLInputObjectType({
|
const inputType = new GraphQLInputObjectType({
|
||||||
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}Input`,
|
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}Input`,
|
||||||
@ -58,6 +59,7 @@ export class InputTypeDefinitionFactory {
|
|||||||
kind,
|
kind,
|
||||||
options,
|
options,
|
||||||
this.inputTypeFactory,
|
this.inputTypeFactory,
|
||||||
|
isNewRelationEnabled,
|
||||||
),
|
),
|
||||||
and: {
|
and: {
|
||||||
type: andOrType,
|
type: andOrType,
|
||||||
@ -81,6 +83,7 @@ export class InputTypeDefinitionFactory {
|
|||||||
kind,
|
kind,
|
||||||
options,
|
options,
|
||||||
this.inputTypeFactory,
|
this.inputTypeFactory,
|
||||||
|
isNewRelationEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export class ObjectTypeDefinitionFactory {
|
|||||||
objectMetadata: ObjectMetadataInterface,
|
objectMetadata: ObjectMetadataInterface,
|
||||||
kind: ObjectTypeDefinitionKind,
|
kind: ObjectTypeDefinitionKind,
|
||||||
options: WorkspaceBuildSchemaOptions,
|
options: WorkspaceBuildSchemaOptions,
|
||||||
|
isNewRelationEnabled = false,
|
||||||
): ObjectTypeDefinition {
|
): ObjectTypeDefinition {
|
||||||
return {
|
return {
|
||||||
target: objectMetadata.id,
|
target: objectMetadata.id,
|
||||||
@ -42,6 +43,7 @@ export class ObjectTypeDefinitionFactory {
|
|||||||
kind,
|
kind,
|
||||||
options,
|
options,
|
||||||
this.outputTypeFactory,
|
this.outputTypeFactory,
|
||||||
|
isNewRelationEnabled,
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -58,7 +58,7 @@ export class TypeMapperService {
|
|||||||
settings?: FieldMetadataSettings<FieldMetadataType>,
|
settings?: FieldMetadataSettings<FieldMetadataType>,
|
||||||
isIdField?: boolean,
|
isIdField?: boolean,
|
||||||
): GraphQLScalarType | undefined {
|
): GraphQLScalarType | undefined {
|
||||||
if (isIdField || settings?.isForeignKey) {
|
if (isIdField || fieldMetadataType === FieldMetadataType.RELATION) {
|
||||||
return GraphQLID;
|
return GraphQLID;
|
||||||
}
|
}
|
||||||
const typeScalarMapping = new Map<FieldMetadataType, GraphQLScalarType>([
|
const typeScalarMapping = new Map<FieldMetadataType, GraphQLScalarType>([
|
||||||
@ -93,7 +93,7 @@ export class TypeMapperService {
|
|||||||
settings?: FieldMetadataSettings<FieldMetadataType>,
|
settings?: FieldMetadataSettings<FieldMetadataType>,
|
||||||
isIdField?: boolean,
|
isIdField?: boolean,
|
||||||
): GraphQLInputObjectType | GraphQLScalarType | undefined {
|
): GraphQLInputObjectType | GraphQLScalarType | undefined {
|
||||||
if (isIdField || settings?.isForeignKey) {
|
if (isIdField || fieldMetadataType === FieldMetadataType.RELATION) {
|
||||||
return IDFilterType;
|
return IDFilterType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,6 +132,7 @@ export class TypeMapperService {
|
|||||||
): GraphQLInputType | undefined {
|
): GraphQLInputType | undefined {
|
||||||
const typeOrderByMapping = new Map<FieldMetadataType, GraphQLEnumType>([
|
const typeOrderByMapping = new Map<FieldMetadataType, GraphQLEnumType>([
|
||||||
[FieldMetadataType.UUID, OrderByDirectionType],
|
[FieldMetadataType.UUID, OrderByDirectionType],
|
||||||
|
[FieldMetadataType.RELATION, OrderByDirectionType],
|
||||||
[FieldMetadataType.TEXT, OrderByDirectionType],
|
[FieldMetadataType.TEXT, OrderByDirectionType],
|
||||||
[FieldMetadataType.DATE_TIME, OrderByDirectionType],
|
[FieldMetadataType.DATE_TIME, OrderByDirectionType],
|
||||||
[FieldMetadataType.DATE, OrderByDirectionType],
|
[FieldMetadataType.DATE, OrderByDirectionType],
|
||||||
|
|||||||
@ -51,11 +51,16 @@ export class TypeDefinitionsGenerator {
|
|||||||
async generate(
|
async generate(
|
||||||
objectMetadataCollection: ObjectMetadataInterface[],
|
objectMetadataCollection: ObjectMetadataInterface[],
|
||||||
options: WorkspaceBuildSchemaOptions,
|
options: WorkspaceBuildSchemaOptions,
|
||||||
|
isNewRelationEnabled: boolean,
|
||||||
) {
|
) {
|
||||||
// Generate composite type objects first because they can be used in dynamic objects
|
// Generate composite type objects first because they can be used in dynamic objects
|
||||||
await this.generateCompositeTypeDefs(options);
|
await this.generateCompositeTypeDefs(options);
|
||||||
// Generate metadata objects
|
// Generate metadata objects
|
||||||
await this.generateMetadataTypeDefs(objectMetadataCollection, options);
|
await this.generateMetadataTypeDefs(
|
||||||
|
objectMetadataCollection,
|
||||||
|
options,
|
||||||
|
isNewRelationEnabled,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,6 +160,7 @@ export class TypeDefinitionsGenerator {
|
|||||||
private async generateMetadataTypeDefs(
|
private async generateMetadataTypeDefs(
|
||||||
dynamicObjectMetadataCollection: ObjectMetadataInterface[],
|
dynamicObjectMetadataCollection: ObjectMetadataInterface[],
|
||||||
options: WorkspaceBuildSchemaOptions,
|
options: WorkspaceBuildSchemaOptions,
|
||||||
|
isNewRelationEnabled: boolean,
|
||||||
) {
|
) {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Generating metadata objects: [${dynamicObjectMetadataCollection
|
`Generating metadata objects: [${dynamicObjectMetadataCollection
|
||||||
@ -164,9 +170,17 @@ export class TypeDefinitionsGenerator {
|
|||||||
|
|
||||||
// Generate dynamic objects
|
// Generate dynamic objects
|
||||||
this.generateEnumTypeDefs(dynamicObjectMetadataCollection, options);
|
this.generateEnumTypeDefs(dynamicObjectMetadataCollection, options);
|
||||||
this.generateObjectTypeDefs(dynamicObjectMetadataCollection, options);
|
this.generateObjectTypeDefs(
|
||||||
|
dynamicObjectMetadataCollection,
|
||||||
|
options,
|
||||||
|
isNewRelationEnabled,
|
||||||
|
);
|
||||||
this.generatePaginationTypeDefs(dynamicObjectMetadataCollection, options);
|
this.generatePaginationTypeDefs(dynamicObjectMetadataCollection, options);
|
||||||
this.generateInputTypeDefs(dynamicObjectMetadataCollection, options);
|
this.generateInputTypeDefs(
|
||||||
|
dynamicObjectMetadataCollection,
|
||||||
|
options,
|
||||||
|
isNewRelationEnabled,
|
||||||
|
);
|
||||||
await this.generateExtendedObjectTypeDefs(
|
await this.generateExtendedObjectTypeDefs(
|
||||||
dynamicObjectMetadataCollection,
|
dynamicObjectMetadataCollection,
|
||||||
options,
|
options,
|
||||||
@ -176,12 +190,14 @@ export class TypeDefinitionsGenerator {
|
|||||||
private generateObjectTypeDefs(
|
private generateObjectTypeDefs(
|
||||||
objectMetadataCollection: ObjectMetadataInterface[] | CompositeType[],
|
objectMetadataCollection: ObjectMetadataInterface[] | CompositeType[],
|
||||||
options: WorkspaceBuildSchemaOptions,
|
options: WorkspaceBuildSchemaOptions,
|
||||||
|
isNewRelationEnabled: boolean,
|
||||||
) {
|
) {
|
||||||
const objectTypeDefs = objectMetadataCollection.map((objectMetadata) =>
|
const objectTypeDefs = objectMetadataCollection.map((objectMetadata) =>
|
||||||
this.objectTypeDefinitionFactory.create(
|
this.objectTypeDefinitionFactory.create(
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
ObjectTypeDefinitionKind.Plain,
|
ObjectTypeDefinitionKind.Plain,
|
||||||
options,
|
options,
|
||||||
|
isNewRelationEnabled,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -209,6 +225,7 @@ export class TypeDefinitionsGenerator {
|
|||||||
private generateInputTypeDefs(
|
private generateInputTypeDefs(
|
||||||
objectMetadataCollection: ObjectMetadataInterface[],
|
objectMetadataCollection: ObjectMetadataInterface[],
|
||||||
options: WorkspaceBuildSchemaOptions,
|
options: WorkspaceBuildSchemaOptions,
|
||||||
|
isNewRelationEnabled: boolean,
|
||||||
) {
|
) {
|
||||||
const inputTypeDefs = objectMetadataCollection
|
const inputTypeDefs = objectMetadataCollection
|
||||||
.map((objectMetadata) => {
|
.map((objectMetadata) => {
|
||||||
@ -226,24 +243,28 @@ export class TypeDefinitionsGenerator {
|
|||||||
objectMetadata,
|
objectMetadata,
|
||||||
InputTypeDefinitionKind.Create,
|
InputTypeDefinitionKind.Create,
|
||||||
options,
|
options,
|
||||||
|
isNewRelationEnabled,
|
||||||
),
|
),
|
||||||
// Input type for update
|
// Input type for update
|
||||||
this.inputTypeDefinitionFactory.create(
|
this.inputTypeDefinitionFactory.create(
|
||||||
optionalExtendedObjectMetadata,
|
optionalExtendedObjectMetadata,
|
||||||
InputTypeDefinitionKind.Update,
|
InputTypeDefinitionKind.Update,
|
||||||
options,
|
options,
|
||||||
|
isNewRelationEnabled,
|
||||||
),
|
),
|
||||||
// Filter input type
|
// Filter input type
|
||||||
this.inputTypeDefinitionFactory.create(
|
this.inputTypeDefinitionFactory.create(
|
||||||
optionalExtendedObjectMetadata,
|
optionalExtendedObjectMetadata,
|
||||||
InputTypeDefinitionKind.Filter,
|
InputTypeDefinitionKind.Filter,
|
||||||
options,
|
options,
|
||||||
|
isNewRelationEnabled,
|
||||||
),
|
),
|
||||||
// OrderBy input type
|
// OrderBy input type
|
||||||
this.inputTypeDefinitionFactory.create(
|
this.inputTypeDefinitionFactory.create(
|
||||||
optionalExtendedObjectMetadata,
|
optionalExtendedObjectMetadata,
|
||||||
InputTypeDefinitionKind.OrderBy,
|
InputTypeDefinitionKind.OrderBy,
|
||||||
options,
|
options,
|
||||||
|
isNewRelationEnabled,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
|
|||||||
@ -8,11 +8,12 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
|||||||
|
|
||||||
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
|
||||||
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
|
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
|
||||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
type TypeFactory<T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind> =
|
type TypeFactory<T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind> =
|
||||||
{
|
{
|
||||||
@ -40,13 +41,21 @@ export const generateFields = <
|
|||||||
kind: T,
|
kind: T,
|
||||||
options: WorkspaceBuildSchemaOptions,
|
options: WorkspaceBuildSchemaOptions,
|
||||||
typeFactory: TypeFactory<T>,
|
typeFactory: TypeFactory<T>,
|
||||||
|
isNewRelationEnabled = false,
|
||||||
): T extends InputTypeDefinitionKind
|
): T extends InputTypeDefinitionKind
|
||||||
? GraphQLInputFieldConfigMap
|
? GraphQLInputFieldConfigMap
|
||||||
: GraphQLFieldConfigMap<any, any> => {
|
: GraphQLFieldConfigMap<any, any> => {
|
||||||
const fields = {};
|
const fields = {};
|
||||||
|
|
||||||
for (const fieldMetadata of objectMetadata.fields) {
|
for (const fieldMetadata of objectMetadata.fields) {
|
||||||
if (isRelationFieldMetadataType(fieldMetadata.type)) {
|
if (
|
||||||
|
isFieldMetadataInterfaceOfType(
|
||||||
|
fieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
) &&
|
||||||
|
(fieldMetadata.settings?.relationType !== RelationType.MANY_TO_ONE ||
|
||||||
|
!isNewRelationEnabled)
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +89,25 @@ export const generateFields = <
|
|||||||
typeFactoryOptions,
|
typeFactoryOptions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
isFieldMetadataInterfaceOfType(
|
||||||
|
fieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
) &&
|
||||||
|
fieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE
|
||||||
|
) {
|
||||||
|
const joinColumnName = fieldMetadata.settings?.joinColumnName;
|
||||||
|
|
||||||
|
if (!joinColumnName) {
|
||||||
|
throw new Error('Join column name is not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[joinColumnName] = {
|
||||||
|
type,
|
||||||
|
description: fieldMetadata.description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fields[fieldMetadata.name] = {
|
fields[fieldMetadata.name] = {
|
||||||
type,
|
type,
|
||||||
description: fieldMetadata.description,
|
description: fieldMetadata.description,
|
||||||
|
|||||||
@ -25,11 +25,13 @@ export class WorkspaceGraphQLSchemaFactory {
|
|||||||
objectMetadataCollection: ObjectMetadataInterface[],
|
objectMetadataCollection: ObjectMetadataInterface[],
|
||||||
workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods,
|
workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods,
|
||||||
options: WorkspaceBuildSchemaOptions = {},
|
options: WorkspaceBuildSchemaOptions = {},
|
||||||
|
isNewRelationEnabled = false,
|
||||||
): Promise<GraphQLSchema> {
|
): Promise<GraphQLSchema> {
|
||||||
// Generate type definitions
|
// Generate type definitions
|
||||||
await this.typeDefinitionsGenerator.generate(
|
await this.typeDefinitionsGenerator.generate(
|
||||||
objectMetadataCollection,
|
objectMetadataCollection,
|
||||||
options,
|
options,
|
||||||
|
isNewRelationEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Generate schema
|
// Generate schema
|
||||||
|
|||||||
@ -149,6 +149,8 @@ export class WorkspaceSchemaFactory {
|
|||||||
await this.workspaceGraphQLSchemaFactory.create(
|
await this.workspaceGraphQLSchemaFactory.create(
|
||||||
objectMetadataCollection,
|
objectMetadataCollection,
|
||||||
workspaceResolverBuilderMethodNames,
|
workspaceResolverBuilderMethodNames,
|
||||||
|
{},
|
||||||
|
isNewRelationEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
usedScalarNames =
|
usedScalarNames =
|
||||||
|
|||||||
@ -31,6 +31,7 @@ describe('checkFields', () => {
|
|||||||
...objectMetadataItemMock,
|
...objectMetadataItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldsByName,
|
||||||
|
fieldsByJoinColumnName: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should check field types', () => {
|
it('should check field types', () => {
|
||||||
|
|||||||
@ -32,6 +32,7 @@ describe('getFieldType', () => {
|
|||||||
...objectMetadataItemMock,
|
...objectMetadataItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldsByName,
|
||||||
|
fieldsByJoinColumnName: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should get field type', () => {
|
it('should get field type', () => {
|
||||||
|
|||||||
@ -61,6 +61,7 @@ describe('mapFieldMetadataToGraphqlQuery', () => {
|
|||||||
...objectMetadataItemMock,
|
...objectMetadataItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldsByName,
|
||||||
|
fieldsByJoinColumnName: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const objectMetadataMapsMock: ObjectMetadataMaps = {
|
const objectMetadataMapsMock: ObjectMetadataMaps = {
|
||||||
|
|||||||
@ -33,6 +33,7 @@ describe('checkFilterEnumValues', () => {
|
|||||||
...objectMetadataItemMock,
|
...objectMetadataItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldsByName,
|
||||||
|
fieldsByJoinColumnName: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should check properly', () => {
|
it('should check properly', () => {
|
||||||
|
|||||||
@ -43,6 +43,7 @@ describe('parseFilter', () => {
|
|||||||
...objectMetadataItemMock,
|
...objectMetadataItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldsByName,
|
||||||
|
fieldsByJoinColumnName: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should parse string filter test 1', () => {
|
it('should parse string filter test 1', () => {
|
||||||
|
|||||||
@ -72,6 +72,7 @@ export class AccessTokenService {
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
'workspaceMember',
|
'workspaceMember',
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||||
|
|||||||
@ -62,6 +62,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fieldsByJoinColumnName: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '',
|
id: '',
|
||||||
@ -140,6 +141,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fieldsByJoinColumnName: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '',
|
id: '',
|
||||||
@ -218,6 +220,7 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fieldsByJoinColumnName: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '',
|
id: '',
|
||||||
@ -243,5 +246,6 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
fieldsById: {},
|
fieldsById: {},
|
||||||
fieldsByName: {},
|
fieldsByName: {},
|
||||||
|
fieldsByJoinColumnName: {},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -74,6 +74,7 @@ export class SearchService {
|
|||||||
|
|
||||||
const queryParser = new GraphqlQueryParser(
|
const queryParser = new GraphqlQueryParser(
|
||||||
objectMetadataItem.fieldsByName,
|
objectMetadataItem.fieldsByName,
|
||||||
|
objectMetadataItem.fieldsByJoinColumnName,
|
||||||
generateObjectMetadataMaps([objectMetadataItem]),
|
generateObjectMetadataMaps([objectMetadataItem]),
|
||||||
featureFlagMap,
|
featureFlagMap,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -25,7 +25,10 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
|||||||
import { CreateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
import { CreateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||||
import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
|
import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
|
||||||
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||||
import { RelationDefinitionDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation-definition.dto';
|
import {
|
||||||
|
RelationDefinitionDTO,
|
||||||
|
RelationDefinitionType,
|
||||||
|
} from 'src/engine/metadata-modules/field-metadata/dtos/relation-definition.dto';
|
||||||
import { RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto';
|
import { RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto';
|
||||||
import {
|
import {
|
||||||
UpdateFieldInput,
|
UpdateFieldInput,
|
||||||
@ -42,6 +45,7 @@ import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-mod
|
|||||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||||
|
import { createDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
|
||||||
|
|
||||||
@UseGuards(WorkspaceAuthGuard)
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
@Resolver(() => FieldMetadataDTO)
|
@Resolver(() => FieldMetadataDTO)
|
||||||
@ -180,6 +184,37 @@ export class FieldMetadataResolver {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsNewRelationEnabled,
|
||||||
|
workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: Remove this once we drop old relations or update the front-end to use the new relation
|
||||||
|
if (isNewRelationEnabled) {
|
||||||
|
const relation = await this.relation(
|
||||||
|
workspace,
|
||||||
|
fieldMetadata as FieldMetadataEntity<FieldMetadataType.RELATION>,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!relation) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Temporary fix as we don't have relationId in the new relation
|
||||||
|
relationId: createDeterministicUuid([
|
||||||
|
relation.sourceFieldMetadata.id,
|
||||||
|
relation.targetFieldMetadata.id,
|
||||||
|
]),
|
||||||
|
direction: relation.type as unknown as RelationDefinitionType,
|
||||||
|
sourceObjectMetadata: relation.sourceObjectMetadata,
|
||||||
|
targetObjectMetadata: relation.targetObjectMetadata,
|
||||||
|
sourceFieldMetadata: relation.sourceFieldMetadata,
|
||||||
|
targetFieldMetadata: relation.targetFieldMetadata,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const relationMetadataItem =
|
const relationMetadataItem =
|
||||||
await context.loaders.relationMetadataLoader.load({
|
await context.loaders.relationMetadataLoader.load({
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export type FieldMetadataDateTimeSettings = {
|
|||||||
export type FieldMetadataRelationSettings = {
|
export type FieldMetadataRelationSettings = {
|
||||||
relationType: RelationType;
|
relationType: RelationType;
|
||||||
onDelete?: RelationOnDeleteAction;
|
onDelete?: RelationOnDeleteAction;
|
||||||
joinColumnName?: string;
|
joinColumnName?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FieldMetadataSettingsMapping = {
|
type FieldMetadataSettingsMapping = {
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/
|
|||||||
import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook';
|
import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook';
|
||||||
import { ObjectMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor';
|
import { ObjectMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/object-metadata/interceptors/object-metadata-graphql-api-exception.interceptor';
|
||||||
import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metadata/object-metadata.resolver';
|
import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metadata/object-metadata.resolver';
|
||||||
|
import { ObjectMetadataFieldRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service';
|
||||||
import { ObjectMetadataMigrationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service';
|
import { ObjectMetadataMigrationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service';
|
||||||
import { ObjectMetadataRelatedRecordsService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service';
|
import { ObjectMetadataRelatedRecordsService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service';
|
||||||
import { ObjectMetadataRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service';
|
import { ObjectMetadataRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service';
|
||||||
@ -63,6 +64,7 @@ import { UpdateObjectPayload } from './dtos/update-object.input';
|
|||||||
ObjectMetadataService,
|
ObjectMetadataService,
|
||||||
ObjectMetadataMigrationService,
|
ObjectMetadataMigrationService,
|
||||||
ObjectMetadataRelationService,
|
ObjectMetadataRelationService,
|
||||||
|
ObjectMetadataFieldRelationService,
|
||||||
ObjectMetadataRelatedRecordsService,
|
ObjectMetadataRelatedRecordsService,
|
||||||
],
|
],
|
||||||
resolvers: [
|
resolvers: [
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import { FindManyOptions, FindOneOptions, In, Not, Repository } from 'typeorm';
|
|||||||
|
|
||||||
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';
|
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';
|
||||||
|
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId';
|
import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId';
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
@ -24,6 +26,7 @@ import {
|
|||||||
ObjectMetadataException,
|
ObjectMetadataException,
|
||||||
ObjectMetadataExceptionCode,
|
ObjectMetadataExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||||
|
import { ObjectMetadataFieldRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service';
|
||||||
import { ObjectMetadataMigrationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service';
|
import { ObjectMetadataMigrationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service';
|
||||||
import { ObjectMetadataRelatedRecordsService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service';
|
import { ObjectMetadataRelatedRecordsService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service';
|
||||||
import { ObjectMetadataRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service';
|
import { ObjectMetadataRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service';
|
||||||
@ -61,9 +64,11 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
private readonly searchVectorService: SearchVectorService,
|
private readonly searchVectorService: SearchVectorService,
|
||||||
private readonly objectMetadataRelationService: ObjectMetadataRelationService,
|
private readonly objectMetadataRelationService: ObjectMetadataRelationService,
|
||||||
|
private readonly objectMetadataFieldRelationService: ObjectMetadataFieldRelationService,
|
||||||
private readonly objectMetadataMigrationService: ObjectMetadataMigrationService,
|
private readonly objectMetadataMigrationService: ObjectMetadataMigrationService,
|
||||||
private readonly objectMetadataRelatedRecordsService: ObjectMetadataRelatedRecordsService,
|
private readonly objectMetadataRelatedRecordsService: ObjectMetadataRelatedRecordsService,
|
||||||
private readonly indexMetadataService: IndexMetadataService,
|
private readonly indexMetadataService: IndexMetadataService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
) {
|
) {
|
||||||
super(objectMetadataRepository);
|
super(objectMetadataRepository);
|
||||||
}
|
}
|
||||||
@ -181,17 +186,33 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
createdObjectMetadata.fields,
|
createdObjectMetadata.fields,
|
||||||
);
|
);
|
||||||
|
|
||||||
const createdRelatedObjectMetadataCollection =
|
const isNewRelationEnabled =
|
||||||
await this.objectMetadataRelationService.createRelationsAndForeignKeysMetadata(
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsNewRelationEnabled,
|
||||||
objectMetadataInput.workspaceId,
|
objectMetadataInput.workspaceId,
|
||||||
createdObjectMetadata,
|
|
||||||
{
|
|
||||||
primaryKeyFieldMetadataSettings:
|
|
||||||
objectMetadataInput.primaryKeyFieldMetadataSettings,
|
|
||||||
primaryKeyColumnType: objectMetadataInput.primaryKeyColumnType,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let createdRelatedObjectMetadataCollection: ObjectMetadataEntity[];
|
||||||
|
|
||||||
|
if (isNewRelationEnabled) {
|
||||||
|
createdRelatedObjectMetadataCollection =
|
||||||
|
await this.objectMetadataFieldRelationService.createRelationsAndForeignKeysMetadata(
|
||||||
|
objectMetadataInput.workspaceId,
|
||||||
|
createdObjectMetadata,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
createdRelatedObjectMetadataCollection =
|
||||||
|
await this.objectMetadataRelationService.createRelationsAndForeignKeysMetadata(
|
||||||
|
objectMetadataInput.workspaceId,
|
||||||
|
createdObjectMetadata,
|
||||||
|
{
|
||||||
|
primaryKeyFieldMetadataSettings:
|
||||||
|
objectMetadataInput.primaryKeyFieldMetadataSettings,
|
||||||
|
primaryKeyColumnType: objectMetadataInput.primaryKeyColumnType,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.objectMetadataMigrationService.createRelationMigrations(
|
await this.objectMetadataMigrationService.createRelationMigrations(
|
||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
createdRelatedObjectMetadataCollection,
|
createdRelatedObjectMetadataCollection,
|
||||||
@ -295,11 +316,19 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
inputPayload,
|
inputPayload,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsNewRelationEnabled,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
if (inputPayload.isActive !== undefined) {
|
if (inputPayload.isActive !== undefined) {
|
||||||
await this.objectMetadataRelationService.updateObjectRelationshipsActivationStatus(
|
// For new relation system, the active status is stitched to the field metadata
|
||||||
inputId,
|
if (!isNewRelationEnabled) {
|
||||||
inputPayload.isActive,
|
await this.objectMetadataRelationService.updateObjectRelationshipsActivationStatus(
|
||||||
);
|
inputId,
|
||||||
|
inputPayload.isActive,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
||||||
@ -342,8 +371,17 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
input: DeleteOneObjectInput,
|
input: DeleteOneObjectInput,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<ObjectMetadataEntity> {
|
): Promise<ObjectMetadataEntity> {
|
||||||
|
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsNewRelationEnabled,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||||
relations: [
|
relations: [
|
||||||
|
'fields',
|
||||||
|
'fields.object',
|
||||||
|
'fields.relationTargetFieldMetadata',
|
||||||
|
'fields.relationTargetFieldMetadata.object',
|
||||||
'fromRelations.fromFieldMetadata',
|
'fromRelations.fromFieldMetadata',
|
||||||
'fromRelations.toFieldMetadata',
|
'fromRelations.toFieldMetadata',
|
||||||
'toRelations.fromFieldMetadata',
|
'toRelations.fromFieldMetadata',
|
||||||
@ -375,6 +413,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
await this.objectMetadataMigrationService.deleteAllRelationsAndDropTable(
|
await this.objectMetadataMigrationService.deleteAllRelationsAndDropTable(
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
isNewRelationEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,6 +426,15 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const fieldMetadataIds = objectMetadata.fields.map((field) => field.id);
|
||||||
|
const relationMetadataIds = objectMetadata.fields
|
||||||
|
.map((field) => field.relationTargetFieldMetadata?.id)
|
||||||
|
.filter(isDefined);
|
||||||
|
|
||||||
|
await this.fieldMetadataRepository.delete({
|
||||||
|
id: In(fieldMetadataIds.concat(relationMetadataIds)),
|
||||||
|
});
|
||||||
|
|
||||||
await this.objectMetadataRepository.delete(objectMetadata.id);
|
await this.objectMetadataRepository.delete(objectMetadata.id);
|
||||||
|
|
||||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||||
@ -478,6 +526,11 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
objectMetadataForUpdate: ObjectMetadataEntity,
|
objectMetadataForUpdate: ObjectMetadataEntity,
|
||||||
inputPayload: UpdateObjectPayload,
|
inputPayload: UpdateObjectPayload,
|
||||||
) {
|
) {
|
||||||
|
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsNewRelationEnabled,
|
||||||
|
objectMetadataForUpdate.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
const newTargetTableName = computeObjectTargetTable(
|
const newTargetTableName = computeObjectTargetTable(
|
||||||
objectMetadataForUpdate,
|
objectMetadataForUpdate,
|
||||||
);
|
);
|
||||||
@ -492,18 +545,33 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
objectMetadataForUpdate.workspaceId,
|
objectMetadataForUpdate.workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const relationsAndForeignKeysMetadata =
|
if (isNewRelationEnabled) {
|
||||||
await this.objectMetadataRelationService.updateRelationsAndForeignKeysMetadata(
|
const relationMetadataCollection =
|
||||||
objectMetadataForUpdate.workspaceId,
|
await this.objectMetadataFieldRelationService.updateRelationsAndForeignKeysMetadata(
|
||||||
objectMetadataForUpdate,
|
objectMetadataForUpdate.workspaceId,
|
||||||
);
|
objectMetadataForUpdate,
|
||||||
|
);
|
||||||
|
|
||||||
await this.objectMetadataMigrationService.createUpdateForeignKeysMigrations(
|
await this.objectMetadataMigrationService.updateRelationMigrations(
|
||||||
existingObjectMetadata,
|
existingObjectMetadata,
|
||||||
objectMetadataForUpdate,
|
objectMetadataForUpdate,
|
||||||
relationsAndForeignKeysMetadata,
|
relationMetadataCollection,
|
||||||
objectMetadataForUpdate.workspaceId,
|
objectMetadataForUpdate.workspaceId,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
const relationsAndForeignKeysMetadata =
|
||||||
|
await this.objectMetadataRelationService.updateRelationsAndForeignKeysMetadata(
|
||||||
|
objectMetadataForUpdate.workspaceId,
|
||||||
|
objectMetadataForUpdate,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.objectMetadataMigrationService.createUpdateForeignKeysMigrations(
|
||||||
|
existingObjectMetadata,
|
||||||
|
objectMetadataForUpdate,
|
||||||
|
relationsAndForeignKeysMetadata,
|
||||||
|
objectMetadataForUpdate.workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.objectMetadataMigrationService.recomputeEnumNames(
|
await this.objectMetadataMigrationService.recomputeEnumNames(
|
||||||
objectMetadataForUpdate,
|
objectMetadataForUpdate,
|
||||||
|
|||||||
@ -0,0 +1,311 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
|
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { buildDescriptionForRelationFieldMetadataOnFromField } from 'src/engine/metadata-modules/object-metadata/utils/build-description-for-relation-field-on-from-field.util';
|
||||||
|
import { buildDescriptionForRelationFieldMetadataOnToField } from 'src/engine/metadata-modules/object-metadata/utils/build-description-for-relation-field-on-to-field.util';
|
||||||
|
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
|
import {
|
||||||
|
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
|
||||||
|
STANDARD_OBJECT_FIELD_IDS,
|
||||||
|
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||||
|
import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons';
|
||||||
|
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||||
|
import { createRelationDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
|
||||||
|
|
||||||
|
const DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS = [
|
||||||
|
STANDARD_OBJECT_IDS.timelineActivity,
|
||||||
|
STANDARD_OBJECT_IDS.favorite,
|
||||||
|
STANDARD_OBJECT_IDS.attachment,
|
||||||
|
STANDARD_OBJECT_IDS.noteTarget,
|
||||||
|
STANDARD_OBJECT_IDS.taskTarget,
|
||||||
|
];
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ObjectMetadataFieldRelationService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
|
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||||
|
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async createRelationsAndForeignKeysMetadata(
|
||||||
|
workspaceId: string,
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity,
|
||||||
|
) {
|
||||||
|
const relatedObjectMetadataCollection = await Promise.all(
|
||||||
|
DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS.map(
|
||||||
|
async (relationObjectMetadataStandardId) =>
|
||||||
|
this.createRelationAndForeignKeyMetadata(
|
||||||
|
workspaceId,
|
||||||
|
sourceObjectMetadata,
|
||||||
|
relationObjectMetadataStandardId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return relatedObjectMetadataCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createRelationAndForeignKeyMetadata(
|
||||||
|
workspaceId: string,
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity,
|
||||||
|
relationObjectMetadataStandardId: string,
|
||||||
|
) {
|
||||||
|
const targetObjectMetadata =
|
||||||
|
await this.objectMetadataRepository.findOneByOrFail({
|
||||||
|
standardId: relationObjectMetadataStandardId,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
isCustom: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.createFieldMetadataRelation(
|
||||||
|
workspaceId,
|
||||||
|
sourceObjectMetadata,
|
||||||
|
targetObjectMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
return targetObjectMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createFieldMetadataRelation(
|
||||||
|
workspaceId: string,
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity,
|
||||||
|
targetObjectMetadata: ObjectMetadataEntity,
|
||||||
|
): Promise<FieldMetadataEntity<FieldMetadataType.RELATION>[]> {
|
||||||
|
const sourceFieldMetadata = this.createSourceFieldMetadata(
|
||||||
|
workspaceId,
|
||||||
|
sourceObjectMetadata,
|
||||||
|
targetObjectMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
const targetFieldMetadata = this.createTargetFieldMetadata(
|
||||||
|
workspaceId,
|
||||||
|
sourceObjectMetadata,
|
||||||
|
targetObjectMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.fieldMetadataRepository.save([
|
||||||
|
{
|
||||||
|
...sourceFieldMetadata,
|
||||||
|
settings: {
|
||||||
|
relationType: RelationType.ONE_TO_MANY,
|
||||||
|
onDelete: RelationOnDeleteAction.CASCADE,
|
||||||
|
},
|
||||||
|
relationTargetObjectMetadataId: targetObjectMetadata.id,
|
||||||
|
relationTargetFieldMetadataId: targetFieldMetadata.id,
|
||||||
|
} as Partial<FieldMetadataEntity<FieldMetadataType.RELATION>>,
|
||||||
|
{
|
||||||
|
...targetFieldMetadata,
|
||||||
|
settings: {
|
||||||
|
relationType: RelationType.MANY_TO_ONE,
|
||||||
|
onDelete: RelationOnDeleteAction.CASCADE,
|
||||||
|
joinColumnName: `${sourceObjectMetadata.nameSingular}Id`,
|
||||||
|
},
|
||||||
|
relationTargetObjectMetadataId: sourceObjectMetadata.id,
|
||||||
|
relationTargetFieldMetadataId: sourceFieldMetadata.id,
|
||||||
|
} as Partial<FieldMetadataEntity<FieldMetadataType.RELATION>>,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateRelationsAndForeignKeysMetadata(
|
||||||
|
workspaceId: string,
|
||||||
|
updatedObjectMetadata: ObjectMetadataEntity,
|
||||||
|
): Promise<
|
||||||
|
{
|
||||||
|
targetObjectMetadata: ObjectMetadataEntity;
|
||||||
|
targetFieldMetadata: FieldMetadataEntity;
|
||||||
|
sourceFieldMetadata: FieldMetadataEntity;
|
||||||
|
}[]
|
||||||
|
> {
|
||||||
|
return await Promise.all(
|
||||||
|
DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS.map(
|
||||||
|
async (relationObjectMetadataStandardId) =>
|
||||||
|
this.updateRelationAndForeignKeyMetadata(
|
||||||
|
workspaceId,
|
||||||
|
updatedObjectMetadata,
|
||||||
|
relationObjectMetadataStandardId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateRelationAndForeignKeyMetadata(
|
||||||
|
workspaceId: string,
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity,
|
||||||
|
targetObjectMetadataStandardId: string,
|
||||||
|
) {
|
||||||
|
const targetObjectMetadata =
|
||||||
|
await this.objectMetadataRepository.findOneByOrFail({
|
||||||
|
standardId: targetObjectMetadataStandardId,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
isCustom: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const targetFieldMetadataUpdateData = this.updateTargetFieldMetadata(
|
||||||
|
sourceObjectMetadata,
|
||||||
|
targetObjectMetadata,
|
||||||
|
);
|
||||||
|
const targetFieldMetadataToUpdate =
|
||||||
|
await this.fieldMetadataRepository.findOneByOrFail({
|
||||||
|
standardId: createRelationDeterministicUuid({
|
||||||
|
objectId: sourceObjectMetadata.id,
|
||||||
|
standardId:
|
||||||
|
STANDARD_OBJECT_FIELD_IDS[targetObjectMetadata.nameSingular].custom,
|
||||||
|
}),
|
||||||
|
objectMetadataId: targetObjectMetadata.id,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
});
|
||||||
|
const targetFieldMetadata = await this.fieldMetadataRepository.save({
|
||||||
|
id: targetFieldMetadataToUpdate.id,
|
||||||
|
...targetFieldMetadataUpdateData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sourceFieldMetadataUpdateData = this.updateSourceFieldMetadata(
|
||||||
|
sourceObjectMetadata,
|
||||||
|
targetObjectMetadata,
|
||||||
|
);
|
||||||
|
const sourceFieldMetadataToUpdate =
|
||||||
|
await this.fieldMetadataRepository.findOneByOrFail({
|
||||||
|
standardId:
|
||||||
|
CUSTOM_OBJECT_STANDARD_FIELD_IDS[targetObjectMetadata.namePlural],
|
||||||
|
objectMetadataId: sourceObjectMetadata.id,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
});
|
||||||
|
const sourceFieldMetadata = await this.fieldMetadataRepository.save({
|
||||||
|
id: sourceFieldMetadataToUpdate.id,
|
||||||
|
...sourceFieldMetadataUpdateData,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
targetObjectMetadata,
|
||||||
|
targetFieldMetadata,
|
||||||
|
sourceFieldMetadata,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSourceFieldMetadata(
|
||||||
|
workspaceId: string,
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity,
|
||||||
|
targetObjectMetadata: ObjectMetadataEntity,
|
||||||
|
): Partial<FieldMetadataEntity<FieldMetadataType.RELATION>> {
|
||||||
|
const relationObjectMetadataNamePlural = targetObjectMetadata.namePlural;
|
||||||
|
|
||||||
|
const { description } = buildDescriptionForRelationFieldMetadataOnFromField(
|
||||||
|
{
|
||||||
|
relationObjectMetadataNamePlural,
|
||||||
|
targetObjectLabelSingular: sourceObjectMetadata.labelSingular,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: uuidV4(),
|
||||||
|
standardId:
|
||||||
|
CUSTOM_OBJECT_STANDARD_FIELD_IDS[relationObjectMetadataNamePlural],
|
||||||
|
objectMetadataId: sourceObjectMetadata.id,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
isCustom: false,
|
||||||
|
isActive: true,
|
||||||
|
isSystem: true,
|
||||||
|
type: FieldMetadataType.RELATION,
|
||||||
|
name: targetObjectMetadata.namePlural,
|
||||||
|
label: capitalize(relationObjectMetadataNamePlural),
|
||||||
|
description,
|
||||||
|
icon:
|
||||||
|
STANDARD_OBJECT_ICONS[targetObjectMetadata.nameSingular] ||
|
||||||
|
'IconBuildingSkyscraper',
|
||||||
|
isNullable: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateSourceFieldMetadata(
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity,
|
||||||
|
targetObjectMetadata: ObjectMetadataEntity,
|
||||||
|
) {
|
||||||
|
const relationObjectMetadataNamePlural = targetObjectMetadata.namePlural;
|
||||||
|
|
||||||
|
const { description } = buildDescriptionForRelationFieldMetadataOnFromField(
|
||||||
|
{
|
||||||
|
relationObjectMetadataNamePlural,
|
||||||
|
targetObjectLabelSingular: sourceObjectMetadata.labelSingular,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private createTargetFieldMetadata(
|
||||||
|
workspaceId: string,
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity,
|
||||||
|
targetObjectMetadata: ObjectMetadataEntity,
|
||||||
|
): Partial<FieldMetadataEntity<FieldMetadataType.RELATION>> {
|
||||||
|
const customStandardFieldId =
|
||||||
|
STANDARD_OBJECT_FIELD_IDS[targetObjectMetadata.nameSingular].custom;
|
||||||
|
|
||||||
|
if (!customStandardFieldId) {
|
||||||
|
throw new Error(
|
||||||
|
`Custom standard field ID not found for ${targetObjectMetadata.nameSingular}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { description } = buildDescriptionForRelationFieldMetadataOnToField({
|
||||||
|
relationObjectMetadataNamePlural: targetObjectMetadata.namePlural,
|
||||||
|
targetObjectLabelSingular: sourceObjectMetadata.labelSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: uuidV4(),
|
||||||
|
name: sourceObjectMetadata.nameSingular,
|
||||||
|
label: sourceObjectMetadata.labelSingular,
|
||||||
|
description,
|
||||||
|
standardId: createRelationDeterministicUuid({
|
||||||
|
objectId: sourceObjectMetadata.id,
|
||||||
|
standardId: customStandardFieldId,
|
||||||
|
}),
|
||||||
|
objectMetadataId: targetObjectMetadata.id,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
isCustom: false,
|
||||||
|
isActive: true,
|
||||||
|
isSystem: true,
|
||||||
|
type: FieldMetadataType.RELATION,
|
||||||
|
icon: 'IconBuildingSkyscraper',
|
||||||
|
isNullable: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateTargetFieldMetadata(
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity,
|
||||||
|
targetObjectMetadata: ObjectMetadataEntity,
|
||||||
|
) {
|
||||||
|
const customStandardFieldId =
|
||||||
|
STANDARD_OBJECT_FIELD_IDS[targetObjectMetadata.nameSingular].custom;
|
||||||
|
|
||||||
|
if (!customStandardFieldId) {
|
||||||
|
throw new Error(
|
||||||
|
`Custom standard field ID not found for ${targetObjectMetadata.nameSingular}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { description } = buildDescriptionForRelationFieldMetadataOnToField({
|
||||||
|
relationObjectMetadataNamePlural: targetObjectMetadata.namePlural,
|
||||||
|
targetObjectLabelSingular: sourceObjectMetadata.labelSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: sourceObjectMetadata.nameSingular,
|
||||||
|
label: sourceObjectMetadata.labelSingular,
|
||||||
|
description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { In, Repository } from 'typeorm';
|
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
import { In, Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
@ -22,6 +24,7 @@ import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace
|
|||||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
import { RELATION_MIGRATION_PRIORITY_PREFIX } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
import { RELATION_MIGRATION_PRIORITY_PREFIX } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -117,6 +120,52 @@ export class ObjectMetadataMigrationService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateRelationMigrations(
|
||||||
|
currentObjectMetadata: ObjectMetadataEntity,
|
||||||
|
alteredObjectMetadata: ObjectMetadataEntity,
|
||||||
|
relationMetadataCollection: {
|
||||||
|
targetObjectMetadata: ObjectMetadataEntity;
|
||||||
|
targetFieldMetadata: FieldMetadataEntity;
|
||||||
|
sourceFieldMetadata: FieldMetadataEntity;
|
||||||
|
}[],
|
||||||
|
workspaceId: string,
|
||||||
|
) {
|
||||||
|
for (const { targetObjectMetadata } of relationMetadataCollection) {
|
||||||
|
const targetTableName = computeObjectTargetTable(targetObjectMetadata);
|
||||||
|
const columnName = `${currentObjectMetadata.nameSingular}Id`;
|
||||||
|
|
||||||
|
await this.workspaceMigrationService.createCustomMigration(
|
||||||
|
generateMigrationName(
|
||||||
|
`rename-${currentObjectMetadata.nameSingular}-to-${alteredObjectMetadata.nameSingular}-in-${targetObjectMetadata.nameSingular}`,
|
||||||
|
),
|
||||||
|
workspaceId,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: targetTableName,
|
||||||
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.ALTER,
|
||||||
|
currentColumnDefinition: {
|
||||||
|
columnName,
|
||||||
|
columnType: 'uuid',
|
||||||
|
isNullable: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
alteredColumnDefinition: {
|
||||||
|
columnName: `${alteredObjectMetadata.nameSingular}Id`,
|
||||||
|
columnType: 'uuid',
|
||||||
|
isNullable: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async createUpdateForeignKeysMigrations(
|
public async createUpdateForeignKeysMigrations(
|
||||||
existingObjectMetadata: ObjectMetadataEntity,
|
existingObjectMetadata: ObjectMetadataEntity,
|
||||||
updatedObjectMetadata: ObjectMetadataEntity,
|
updatedObjectMetadata: ObjectMetadataEntity,
|
||||||
@ -173,15 +222,16 @@ export class ObjectMetadataMigrationService {
|
|||||||
public async deleteAllRelationsAndDropTable(
|
public async deleteAllRelationsAndDropTable(
|
||||||
objectMetadata: ObjectMetadataEntity,
|
objectMetadata: ObjectMetadataEntity,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
isNewRelationEnabled: boolean,
|
||||||
) {
|
) {
|
||||||
const relationsToDelete: RelationToDelete[] = [];
|
const relationsMetadataToDelete: RelationToDelete[] = [];
|
||||||
|
|
||||||
// TODO: Most of this logic should be moved to relation-metadata.service.ts
|
// TODO: Most of this logic should be moved to relation-metadata.service.ts
|
||||||
for (const relation of [
|
for (const relation of [
|
||||||
...objectMetadata.fromRelations,
|
...objectMetadata.fromRelations,
|
||||||
...objectMetadata.toRelations,
|
...objectMetadata.toRelations,
|
||||||
]) {
|
]) {
|
||||||
relationsToDelete.push({
|
relationsMetadataToDelete.push({
|
||||||
id: relation.id,
|
id: relation.id,
|
||||||
fromFieldMetadataId: relation.fromFieldMetadata.id,
|
fromFieldMetadataId: relation.fromFieldMetadata.id,
|
||||||
toFieldMetadataId: relation.toFieldMetadata.id,
|
toFieldMetadataId: relation.toFieldMetadata.id,
|
||||||
@ -201,13 +251,13 @@ export class ObjectMetadataMigrationService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relationsToDelete.length > 0) {
|
if (relationsMetadataToDelete.length > 0) {
|
||||||
await this.relationMetadataRepository.delete(
|
await this.relationMetadataRepository.delete(
|
||||||
relationsToDelete.map((relation) => relation.id),
|
relationsMetadataToDelete.map((relation) => relation.id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const relationToDelete of relationsToDelete) {
|
for (const relationToDelete of relationsMetadataToDelete) {
|
||||||
const foreignKeyFieldsToDelete = await this.fieldMetadataRepository.find({
|
const foreignKeyFieldsToDelete = await this.fieldMetadataRepository.find({
|
||||||
where: {
|
where: {
|
||||||
name: `${relationToDelete.toFieldMetadataName}Id`,
|
name: `${relationToDelete.toFieldMetadataName}Id`,
|
||||||
@ -254,6 +304,61 @@ export class ObjectMetadataMigrationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isNewRelationEnabled) {
|
||||||
|
const manyToOneRelationFieldsToDelete = objectMetadata.fields.filter(
|
||||||
|
(field) =>
|
||||||
|
isFieldMetadataInterfaceOfType(field, FieldMetadataType.RELATION) &&
|
||||||
|
(field as FieldMetadataEntity<FieldMetadataType.RELATION>).settings
|
||||||
|
?.relationType === RelationType.MANY_TO_ONE,
|
||||||
|
) as FieldMetadataEntity<FieldMetadataType.RELATION>[];
|
||||||
|
|
||||||
|
const oneToManyRelationFieldsToDelete = objectMetadata.fields.filter(
|
||||||
|
(field) =>
|
||||||
|
isFieldMetadataInterfaceOfType(field, FieldMetadataType.RELATION) &&
|
||||||
|
(field as FieldMetadataEntity<FieldMetadataType.RELATION>).settings
|
||||||
|
?.relationType === RelationType.ONE_TO_MANY,
|
||||||
|
);
|
||||||
|
|
||||||
|
const relationFieldsToDelete = [
|
||||||
|
...manyToOneRelationFieldsToDelete,
|
||||||
|
...(oneToManyRelationFieldsToDelete.map(
|
||||||
|
(field) => field.relationTargetFieldMetadata,
|
||||||
|
) as FieldMetadataEntity<FieldMetadataType.RELATION>[]),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const relationFieldToDelete of relationFieldsToDelete) {
|
||||||
|
const joinColumnName = relationFieldToDelete.settings?.joinColumnName;
|
||||||
|
|
||||||
|
if (!joinColumnName) {
|
||||||
|
throw new Error(
|
||||||
|
`Join column name is not set for relation field ${relationFieldToDelete.name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.workspaceMigrationService.createCustomMigration(
|
||||||
|
generateMigrationName(
|
||||||
|
`delete-${RELATION_MIGRATION_PRIORITY_PREFIX}-${relationFieldToDelete.name}`,
|
||||||
|
),
|
||||||
|
workspaceId,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: computeTableName(
|
||||||
|
relationFieldToDelete.object.nameSingular,
|
||||||
|
relationFieldToDelete.object.isCustom,
|
||||||
|
),
|
||||||
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.DROP,
|
||||||
|
columnName: joinColumnName,
|
||||||
|
} satisfies WorkspaceMigrationColumnDrop,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DROP TABLE
|
// DROP TABLE
|
||||||
await this.workspaceMigrationService.createCustomMigration(
|
await this.workspaceMigrationService.createCustomMigration(
|
||||||
generateMigrationName(`delete-${objectMetadata.nameSingular}`),
|
generateMigrationName(`delete-${objectMetadata.nameSingular}`),
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export const buildMigrationsForCustomObjectRelations = (
|
|||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
action: WorkspaceMigrationColumnActionType.CREATE,
|
action: WorkspaceMigrationColumnActionType.CREATE,
|
||||||
|
// TODO: When we get rid of this and use the sync metadata, columnName must be based on the joinColumnName from the field metadata settings
|
||||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
||||||
isForeignKey: true,
|
isForeignKey: true,
|
||||||
}),
|
}),
|
||||||
@ -38,6 +39,7 @@ export const buildMigrationsForCustomObjectRelations = (
|
|||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
||||||
|
// TODO: When we get rid of this and use the sync metadata, columnName must be based on the joinColumnName from the field metadata settings
|
||||||
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
columnName: computeColumnName(createdObjectMetadata.nameSingular, {
|
||||||
isForeignKey: true,
|
isForeignKey: true,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -3,13 +3,17 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
|
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
import camelCase from 'lodash.camelcase';
|
import camelCase from 'lodash.camelcase';
|
||||||
import { FindOneOptions, In, Repository } from 'typeorm';
|
|
||||||
import { v4 as uuidV4 } from 'uuid';
|
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { FindOneOptions, In, Repository } from 'typeorm';
|
||||||
|
import { v4 as uuidV4 } from 'uuid';
|
||||||
|
|
||||||
|
import { FieldMetadataDefaultSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
||||||
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
|
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
|
||||||
@ -20,6 +24,7 @@ import {
|
|||||||
RelationMetadataException,
|
RelationMetadataException,
|
||||||
RelationMetadataExceptionCode,
|
RelationMetadataExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception';
|
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception';
|
||||||
|
import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception';
|
||||||
import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils';
|
import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils';
|
||||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||||
@ -34,7 +39,6 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target
|
|||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||||
import { BASE_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
import { BASE_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||||
import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RelationMetadataEntity,
|
RelationMetadataEntity,
|
||||||
@ -56,6 +60,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
private readonly indexMetadataService: IndexMetadataService,
|
private readonly indexMetadataService: IndexMetadataService,
|
||||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
) {
|
) {
|
||||||
super(relationMetadataRepository);
|
super(relationMetadataRepository);
|
||||||
}
|
}
|
||||||
@ -67,6 +72,11 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
relationMetadataInput,
|
relationMetadataInput,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsNewRelationEnabled,
|
||||||
|
relationMetadataInput.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
validateMetadataNameOrThrow(relationMetadataInput.fromName);
|
validateMetadataNameOrThrow(relationMetadataInput.fromName);
|
||||||
validateMetadataNameOrThrow(relationMetadataInput.toName);
|
validateMetadataNameOrThrow(relationMetadataInput.toName);
|
||||||
@ -92,21 +102,31 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
const toId = uuidV4();
|
const toId = uuidV4();
|
||||||
|
|
||||||
const createdRelationFieldsMetadata =
|
const createdRelationFieldsMetadata =
|
||||||
await this.fieldMetadataRepository.save([
|
await this.fieldMetadataRepository.save(
|
||||||
this.createFieldMetadataForRelationMetadata(
|
[
|
||||||
relationMetadataInput,
|
this.createFieldMetadataForRelationMetadata(
|
||||||
'from',
|
relationMetadataInput,
|
||||||
isCustom,
|
'from',
|
||||||
fromId,
|
isCustom,
|
||||||
),
|
fromId,
|
||||||
this.createFieldMetadataForRelationMetadata(
|
),
|
||||||
relationMetadataInput,
|
this.createFieldMetadataForRelationMetadata(
|
||||||
'to',
|
relationMetadataInput,
|
||||||
isCustom,
|
'to',
|
||||||
toId,
|
isCustom,
|
||||||
),
|
toId,
|
||||||
this.createForeignKeyFieldMetadata(relationMetadataInput, columnName),
|
),
|
||||||
]);
|
// We don't want to create the join column field metadata for new relation
|
||||||
|
...(isNewRelationEnabled
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
this.createForeignKeyFieldMetadata(
|
||||||
|
relationMetadataInput,
|
||||||
|
columnName,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
].flat(),
|
||||||
|
);
|
||||||
|
|
||||||
const createdRelationMetadata = await super.createOne({
|
const createdRelationMetadata = await super.createOne({
|
||||||
...relationMetadataInput,
|
...relationMetadataInput,
|
||||||
@ -114,6 +134,46 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
toFieldMetadataId: toId,
|
toFieldMetadataId: toId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fromFieldMetadata = createdRelationFieldsMetadata.find(
|
||||||
|
(fieldMetadata) => fieldMetadata.id === fromId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fromFieldMetadata) {
|
||||||
|
throw new RelationMetadataException(
|
||||||
|
`From field metadata not found`,
|
||||||
|
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toFieldMetadata = createdRelationFieldsMetadata.find(
|
||||||
|
(fieldMetadata) => fieldMetadata.id === toId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!toFieldMetadata) {
|
||||||
|
throw new RelationMetadataException(
|
||||||
|
`To field metadata not found`,
|
||||||
|
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.fieldMetadataRepository.update(fromId, {
|
||||||
|
settings: {
|
||||||
|
relationType: RelationType.ONE_TO_MANY,
|
||||||
|
} as Partial<FieldMetadataDefaultSettings>,
|
||||||
|
relationTargetFieldMetadataId: toId,
|
||||||
|
relationTargetObjectMetadataId: relationMetadataInput.toObjectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.fieldMetadataRepository.update(toId, {
|
||||||
|
settings: {
|
||||||
|
relationType: RelationType.MANY_TO_ONE,
|
||||||
|
joinColumnName: `${relationMetadataInput.toName}Id`,
|
||||||
|
} as Partial<FieldMetadataDefaultSettings>,
|
||||||
|
relationTargetFieldMetadataId: fromId,
|
||||||
|
relationTargetObjectMetadataId:
|
||||||
|
relationMetadataInput.fromObjectMetadataId,
|
||||||
|
});
|
||||||
|
|
||||||
await this.createWorkspaceCustomMigration(
|
await this.createWorkspaceCustomMigration(
|
||||||
relationMetadataInput,
|
relationMetadataInput,
|
||||||
objectMetadataMap,
|
objectMetadataMap,
|
||||||
@ -123,17 +183,6 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
const toObjectMetadata =
|
const toObjectMetadata =
|
||||||
objectMetadataMap[relationMetadataInput.toObjectMetadataId];
|
objectMetadataMap[relationMetadataInput.toObjectMetadataId];
|
||||||
|
|
||||||
const foreignKeyFieldMetadata = createdRelationFieldsMetadata.find(
|
|
||||||
(fieldMetadata) => fieldMetadata.type === FieldMetadataType.UUID,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!foreignKeyFieldMetadata) {
|
|
||||||
throw new RelationMetadataException(
|
|
||||||
`ForeignKey field metadata not found`,
|
|
||||||
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletedAtFieldMetadata = toObjectMetadata.fields.find(
|
const deletedAtFieldMetadata = toObjectMetadata.fields.find(
|
||||||
(fieldMetadata) =>
|
(fieldMetadata) =>
|
||||||
fieldMetadata.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
|
fieldMetadata.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
|
||||||
@ -141,16 +190,29 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
|
|
||||||
this.throwIfDeletedAtFieldMetadataNotFound(deletedAtFieldMetadata);
|
this.throwIfDeletedAtFieldMetadataNotFound(deletedAtFieldMetadata);
|
||||||
|
|
||||||
await this.indexMetadataService.createIndexMetadata(
|
if (!isNewRelationEnabled) {
|
||||||
relationMetadataInput.workspaceId,
|
const foreignKeyFieldMetadata = createdRelationFieldsMetadata.find(
|
||||||
toObjectMetadata,
|
(fieldMetadata) => fieldMetadata.type === FieldMetadataType.UUID,
|
||||||
[
|
);
|
||||||
foreignKeyFieldMetadata,
|
|
||||||
deletedAtFieldMetadata as FieldMetadataEntity<FieldMetadataType>,
|
if (!foreignKeyFieldMetadata) {
|
||||||
],
|
throw new RelationMetadataException(
|
||||||
false,
|
`ForeignKey field metadata not found`,
|
||||||
false,
|
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.indexMetadataService.createIndexMetadata(
|
||||||
|
relationMetadataInput.workspaceId,
|
||||||
|
toObjectMetadata,
|
||||||
|
[
|
||||||
|
foreignKeyFieldMetadata,
|
||||||
|
deletedAtFieldMetadata as FieldMetadataEntity<FieldMetadataType>,
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
||||||
relationMetadataInput.workspaceId,
|
relationMetadataInput.workspaceId,
|
||||||
|
|||||||
@ -5,4 +5,5 @@ import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metada
|
|||||||
export type ObjectMetadataItemWithFieldMaps = ObjectMetadataInterface & {
|
export type ObjectMetadataItemWithFieldMaps = ObjectMetadataInterface & {
|
||||||
fieldsById: FieldMetadataMap;
|
fieldsById: FieldMetadataMap;
|
||||||
fieldsByName: FieldMetadataMap;
|
fieldsByName: FieldMetadataMap;
|
||||||
|
fieldsByJoinColumnName: FieldMetadataMap;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
|
|
||||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
export const generateObjectMetadataMaps = (
|
export const generateObjectMetadataMaps = (
|
||||||
objectMetadataCollection: ObjectMetadataInterface[],
|
objectMetadataCollection: ObjectMetadataInterface[],
|
||||||
@ -15,8 +18,21 @@ export const generateObjectMetadataMaps = (
|
|||||||
for (const objectMetadata of objectMetadataCollection) {
|
for (const objectMetadata of objectMetadataCollection) {
|
||||||
const fieldsByIdMap: FieldMetadataMap = {};
|
const fieldsByIdMap: FieldMetadataMap = {};
|
||||||
const fieldsByNameMap: FieldMetadataMap = {};
|
const fieldsByNameMap: FieldMetadataMap = {};
|
||||||
|
const fieldsByJoinColumnNameMap: FieldMetadataMap = {};
|
||||||
|
|
||||||
for (const fieldMetadata of objectMetadata.fields) {
|
for (const fieldMetadata of objectMetadata.fields) {
|
||||||
|
if (
|
||||||
|
isFieldMetadataInterfaceOfType(
|
||||||
|
fieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (fieldMetadata.settings?.joinColumnName) {
|
||||||
|
fieldsByJoinColumnNameMap[fieldMetadata.settings.joinColumnName] =
|
||||||
|
fieldMetadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fieldsByNameMap[fieldMetadata.name] = fieldMetadata;
|
fieldsByNameMap[fieldMetadata.name] = fieldMetadata;
|
||||||
fieldsByIdMap[fieldMetadata.id] = fieldMetadata;
|
fieldsByIdMap[fieldMetadata.id] = fieldMetadata;
|
||||||
}
|
}
|
||||||
@ -25,6 +41,7 @@ export const generateObjectMetadataMaps = (
|
|||||||
...objectMetadata,
|
...objectMetadata,
|
||||||
fieldsById: fieldsByIdMap,
|
fieldsById: fieldsByIdMap,
|
||||||
fieldsByName: fieldsByNameMap,
|
fieldsByName: fieldsByNameMap,
|
||||||
|
fieldsByJoinColumnName: fieldsByJoinColumnNameMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
objectMetadataMaps.byId[objectMetadata.id] = processedObjectMetadata;
|
objectMetadataMaps.byId[objectMetadata.id] = processedObjectMetadata;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
|
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
|
||||||
import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||||
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
|
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
|
||||||
|
import { RelationColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/relation-column-action.factory';
|
||||||
import { TsVectorColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/ts-vector-column-action.factory';
|
import { TsVectorColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/ts-vector-column-action.factory';
|
||||||
|
|
||||||
export const workspaceColumnActionFactories = [
|
export const workspaceColumnActionFactories = [
|
||||||
@ -8,4 +9,5 @@ export const workspaceColumnActionFactories = [
|
|||||||
BasicColumnActionFactory,
|
BasicColumnActionFactory,
|
||||||
EnumColumnActionFactory,
|
EnumColumnActionFactory,
|
||||||
CompositeColumnActionFactory,
|
CompositeColumnActionFactory,
|
||||||
|
RelationColumnActionFactory,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -0,0 +1,97 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
import { WorkspaceColumnActionOptions } from 'src/engine/metadata-modules/workspace-migration/interfaces/workspace-column-action-options.interface';
|
||||||
|
|
||||||
|
import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory';
|
||||||
|
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationColumnActionType,
|
||||||
|
WorkspaceMigrationColumnAlter,
|
||||||
|
WorkspaceMigrationColumnCreate,
|
||||||
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationException,
|
||||||
|
WorkspaceMigrationExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RelationColumnActionFactory extends ColumnActionAbstractFactory<FieldMetadataType.RELATION> {
|
||||||
|
protected readonly logger = new Logger(RelationColumnActionFactory.name);
|
||||||
|
|
||||||
|
protected handleCreateAction(
|
||||||
|
fieldMetadata: FieldMetadataInterface<FieldMetadataType.RELATION>,
|
||||||
|
_options?: WorkspaceColumnActionOptions,
|
||||||
|
): WorkspaceMigrationColumnCreate[] {
|
||||||
|
if (!fieldMetadata.settings || !fieldMetadata.settings.joinColumnName) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const joinColumnName = fieldMetadata.settings.joinColumnName;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.CREATE,
|
||||||
|
columnName: joinColumnName,
|
||||||
|
columnType: fieldMetadataTypeToColumnType(FieldMetadataType.UUID),
|
||||||
|
isArray: false,
|
||||||
|
isNullable: fieldMetadata.isNullable ?? true,
|
||||||
|
isUnique:
|
||||||
|
fieldMetadata.settings.relationType === RelationType.ONE_TO_ONE,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleAlterAction(
|
||||||
|
currentFieldMetadata: FieldMetadataInterface<FieldMetadataType.RELATION>,
|
||||||
|
alteredFieldMetadata: FieldMetadataInterface<FieldMetadataType.RELATION>,
|
||||||
|
_options?: WorkspaceColumnActionOptions,
|
||||||
|
): WorkspaceMigrationColumnAlter[] {
|
||||||
|
if (!currentFieldMetadata.settings || !alteredFieldMetadata.settings) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentJoinColumnName = currentFieldMetadata.settings.joinColumnName;
|
||||||
|
const alteredJoinColumnName = alteredFieldMetadata.settings.joinColumnName;
|
||||||
|
|
||||||
|
if (!currentJoinColumnName || !alteredJoinColumnName) {
|
||||||
|
this.logger.error(
|
||||||
|
`Column name not found for current or altered field metadata, can be due to a missing or an invalid target column map. Current column name: ${currentJoinColumnName}, Altered column name: ${alteredJoinColumnName}.`,
|
||||||
|
);
|
||||||
|
throw new WorkspaceMigrationException(
|
||||||
|
`Column name not found for current or altered field metadata`,
|
||||||
|
WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.ALTER,
|
||||||
|
currentColumnDefinition: {
|
||||||
|
columnName: currentJoinColumnName,
|
||||||
|
columnType: fieldMetadataTypeToColumnType(FieldMetadataType.UUID),
|
||||||
|
isArray: false,
|
||||||
|
isNullable: currentFieldMetadata.isNullable ?? true,
|
||||||
|
isUnique:
|
||||||
|
currentFieldMetadata.settings.relationType ===
|
||||||
|
RelationType.ONE_TO_ONE,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
alteredColumnDefinition: {
|
||||||
|
columnName: alteredJoinColumnName,
|
||||||
|
columnType: fieldMetadataTypeToColumnType(FieldMetadataType.UUID),
|
||||||
|
isArray: false,
|
||||||
|
isNullable: alteredFieldMetadata.isNullable ?? true,
|
||||||
|
isUnique:
|
||||||
|
alteredFieldMetadata.settings.relationType ===
|
||||||
|
RelationType.ONE_TO_ONE,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import { WorkspaceColumnActionOptions } from 'src/engine/metadata-modules/worksp
|
|||||||
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
|
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
|
||||||
import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||||
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
|
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
|
||||||
|
import { RelationColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/relation-column-action.factory';
|
||||||
import { TsVectorColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/ts-vector-column-action.factory';
|
import { TsVectorColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/ts-vector-column-action.factory';
|
||||||
import {
|
import {
|
||||||
WorkspaceMigrationColumnAction,
|
WorkspaceMigrationColumnAction,
|
||||||
@ -35,6 +36,7 @@ export class WorkspaceMigrationFactory {
|
|||||||
private readonly tsVectorColumnActionFactory: TsVectorColumnActionFactory,
|
private readonly tsVectorColumnActionFactory: TsVectorColumnActionFactory,
|
||||||
private readonly enumColumnActionFactory: EnumColumnActionFactory,
|
private readonly enumColumnActionFactory: EnumColumnActionFactory,
|
||||||
private readonly compositeColumnActionFactory: CompositeColumnActionFactory,
|
private readonly compositeColumnActionFactory: CompositeColumnActionFactory,
|
||||||
|
private readonly relationColumnActionFactory: RelationColumnActionFactory,
|
||||||
) {
|
) {
|
||||||
this.factoriesMap = new Map<
|
this.factoriesMap = new Map<
|
||||||
FieldMetadataType,
|
FieldMetadataType,
|
||||||
@ -98,6 +100,10 @@ export class WorkspaceMigrationFactory {
|
|||||||
FieldMetadataType.RICH_TEXT_V2,
|
FieldMetadataType.RICH_TEXT_V2,
|
||||||
{ factory: this.compositeColumnActionFactory },
|
{ factory: this.compositeColumnActionFactory },
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
{ factory: this.relationColumnActionFactory },
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import { msg } from '@lingui/core/macro';
|
import { msg } from '@lingui/core/macro';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
|
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
|
||||||
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||||
import { DEFAULT_LABEL_IDENTIFIER_FIELD_NAME } from 'src/engine/metadata-modules/object-metadata/object-metadata.constants';
|
import { DEFAULT_LABEL_IDENTIFIER_FIELD_NAME } from 'src/engine/metadata-modules/object-metadata/object-metadata.constants';
|
||||||
import {
|
|
||||||
RelationMetadataType,
|
|
||||||
RelationOnDeleteAction,
|
|
||||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
|
||||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||||
import { WorkspaceCustomEntity } from 'src/engine/twenty-orm/decorators/workspace-custom-entity.decorator';
|
import { WorkspaceCustomEntity } from 'src/engine/twenty-orm/decorators/workspace-custom-entity.decorator';
|
||||||
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator';
|
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator';
|
||||||
@ -67,7 +66,7 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
|
|||||||
@WorkspaceRelation({
|
@WorkspaceRelation({
|
||||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.noteTargets,
|
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.noteTargets,
|
||||||
label: msg`Notes`,
|
label: msg`Notes`,
|
||||||
type: RelationMetadataType.ONE_TO_MANY,
|
type: RelationType.ONE_TO_MANY,
|
||||||
description: (objectMetadata) => {
|
description: (objectMetadata) => {
|
||||||
const label = objectMetadata.labelSingular;
|
const label = objectMetadata.labelSingular;
|
||||||
|
|
||||||
@ -83,7 +82,7 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
|
|||||||
@WorkspaceRelation({
|
@WorkspaceRelation({
|
||||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.taskTargets,
|
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.taskTargets,
|
||||||
label: msg`Tasks`,
|
label: msg`Tasks`,
|
||||||
type: RelationMetadataType.ONE_TO_MANY,
|
type: RelationType.ONE_TO_MANY,
|
||||||
description: (objectMetadata) => {
|
description: (objectMetadata) => {
|
||||||
const label = objectMetadata.labelSingular;
|
const label = objectMetadata.labelSingular;
|
||||||
|
|
||||||
@ -99,7 +98,7 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
|
|||||||
@WorkspaceRelation({
|
@WorkspaceRelation({
|
||||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.favorites,
|
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.favorites,
|
||||||
label: msg`Favorites`,
|
label: msg`Favorites`,
|
||||||
type: RelationMetadataType.ONE_TO_MANY,
|
type: RelationType.ONE_TO_MANY,
|
||||||
description: (objectMetadata) => {
|
description: (objectMetadata) => {
|
||||||
const label = objectMetadata.labelSingular;
|
const label = objectMetadata.labelSingular;
|
||||||
|
|
||||||
@ -116,7 +115,7 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
|
|||||||
@WorkspaceRelation({
|
@WorkspaceRelation({
|
||||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.attachments,
|
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.attachments,
|
||||||
label: msg`Attachments`,
|
label: msg`Attachments`,
|
||||||
type: RelationMetadataType.ONE_TO_MANY,
|
type: RelationType.ONE_TO_MANY,
|
||||||
description: (objectMetadata) => {
|
description: (objectMetadata) => {
|
||||||
const label = objectMetadata.labelSingular;
|
const label = objectMetadata.labelSingular;
|
||||||
|
|
||||||
@ -132,7 +131,7 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
|
|||||||
@WorkspaceRelation({
|
@WorkspaceRelation({
|
||||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.timelineActivities,
|
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.timelineActivities,
|
||||||
label: msg`Timeline Activities`,
|
label: msg`Timeline Activities`,
|
||||||
type: RelationMetadataType.ONE_TO_MANY,
|
type: RelationType.ONE_TO_MANY,
|
||||||
description: (objectMetadata) => {
|
description: (objectMetadata) => {
|
||||||
const label = objectMetadata.labelSingular;
|
const label = objectMetadata.labelSingular;
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
import { ObjectType } from 'typeorm';
|
import { ObjectType } from 'typeorm';
|
||||||
|
|
||||||
|
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
import { WorkspaceDynamicRelationMetadataArgsFactory } from 'src/engine/twenty-orm/interfaces/workspace-dynamic-relation-metadata-args.interface';
|
import { WorkspaceDynamicRelationMetadataArgsFactory } from 'src/engine/twenty-orm/interfaces/workspace-dynamic-relation-metadata-args.interface';
|
||||||
|
|
||||||
import {
|
|
||||||
RelationMetadataType,
|
|
||||||
RelationOnDeleteAction,
|
|
||||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
|
||||||
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
|
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
|
||||||
import { TypedReflect } from 'src/utils/typed-reflect';
|
import { TypedReflect } from 'src/utils/typed-reflect';
|
||||||
|
|
||||||
interface WorkspaceBaseDynamicRelationOptions<TClass> {
|
interface WorkspaceBaseDynamicRelationOptions<TClass> {
|
||||||
type: RelationMetadataType;
|
type: RelationType;
|
||||||
argsFactory: WorkspaceDynamicRelationMetadataArgsFactory;
|
argsFactory: WorkspaceDynamicRelationMetadataArgsFactory;
|
||||||
inverseSideTarget: () => ObjectType<TClass>;
|
inverseSideTarget: () => ObjectType<TClass>;
|
||||||
inverseSideFieldKey?: keyof TClass;
|
inverseSideFieldKey?: keyof TClass;
|
||||||
|
|||||||
@ -28,6 +28,15 @@ export function WorkspaceFieldIndex(
|
|||||||
...additionalDefaultColumnsForIndex,
|
...additionalDefaultColumnsForIndex,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// TODO: Remove this when we are handling properly indexes for new relation metadata
|
||||||
|
if (
|
||||||
|
process.env.SYNC_METADATA_INDEX_ENABLED === 'false' ||
|
||||||
|
process.env.SYNC_METADATA_INDEX_ENABLED === '' ||
|
||||||
|
process.env.SYNC_METADATA_INDEX_ENABLED === undefined
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
metadataArgsStorage.addIndexes({
|
metadataArgsStorage.addIndexes({
|
||||||
name: `IDX_${generateDeterministicIndexName([
|
name: `IDX_${generateDeterministicIndexName([
|
||||||
convertClassNameToObjectMetadataName(target.constructor.name),
|
convertClassNameToObjectMetadataName(target.constructor.name),
|
||||||
|
|||||||
@ -11,7 +11,11 @@ import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args
|
|||||||
import { TypedReflect } from 'src/utils/typed-reflect';
|
import { TypedReflect } from 'src/utils/typed-reflect';
|
||||||
|
|
||||||
export interface WorkspaceFieldOptions<
|
export interface WorkspaceFieldOptions<
|
||||||
T extends FieldMetadataType = FieldMetadataType,
|
T extends FieldMetadataType = Exclude<
|
||||||
|
FieldMetadataType,
|
||||||
|
// Use @WorkspaceRelation or @WorkspaceDynamicRelation for relation fields
|
||||||
|
FieldMetadataType.RELATION
|
||||||
|
>,
|
||||||
> {
|
> {
|
||||||
standardId: string;
|
standardId: string;
|
||||||
type: T;
|
type: T;
|
||||||
|
|||||||
@ -18,6 +18,15 @@ export function WorkspaceIndex(
|
|||||||
throw new Error('Class level WorkspaceIndex should be used with columns');
|
throw new Error('Class level WorkspaceIndex should be used with columns');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this when we are handling properly indexes for new relation metadata
|
||||||
|
if (
|
||||||
|
process.env.SYNC_METADATA_INDEX_ENABLED === 'false' ||
|
||||||
|
process.env.SYNC_METADATA_INDEX_ENABLED === '' ||
|
||||||
|
process.env.SYNC_METADATA_INDEX_ENABLED === undefined
|
||||||
|
) {
|
||||||
|
return (_target: any) => {};
|
||||||
|
}
|
||||||
|
|
||||||
return (target: any) => {
|
return (target: any) => {
|
||||||
const gate = TypedReflect.getMetadata(
|
const gate = TypedReflect.getMetadata(
|
||||||
'workspace:gate-metadata-args',
|
'workspace:gate-metadata-args',
|
||||||
|
|||||||
@ -17,17 +17,20 @@ export function WorkspaceIsUnique(): PropertyDecorator {
|
|||||||
|
|
||||||
const columns = [propertyKey.toString()];
|
const columns = [propertyKey.toString()];
|
||||||
|
|
||||||
metadataArgsStorage.addIndexes({
|
// TODO: Remove this when we are handling properly indexes for new relation metadata
|
||||||
name: `IDX_UNIQUE_${generateDeterministicIndexName([
|
if (process.env.SYNC_METADATA_INDEX_ENABLED === 'true') {
|
||||||
convertClassNameToObjectMetadataName(target.constructor.name),
|
metadataArgsStorage.addIndexes({
|
||||||
...columns,
|
name: `IDX_UNIQUE_${generateDeterministicIndexName([
|
||||||
])}`,
|
convertClassNameToObjectMetadataName(target.constructor.name),
|
||||||
columns,
|
...columns,
|
||||||
target: target.constructor,
|
])}`,
|
||||||
gate,
|
columns,
|
||||||
isUnique: true,
|
target: target.constructor,
|
||||||
whereClause: null,
|
gate,
|
||||||
});
|
isUnique: true,
|
||||||
|
whereClause: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return TypedReflect.defineMetadata(
|
return TypedReflect.defineMetadata(
|
||||||
'workspace:is-unique-metadata-args',
|
'workspace:is-unique-metadata-args',
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import { MessageDescriptor } from '@lingui/core';
|
import { MessageDescriptor } from '@lingui/core';
|
||||||
import { ObjectType } from 'typeorm';
|
import { ObjectType } from 'typeorm';
|
||||||
|
|
||||||
|
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import {
|
|
||||||
RelationMetadataType,
|
|
||||||
RelationOnDeleteAction,
|
|
||||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
|
||||||
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
|
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
|
||||||
import { TypedReflect } from 'src/utils/typed-reflect';
|
import { TypedReflect } from 'src/utils/typed-reflect';
|
||||||
|
|
||||||
interface WorkspaceRelationOptions<TClass> {
|
interface WorkspaceRelationBaseOptions<TClass> {
|
||||||
standardId: string;
|
standardId: string;
|
||||||
label:
|
label:
|
||||||
| MessageDescriptor
|
| MessageDescriptor
|
||||||
@ -18,12 +17,26 @@ interface WorkspaceRelationOptions<TClass> {
|
|||||||
| MessageDescriptor
|
| MessageDescriptor
|
||||||
| ((objectMetadata: ObjectMetadataEntity) => MessageDescriptor);
|
| ((objectMetadata: ObjectMetadataEntity) => MessageDescriptor);
|
||||||
icon?: string;
|
icon?: string;
|
||||||
type: RelationMetadataType;
|
|
||||||
inverseSideTarget: () => ObjectType<TClass>;
|
inverseSideTarget: () => ObjectType<TClass>;
|
||||||
inverseSideFieldKey?: keyof TClass;
|
inverseSideFieldKey?: keyof TClass;
|
||||||
onDelete?: RelationOnDeleteAction;
|
onDelete?: RelationOnDeleteAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WorkspaceOtherRelationOptions<TClass>
|
||||||
|
extends WorkspaceRelationBaseOptions<TClass> {
|
||||||
|
type: RelationType.ONE_TO_MANY | RelationType.ONE_TO_ONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkspaceManyToOneRelationOptions<TClass extends object>
|
||||||
|
extends WorkspaceRelationBaseOptions<TClass> {
|
||||||
|
type: RelationType.MANY_TO_ONE;
|
||||||
|
inverseSideFieldKey: keyof TClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkspaceRelationOptions<TClass extends object> =
|
||||||
|
| WorkspaceOtherRelationOptions<TClass>
|
||||||
|
| WorkspaceManyToOneRelationOptions<TClass>;
|
||||||
|
|
||||||
export function WorkspaceRelation<TClass extends object>(
|
export function WorkspaceRelation<TClass extends object>(
|
||||||
options: WorkspaceRelationOptions<TClass>,
|
options: WorkspaceRelationOptions<TClass>,
|
||||||
): PropertyDecorator {
|
): PropertyDecorator {
|
||||||
|
|||||||
@ -14,4 +14,5 @@ export enum TwentyORMExceptionCode {
|
|||||||
ROLES_PERMISSIONS_VERSION_NOT_FOUND = 'ROLES_PERMISSIONS_VERSION_NOT_FOUND',
|
ROLES_PERMISSIONS_VERSION_NOT_FOUND = 'ROLES_PERMISSIONS_VERSION_NOT_FOUND',
|
||||||
FEATURE_FLAG_MAP_VERSION_NOT_FOUND = 'FEATURE_FLAG_MAP_VERSION_NOT_FOUND',
|
FEATURE_FLAG_MAP_VERSION_NOT_FOUND = 'FEATURE_FLAG_MAP_VERSION_NOT_FOUND',
|
||||||
USER_WORKSPACE_ROLE_MAP_VERSION_NOT_FOUND = 'USER_WORKSPACE_ROLE_MAP_VERSION_NOT_FOUND',
|
USER_WORKSPACE_ROLE_MAP_VERSION_NOT_FOUND = 'USER_WORKSPACE_ROLE_MAP_VERSION_NOT_FOUND',
|
||||||
|
MALFORMED_METADATA = 'MALFORMED_METADATA',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { ColumnType, EntitySchemaColumnOptions } from 'typeorm';
|
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { ColumnType, EntitySchemaColumnOptions } from 'typeorm';
|
||||||
|
|
||||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
@ -12,7 +14,11 @@ import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metad
|
|||||||
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
|
import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-default-value';
|
||||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||||
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
|
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
|
||||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
import {
|
||||||
|
TwentyORMException,
|
||||||
|
TwentyORMExceptionCode,
|
||||||
|
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||||
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
type EntitySchemaColumnMap = {
|
type EntitySchemaColumnMap = {
|
||||||
[key: string]: EntitySchemaColumnOptions;
|
[key: string]: EntitySchemaColumnOptions;
|
||||||
@ -20,7 +26,10 @@ type EntitySchemaColumnMap = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EntitySchemaColumnFactory {
|
export class EntitySchemaColumnFactory {
|
||||||
create(fieldMetadataMapByName: FieldMetadataMap): EntitySchemaColumnMap {
|
create(
|
||||||
|
fieldMetadataMapByName: FieldMetadataMap,
|
||||||
|
isNewRelationEnabled: boolean,
|
||||||
|
): EntitySchemaColumnMap {
|
||||||
let entitySchemaColumnMap: EntitySchemaColumnMap = {};
|
let entitySchemaColumnMap: EntitySchemaColumnMap = {};
|
||||||
|
|
||||||
const fieldMetadataCollection = Object.values(fieldMetadataMapByName);
|
const fieldMetadataCollection = Object.values(fieldMetadataMapByName);
|
||||||
@ -28,33 +37,63 @@ export class EntitySchemaColumnFactory {
|
|||||||
for (const fieldMetadata of fieldMetadataCollection) {
|
for (const fieldMetadata of fieldMetadataCollection) {
|
||||||
const key = fieldMetadata.name;
|
const key = fieldMetadata.name;
|
||||||
|
|
||||||
if (isRelationFieldMetadataType(fieldMetadata.type)) {
|
if (
|
||||||
const relationMetadata =
|
isFieldMetadataInterfaceOfType(
|
||||||
fieldMetadata.fromRelationMetadata ??
|
fieldMetadata,
|
||||||
fieldMetadata.toRelationMetadata;
|
FieldMetadataType.RELATION,
|
||||||
|
|
||||||
if (!relationMetadata) {
|
|
||||||
throw new Error(
|
|
||||||
`Relation metadata is missing for field ${fieldMetadata.name}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const joinColumnKey = fieldMetadata.name + 'Id';
|
|
||||||
const joinColumn = fieldMetadataCollection.find(
|
|
||||||
(field) => field.name === joinColumnKey,
|
|
||||||
)
|
)
|
||||||
? joinColumnKey
|
) {
|
||||||
: null;
|
if (!isNewRelationEnabled) {
|
||||||
|
const relationMetadata =
|
||||||
|
fieldMetadata.fromRelationMetadata ??
|
||||||
|
fieldMetadata.toRelationMetadata;
|
||||||
|
|
||||||
if (joinColumn) {
|
if (!relationMetadata) {
|
||||||
entitySchemaColumnMap[joinColumn] = {
|
throw new Error(
|
||||||
name: joinColumn,
|
`Relation metadata is missing for field ${fieldMetadata.name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const joinColumnKey = fieldMetadata.name + 'Id';
|
||||||
|
const joinColumn = fieldMetadataCollection.find(
|
||||||
|
(field) => field.name === joinColumnKey,
|
||||||
|
)
|
||||||
|
? joinColumnKey
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (joinColumn) {
|
||||||
|
entitySchemaColumnMap[joinColumn] = {
|
||||||
|
name: joinColumn,
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: fieldMetadata.isNullable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
const isManyToOneRelation =
|
||||||
|
fieldMetadata.settings?.relationType === RelationType.MANY_TO_ONE;
|
||||||
|
const joinColumnName = fieldMetadata.settings?.joinColumnName;
|
||||||
|
|
||||||
|
if (!isManyToOneRelation) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDefined(joinColumnName)) {
|
||||||
|
throw new TwentyORMException(
|
||||||
|
`Field ${fieldMetadata.id} of type ${fieldMetadata.type} is a many to one relation but does not have a join column name`,
|
||||||
|
TwentyORMExceptionCode.MALFORMED_METADATA,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
entitySchemaColumnMap[joinColumnName] = {
|
||||||
|
name: joinColumnName,
|
||||||
type: 'uuid',
|
type: 'uuid',
|
||||||
nullable: fieldMetadata.isNullable,
|
nullable: fieldMetadata.isNullable,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
if (isCompositeFieldMetadataType(fieldMetadata.type)) {
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import { EntitySchemaRelationOptions } from 'typeorm';
|
import { EntitySchemaRelationOptions } from 'typeorm';
|
||||||
|
|
||||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
import { determineRelationDetails } from 'src/engine/twenty-orm/utils/determine-relation-details.util';
|
import { determineRelationDetails } from 'src/engine/twenty-orm/utils/determine-relation-details.util';
|
||||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
import { determineSchemaRelationDetails } from 'src/engine/twenty-orm/utils/determine-schema-relation-details.util';
|
||||||
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
type EntitySchemaRelationMap = {
|
type EntitySchemaRelationMap = {
|
||||||
[key: string]: EntitySchemaRelationOptions;
|
[key: string]: EntitySchemaRelationOptions;
|
||||||
@ -18,37 +20,64 @@ export class EntitySchemaRelationFactory {
|
|||||||
async create(
|
async create(
|
||||||
fieldMetadataMapByName: FieldMetadataMap,
|
fieldMetadataMapByName: FieldMetadataMap,
|
||||||
objectMetadataMaps: ObjectMetadataMaps,
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
|
isNewRelationEnabled: boolean,
|
||||||
): Promise<EntitySchemaRelationMap> {
|
): Promise<EntitySchemaRelationMap> {
|
||||||
const entitySchemaRelationMap: EntitySchemaRelationMap = {};
|
const entitySchemaRelationMap: EntitySchemaRelationMap = {};
|
||||||
|
|
||||||
const fieldMetadataCollection = Object.values(fieldMetadataMapByName);
|
const fieldMetadataCollection = Object.values(fieldMetadataMapByName);
|
||||||
|
|
||||||
for (const fieldMetadata of fieldMetadataCollection) {
|
for (const fieldMetadata of fieldMetadataCollection) {
|
||||||
if (!isRelationFieldMetadataType(fieldMetadata.type)) {
|
if (
|
||||||
|
!isFieldMetadataInterfaceOfType(
|
||||||
|
fieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
)
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const relationMetadata =
|
if (!isNewRelationEnabled) {
|
||||||
fieldMetadata.fromRelationMetadata ?? fieldMetadata.toRelationMetadata;
|
const relationMetadata =
|
||||||
|
fieldMetadata.fromRelationMetadata ??
|
||||||
|
fieldMetadata.toRelationMetadata;
|
||||||
|
|
||||||
if (!relationMetadata) {
|
if (!relationMetadata) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Relation metadata is missing for field ${fieldMetadata.name}`,
|
`Relation metadata is missing for field ${fieldMetadata.name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const relationDetails = await determineRelationDetails(
|
||||||
|
fieldMetadata,
|
||||||
|
relationMetadata,
|
||||||
|
objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
entitySchemaRelationMap[fieldMetadata.name] = {
|
||||||
|
type: relationDetails.relationType,
|
||||||
|
target: relationDetails.target,
|
||||||
|
inverseSide: relationDetails.inverseSide,
|
||||||
|
joinColumn: relationDetails.joinColumn,
|
||||||
|
} satisfies EntitySchemaRelationOptions;
|
||||||
|
} else {
|
||||||
|
if (!fieldMetadata.settings) {
|
||||||
|
throw new Error(
|
||||||
|
`Field metadata settings are missing for field ${fieldMetadata.name}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaRelationDetails = await determineSchemaRelationDetails(
|
||||||
|
fieldMetadata,
|
||||||
|
objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
entitySchemaRelationMap[fieldMetadata.name] = {
|
||||||
|
type: schemaRelationDetails.relationType,
|
||||||
|
target: schemaRelationDetails.target,
|
||||||
|
inverseSide: schemaRelationDetails.inverseSide,
|
||||||
|
joinColumn: schemaRelationDetails.joinColumn,
|
||||||
|
} satisfies EntitySchemaRelationOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const relationDetails = await determineRelationDetails(
|
|
||||||
fieldMetadata,
|
|
||||||
relationMetadata,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
entitySchemaRelationMap[fieldMetadata.name] = {
|
|
||||||
type: relationDetails.relationType,
|
|
||||||
target: relationDetails.target,
|
|
||||||
inverseSide: relationDetails.inverseSide,
|
|
||||||
joinColumn: relationDetails.joinColumn,
|
|
||||||
} satisfies EntitySchemaRelationOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return entitySchemaRelationMap;
|
return entitySchemaRelationMap;
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
import { EntitySchema } from 'typeorm';
|
import { EntitySchema } from 'typeorm';
|
||||||
|
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
import { EntitySchemaColumnFactory } from 'src/engine/twenty-orm/factories/entity-schema-column.factory';
|
import { EntitySchemaColumnFactory } from 'src/engine/twenty-orm/factories/entity-schema-column.factory';
|
||||||
@ -14,6 +16,7 @@ export class EntitySchemaFactory {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly entitySchemaColumnFactory: EntitySchemaColumnFactory,
|
private readonly entitySchemaColumnFactory: EntitySchemaColumnFactory,
|
||||||
private readonly entitySchemaRelationFactory: EntitySchemaRelationFactory,
|
private readonly entitySchemaRelationFactory: EntitySchemaRelationFactory,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
@ -22,13 +25,20 @@ export class EntitySchemaFactory {
|
|||||||
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps: ObjectMetadataMaps,
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
): Promise<EntitySchema> {
|
): Promise<EntitySchema> {
|
||||||
|
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsNewRelationEnabled,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
const columns = this.entitySchemaColumnFactory.create(
|
const columns = this.entitySchemaColumnFactory.create(
|
||||||
objectMetadata.fieldsByName,
|
objectMetadata.fieldsByName,
|
||||||
|
isNewRelationEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
const relations = await this.entitySchemaRelationFactory.create(
|
const relations = await this.entitySchemaRelationFactory.create(
|
||||||
objectMetadata.fieldsByName,
|
objectMetadata.fieldsByName,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
isNewRelationEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
const entitySchema = new EntitySchema({
|
const entitySchema = new EntitySchema({
|
||||||
|
|||||||
@ -147,6 +147,7 @@ export class WorkspaceDatasourceFactory {
|
|||||||
{
|
{
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataMaps: cachedObjectMetadataMaps,
|
objectMetadataMaps: cachedObjectMetadataMaps,
|
||||||
|
featureFlagsMap: cachedFeatureFlagMap,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url:
|
url:
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { ObjectType } from 'typeorm';
|
import { ObjectType } from 'typeorm';
|
||||||
|
|
||||||
|
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
import { Gate } from 'src/engine/twenty-orm/interfaces/gate.interface';
|
import { Gate } from 'src/engine/twenty-orm/interfaces/gate.interface';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import {
|
|
||||||
RelationMetadataType,
|
|
||||||
RelationOnDeleteAction,
|
|
||||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
|
||||||
|
|
||||||
export type WorkspaceDynamicRelationMetadataArgsFactory = (
|
export type WorkspaceDynamicRelationMetadataArgsFactory = (
|
||||||
oppositeObjectMetadata: ObjectMetadataEntity,
|
oppositeObjectMetadata: ObjectMetadataEntity,
|
||||||
@ -57,7 +55,7 @@ export interface WorkspaceDynamicRelationMetadataArgs {
|
|||||||
/**
|
/**
|
||||||
* Relation type.
|
* Relation type.
|
||||||
*/
|
*/
|
||||||
readonly type: RelationMetadataType;
|
readonly type: RelationType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relation inverse side target.
|
* Relation inverse side target.
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
|
|
||||||
export interface WorkspaceInternalContext {
|
export interface WorkspaceInternalContext {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
|
featureFlagsMap: Record<FeatureFlagKey, boolean>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { ObjectType } from 'typeorm';
|
import { ObjectType } from 'typeorm';
|
||||||
|
|
||||||
|
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
import { Gate } from 'src/engine/twenty-orm/interfaces/gate.interface';
|
import { Gate } from 'src/engine/twenty-orm/interfaces/gate.interface';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import {
|
|
||||||
RelationMetadataType,
|
|
||||||
RelationOnDeleteAction,
|
|
||||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
|
||||||
|
|
||||||
export interface WorkspaceRelationMetadataArgs {
|
export interface WorkspaceRelationMetadataArgs {
|
||||||
/**
|
/**
|
||||||
@ -33,7 +31,7 @@ export interface WorkspaceRelationMetadataArgs {
|
|||||||
/**
|
/**
|
||||||
* Relation type.
|
* Relation type.
|
||||||
*/
|
*/
|
||||||
readonly type: RelationMetadataType;
|
readonly type: RelationType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relation description.
|
* Relation description.
|
||||||
|
|||||||
@ -715,7 +715,14 @@ export class WorkspaceRepository<
|
|||||||
objectMetadata ??= await this.getObjectMetadataFromTarget();
|
objectMetadata ??= await this.getObjectMetadataFromTarget();
|
||||||
|
|
||||||
const objectMetadataMaps = this.internalContext.objectMetadataMaps;
|
const objectMetadataMaps = this.internalContext.objectMetadataMaps;
|
||||||
|
const isNewRelationEnabled =
|
||||||
|
this.internalContext.featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled];
|
||||||
|
|
||||||
return formatResult(data, objectMetadata, objectMetadataMaps) as T;
|
return formatResult(
|
||||||
|
data,
|
||||||
|
objectMetadata,
|
||||||
|
objectMetadataMaps,
|
||||||
|
isNewRelationEnabled,
|
||||||
|
) as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
|
export const converRelationTypeToTypeORMRelationType = (type: RelationType) => {
|
||||||
|
switch (type) {
|
||||||
|
case RelationType.ONE_TO_MANY:
|
||||||
|
return 'one-to-many';
|
||||||
|
case RelationType.MANY_TO_ONE:
|
||||||
|
return 'many-to-one';
|
||||||
|
case RelationType.ONE_TO_ONE:
|
||||||
|
return 'one-to-one';
|
||||||
|
default:
|
||||||
|
throw new Error(`Invalid relation type: ${type}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
import { RelationType } from 'typeorm/metadata/types/RelationTypes';
|
||||||
|
|
||||||
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
|
||||||
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
|
import { converRelationTypeToTypeORMRelationType } from 'src/engine/twenty-orm/utils/convert-relation-type-to-typeorm-relation-type.util';
|
||||||
|
|
||||||
|
interface RelationDetails {
|
||||||
|
relationType: RelationType;
|
||||||
|
target: string;
|
||||||
|
inverseSide: string;
|
||||||
|
joinColumn: { name: string } | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function determineSchemaRelationDetails(
|
||||||
|
fieldMetadata: FieldMetadataInterface<FieldMetadataType.RELATION>,
|
||||||
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
|
): Promise<RelationDetails> {
|
||||||
|
if (!fieldMetadata.settings) {
|
||||||
|
throw new Error('Field metadata settings are missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const relationType = converRelationTypeToTypeORMRelationType(
|
||||||
|
fieldMetadata.settings.relationType,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fieldMetadata.relationTargetObjectMetadataId) {
|
||||||
|
throw new Error('Relation target object metadata ID is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceObjectMetadata =
|
||||||
|
objectMetadataMaps.byId[fieldMetadata.objectMetadataId];
|
||||||
|
const targetObjectMetadata =
|
||||||
|
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||||
|
|
||||||
|
if (!sourceObjectMetadata || !targetObjectMetadata) {
|
||||||
|
throw new Error('Object metadata not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fieldMetadata.relationTargetFieldMetadataId) {
|
||||||
|
throw new Error('Relation target field metadata ID is missing');
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetFieldMetadata =
|
||||||
|
targetObjectMetadata.fieldsById[
|
||||||
|
fieldMetadata.relationTargetFieldMetadataId
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!targetFieldMetadata) {
|
||||||
|
throw new Error('Target field metadata not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
relationType,
|
||||||
|
target: targetObjectMetadata.nameSingular,
|
||||||
|
inverseSide: targetFieldMetadata.name,
|
||||||
|
joinColumn: fieldMetadata.settings.joinColumnName
|
||||||
|
? { name: fieldMetadata.settings.joinColumnName }
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@ import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-meta
|
|||||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||||
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
export function formatData<T>(
|
export function formatData<T>(
|
||||||
data: T,
|
data: T,
|
||||||
@ -23,9 +24,28 @@ export function formatData<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newData: Record<string, any> = {};
|
const newData: Record<string, any> = {};
|
||||||
|
const fieldMetadataByJoinColumnName =
|
||||||
|
objectMetadataItemWithFieldMaps.fields.reduce((acc, fieldMetadata) => {
|
||||||
|
if (
|
||||||
|
isFieldMetadataInterfaceOfType(
|
||||||
|
fieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const joinColumnName = fieldMetadata.settings?.joinColumnName;
|
||||||
|
|
||||||
|
if (joinColumnName) {
|
||||||
|
acc.set(joinColumnName, fieldMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, new Map<string, FieldMetadataInterface>());
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
const fieldMetadata = objectMetadataItemWithFieldMaps.fieldsByName[key];
|
const fieldMetadata =
|
||||||
|
objectMetadataItemWithFieldMaps.fieldsByName[key] ||
|
||||||
|
fieldMetadataByJoinColumnName.get(key);
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/typ
|
|||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
import { computeRelationType } from 'src/engine/twenty-orm/utils/compute-relation-type.util';
|
import { computeRelationType } from 'src/engine/twenty-orm/utils/compute-relation-type.util';
|
||||||
import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection';
|
import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection';
|
||||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
import { isDate } from 'src/utils/date/isDate';
|
import { isDate } from 'src/utils/date/isDate';
|
||||||
import { isValidDate } from 'src/utils/date/isValidDate';
|
import { isValidDate } from 'src/utils/date/isValidDate';
|
||||||
|
|
||||||
@ -21,6 +21,7 @@ export function formatResult<T>(
|
|||||||
data: any,
|
data: any,
|
||||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps: ObjectMetadataMaps,
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
|
isNewRelationEnabled: boolean,
|
||||||
): T {
|
): T {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return data;
|
return data;
|
||||||
@ -28,7 +29,12 @@ export function formatResult<T>(
|
|||||||
|
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
return data.map((item) =>
|
return data.map((item) =>
|
||||||
formatResult(item, objectMetadataItemWithFieldMaps, objectMetadataMaps),
|
formatResult(
|
||||||
|
item,
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
objectMetadataMaps,
|
||||||
|
isNewRelationEnabled,
|
||||||
|
),
|
||||||
) as T;
|
) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,38 +50,57 @@ export function formatResult<T>(
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
const relationMetadataMap = new Map(
|
const relationMetadataMap: Map<
|
||||||
Object.values(objectMetadataItemWithFieldMaps.fieldsById)
|
string,
|
||||||
.filter(({ type }) => isRelationFieldMetadataType(type))
|
{
|
||||||
.map((fieldMetadata) => [
|
relationMetadata: RelationMetadataEntity | undefined;
|
||||||
fieldMetadata.name,
|
relationType: string;
|
||||||
{
|
}
|
||||||
relationMetadata:
|
> = isNewRelationEnabled
|
||||||
fieldMetadata.fromRelationMetadata ??
|
? new Map()
|
||||||
fieldMetadata.toRelationMetadata,
|
: new Map(
|
||||||
relationType: computeRelationType(
|
Object.values(objectMetadataItemWithFieldMaps.fieldsById)
|
||||||
fieldMetadata,
|
.filter((fieldMetadata) =>
|
||||||
fieldMetadata.fromRelationMetadata ??
|
isFieldMetadataInterfaceOfType(
|
||||||
(fieldMetadata.toRelationMetadata as RelationMetadataEntity),
|
fieldMetadata,
|
||||||
),
|
FieldMetadataType.RELATION,
|
||||||
},
|
),
|
||||||
]),
|
)
|
||||||
);
|
.map((fieldMetadata) => [
|
||||||
|
fieldMetadata.name,
|
||||||
|
{
|
||||||
|
relationMetadata:
|
||||||
|
fieldMetadata.fromRelationMetadata ??
|
||||||
|
fieldMetadata.toRelationMetadata,
|
||||||
|
relationType: computeRelationType(
|
||||||
|
fieldMetadata,
|
||||||
|
fieldMetadata.fromRelationMetadata ??
|
||||||
|
(fieldMetadata.toRelationMetadata as RelationMetadataEntity),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
const newData: object = {};
|
const newData: object = {};
|
||||||
const objectMetadaItemFieldsByName =
|
const objectMetadaItemFieldsByName =
|
||||||
objectMetadataMaps.byId[objectMetadataItemWithFieldMaps.id]?.fieldsByName;
|
objectMetadataMaps.byId[objectMetadataItemWithFieldMaps.id]?.fieldsByName;
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
for (const [key, value] of Object.entries(data)) {
|
||||||
const compositePropertyArgs = compositeFieldMetadataMap.get(key);
|
const compositePropertyArgs = compositeFieldMetadataMap.get(key);
|
||||||
const { relationMetadata, relationType } =
|
const fieldMetadata = objectMetadataItemWithFieldMaps.fieldsById[key];
|
||||||
relationMetadataMap.get(key) ?? {};
|
const isRelation = fieldMetadata
|
||||||
|
? isFieldMetadataInterfaceOfType(
|
||||||
|
fieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
|
||||||
if (!compositePropertyArgs && !relationMetadata) {
|
if (!compositePropertyArgs && !isRelation) {
|
||||||
if (isPlainObject(value)) {
|
if (isPlainObject(value)) {
|
||||||
newData[key] = formatResult(
|
newData[key] = formatResult(
|
||||||
value,
|
value,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
isNewRelationEnabled,
|
||||||
);
|
);
|
||||||
} else if (objectMetadaItemFieldsByName[key]) {
|
} else if (objectMetadaItemFieldsByName[key]) {
|
||||||
newData[key] = formatFieldMetadataValue(
|
newData[key] = formatFieldMetadataValue(
|
||||||
@ -89,31 +114,63 @@ export function formatResult<T>(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relationMetadata) {
|
if (!isNewRelationEnabled) {
|
||||||
const toObjectMetadata =
|
const { relationMetadata, relationType } =
|
||||||
objectMetadataMaps.byId[relationMetadata.toObjectMetadataId];
|
relationMetadataMap.get(key) ?? {};
|
||||||
|
|
||||||
const fromObjectMetadata =
|
if (relationMetadata) {
|
||||||
objectMetadataMaps.byId[relationMetadata.fromObjectMetadataId];
|
const toObjectMetadata =
|
||||||
|
objectMetadataMaps.byId[relationMetadata.toObjectMetadataId];
|
||||||
|
|
||||||
if (!toObjectMetadata) {
|
const fromObjectMetadata =
|
||||||
throw new Error(
|
objectMetadataMaps.byId[relationMetadata.fromObjectMetadataId];
|
||||||
`Object metadata for object metadataId "${relationMetadata.toObjectMetadataId}" is missing`,
|
|
||||||
|
if (!toObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`Object metadata for object metadataId "${relationMetadata.toObjectMetadataId}" is missing`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fromObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`Object metadata for object metadataId "${relationMetadata.fromObjectMetadataId}" is missing`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
newData[key] = formatResult(
|
||||||
|
value,
|
||||||
|
relationType === 'one-to-many'
|
||||||
|
? toObjectMetadata
|
||||||
|
: fromObjectMetadata,
|
||||||
|
objectMetadataMaps,
|
||||||
|
isNewRelationEnabled,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isRelation) {
|
||||||
|
if (!fieldMetadata.relationTargetObjectMetadataId) {
|
||||||
|
throw new Error(
|
||||||
|
`Relation target object metadata ID is missing for field "${key}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetObjectMetadata =
|
||||||
|
objectMetadataMaps.byId[fieldMetadata.relationTargetObjectMetadataId];
|
||||||
|
|
||||||
|
if (!targetObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`Object metadata for object metadataId "${fieldMetadata.relationTargetObjectMetadataId}" is missing`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
newData[key] = formatResult(
|
||||||
|
value,
|
||||||
|
targetObjectMetadata,
|
||||||
|
objectMetadataMaps,
|
||||||
|
isNewRelationEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fromObjectMetadata) {
|
|
||||||
throw new Error(
|
|
||||||
`Object metadata for object metadataId "${relationMetadata.fromObjectMetadataId}" is missing`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
newData[key] = formatResult(
|
|
||||||
value,
|
|
||||||
relationType === 'one-to-many' ? toObjectMetadata : fromObjectMetadata,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!compositePropertyArgs) {
|
if (!compositePropertyArgs) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
import { WorkspaceJoinColumnsMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-join-columns-metadata-args.interface';
|
import { WorkspaceJoinColumnsMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-join-columns-metadata-args.interface';
|
||||||
import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface';
|
import { WorkspaceRelationMetadataArgs } from 'src/engine/twenty-orm/interfaces/workspace-relation-metadata-args.interface';
|
||||||
|
|
||||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
|
||||||
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
|
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
|
||||||
|
|
||||||
export const getJoinColumn = (
|
export const getJoinColumn = (
|
||||||
@ -9,10 +9,7 @@ export const getJoinColumn = (
|
|||||||
relationMetadataArgs: WorkspaceRelationMetadataArgs,
|
relationMetadataArgs: WorkspaceRelationMetadataArgs,
|
||||||
opposite = false,
|
opposite = false,
|
||||||
): string | null => {
|
): string | null => {
|
||||||
if (
|
if (relationMetadataArgs.type === RelationType.ONE_TO_MANY) {
|
||||||
relationMetadataArgs.type === RelationMetadataType.ONE_TO_MANY ||
|
|
||||||
relationMetadataArgs.type === RelationMetadataType.MANY_TO_MANY
|
|
||||||
) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,7 +38,7 @@ export const getJoinColumn = (
|
|||||||
|
|
||||||
// If we're in a ONE_TO_ONE relation and there are no join columns, we need to find the join column on the inverse side
|
// If we're in a ONE_TO_ONE relation and there are no join columns, we need to find the join column on the inverse side
|
||||||
if (
|
if (
|
||||||
relationMetadataArgs.type === RelationMetadataType.ONE_TO_ONE &&
|
relationMetadataArgs.type === RelationType.ONE_TO_ONE &&
|
||||||
filteredJoinColumnsMetadataArgsCollection.length === 0 &&
|
filteredJoinColumnsMetadataArgsCollection.length === 0 &&
|
||||||
!opposite
|
!opposite
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -4,25 +4,22 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada
|
|||||||
|
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
|
||||||
export function isFieldMetadataOfType<
|
export function isFieldMetadataInterfaceOfType<
|
||||||
Field extends FieldMetadataInterface<FieldMetadataType>,
|
Field extends FieldMetadataInterface<FieldMetadataType>,
|
||||||
Type extends FieldMetadataType,
|
Type extends FieldMetadataType,
|
||||||
>(
|
>(
|
||||||
fieldMetadata: Field,
|
fieldMetadata: Field,
|
||||||
type: Type,
|
type: Type,
|
||||||
): fieldMetadata is Field & FieldMetadataInterface<Type>;
|
): fieldMetadata is Field & FieldMetadataInterface<Type> {
|
||||||
export function isFieldMetadataOfType<
|
return fieldMetadata.type === type;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFieldMetadataEntityOfType<
|
||||||
Field extends FieldMetadataEntity<FieldMetadataType>,
|
Field extends FieldMetadataEntity<FieldMetadataType>,
|
||||||
Type extends FieldMetadataType,
|
Type extends FieldMetadataType,
|
||||||
>(
|
>(
|
||||||
fieldMetadata: Field,
|
fieldMetadata: Field,
|
||||||
type: Type,
|
type: Type,
|
||||||
): fieldMetadata is Field & FieldMetadataEntity<Type>;
|
): fieldMetadata is Field & FieldMetadataEntity<Type> {
|
||||||
export function isFieldMetadataOfType<
|
|
||||||
Field extends
|
|
||||||
| FieldMetadataInterface<FieldMetadataType>
|
|
||||||
| FieldMetadataEntity<FieldMetadataType>,
|
|
||||||
Type extends FieldMetadataType,
|
|
||||||
>(fieldMetadata: Field, type: Type): boolean {
|
|
||||||
return fieldMetadata.type === type;
|
return fieldMetadata.type === type;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,9 +93,13 @@ export class WorkspaceManagerService {
|
|||||||
schemaName,
|
schemaName,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const featureFlags =
|
||||||
|
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||||
|
|
||||||
await this.workspaceSyncMetadataService.synchronize({
|
await this.workspaceSyncMetadataService.synchronize({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
dataSourceId: dataSourceMetadata.id,
|
dataSourceId: dataSourceMetadata.id,
|
||||||
|
featureFlags,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dataSourceMetadataCreationEnd = performance.now();
|
const dataSourceMetadataCreationEnd = performance.now();
|
||||||
@ -145,9 +149,13 @@ export class WorkspaceManagerService {
|
|||||||
schemaName,
|
schemaName,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const featureFlags =
|
||||||
|
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||||
|
|
||||||
await this.workspaceSyncMetadataService.synchronize({
|
await this.workspaceSyncMetadataService.synchronize({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
dataSourceId: dataSourceMetadata.id,
|
dataSourceId: dataSourceMetadata.id,
|
||||||
|
featureFlags,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.prefillWorkspaceWithDemoObjects(dataSourceMetadata, workspaceId);
|
await this.prefillWorkspaceWithDemoObjects(dataSourceMetadata, workspaceId);
|
||||||
@ -165,9 +173,13 @@ export class WorkspaceManagerService {
|
|||||||
schemaName,
|
schemaName,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const featureFlags =
|
||||||
|
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||||
|
|
||||||
await this.workspaceSyncMetadataService.synchronize({
|
await this.workspaceSyncMetadataService.synchronize({
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
dataSourceId: dataSourceMetadata.id,
|
dataSourceId: dataSourceMetadata.id,
|
||||||
|
featureFlags,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.initPermissionsDev(workspaceId);
|
await this.initPermissionsDev(workspaceId);
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import { WorkspaceMigrationIndexFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-index.factory';
|
import { WorkspaceMigrationIndexFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-index.factory';
|
||||||
|
|
||||||
import { WorkspaceMigrationObjectFactory } from './workspace-migration-object.factory';
|
import { WorkspaceMigrationFieldRelationFactory } from './workspace-migration-field-relation.factory';
|
||||||
import { WorkspaceMigrationFieldFactory } from './workspace-migration-field.factory';
|
import { WorkspaceMigrationFieldFactory } from './workspace-migration-field.factory';
|
||||||
|
import { WorkspaceMigrationObjectFactory } from './workspace-migration-object.factory';
|
||||||
import { WorkspaceMigrationRelationFactory } from './workspace-migration-relation.factory';
|
import { WorkspaceMigrationRelationFactory } from './workspace-migration-relation.factory';
|
||||||
|
|
||||||
export const workspaceMigrationBuilderFactories = [
|
export const workspaceMigrationBuilderFactories = [
|
||||||
WorkspaceMigrationObjectFactory,
|
WorkspaceMigrationObjectFactory,
|
||||||
WorkspaceMigrationFieldFactory,
|
WorkspaceMigrationFieldFactory,
|
||||||
|
WorkspaceMigrationFieldRelationFactory,
|
||||||
WorkspaceMigrationRelationFactory,
|
WorkspaceMigrationRelationFactory,
|
||||||
WorkspaceMigrationIndexFactory,
|
WorkspaceMigrationIndexFactory,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -0,0 +1,388 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||||
|
|
||||||
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
||||||
|
import {
|
||||||
|
WorkspaceMigrationColumnActionType,
|
||||||
|
WorkspaceMigrationEntity,
|
||||||
|
WorkspaceMigrationTableAction,
|
||||||
|
WorkspaceMigrationTableActionType,
|
||||||
|
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||||
|
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
|
||||||
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
|
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
import { FieldMetadataUpdate } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory';
|
||||||
|
import { camelCase } from 'src/utils/camel-case';
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspaceMigrationFieldRelationFactory {
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(
|
||||||
|
originalObjectMetadataCollection: ObjectMetadataEntity[],
|
||||||
|
fieldMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||||
|
action:
|
||||||
|
| WorkspaceMigrationBuilderAction.CREATE
|
||||||
|
| WorkspaceMigrationBuilderAction.DELETE,
|
||||||
|
): Promise<Partial<WorkspaceMigrationEntity>[]>;
|
||||||
|
|
||||||
|
async create(
|
||||||
|
originalObjectMetadataCollection: ObjectMetadataEntity[],
|
||||||
|
fieldMetadataUpdateCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[],
|
||||||
|
action: WorkspaceMigrationBuilderAction.UPDATE,
|
||||||
|
): Promise<Partial<WorkspaceMigrationEntity>[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletion of the relation is handled by field deletion
|
||||||
|
*/
|
||||||
|
async create(
|
||||||
|
originalObjectMetadataCollection: ObjectMetadataEntity[],
|
||||||
|
fieldMetadataCollectionOrFieldMetadataUpdateCollection:
|
||||||
|
| FieldMetadataEntity<FieldMetadataType.RELATION>[]
|
||||||
|
| FieldMetadataUpdate<FieldMetadataType.RELATION>[],
|
||||||
|
action: WorkspaceMigrationBuilderAction,
|
||||||
|
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||||
|
const originalObjectMetadataMap = originalObjectMetadataCollection.reduce(
|
||||||
|
(result, currentObject) => {
|
||||||
|
result[currentObject.id] = currentObject;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
{} as Record<string, ObjectMetadataEntity>,
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case WorkspaceMigrationBuilderAction.CREATE:
|
||||||
|
return this.createFieldRelationMigration(
|
||||||
|
originalObjectMetadataMap,
|
||||||
|
fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||||
|
);
|
||||||
|
case WorkspaceMigrationBuilderAction.UPDATE:
|
||||||
|
return this.updateFieldRelationMigration(
|
||||||
|
originalObjectMetadataMap,
|
||||||
|
fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataUpdate<FieldMetadataType.RELATION>[],
|
||||||
|
);
|
||||||
|
case WorkspaceMigrationBuilderAction.DELETE:
|
||||||
|
return this.deleteFieldRelationMigration(
|
||||||
|
originalObjectMetadataMap,
|
||||||
|
fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateFieldRelationMigration(
|
||||||
|
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||||
|
fieldMetadataUpdateCollection: FieldMetadataUpdate<FieldMetadataType.RELATION>[],
|
||||||
|
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||||
|
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||||
|
|
||||||
|
for (const {
|
||||||
|
altered: sourceFieldMetadata,
|
||||||
|
} of fieldMetadataUpdateCollection) {
|
||||||
|
const sourceObjectMetadata =
|
||||||
|
originalObjectMetadataMap[sourceFieldMetadata.objectMetadataId];
|
||||||
|
const targetObjectMetadata =
|
||||||
|
originalObjectMetadataMap[
|
||||||
|
sourceFieldMetadata.relationTargetObjectMetadataId
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!sourceObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`ObjectMetadata with id ${sourceFieldMetadata.objectMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`ObjectMetadata with id ${sourceFieldMetadata.relationTargetObjectMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetFieldMetadata = targetObjectMetadata.fields.find(
|
||||||
|
(field) =>
|
||||||
|
field.id === sourceFieldMetadata.relationTargetFieldMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!targetFieldMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isFieldMetadataEntityOfType(
|
||||||
|
targetFieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} is not a relation`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetFieldMetadata.settings) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrations: WorkspaceMigrationTableAction[] = [
|
||||||
|
{
|
||||||
|
name: computeObjectTargetTable(targetObjectMetadata),
|
||||||
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY,
|
||||||
|
columnName: `${camelCase(targetFieldMetadata.name)}Id`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: computeObjectTargetTable(targetObjectMetadata),
|
||||||
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
||||||
|
columnName: `${camelCase(targetFieldMetadata.name)}Id`,
|
||||||
|
referencedTableName:
|
||||||
|
computeObjectTargetTable(sourceObjectMetadata),
|
||||||
|
referencedTableColumnName: 'id',
|
||||||
|
isUnique:
|
||||||
|
targetFieldMetadata.settings.relationType ===
|
||||||
|
RelationType.ONE_TO_ONE,
|
||||||
|
onDelete: targetFieldMetadata.settings.onDelete,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
workspaceMigrations.push({
|
||||||
|
workspaceId: sourceFieldMetadata.workspaceId,
|
||||||
|
name: generateMigrationName(
|
||||||
|
`update-relation-from-${sourceObjectMetadata.nameSingular}-to-${targetObjectMetadata.nameSingular}`,
|
||||||
|
),
|
||||||
|
isCustom: false,
|
||||||
|
migrations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspaceMigrations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createFieldRelationMigration(
|
||||||
|
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||||
|
fieldRelationMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||||
|
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||||
|
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||||
|
|
||||||
|
for (const sourceFieldMetadata of fieldRelationMetadataCollection) {
|
||||||
|
const sourceObjectMetadata =
|
||||||
|
originalObjectMetadataMap[sourceFieldMetadata.objectMetadataId];
|
||||||
|
const targetObjectMetadata =
|
||||||
|
originalObjectMetadataMap[
|
||||||
|
sourceFieldMetadata.relationTargetObjectMetadataId
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!sourceFieldMetadata.settings) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're creating it from `ONE_TO_MANY` with the join column so we don't need to create a migration for `MANY_TO_ONE`
|
||||||
|
if (
|
||||||
|
sourceFieldMetadata.settings.relationType === RelationType.MANY_TO_ONE
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sourceObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`ObjectMetadata with id ${sourceFieldMetadata.objectMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`ObjectMetadata with id ${sourceFieldMetadata.relationTargetObjectMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetFieldMetadata = targetObjectMetadata.fields.find(
|
||||||
|
(field) =>
|
||||||
|
field.id === sourceFieldMetadata.relationTargetFieldMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!targetFieldMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isFieldMetadataEntityOfType(
|
||||||
|
targetFieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} is not a relation`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetFieldMetadata.settings) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetFieldMetadata.settings.joinColumnName) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrations: WorkspaceMigrationTableAction[] = [
|
||||||
|
{
|
||||||
|
name: computeObjectTargetTable(targetObjectMetadata),
|
||||||
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
|
columns: [
|
||||||
|
...this.workspaceMigrationFactory.createColumnActions(
|
||||||
|
WorkspaceMigrationColumnActionType.CREATE,
|
||||||
|
targetFieldMetadata,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: computeObjectTargetTable(targetObjectMetadata),
|
||||||
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.CREATE_FOREIGN_KEY,
|
||||||
|
columnName:
|
||||||
|
targetFieldMetadata.settings.joinColumnName ??
|
||||||
|
`${camelCase(targetFieldMetadata.name)}Id`,
|
||||||
|
referencedTableName:
|
||||||
|
computeObjectTargetTable(sourceObjectMetadata),
|
||||||
|
referencedTableColumnName: 'id',
|
||||||
|
isUnique:
|
||||||
|
targetFieldMetadata.settings.relationType ===
|
||||||
|
RelationType.ONE_TO_ONE,
|
||||||
|
onDelete: targetFieldMetadata.settings.onDelete,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
workspaceMigrations.push({
|
||||||
|
workspaceId: sourceFieldMetadata.workspaceId,
|
||||||
|
name: generateMigrationName(
|
||||||
|
`create-relation-from-${sourceObjectMetadata.nameSingular}-to-${targetObjectMetadata.nameSingular}`,
|
||||||
|
),
|
||||||
|
isCustom: false,
|
||||||
|
migrations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspaceMigrations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async deleteFieldRelationMigration(
|
||||||
|
originalObjectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||||
|
fieldRelationMetadataCollection: FieldMetadataEntity<FieldMetadataType.RELATION>[],
|
||||||
|
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||||
|
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||||
|
|
||||||
|
for (const sourceFieldMetadata of fieldRelationMetadataCollection) {
|
||||||
|
const sourceObjectMetadata =
|
||||||
|
originalObjectMetadataMap[sourceFieldMetadata.objectMetadataId];
|
||||||
|
const targetObjectMetadata =
|
||||||
|
originalObjectMetadataMap[
|
||||||
|
sourceFieldMetadata.relationTargetObjectMetadataId
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!sourceObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`ObjectMetadata with id ${sourceFieldMetadata.objectMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`ObjectMetadata with id ${sourceFieldMetadata.relationTargetObjectMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetFieldMetadata = targetObjectMetadata.fields.find(
|
||||||
|
(field) =>
|
||||||
|
field.id === sourceFieldMetadata.relationTargetFieldMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!targetFieldMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} not found`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isFieldMetadataEntityOfType(
|
||||||
|
targetFieldMetadata,
|
||||||
|
FieldMetadataType.RELATION,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata with id ${sourceFieldMetadata.relationTargetFieldMetadataId} is not a relation`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetFieldMetadata.settings) {
|
||||||
|
throw new Error(
|
||||||
|
`FieldMetadata for relation with id ${sourceFieldMetadata.id} has no settings`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrations: WorkspaceMigrationTableAction[] = [
|
||||||
|
{
|
||||||
|
name: computeObjectTargetTable(targetObjectMetadata),
|
||||||
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY,
|
||||||
|
columnName: `${camelCase(targetFieldMetadata.name)}Id`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: computeObjectTargetTable(targetObjectMetadata),
|
||||||
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
action: WorkspaceMigrationColumnActionType.DROP,
|
||||||
|
columnName:
|
||||||
|
targetFieldMetadata.settings.joinColumnName ??
|
||||||
|
`${camelCase(targetFieldMetadata.name)}Id`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
workspaceMigrations.push({
|
||||||
|
workspaceId: sourceFieldMetadata.workspaceId,
|
||||||
|
name: generateMigrationName(
|
||||||
|
`update-relation-from-${sourceObjectMetadata.nameSingular}-to-${targetObjectMetadata.nameSingular}`,
|
||||||
|
),
|
||||||
|
isCustom: false,
|
||||||
|
migrations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspaceMigrations;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
|||||||
|
|
||||||
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||||
|
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
||||||
@ -18,15 +20,18 @@ import {
|
|||||||
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
|
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
|
||||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
|
|
||||||
export interface FieldMetadataUpdate {
|
export interface FieldMetadataUpdate<
|
||||||
current: FieldMetadataEntity;
|
Type extends FieldMetadataType = FieldMetadataType,
|
||||||
altered: FieldMetadataEntity;
|
> {
|
||||||
|
current: FieldMetadataEntity<Type>;
|
||||||
|
altered: FieldMetadataEntity<Type>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceMigrationFieldFactory {
|
export class WorkspaceMigrationFieldFactory {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
@ -86,6 +91,17 @@ export class WorkspaceMigrationFieldFactory {
|
|||||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||||
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||||
|
|
||||||
|
if (fieldMetadataCollection.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceId = fieldMetadataCollection[0]?.workspaceId;
|
||||||
|
|
||||||
|
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsNewRelationEnabled,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
const fieldMetadataCollectionGroupByObjectMetadataId =
|
const fieldMetadataCollectionGroupByObjectMetadataId =
|
||||||
fieldMetadataCollection.reduce(
|
fieldMetadataCollection.reduce(
|
||||||
(result, currentFieldMetadata) => {
|
(result, currentFieldMetadata) => {
|
||||||
@ -110,7 +126,10 @@ export class WorkspaceMigrationFieldFactory {
|
|||||||
|
|
||||||
for (const fieldMetadata of fieldMetadataCollection) {
|
for (const fieldMetadata of fieldMetadataCollection) {
|
||||||
// Relations are handled in workspace-migration-relation.factory.ts
|
// Relations are handled in workspace-migration-relation.factory.ts
|
||||||
if (fieldMetadata.type === FieldMetadataType.RELATION) {
|
if (
|
||||||
|
!isNewRelationEnabled &&
|
||||||
|
fieldMetadata.type === FieldMetadataType.RELATION
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +146,7 @@ export class WorkspaceMigrationFieldFactory {
|
|||||||
name: generateMigrationName(
|
name: generateMigrationName(
|
||||||
`create-${objectMetadata.nameSingular}-fields`,
|
`create-${objectMetadata.nameSingular}-fields`,
|
||||||
),
|
),
|
||||||
isCustom: false,
|
isCustom: objectMetadata.isCustom,
|
||||||
migrations: [
|
migrations: [
|
||||||
{
|
{
|
||||||
name: computeObjectTargetTable(
|
name: computeObjectTargetTable(
|
||||||
@ -149,9 +168,23 @@ export class WorkspaceMigrationFieldFactory {
|
|||||||
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
): Promise<Partial<WorkspaceMigrationEntity>[]> {
|
||||||
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
|
||||||
|
|
||||||
|
if (fieldMetadataUpdateCollection.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceId = fieldMetadataUpdateCollection[0]?.current.workspaceId;
|
||||||
|
|
||||||
|
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsNewRelationEnabled,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
for (const fieldMetadataUpdate of fieldMetadataUpdateCollection) {
|
for (const fieldMetadataUpdate of fieldMetadataUpdateCollection) {
|
||||||
// Skip relations, because they're just representation and not real columns
|
// Skip relations, because they're just representation and not real columns
|
||||||
if (fieldMetadataUpdate.altered.type === FieldMetadataType.RELATION) {
|
if (
|
||||||
|
!isNewRelationEnabled &&
|
||||||
|
fieldMetadataUpdate.altered.type === FieldMetadataType.RELATION
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +244,7 @@ export class WorkspaceMigrationFieldFactory {
|
|||||||
name: generateMigrationName(
|
name: generateMigrationName(
|
||||||
`update-${fieldMetadataUpdate.altered.name}`,
|
`update-${fieldMetadataUpdate.altered.name}`,
|
||||||
),
|
),
|
||||||
isCustom: false,
|
isCustom: fieldMetadataUpdate.altered.isCustom,
|
||||||
migrations,
|
migrations,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -250,7 +283,7 @@ export class WorkspaceMigrationFieldFactory {
|
|||||||
workspaceMigrations.push({
|
workspaceMigrations.push({
|
||||||
workspaceId: fieldMetadata.workspaceId,
|
workspaceId: fieldMetadata.workspaceId,
|
||||||
name: generateMigrationName(`delete-${fieldMetadata.name}`),
|
name: generateMigrationName(`delete-${fieldMetadata.name}`),
|
||||||
isCustom: false,
|
isCustom: fieldMetadata.isCustom,
|
||||||
migrations,
|
migrations,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,7 +109,7 @@ export class WorkspaceMigrationObjectFactory {
|
|||||||
workspaceMigrations.push({
|
workspaceMigrations.push({
|
||||||
workspaceId: objectMetadata.workspaceId,
|
workspaceId: objectMetadata.workspaceId,
|
||||||
name: generateMigrationName(`create-${objectMetadata.nameSingular}`),
|
name: generateMigrationName(`create-${objectMetadata.nameSingular}`),
|
||||||
isCustom: false,
|
isCustom: objectMetadata.isCustom,
|
||||||
migrations,
|
migrations,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ export class WorkspaceMigrationObjectFactory {
|
|||||||
name: generateMigrationName(
|
name: generateMigrationName(
|
||||||
`rename-${objectMetadataUpdate.current.nameSingular}`,
|
`rename-${objectMetadataUpdate.current.nameSingular}`,
|
||||||
),
|
),
|
||||||
isCustom: false,
|
isCustom: objectMetadataUpdate.altered.isCustom,
|
||||||
migrations: [
|
migrations: [
|
||||||
{
|
{
|
||||||
name: oldTableName,
|
name: oldTableName,
|
||||||
@ -167,7 +167,7 @@ export class WorkspaceMigrationObjectFactory {
|
|||||||
workspaceMigrations.push({
|
workspaceMigrations.push({
|
||||||
workspaceId: objectMetadata.workspaceId,
|
workspaceId: objectMetadata.workspaceId,
|
||||||
name: generateMigrationName(`delete-${objectMetadata.nameSingular}`),
|
name: generateMigrationName(`delete-${objectMetadata.nameSingular}`),
|
||||||
isCustom: false,
|
isCustom: objectMetadata.isCustom,
|
||||||
migrations: [
|
migrations: [
|
||||||
...(relationMetadataCollection ?? []).map(
|
...(relationMetadataCollection ?? []).map(
|
||||||
(relationMetadata) =>
|
(relationMetadata) =>
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||||
|
|
||||||
import { workspaceMigrationBuilderFactories } from './factories';
|
import { workspaceMigrationBuilderFactories } from './factories';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [WorkspaceMigrationModule],
|
imports: [WorkspaceMigrationModule, FeatureFlagModule],
|
||||||
providers: [...workspaceMigrationBuilderFactories],
|
providers: [...workspaceMigrationBuilderFactories],
|
||||||
exports: [...workspaceMigrationBuilderFactories],
|
exports: [...workspaceMigrationBuilderFactories],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -3,6 +3,10 @@ import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-met
|
|||||||
export const convertOnDeleteActionToOnDelete = (
|
export const convertOnDeleteActionToOnDelete = (
|
||||||
onDeleteAction: RelationOnDeleteAction | undefined,
|
onDeleteAction: RelationOnDeleteAction | undefined,
|
||||||
): 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | undefined => {
|
): 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | undefined => {
|
||||||
|
if (!onDeleteAction) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
switch (onDeleteAction) {
|
switch (onDeleteAction) {
|
||||||
case 'CASCADE':
|
case 'CASCADE':
|
||||||
return 'CASCADE';
|
return 'CASCADE';
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||||
RunOnWorkspaceArgs,
|
RunOnWorkspaceArgs,
|
||||||
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
@ -25,6 +26,7 @@ export class SyncWorkspaceMetadataCommand extends ActiveOrSuspendedWorkspacesMig
|
|||||||
private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService,
|
private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService,
|
||||||
private readonly dataSourceService: DataSourceService,
|
private readonly dataSourceService: DataSourceService,
|
||||||
private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService,
|
private readonly syncWorkspaceLoggerService: SyncWorkspaceLoggerService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
) {
|
) {
|
||||||
super(workspaceRepository, twentyORMGlobalManager);
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
@ -45,11 +47,15 @@ export class SyncWorkspaceMetadataCommand extends ActiveOrSuspendedWorkspacesMig
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const featureFlags =
|
||||||
|
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||||
|
|
||||||
const { storage, workspaceMigrations } =
|
const { storage, workspaceMigrations } =
|
||||||
await this.workspaceSyncMetadataService.synchronize(
|
await this.workspaceSyncMetadataService.synchronize(
|
||||||
{
|
{
|
||||||
workspaceId,
|
workspaceId,
|
||||||
dataSourceId: dataSourceMetadata.id,
|
dataSourceId: dataSourceMetadata.id,
|
||||||
|
featureFlags,
|
||||||
},
|
},
|
||||||
{ applyChanges: !options.dryRun },
|
{ applyChanges: !options.dryRun },
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
@ -19,6 +20,7 @@ import { SyncWorkspaceMetadataCommand } from './sync-workspace-metadata.command'
|
|||||||
WorkspaceModule,
|
WorkspaceModule,
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
WorkspaceDataSourceModule,
|
WorkspaceDataSourceModule,
|
||||||
|
FeatureFlagModule,
|
||||||
TypeOrmModule.forFeature([Workspace], 'core'),
|
TypeOrmModule.forFeature([Workspace], 'core'),
|
||||||
SyncWorkspaceLoggerModule,
|
SyncWorkspaceLoggerModule,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { WorkspaceFieldRelationComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field-relation.comparator';
|
||||||
import { WorkspaceIndexComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator';
|
import { WorkspaceIndexComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator';
|
||||||
|
|
||||||
import { WorkspaceFieldComparator } from './workspace-field.comparator';
|
import { WorkspaceFieldComparator } from './workspace-field.comparator';
|
||||||
@ -6,6 +7,7 @@ import { WorkspaceRelationComparator } from './workspace-relation.comparator';
|
|||||||
|
|
||||||
export const workspaceSyncMetadataComparators = [
|
export const workspaceSyncMetadataComparators = [
|
||||||
WorkspaceFieldComparator,
|
WorkspaceFieldComparator,
|
||||||
|
WorkspaceFieldRelationComparator,
|
||||||
WorkspaceObjectComparator,
|
WorkspaceObjectComparator,
|
||||||
WorkspaceRelationComparator,
|
WorkspaceRelationComparator,
|
||||||
WorkspaceIndexComparator,
|
WorkspaceIndexComparator,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user