@ -20,6 +20,7 @@ import {
|
||||
} from 'src/core/feature-flag/feature-flag.entity';
|
||||
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
import DeleteInactiveWorkspaceEmail from 'src/workspace/cron/clean-inactive-workspaces/delete-inactive-workspaces.email';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
const MILLISECONDS_IN_ONE_DAY = 1000 * 3600 * 24;
|
||||
|
||||
@ -54,7 +55,7 @@ export class CleanInactiveWorkspaceJob implements MessageQueueJob<undefined> {
|
||||
(objectMetadata) =>
|
||||
objectMetadata.workspaceId === dataSource.workspaceId,
|
||||
)
|
||||
.map((objectMetadata) => objectMetadata.targetTableName);
|
||||
.map((objectMetadata) => computeObjectTargetTable(objectMetadata));
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSource);
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
export const computeObjectTargetTable = (
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
) => {
|
||||
const prefix = objectMetadata.isCustom ? '_' : '';
|
||||
|
||||
return `${prefix}${objectMetadata.nameSingular}`;
|
||||
};
|
||||
@ -9,6 +9,7 @@ import { WorkspaceHealthOptions } from 'src/workspace/workspace-health/interface
|
||||
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
|
||||
import { validName } from 'src/workspace/workspace-health/utils/valid-name.util';
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectMetadataHealthService {
|
||||
@ -54,14 +55,17 @@ export class ObjectMetadataHealthService {
|
||||
|
||||
// Check if the table exist in database
|
||||
const tableExist = await mainDataSource.query(
|
||||
`SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = '${schemaName}' AND table_name = '${objectMetadata.targetTableName}')`,
|
||||
`SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = '${schemaName}'
|
||||
AND table_name = '${computeObjectTargetTable(objectMetadata)}')`,
|
||||
);
|
||||
|
||||
if (!tableExist) {
|
||||
issues.push({
|
||||
type: WorkspaceHealthIssueType.MISSING_TABLE,
|
||||
objectMetadata,
|
||||
message: `Table ${objectMetadata.targetTableName} not found in schema ${schemaName}`,
|
||||
message: `Table ${computeObjectTargetTable(
|
||||
objectMetadata,
|
||||
)} not found in schema ${schemaName}`,
|
||||
});
|
||||
|
||||
return issues;
|
||||
@ -80,30 +84,13 @@ export class ObjectMetadataHealthService {
|
||||
): WorkspaceHealthIssue[] {
|
||||
const issues: WorkspaceHealthIssue[] = [];
|
||||
|
||||
if (
|
||||
objectMetadata.isCustom &&
|
||||
!objectMetadata.targetTableName.startsWith('_')
|
||||
) {
|
||||
issues.push({
|
||||
type: WorkspaceHealthIssueType.TABLE_NAME_SHOULD_BE_CUSTOM,
|
||||
objectMetadata,
|
||||
message: `Table ${objectMetadata.targetTableName} is marked as custom but doesn't start with "_"`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!objectMetadata.targetTableName) {
|
||||
issues.push({
|
||||
type: WorkspaceHealthIssueType.TABLE_TARGET_TABLE_NAME_NOT_VALID,
|
||||
objectMetadata,
|
||||
message: `Table ${objectMetadata.targetTableName} doesn't have a valid target table name`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!objectMetadata.dataSourceId) {
|
||||
issues.push({
|
||||
type: WorkspaceHealthIssueType.TABLE_DATA_SOURCE_ID_NOT_VALID,
|
||||
objectMetadata,
|
||||
message: `Table ${objectMetadata.targetTableName} doesn't have a data source`,
|
||||
message: `Table ${computeObjectTargetTable(
|
||||
objectMetadata,
|
||||
)} doesn't have a data source`,
|
||||
});
|
||||
}
|
||||
|
||||
@ -118,7 +105,9 @@ export class ObjectMetadataHealthService {
|
||||
issues.push({
|
||||
type: WorkspaceHealthIssueType.TABLE_NAME_NOT_VALID,
|
||||
objectMetadata,
|
||||
message: `Table ${objectMetadata.targetTableName} doesn't have a valid name or label`,
|
||||
message: `Table ${computeObjectTargetTable(
|
||||
objectMetadata,
|
||||
)} doesn't have a valid name or label`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metad
|
||||
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||
import { ObjectMetadataHealthService } from 'src/workspace/workspace-health/services/object-metadata-health.service';
|
||||
import { FieldMetadataHealthService } from 'src/workspace/workspace-health/services/field-metadata-health.service';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceHealthService {
|
||||
@ -68,7 +69,7 @@ export class WorkspaceHealthService {
|
||||
// Check fields metadata health
|
||||
const fieldIssues = await this.fieldMetadataHealthService.healthCheck(
|
||||
schemaName,
|
||||
objectMetadata.targetTableName,
|
||||
computeObjectTargetTable(objectMetadata),
|
||||
objectMetadata.fields,
|
||||
options,
|
||||
);
|
||||
|
||||
@ -1,237 +0,0 @@
|
||||
// import { GraphQLResolveInfo } from 'graphql';
|
||||
|
||||
// import { FieldMetadataTargetColumnMap } from 'src/metadata/field-metadata/interfaces/field-metadata-target-column-map.interface';
|
||||
|
||||
// import { FieldMetadata } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
// import {
|
||||
// PGGraphQLQueryBuilder,
|
||||
// PGGraphQLQueryBuilderOptions,
|
||||
// } from 'src/tenant/resolver-builder/pg-graphql/pg-graphql-query-builder';
|
||||
|
||||
// const testUUID = '123e4567-e89b-12d3-a456-426614174001';
|
||||
|
||||
// const normalizeWhitespace = (str) => str.replace(/\s+/g, '');
|
||||
|
||||
// // Mocking dependencies
|
||||
// jest.mock('uuid', () => ({
|
||||
// v4: jest.fn(() => testUUID),
|
||||
// }));
|
||||
|
||||
// jest.mock('graphql-fields', () =>
|
||||
// jest.fn(() => ({
|
||||
// name: true,
|
||||
// age: true,
|
||||
// complexField: {
|
||||
// subField1: true,
|
||||
// subField2: true,
|
||||
// },
|
||||
// })),
|
||||
// );
|
||||
|
||||
// describe('PGGraphQLQueryBuilder', () => {
|
||||
// let queryBuilder;
|
||||
// let mockOptions: PGGraphQLQueryBuilderOptions;
|
||||
|
||||
// beforeEach(() => {
|
||||
// const fieldMetadataCollection = [
|
||||
// {
|
||||
// name: 'name',
|
||||
// targetColumnMap: {
|
||||
// value: 'column_name',
|
||||
// } as FieldMetadataTargetColumnMap,
|
||||
// },
|
||||
// {
|
||||
// name: 'age',
|
||||
// targetColumnMap: {
|
||||
// value: 'column_age',
|
||||
// } as FieldMetadataTargetColumnMap,
|
||||
// },
|
||||
// {
|
||||
// name: 'complexField',
|
||||
// targetColumnMap: {
|
||||
// subField1: 'column_subField1',
|
||||
// subField2: 'column_subField2',
|
||||
// } as FieldMetadataTargetColumnMap,
|
||||
// },
|
||||
// ] as FieldMetadata[];
|
||||
|
||||
// mockOptions = {
|
||||
// targetTableName: 'TestTable',
|
||||
// info: {} as GraphQLResolveInfo,
|
||||
// fieldMetadataCollection,
|
||||
// };
|
||||
|
||||
// queryBuilder = new PGGraphQLQueryBuilder(mockOptions);
|
||||
// });
|
||||
|
||||
// test('findMany generates correct query with no arguments', () => {
|
||||
// const query = queryBuilder.findMany();
|
||||
|
||||
// expect(normalizeWhitespace(query)).toBe(
|
||||
// normalizeWhitespace(`
|
||||
// query {
|
||||
// TestTableCollection {
|
||||
// name: column_name
|
||||
// age: column_age
|
||||
// ___complexField_subField1: column_subField1
|
||||
// ___complexField_subField2: column_subField2
|
||||
// }
|
||||
// }
|
||||
// `),
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('findMany generates correct query with filter parameters', () => {
|
||||
// const args = {
|
||||
// filter: {
|
||||
// name: { eq: 'Alice' },
|
||||
// age: { gt: 20 },
|
||||
// },
|
||||
// };
|
||||
// const query = queryBuilder.findMany(args);
|
||||
|
||||
// expect(normalizeWhitespace(query)).toBe(
|
||||
// normalizeWhitespace(`
|
||||
// query {
|
||||
// TestTableCollection(filter: { column_name: { eq: "Alice" }, column_age: { gt: 20 } }) {
|
||||
// name: column_name
|
||||
// age: column_age
|
||||
// ___complexField_subField1: column_subField1
|
||||
// ___complexField_subField2: column_subField2
|
||||
// }
|
||||
// }
|
||||
// `),
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('findMany generates correct query with combined pagination parameters', () => {
|
||||
// const args = {
|
||||
// first: 5,
|
||||
// after: 'someCursor',
|
||||
// before: 'anotherCursor',
|
||||
// last: 3,
|
||||
// };
|
||||
// const query = queryBuilder.findMany(args);
|
||||
|
||||
// expect(normalizeWhitespace(query)).toBe(
|
||||
// normalizeWhitespace(`
|
||||
// query {
|
||||
// TestTableCollection(
|
||||
// first: 5,
|
||||
// after: "someCursor",
|
||||
// before: "anotherCursor",
|
||||
// last: 3
|
||||
// ) {
|
||||
// name: column_name
|
||||
// age: column_age
|
||||
// ___complexField_subField1: column_subField1
|
||||
// ___complexField_subField2: column_subField2
|
||||
// }
|
||||
// }
|
||||
// `),
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('findOne generates correct query with ID filter', () => {
|
||||
// const args = { filter: { id: { eq: testUUID } } };
|
||||
// const query = queryBuilder.findOne(args);
|
||||
|
||||
// expect(normalizeWhitespace(query)).toBe(
|
||||
// normalizeWhitespace(`
|
||||
// query {
|
||||
// TestTableCollection(filter: { id: { eq: "${testUUID}" } }) {
|
||||
// edges {
|
||||
// node {
|
||||
// name: column_name
|
||||
// age: column_age
|
||||
// ___complexField_subField1: column_subField1
|
||||
// ___complexField_subField2: column_subField2
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// `),
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('createMany generates correct mutation with complex and nested fields', () => {
|
||||
// const args = {
|
||||
// data: [
|
||||
// {
|
||||
// name: 'Alice',
|
||||
// age: 30,
|
||||
// complexField: {
|
||||
// subField1: 'data1',
|
||||
// subField2: 'data2',
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// const query = queryBuilder.createMany(args);
|
||||
|
||||
// expect(normalizeWhitespace(query)).toBe(
|
||||
// normalizeWhitespace(`
|
||||
// mutation {
|
||||
// insertIntoTestTableCollection(objects: [{
|
||||
// id: "${testUUID}",
|
||||
// column_name: "Alice",
|
||||
// column_age: 30,
|
||||
// column_subField1: "data1",
|
||||
// column_subField2: "data2"
|
||||
// }]) {
|
||||
// affectedCount
|
||||
// records {
|
||||
// name: column_name
|
||||
// age: column_age
|
||||
// ___complexField_subField1: column_subField1
|
||||
// ___complexField_subField2: column_subField2
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// `),
|
||||
// );
|
||||
// });
|
||||
|
||||
// test('updateOne generates correct mutation with complex and nested fields', () => {
|
||||
// const args = {
|
||||
// id: '1',
|
||||
// data: {
|
||||
// name: 'Bob',
|
||||
// age: 40,
|
||||
// complexField: {
|
||||
// subField1: 'newData1',
|
||||
// subField2: 'newData2',
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
// const query = queryBuilder.updateOne(args);
|
||||
|
||||
// expect(normalizeWhitespace(query)).toBe(
|
||||
// normalizeWhitespace(`
|
||||
// mutation {
|
||||
// updateTestTableCollection(
|
||||
// set: {
|
||||
// column_name: "Bob",
|
||||
// column_age: 40,
|
||||
// column_subField1: "newData1",
|
||||
// column_subField2: "newData2"
|
||||
// },
|
||||
// filter: { id: { eq: "1" } }
|
||||
// ) {
|
||||
// affectedCount
|
||||
// records {
|
||||
// name: column_name
|
||||
// age: column_age
|
||||
// ___complexField_subField1: column_subField1
|
||||
// ___complexField_subField2: column_subField2
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// `),
|
||||
// );
|
||||
// });
|
||||
// });
|
||||
|
||||
it('should pass', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
@ -7,6 +7,7 @@ import { Record as IRecord } from 'src/workspace/workspace-query-builder/interfa
|
||||
import { CreateManyResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
import { FieldsStringFactory } from './fields-string.factory';
|
||||
import { ArgsAliasFactory } from './args-alias.factory';
|
||||
@ -36,9 +37,9 @@ export class CreateManyQueryFactory {
|
||||
|
||||
return `
|
||||
mutation {
|
||||
insertInto${
|
||||
options.targetTableName
|
||||
}Collection(objects: ${stringifyWithoutKeyQuote(
|
||||
insertInto${computeObjectTargetTable(
|
||||
options.objectMetadataItem,
|
||||
)}Collection(objects: ${stringifyWithoutKeyQuote(
|
||||
computedArgs.data.map((datum) => ({
|
||||
id: uuidv4(),
|
||||
...datum,
|
||||
|
||||
@ -4,6 +4,7 @@ import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-buil
|
||||
import { DeleteManyResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
import { FieldsStringFactory } from './fields-string.factory';
|
||||
|
||||
@ -23,9 +24,9 @@ export class DeleteManyQueryFactory {
|
||||
|
||||
return `
|
||||
mutation {
|
||||
deleteFrom${
|
||||
options.targetTableName
|
||||
}Collection(filter: ${stringifyWithoutKeyQuote(
|
||||
deleteFrom${computeObjectTargetTable(
|
||||
options.objectMetadataItem,
|
||||
)}Collection(filter: ${stringifyWithoutKeyQuote(
|
||||
args.filter,
|
||||
)}, atMost: 30) {
|
||||
affectedCount
|
||||
|
||||
@ -3,6 +3,8 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-builder/interfaces/workspace-query-builder-options.interface';
|
||||
import { DeleteOneResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
import { FieldsStringFactory } from './fields-string.factory';
|
||||
|
||||
@Injectable()
|
||||
@ -23,7 +25,9 @@ export class DeleteOneQueryFactory {
|
||||
|
||||
return `
|
||||
mutation {
|
||||
deleteFrom${options.targetTableName}Collection(filter: { id: { eq: "${args.id}" } }) {
|
||||
deleteFrom${computeObjectTargetTable(
|
||||
options.objectMetadataItem,
|
||||
)}Collection(filter: { id: { eq: "${args.id}" } }) {
|
||||
affectedCount
|
||||
records {
|
||||
${fieldsString}
|
||||
|
||||
@ -9,6 +9,7 @@ import { FindManyResolverArgs } from 'src/workspace/workspace-resolver-builder/i
|
||||
|
||||
import { ArgsStringFactory } from './args-string.factory';
|
||||
import { FieldsStringFactory } from './fields-string.factory';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
@Injectable()
|
||||
export class FindManyQueryFactory {
|
||||
@ -38,7 +39,7 @@ export class FindManyQueryFactory {
|
||||
|
||||
return `
|
||||
query {
|
||||
${options.targetTableName}Collection${
|
||||
${computeObjectTargetTable(options.objectMetadataItem)}Collection${
|
||||
argsString ? `(${argsString})` : ''
|
||||
} {
|
||||
${fieldsString}
|
||||
|
||||
@ -4,6 +4,8 @@ import { WorkspaceQueryBuilderOptions } from 'src/workspace/workspace-query-buil
|
||||
import { RecordFilter } from 'src/workspace/workspace-query-builder/interfaces/record.interface';
|
||||
import { FindOneResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
import { ArgsStringFactory } from './args-string.factory';
|
||||
import { FieldsStringFactory } from './fields-string.factory';
|
||||
|
||||
@ -32,7 +34,7 @@ export class FindOneQueryFactory {
|
||||
|
||||
return `
|
||||
query {
|
||||
${options.targetTableName}Collection${
|
||||
${computeObjectTargetTable(options.objectMetadataItem)}Collection${
|
||||
argsString ? `(${argsString})` : ''
|
||||
} {
|
||||
edges {
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
} from 'src/workspace/utils/deduce-relation-direction.util';
|
||||
import { getFieldArgumentsByKey } from 'src/workspace/workspace-query-builder/utils/get-field-arguments-by-key.util';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
import { FieldsStringFactory } from './fields-string.factory';
|
||||
import { ArgsStringFactory } from './args-string.factory';
|
||||
@ -109,25 +110,27 @@ export class RelationFieldAliasFactory {
|
||||
);
|
||||
|
||||
return `
|
||||
${fieldKey}: ${referencedObjectMetadata.targetTableName}Collection${
|
||||
argsString ? `(${argsString})` : ''
|
||||
} {
|
||||
${fieldKey}: ${computeObjectTargetTable(
|
||||
referencedObjectMetadata,
|
||||
)}Collection${argsString ? `(${argsString})` : ''} {
|
||||
${fieldsString}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
let relationAlias = fieldMetadata.isCustom
|
||||
? `${fieldKey}: ${referencedObjectMetadata.targetTableName}`
|
||||
? `${fieldKey}: _${fieldMetadata.name}`
|
||||
: fieldKey;
|
||||
|
||||
// For one to one relations, pg_graphql use the targetTableName on the side that is not storing the foreign key
|
||||
// For one to one relations, pg_graphql use the target TableName on the side that is not storing the foreign key
|
||||
// so we need to alias it to the field key
|
||||
if (
|
||||
relationMetadata.relationType === RelationMetadataType.ONE_TO_ONE &&
|
||||
relationDirection === RelationDirection.FROM
|
||||
) {
|
||||
relationAlias = `${fieldKey}: ${referencedObjectMetadata.targetTableName}`;
|
||||
relationAlias = `${fieldKey}: ${computeObjectTargetTable(
|
||||
referencedObjectMetadata,
|
||||
)}`;
|
||||
}
|
||||
const fieldsString =
|
||||
await this.fieldsStringFactory.createFieldsStringRecursive(
|
||||
|
||||
@ -10,6 +10,7 @@ import { UpdateManyResolverArgs } from 'src/workspace/workspace-resolver-builder
|
||||
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
|
||||
import { FieldsStringFactory } from 'src/workspace/workspace-query-builder/factories/fields-string.factory';
|
||||
import { ArgsAliasFactory } from 'src/workspace/workspace-query-builder/factories/args-alias.factory';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
@Injectable()
|
||||
export class UpdateManyQueryFactory {
|
||||
@ -43,7 +44,7 @@ export class UpdateManyQueryFactory {
|
||||
|
||||
return `
|
||||
mutation {
|
||||
update${options.targetTableName}Collection(
|
||||
update${computeObjectTargetTable(options.objectMetadataItem)}Collection(
|
||||
set: ${stringifyWithoutKeyQuote(argsData)},
|
||||
filter: ${stringifyWithoutKeyQuote(args.filter)},
|
||||
) {
|
||||
|
||||
@ -5,6 +5,7 @@ import { Record as IRecord } from 'src/workspace/workspace-query-builder/interfa
|
||||
import { UpdateOneResolverArgs } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { stringifyWithoutKeyQuote } from 'src/workspace/workspace-query-builder/utils/stringify-without-key-quote.util';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
import { FieldsStringFactory } from './fields-string.factory';
|
||||
import { ArgsAliasFactory } from './args-alias.factory';
|
||||
@ -39,9 +40,9 @@ export class UpdateOneQueryFactory {
|
||||
|
||||
return `
|
||||
mutation {
|
||||
update${
|
||||
options.targetTableName
|
||||
}Collection(set: ${stringifyWithoutKeyQuote(
|
||||
update${computeObjectTargetTable(
|
||||
options.objectMetadataItem,
|
||||
)}Collection(set: ${stringifyWithoutKeyQuote(
|
||||
argsData,
|
||||
)}, filter: { id: { eq: "${computedArgs.id}" } }) {
|
||||
affectedCount
|
||||
|
||||
@ -4,7 +4,7 @@ import { FieldMetadataInterface } from 'src/metadata/field-metadata/interfaces/f
|
||||
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
export interface WorkspaceQueryBuilderOptions {
|
||||
targetTableName: string;
|
||||
objectMetadataItem: ObjectMetadataInterface;
|
||||
info: GraphQLResolveInfo;
|
||||
fieldMetadataCollection: FieldMetadataInterface[];
|
||||
objectMetadataCollection: ObjectMetadataInterface[];
|
||||
|
||||
@ -4,9 +4,9 @@ import { FieldMetadataInterface } from 'src/metadata/field-metadata/interfaces/f
|
||||
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
export interface WorkspaceQueryRunnerOptions {
|
||||
targetTableName: string;
|
||||
workspaceId: string;
|
||||
info: GraphQLResolveInfo;
|
||||
objectMetadataItem: ObjectMetadataInterface;
|
||||
fieldMetadataCollection: FieldMetadataInterface[];
|
||||
objectMetadataCollection: ObjectMetadataInterface[];
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface';
|
||||
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
|
||||
@ -20,7 +21,7 @@ export enum CallWebhookJobsJobOperation {
|
||||
|
||||
export type CallWebhookJobsJobData = {
|
||||
workspaceId: string;
|
||||
objectNameSingular: string;
|
||||
objectMetadataItem: ObjectMetadataInterface;
|
||||
recordData: any;
|
||||
operation: CallWebhookJobsJobOperation;
|
||||
};
|
||||
@ -43,7 +44,7 @@ export class CallWebhookJobsJob
|
||||
const objectMetadataItem =
|
||||
await this.objectMetadataService.findOneOrFailWithinWorkspace(
|
||||
data.workspaceId,
|
||||
{ where: { nameSingular: data.objectNameSingular } },
|
||||
{ where: { nameSingular: data.objectMetadataItem.nameSingular } },
|
||||
);
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
|
||||
@ -39,6 +39,8 @@ export const parseResult = (obj: any): any => {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
result[key] = parseResult(obj[key]);
|
||||
} else if (key === '__typename') {
|
||||
result[key] = obj[key].replace(/^_*/, '');
|
||||
} else if (isSpecialKey(key)) {
|
||||
handleSpecialKey(result, key, obj[key]);
|
||||
} else {
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
UpdateManyResolverArgs,
|
||||
UpdateOneResolverArgs,
|
||||
} from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { WorkspaceQueryBuilderFactory } from 'src/workspace/workspace-query-builder/workspace-query-builder.factory';
|
||||
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||
@ -35,6 +36,7 @@ import {
|
||||
import { parseResult } from 'src/workspace/workspace-query-runner/utils/parse-result.util';
|
||||
import { ExceptionHandlerService } from 'src/integrations/exception-handler/exception-handler.service';
|
||||
import { handleExceptionAndConvertToGraphQLError } from 'src/filters/utils/global-exception-handler.util';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-optionts.interface';
|
||||
import {
|
||||
@ -63,7 +65,7 @@ export class WorkspaceQueryRunnerService {
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<IConnection<Record> | undefined> {
|
||||
try {
|
||||
const { workspaceId, targetTableName } = options;
|
||||
const { workspaceId, objectMetadataItem } = options;
|
||||
const start = performance.now();
|
||||
|
||||
const query = await this.workspaceQueryBuilderFactory.findMany(
|
||||
@ -76,7 +78,11 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
console.log(`query time: ${end - start} ms`);
|
||||
|
||||
return this.parseResult<IConnection<Record>>(result, targetTableName, '');
|
||||
return this.parseResult<IConnection<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'',
|
||||
);
|
||||
} catch (exception) {
|
||||
const error = handleExceptionAndConvertToGraphQLError(
|
||||
exception,
|
||||
@ -98,7 +104,7 @@ export class WorkspaceQueryRunnerService {
|
||||
if (!args.filter || Object.keys(args.filter).length === 0) {
|
||||
throw new BadRequestException('Missing filter argument');
|
||||
}
|
||||
const { workspaceId, targetTableName } = options;
|
||||
const { workspaceId, objectMetadataItem } = options;
|
||||
const query = await this.workspaceQueryBuilderFactory.findOne(
|
||||
args,
|
||||
options,
|
||||
@ -106,7 +112,7 @@ export class WorkspaceQueryRunnerService {
|
||||
const result = await this.execute(query, workspaceId);
|
||||
const parsedResult = this.parseResult<IConnection<Record>>(
|
||||
result,
|
||||
targetTableName,
|
||||
objectMetadataItem,
|
||||
'',
|
||||
);
|
||||
|
||||
@ -126,16 +132,17 @@ export class WorkspaceQueryRunnerService {
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<Record[] | undefined> {
|
||||
try {
|
||||
const { workspaceId, targetTableName } = options;
|
||||
const { workspaceId, objectMetadataItem } = options;
|
||||
const query = await this.workspaceQueryBuilderFactory.createMany(
|
||||
args,
|
||||
options,
|
||||
);
|
||||
|
||||
const result = await this.execute(query, workspaceId);
|
||||
|
||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
targetTableName,
|
||||
objectMetadataItem,
|
||||
'insertInto',
|
||||
)?.records;
|
||||
|
||||
@ -170,7 +177,7 @@ export class WorkspaceQueryRunnerService {
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<Record | undefined> {
|
||||
try {
|
||||
const { workspaceId, targetTableName } = options;
|
||||
const { workspaceId, objectMetadataItem } = options;
|
||||
const query = await this.workspaceQueryBuilderFactory.updateOne(
|
||||
args,
|
||||
options,
|
||||
@ -179,7 +186,7 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
targetTableName,
|
||||
objectMetadataItem,
|
||||
'update',
|
||||
)?.records;
|
||||
|
||||
@ -205,7 +212,7 @@ export class WorkspaceQueryRunnerService {
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<Record | undefined> {
|
||||
try {
|
||||
const { workspaceId, targetTableName } = options;
|
||||
const { workspaceId, objectMetadataItem } = options;
|
||||
const query = await this.workspaceQueryBuilderFactory.deleteOne(
|
||||
args,
|
||||
options,
|
||||
@ -214,7 +221,7 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
targetTableName,
|
||||
objectMetadataItem,
|
||||
'deleteFrom',
|
||||
)?.records;
|
||||
|
||||
@ -240,7 +247,7 @@ export class WorkspaceQueryRunnerService {
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<Record[] | undefined> {
|
||||
try {
|
||||
const { workspaceId, targetTableName } = options;
|
||||
const { workspaceId, objectMetadataItem } = options;
|
||||
const query = await this.workspaceQueryBuilderFactory.updateMany(
|
||||
args,
|
||||
options,
|
||||
@ -249,7 +256,7 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
targetTableName,
|
||||
objectMetadataItem,
|
||||
'update',
|
||||
)?.records;
|
||||
|
||||
@ -278,7 +285,7 @@ export class WorkspaceQueryRunnerService {
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<Record[] | undefined> {
|
||||
try {
|
||||
const { workspaceId, targetTableName } = options;
|
||||
const { workspaceId, objectMetadataItem } = options;
|
||||
const query = await this.workspaceQueryBuilderFactory.deleteMany(
|
||||
args,
|
||||
options,
|
||||
@ -287,7 +294,7 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
targetTableName,
|
||||
objectMetadataItem,
|
||||
'deleteFrom',
|
||||
)?.records;
|
||||
|
||||
@ -334,18 +341,20 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
private parseResult<Result>(
|
||||
graphqlResult: PGGraphQLResult | undefined,
|
||||
targetTableName: string,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
command: string,
|
||||
): Result {
|
||||
const entityKey = `${command}${targetTableName}Collection`;
|
||||
const entityKey = `${command}${computeObjectTargetTable(
|
||||
objectMetadataItem,
|
||||
)}Collection`;
|
||||
const result = graphqlResult?.[0]?.resolve?.data?.[entityKey];
|
||||
const errors = graphqlResult?.[0]?.resolve?.errors;
|
||||
|
||||
if (!result) {
|
||||
throw new InternalServerErrorException(
|
||||
`GraphQL errors on ${command}${targetTableName}: ${JSON.stringify(
|
||||
errors,
|
||||
)}`,
|
||||
`GraphQL errors on ${command}${
|
||||
objectMetadataItem.nameSingular
|
||||
}: ${JSON.stringify(errors)}`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -354,13 +363,13 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
async executeAndParse<Result>(
|
||||
query: string,
|
||||
targetTableName: string,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
command: string,
|
||||
workspaceId: string,
|
||||
): Promise<Result> {
|
||||
const result = await this.execute(query, workspaceId);
|
||||
|
||||
return this.parseResult(result, targetTableName, command);
|
||||
return this.parseResult(result, objectMetadataItem, command);
|
||||
}
|
||||
|
||||
async triggerWebhooks<Record>(
|
||||
@ -378,7 +387,7 @@ export class WorkspaceQueryRunnerService {
|
||||
recordData: jobData,
|
||||
workspaceId: options.workspaceId,
|
||||
operation,
|
||||
objectNameSingular: options.targetTableName,
|
||||
objectMetadataItem: options.objectMetadataItem,
|
||||
},
|
||||
{ retryLimit: 3 },
|
||||
);
|
||||
|
||||
@ -26,7 +26,7 @@ export class CreateManyResolverFactory
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.workspaceQueryRunnerService.createMany(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
objectMetadataItem: internalContext.objectMetadataItem,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
|
||||
@ -26,7 +26,7 @@ export class CreateOneResolverFactory
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.workspaceQueryRunnerService.createOne(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
objectMetadataItem: internalContext.objectMetadataItem,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
|
||||
@ -26,7 +26,7 @@ export class DeleteManyResolverFactory
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.workspaceQueryRunnerService.deleteMany(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
objectMetadataItem: internalContext.objectMetadataItem,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
|
||||
@ -26,7 +26,7 @@ export class DeleteOneResolverFactory
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.workspaceQueryRunnerService.deleteOne(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
objectMetadataItem: internalContext.objectMetadataItem,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
|
||||
@ -32,7 +32,7 @@ export class ExecuteQuickActionOnOneResolverFactory
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.executeQuickActionOnOne(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
objectMetadataItem: internalContext.objectMetadataItem,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
@ -45,11 +45,12 @@ export class ExecuteQuickActionOnOneResolverFactory
|
||||
args: DeleteOneResolverArgs,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
): Promise<Record | undefined> {
|
||||
switch (options.targetTableName) {
|
||||
switch (options.objectMetadataItem.nameSingular) {
|
||||
case 'company': {
|
||||
await this.quickActionsService.executeQuickActionOnCompany(
|
||||
args.id,
|
||||
options.workspaceId,
|
||||
options.objectMetadataItem,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@ -57,6 +58,7 @@ export class ExecuteQuickActionOnOneResolverFactory
|
||||
await this.quickActionsService.createCompanyFromPerson(
|
||||
args.id,
|
||||
options.workspaceId,
|
||||
options.objectMetadataCollection,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ export class FindManyResolverFactory
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.workspaceQueryRunnerService.findMany(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
objectMetadataItem: internalContext.objectMetadataItem,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
|
||||
@ -26,7 +26,7 @@ export class FindOneResolverFactory
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.workspaceQueryRunnerService.findOne(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
objectMetadataItem: internalContext.objectMetadataItem,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
|
||||
@ -26,7 +26,7 @@ export class UpdateManyResolverFactory
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.workspaceQueryRunnerService.updateMany(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
objectMetadataItem: internalContext.objectMetadataItem,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
|
||||
@ -26,7 +26,7 @@ export class UpdateOneResolverFactory
|
||||
|
||||
return (_source, args, context, info) => {
|
||||
return this.workspaceQueryRunnerService.updateOne(args, {
|
||||
targetTableName: internalContext.targetTableName,
|
||||
objectMetadataItem: internalContext.objectMetadataItem,
|
||||
workspaceId: internalContext.workspaceId,
|
||||
info,
|
||||
fieldMetadataCollection: internalContext.fieldMetadataCollection,
|
||||
|
||||
@ -79,7 +79,7 @@ export class WorkspaceResolverFactory {
|
||||
|
||||
resolvers.Query[resolverName] = resolverFactory.create({
|
||||
workspaceId,
|
||||
targetTableName: objectMetadata.targetTableName,
|
||||
objectMetadataItem: objectMetadata,
|
||||
fieldMetadataCollection: objectMetadata.fields,
|
||||
objectMetadataCollection: objectMetadataCollection,
|
||||
});
|
||||
@ -102,7 +102,7 @@ export class WorkspaceResolverFactory {
|
||||
|
||||
resolvers.Mutation[resolverName] = resolverFactory.create({
|
||||
workspaceId,
|
||||
targetTableName: objectMetadata.targetTableName,
|
||||
objectMetadataItem: objectMetadata,
|
||||
fieldMetadataCollection: objectMetadata.fields,
|
||||
objectMetadataCollection: objectMetadataCollection,
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@ import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/
|
||||
|
||||
export interface WorkspaceSchemaBuilderContext {
|
||||
workspaceId: string;
|
||||
targetTableName: string;
|
||||
objectMetadataItem: ObjectMetadataInterface;
|
||||
fieldMetadataCollection: FieldMetadataInterface[];
|
||||
objectMetadataCollection: ObjectMetadataInterface[];
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ export function ObjectMetadata(
|
||||
{
|
||||
nameSingular: objectName,
|
||||
...params,
|
||||
targetTableName: objectName,
|
||||
targetTableName: 'DEPRECATED',
|
||||
isSystem,
|
||||
isCustom: false,
|
||||
description: params.description,
|
||||
|
||||
@ -37,6 +37,7 @@ import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/work
|
||||
import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import { ReflectiveMetadataFactory } from 'src/workspace/workspace-sync-metadata/reflective-metadata.factory';
|
||||
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSyncMetadataService {
|
||||
@ -391,7 +392,7 @@ export class WorkspaceSyncMetadataService {
|
||||
objectsToCreate.map((object) => {
|
||||
const migrations = [
|
||||
{
|
||||
name: object.targetTableName,
|
||||
name: computeObjectTargetTable(object),
|
||||
action: 'create',
|
||||
} satisfies WorkspaceMigrationTableAction,
|
||||
...Object.values(object.fields)
|
||||
@ -399,7 +400,7 @@ export class WorkspaceSyncMetadataService {
|
||||
.map(
|
||||
(field) =>
|
||||
({
|
||||
name: object.targetTableName,
|
||||
name: computeObjectTargetTable(object),
|
||||
action: 'alter',
|
||||
columns: this.workspaceMigrationFactory.createColumnActions(
|
||||
WorkspaceMigrationColumnActionType.CREATE,
|
||||
@ -433,7 +434,9 @@ export class WorkspaceSyncMetadataService {
|
||||
fieldsToCreate.map((field) => {
|
||||
const migrations = [
|
||||
{
|
||||
name: objectsInDbById[field.objectMetadataId].targetTableName,
|
||||
name: computeObjectTargetTable(
|
||||
objectsInDbById[field.objectMetadataId],
|
||||
),
|
||||
action: 'alter',
|
||||
columns: this.workspaceMigrationFactory.createColumnActions(
|
||||
WorkspaceMigrationColumnActionType.CREATE,
|
||||
@ -454,7 +457,9 @@ export class WorkspaceSyncMetadataService {
|
||||
fieldsToDelete.map((field) => {
|
||||
const migrations = [
|
||||
{
|
||||
name: objectsInDbById[field.objectMetadataId].targetTableName,
|
||||
name: computeObjectTargetTable(
|
||||
objectsInDbById[field.objectMetadataId],
|
||||
),
|
||||
action: 'alter',
|
||||
columns: [
|
||||
{
|
||||
@ -519,13 +524,14 @@ export class WorkspaceSyncMetadataService {
|
||||
|
||||
const migrations = [
|
||||
{
|
||||
name: toObjectMetadata.targetTableName,
|
||||
name: computeObjectTargetTable(toObjectMetadata),
|
||||
action: 'alter',
|
||||
columns: [
|
||||
{
|
||||
action: WorkspaceMigrationColumnActionType.RELATION,
|
||||
columnName: `${camelCase(toFieldMetadata.name)}Id`,
|
||||
referencedTableName: fromObjectMetadata.targetTableName,
|
||||
referencedTableName:
|
||||
computeObjectTargetTable(fromObjectMetadata),
|
||||
referencedTableColumnName: 'id',
|
||||
isUnique:
|
||||
relation.relationType === RelationMetadataType.ONE_TO_ONE,
|
||||
|
||||
Reference in New Issue
Block a user