Improve performance on metadata computation (#12785)
In this PR: ## Improve recompute metadata cache performance. We are aiming for ~100ms Deleting relationMetadata table and FKs pointing on it Fetching indexMetadata and indexFieldMetadata in a separate query as typeorm is suboptimizing ## Remove caching lock As recomputing the metadata cache is lighter, we try to stop preventing multiple concurrent computations. This also simplifies interfaces ## Introduce self recovery mecanisms to recompute cache automatically if corrupted Aka getFreshObjectMetadataMaps ## custom object resolver performance improvement: 1sec to 200ms Double check queries and indexes used while creating a custom object Remove the queries to db to use the cached objectMetadataMap ## reduce objectMetadataMaps to 500kb <img width="222" alt="image" src="https://github.com/user-attachments/assets/2370dc80-49b6-4b63-8d5e-30c5ebdaa062" /> We used to stored 3 fieldMetadataMaps (byId, byName, byJoinColumnName). While this is great for devXP, this is not great for performances. Using the same mecanisme as for objectMetadataMap: we only keep byIdMap and introduce two otherMaps to idByName, idByJoinColumnName to make the bridge ## Add dataloader on IndexMetadata (aka indexMetadataList in the API) ## Improve field resolver performances too ## Deprecate ClientConfig
This commit is contained in:
@ -30,7 +30,7 @@ const documents = {
|
|||||||
"\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSearchable\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n isLabelSyncedWithName\n }\n }\n": types.UpdateOneObjectMetadataItemDocument,
|
"\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSearchable\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n isLabelSyncedWithName\n }\n }\n": types.UpdateOneObjectMetadataItemDocument,
|
||||||
"\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSearchable\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n isLabelSyncedWithName\n }\n }\n": types.DeleteOneObjectMetadataItemDocument,
|
"\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n isSearchable\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n isLabelSyncedWithName\n }\n }\n": types.DeleteOneObjectMetadataItemDocument,
|
||||||
"\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n": types.DeleteOneFieldMetadataItemDocument,
|
"\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n": types.DeleteOneFieldMetadataItemDocument,
|
||||||
"\n query ObjectMetadataItems {\n objects(paging: { first: 1000 }) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n shortcut\n isLabelSyncedWithName\n isSearchable\n duplicateCriteria\n indexMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n indexFieldMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n order\n fieldMetadataId\n }\n }\n }\n }\n }\n }\n fieldsList {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n isLabelSyncedWithName\n relation {\n type\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n": types.ObjectMetadataItemsDocument,
|
"\n query ObjectMetadataItems {\n objects(paging: { first: 1000 }) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n shortcut\n isLabelSyncedWithName\n isSearchable\n duplicateCriteria\n indexMetadataList {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n }\n fieldsList {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n isLabelSyncedWithName\n relation {\n type\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n": types.ObjectMetadataItemsDocument,
|
||||||
"\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n timeoutSeconds\n latestVersion\n latestVersionInputSchema\n publishedVersions\n createdAt\n updatedAt\n }\n": types.ServerlessFunctionFieldsFragmentDoc,
|
"\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n timeoutSeconds\n latestVersion\n latestVersionInputSchema\n publishedVersions\n createdAt\n updatedAt\n }\n": types.ServerlessFunctionFieldsFragmentDoc,
|
||||||
"\n \n mutation CreateOneServerlessFunctionItem(\n $input: CreateServerlessFunctionInput!\n ) {\n createOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.CreateOneServerlessFunctionItemDocument,
|
"\n \n mutation CreateOneServerlessFunctionItem(\n $input: CreateServerlessFunctionInput!\n ) {\n createOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.CreateOneServerlessFunctionItemDocument,
|
||||||
"\n \n mutation DeleteOneServerlessFunction($input: ServerlessFunctionIdInput!) {\n deleteOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.DeleteOneServerlessFunctionDocument,
|
"\n \n mutation DeleteOneServerlessFunction($input: ServerlessFunctionIdInput!) {\n deleteOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.DeleteOneServerlessFunctionDocument,
|
||||||
@ -128,7 +128,7 @@ export function graphql(source: "\n mutation DeleteOneFieldMetadataItem($idToDe
|
|||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
export function graphql(source: "\n query ObjectMetadataItems {\n objects(paging: { first: 1000 }) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n shortcut\n isLabelSyncedWithName\n isSearchable\n duplicateCriteria\n indexMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n indexFieldMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n order\n fieldMetadataId\n }\n }\n }\n }\n }\n }\n fieldsList {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n isLabelSyncedWithName\n relation {\n type\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n"): (typeof documents)["\n query ObjectMetadataItems {\n objects(paging: { first: 1000 }) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n shortcut\n isLabelSyncedWithName\n isSearchable\n duplicateCriteria\n indexMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n indexFieldMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n order\n fieldMetadataId\n }\n }\n }\n }\n }\n }\n fieldsList {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n isLabelSyncedWithName\n relation {\n type\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n"];
|
export function graphql(source: "\n query ObjectMetadataItems {\n objects(paging: { first: 1000 }) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n shortcut\n isLabelSyncedWithName\n isSearchable\n duplicateCriteria\n indexMetadataList {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n }\n fieldsList {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n isLabelSyncedWithName\n relation {\n type\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n"): (typeof documents)["\n query ObjectMetadataItems {\n objects(paging: { first: 1000 }) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n shortcut\n isLabelSyncedWithName\n isSearchable\n duplicateCriteria\n indexMetadataList {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n }\n fieldsList {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n isLabelSyncedWithName\n relation {\n type\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n"];
|
||||||
/**
|
/**
|
||||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||||
*/
|
*/
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -338,34 +338,6 @@ export type ClientAiModelConfig = {
|
|||||||
provider: ModelProvider;
|
provider: ModelProvider;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ClientConfig = {
|
|
||||||
__typename?: 'ClientConfig';
|
|
||||||
aiModels: Array<ClientAiModelConfig>;
|
|
||||||
analyticsEnabled: Scalars['Boolean'];
|
|
||||||
api: ApiConfig;
|
|
||||||
authProviders: AuthProviders;
|
|
||||||
billing: Billing;
|
|
||||||
calendarBookingPageId?: Maybe<Scalars['String']>;
|
|
||||||
canManageFeatureFlags: Scalars['Boolean'];
|
|
||||||
captcha: Captcha;
|
|
||||||
chromeExtensionId?: Maybe<Scalars['String']>;
|
|
||||||
debugMode: Scalars['Boolean'];
|
|
||||||
defaultSubdomain?: Maybe<Scalars['String']>;
|
|
||||||
frontDomain: Scalars['String'];
|
|
||||||
isAttachmentPreviewEnabled: Scalars['Boolean'];
|
|
||||||
isConfigVariablesInDbEnabled: Scalars['Boolean'];
|
|
||||||
isEmailVerificationRequired: Scalars['Boolean'];
|
|
||||||
isGoogleCalendarEnabled: Scalars['Boolean'];
|
|
||||||
isGoogleMessagingEnabled: Scalars['Boolean'];
|
|
||||||
isMicrosoftCalendarEnabled: Scalars['Boolean'];
|
|
||||||
isMicrosoftMessagingEnabled: Scalars['Boolean'];
|
|
||||||
isMultiWorkspaceEnabled: Scalars['Boolean'];
|
|
||||||
publicFeatureFlags: Array<PublicFeatureFlag>;
|
|
||||||
sentry: Sentry;
|
|
||||||
signInPrefilled: Scalars['Boolean'];
|
|
||||||
support: Support;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ComputeStepOutputSchemaInput = {
|
export type ComputeStepOutputSchemaInput = {
|
||||||
/** Step JSON format */
|
/** Step JSON format */
|
||||||
step: Scalars['JSON'];
|
step: Scalars['JSON'];
|
||||||
@ -1410,6 +1382,7 @@ export type Object = {
|
|||||||
icon?: Maybe<Scalars['String']>;
|
icon?: Maybe<Scalars['String']>;
|
||||||
id: Scalars['UUID'];
|
id: Scalars['UUID'];
|
||||||
imageIdentifierFieldMetadataId?: Maybe<Scalars['String']>;
|
imageIdentifierFieldMetadataId?: Maybe<Scalars['String']>;
|
||||||
|
indexMetadataList: Array<Index>;
|
||||||
indexMetadatas: ObjectIndexMetadatasConnection;
|
indexMetadatas: ObjectIndexMetadatasConnection;
|
||||||
isActive: Scalars['Boolean'];
|
isActive: Scalars['Boolean'];
|
||||||
isCustom: Scalars['Boolean'];
|
isCustom: Scalars['Boolean'];
|
||||||
@ -1609,7 +1582,6 @@ export type Query = {
|
|||||||
billingPortalSession: BillingSessionOutput;
|
billingPortalSession: BillingSessionOutput;
|
||||||
checkUserExists: CheckUserExistOutput;
|
checkUserExists: CheckUserExistOutput;
|
||||||
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid;
|
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid;
|
||||||
clientConfig: ClientConfig;
|
|
||||||
currentUser: User;
|
currentUser: User;
|
||||||
currentWorkspace: Workspace;
|
currentWorkspace: Workspace;
|
||||||
field: Field;
|
field: Field;
|
||||||
@ -2821,11 +2793,6 @@ export type GetMeteredProductsUsageQueryVariables = Exact<{ [key: string]: never
|
|||||||
|
|
||||||
export type GetMeteredProductsUsageQuery = { __typename?: 'Query', getMeteredProductsUsage: Array<{ __typename?: 'BillingMeteredProductUsageOutput', productKey: BillingProductKey, usageQuantity: number, freeTierQuantity: number, freeTrialQuantity: number, unitPriceCents: number, totalCostCents: number }> };
|
export type GetMeteredProductsUsageQuery = { __typename?: 'Query', getMeteredProductsUsage: Array<{ __typename?: 'BillingMeteredProductUsageOutput', productKey: BillingProductKey, usageQuantity: number, freeTierQuantity: number, freeTrialQuantity: number, unitPriceCents: number, totalCostCents: number }> };
|
||||||
|
|
||||||
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
|
|
||||||
|
|
||||||
|
|
||||||
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isEmailVerificationRequired: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, isAttachmentPreviewEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, isMicrosoftMessagingEnabled: boolean, isMicrosoftCalendarEnabled: boolean, isGoogleMessagingEnabled: boolean, isGoogleCalendarEnabled: boolean, isConfigVariablesInDbEnabled: boolean, calendarBookingPageId?: string | null, aiModels: Array<{ __typename?: 'ClientAIModelConfig', modelId: string, label: string, provider: ModelProvider, inputCostPer1kTokensInCredits: number, outputCostPer1kTokensInCredits: number }>, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, trialPeriods: Array<{ __typename?: 'BillingTrialPeriodDTO', duration: number, isCreditCardRequired: boolean }> }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: SupportDriver, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number }, publicFeatureFlags: Array<{ __typename?: 'PublicFeatureFlag', key: FeatureFlagKey, metadata: { __typename?: 'PublicFeatureFlagMetadata', label: string, description: string, imagePath: string } }> } };
|
|
||||||
|
|
||||||
export type SearchQueryVariables = Exact<{
|
export type SearchQueryVariables = Exact<{
|
||||||
searchInput: Scalars['String'];
|
searchInput: Scalars['String'];
|
||||||
limit: Scalars['Int'];
|
limit: Scalars['Int'];
|
||||||
@ -4792,106 +4759,6 @@ export function useGetMeteredProductsUsageLazyQuery(baseOptions?: Apollo.LazyQue
|
|||||||
export type GetMeteredProductsUsageQueryHookResult = ReturnType<typeof useGetMeteredProductsUsageQuery>;
|
export type GetMeteredProductsUsageQueryHookResult = ReturnType<typeof useGetMeteredProductsUsageQuery>;
|
||||||
export type GetMeteredProductsUsageLazyQueryHookResult = ReturnType<typeof useGetMeteredProductsUsageLazyQuery>;
|
export type GetMeteredProductsUsageLazyQueryHookResult = ReturnType<typeof useGetMeteredProductsUsageLazyQuery>;
|
||||||
export type GetMeteredProductsUsageQueryResult = Apollo.QueryResult<GetMeteredProductsUsageQuery, GetMeteredProductsUsageQueryVariables>;
|
export type GetMeteredProductsUsageQueryResult = Apollo.QueryResult<GetMeteredProductsUsageQuery, GetMeteredProductsUsageQueryVariables>;
|
||||||
export const GetClientConfigDocument = gql`
|
|
||||||
query GetClientConfig {
|
|
||||||
clientConfig {
|
|
||||||
aiModels {
|
|
||||||
modelId
|
|
||||||
label
|
|
||||||
provider
|
|
||||||
inputCostPer1kTokensInCredits
|
|
||||||
outputCostPer1kTokensInCredits
|
|
||||||
}
|
|
||||||
billing {
|
|
||||||
isBillingEnabled
|
|
||||||
billingUrl
|
|
||||||
trialPeriods {
|
|
||||||
duration
|
|
||||||
isCreditCardRequired
|
|
||||||
}
|
|
||||||
}
|
|
||||||
authProviders {
|
|
||||||
google
|
|
||||||
password
|
|
||||||
microsoft
|
|
||||||
sso {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
type
|
|
||||||
status
|
|
||||||
issuer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
signInPrefilled
|
|
||||||
isMultiWorkspaceEnabled
|
|
||||||
isEmailVerificationRequired
|
|
||||||
defaultSubdomain
|
|
||||||
frontDomain
|
|
||||||
debugMode
|
|
||||||
analyticsEnabled
|
|
||||||
isAttachmentPreviewEnabled
|
|
||||||
support {
|
|
||||||
supportDriver
|
|
||||||
supportFrontChatId
|
|
||||||
}
|
|
||||||
sentry {
|
|
||||||
dsn
|
|
||||||
environment
|
|
||||||
release
|
|
||||||
}
|
|
||||||
captcha {
|
|
||||||
provider
|
|
||||||
siteKey
|
|
||||||
}
|
|
||||||
api {
|
|
||||||
mutationMaximumAffectedRecords
|
|
||||||
}
|
|
||||||
chromeExtensionId
|
|
||||||
canManageFeatureFlags
|
|
||||||
publicFeatureFlags {
|
|
||||||
key
|
|
||||||
metadata {
|
|
||||||
label
|
|
||||||
description
|
|
||||||
imagePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isMicrosoftMessagingEnabled
|
|
||||||
isMicrosoftCalendarEnabled
|
|
||||||
isGoogleMessagingEnabled
|
|
||||||
isGoogleCalendarEnabled
|
|
||||||
isConfigVariablesInDbEnabled
|
|
||||||
calendarBookingPageId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useGetClientConfigQuery__
|
|
||||||
*
|
|
||||||
* To run a query within a React component, call `useGetClientConfigQuery` and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useGetClientConfigQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
|
||||||
* you can use to render your UI.
|
|
||||||
*
|
|
||||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const { data, loading, error } = useGetClientConfigQuery({
|
|
||||||
* variables: {
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useGetClientConfigQuery(baseOptions?: Apollo.QueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
|
|
||||||
}
|
|
||||||
export function useGetClientConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetClientConfigQuery, GetClientConfigQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useLazyQuery<GetClientConfigQuery, GetClientConfigQueryVariables>(GetClientConfigDocument, options);
|
|
||||||
}
|
|
||||||
export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>;
|
|
||||||
export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>;
|
|
||||||
export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>;
|
|
||||||
export const SearchDocument = gql`
|
export const SearchDocument = gql`
|
||||||
query Search($searchInput: String!, $limit: Int!, $after: String, $excludedObjectNameSingulars: [String!], $includedObjectNameSingulars: [String!], $filter: ObjectRecordFilterInput) {
|
query Search($searchInput: String!, $limit: Int!, $after: String, $excludedObjectNameSingulars: [String!], $includedObjectNameSingulars: [String!], $filter: ObjectRecordFilterInput) {
|
||||||
search(
|
search(
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { within } from '@storybook/test';
|
|||||||
import { HttpResponse, graphql, http } from 'msw';
|
import { HttpResponse, graphql, http } from 'msw';
|
||||||
|
|
||||||
import { GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN } from '@/auth/graphql/queries/getPublicWorkspaceDataByDomain';
|
import { GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN } from '@/auth/graphql/queries/getPublicWorkspaceDataByDomain';
|
||||||
import { GET_CLIENT_CONFIG } from '@/client-config/graphql/queries/getClientConfig';
|
|
||||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
@ -32,13 +31,6 @@ const userMetadataLoaderMocks = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
graphql.query(getOperationName(GET_CLIENT_CONFIG) ?? '', () => {
|
|
||||||
return HttpResponse.json({
|
|
||||||
data: {
|
|
||||||
clientConfig: mockedClientConfig,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
graphql.query(
|
graphql.query(
|
||||||
getOperationName(GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN) ?? '',
|
getOperationName(GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN) ?? '',
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@ -1,75 +0,0 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
|
|
||||||
export const GET_CLIENT_CONFIG = gql`
|
|
||||||
query GetClientConfig {
|
|
||||||
clientConfig {
|
|
||||||
aiModels {
|
|
||||||
modelId
|
|
||||||
label
|
|
||||||
provider
|
|
||||||
inputCostPer1kTokensInCredits
|
|
||||||
outputCostPer1kTokensInCredits
|
|
||||||
}
|
|
||||||
billing {
|
|
||||||
isBillingEnabled
|
|
||||||
billingUrl
|
|
||||||
trialPeriods {
|
|
||||||
duration
|
|
||||||
isCreditCardRequired
|
|
||||||
}
|
|
||||||
}
|
|
||||||
authProviders {
|
|
||||||
google
|
|
||||||
password
|
|
||||||
microsoft
|
|
||||||
sso {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
type
|
|
||||||
status
|
|
||||||
issuer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
signInPrefilled
|
|
||||||
isMultiWorkspaceEnabled
|
|
||||||
isEmailVerificationRequired
|
|
||||||
defaultSubdomain
|
|
||||||
frontDomain
|
|
||||||
debugMode
|
|
||||||
analyticsEnabled
|
|
||||||
isAttachmentPreviewEnabled
|
|
||||||
support {
|
|
||||||
supportDriver
|
|
||||||
supportFrontChatId
|
|
||||||
}
|
|
||||||
sentry {
|
|
||||||
dsn
|
|
||||||
environment
|
|
||||||
release
|
|
||||||
}
|
|
||||||
captcha {
|
|
||||||
provider
|
|
||||||
siteKey
|
|
||||||
}
|
|
||||||
api {
|
|
||||||
mutationMaximumAffectedRecords
|
|
||||||
}
|
|
||||||
chromeExtensionId
|
|
||||||
canManageFeatureFlags
|
|
||||||
publicFeatureFlags {
|
|
||||||
key
|
|
||||||
metadata {
|
|
||||||
label
|
|
||||||
description
|
|
||||||
imagePath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isMicrosoftMessagingEnabled
|
|
||||||
isMicrosoftCalendarEnabled
|
|
||||||
isGoogleMessagingEnabled
|
|
||||||
isGoogleCalendarEnabled
|
|
||||||
isConfigVariablesInDbEnabled
|
|
||||||
calendarBookingPageId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
|
import { ClientConfig } from '@/client-config/types/ClientConfig';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { ClientConfig } from '~/generated/graphql';
|
|
||||||
import { clientConfigApiStatusState } from '../states/clientConfigApiStatusState';
|
import { clientConfigApiStatusState } from '../states/clientConfigApiStatusState';
|
||||||
import { getClientConfig } from '../utils/getClientConfig';
|
import { getClientConfig } from '../utils/getClientConfig';
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import { ClientConfig } from '@/client-config/types/ClientConfig';
|
||||||
import { createState } from 'twenty-ui/utilities';
|
import { createState } from 'twenty-ui/utilities';
|
||||||
import { ClientConfig } from '~/generated/graphql';
|
|
||||||
|
|
||||||
type ClientConfigApiStatus = {
|
type ClientConfigApiStatus = {
|
||||||
isLoadedOnce: boolean;
|
isLoadedOnce: boolean;
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
import {
|
||||||
|
ApiConfig,
|
||||||
|
AuthProviders,
|
||||||
|
Billing,
|
||||||
|
Captcha,
|
||||||
|
ClientAiModelConfig,
|
||||||
|
PublicFeatureFlag,
|
||||||
|
Sentry,
|
||||||
|
Support,
|
||||||
|
} from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
export type ClientConfig = {
|
||||||
|
aiModels: Array<ClientAiModelConfig>;
|
||||||
|
analyticsEnabled: boolean;
|
||||||
|
api: ApiConfig;
|
||||||
|
authProviders: AuthProviders;
|
||||||
|
billing: Billing;
|
||||||
|
calendarBookingPageId?: string;
|
||||||
|
canManageFeatureFlags: boolean;
|
||||||
|
captcha: Captcha;
|
||||||
|
chromeExtensionId?: string;
|
||||||
|
debugMode: boolean;
|
||||||
|
defaultSubdomain?: string;
|
||||||
|
frontDomain: string;
|
||||||
|
isAttachmentPreviewEnabled: boolean;
|
||||||
|
isConfigVariablesInDbEnabled: boolean;
|
||||||
|
isEmailVerificationRequired: boolean;
|
||||||
|
isGoogleCalendarEnabled: boolean;
|
||||||
|
isGoogleMessagingEnabled: boolean;
|
||||||
|
isMicrosoftCalendarEnabled: boolean;
|
||||||
|
isMicrosoftMessagingEnabled: boolean;
|
||||||
|
isMultiWorkspaceEnabled: boolean;
|
||||||
|
publicFeatureFlags: Array<PublicFeatureFlag>;
|
||||||
|
sentry: Sentry;
|
||||||
|
signInPrefilled: boolean;
|
||||||
|
support: Support;
|
||||||
|
};
|
||||||
@ -1,5 +1,5 @@
|
|||||||
|
import { ClientConfig } from '@/client-config/types/ClientConfig';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import { ClientConfig } from '~/generated/graphql';
|
|
||||||
|
|
||||||
export const getClientConfig = async (): Promise<ClientConfig> => {
|
export const getClientConfig = async (): Promise<ClientConfig> => {
|
||||||
const response = await fetch(`${REACT_APP_SERVER_BASE_URL}/client-config`, {
|
const response = await fetch(`${REACT_APP_SERVER_BASE_URL}/client-config`, {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ClientConfig } from '~/generated/graphql';
|
import { ClientConfig } from '@/client-config/types/ClientConfig';
|
||||||
import { createState } from 'twenty-ui/utilities';
|
import { createState } from 'twenty-ui/utilities';
|
||||||
|
|
||||||
export const domainConfigurationState = createState<
|
export const domainConfigurationState = createState<
|
||||||
|
|||||||
@ -25,29 +25,14 @@ export const FIND_MANY_OBJECT_METADATA_ITEMS = gql`
|
|||||||
isLabelSyncedWithName
|
isLabelSyncedWithName
|
||||||
isSearchable
|
isSearchable
|
||||||
duplicateCriteria
|
duplicateCriteria
|
||||||
indexMetadatas(paging: { first: 100 }) {
|
indexMetadataList {
|
||||||
edges {
|
id
|
||||||
node {
|
createdAt
|
||||||
id
|
updatedAt
|
||||||
createdAt
|
name
|
||||||
updatedAt
|
indexWhereClause
|
||||||
name
|
indexType
|
||||||
indexWhereClause
|
isUnique
|
||||||
indexType
|
|
||||||
isUnique
|
|
||||||
indexFieldMetadatas(paging: { first: 100 }) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
order
|
|
||||||
fieldMetadataId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fieldsList {
|
fieldsList {
|
||||||
id
|
id
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export type ObjectMetadataItem = Omit<
|
|||||||
| 'indexMetadatas'
|
| 'indexMetadatas'
|
||||||
| 'labelIdentifierFieldMetadataId'
|
| 'labelIdentifierFieldMetadataId'
|
||||||
| 'fieldsList'
|
| 'fieldsList'
|
||||||
|
| 'indexMetadataList'
|
||||||
> & {
|
> & {
|
||||||
__typename?: string;
|
__typename?: string;
|
||||||
fields: FieldMetadataItem[];
|
fields: FieldMetadataItem[];
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { IndexMetadataItem } from '@/object-metadata/types/IndexMetadataItem';
|
||||||
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
|
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
|
||||||
import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';
|
import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';
|
||||||
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '../types/ObjectMetadataItem';
|
||||||
@ -14,19 +15,21 @@ export const mapPaginatedObjectMetadataItemsToObjectMetadataItems = ({
|
|||||||
object.node.labelIdentifierFieldMetadataId,
|
object.node.labelIdentifierFieldMetadataId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { fieldsList, ...objectWithoutFieldsList } = object.node;
|
const { fieldsList, indexMetadataList, ...objectWithoutFieldsList } =
|
||||||
|
object.node;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...objectWithoutFieldsList,
|
...objectWithoutFieldsList,
|
||||||
fields: fieldsList,
|
fields: fieldsList,
|
||||||
labelIdentifierFieldMetadataId,
|
labelIdentifierFieldMetadataId,
|
||||||
indexMetadatas: object.node.indexMetadatas?.edges.map((index) => ({
|
indexMetadatas: indexMetadataList.map(
|
||||||
...index.node,
|
(index) =>
|
||||||
indexFieldMetadatas: index.node.indexFieldMetadatas?.edges.map(
|
({
|
||||||
(indexField) => indexField.node,
|
...index,
|
||||||
),
|
indexFieldMetadatas: [],
|
||||||
})),
|
}) satisfies IndexMetadataItem,
|
||||||
};
|
),
|
||||||
|
} satisfies ObjectMetadataItem;
|
||||||
}) ?? [];
|
}) ?? [];
|
||||||
|
|
||||||
return formattedObjects;
|
return formattedObjects;
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { getOperationName } from '@apollo/client/utilities';
|
|||||||
import { graphql, GraphQLQuery, http, HttpResponse } from 'msw';
|
import { graphql, GraphQLQuery, http, HttpResponse } from 'msw';
|
||||||
|
|
||||||
import { TRACK_ANALYTICS } from '@/analytics/graphql/queries/track';
|
import { TRACK_ANALYTICS } from '@/analytics/graphql/queries/track';
|
||||||
import { GET_CLIENT_CONFIG } from '@/client-config/graphql/queries/getClientConfig';
|
|
||||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
@ -15,11 +14,11 @@ import { mockedFavoritesData } from '~/testing/mock-data/favorite';
|
|||||||
import { mockedFavoriteFoldersData } from '~/testing/mock-data/favorite-folders';
|
import { mockedFavoriteFoldersData } from '~/testing/mock-data/favorite-folders';
|
||||||
import { mockedNotes } from '~/testing/mock-data/notes';
|
import { mockedNotes } from '~/testing/mock-data/notes';
|
||||||
import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people';
|
import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people';
|
||||||
|
import { mockedPublicWorkspaceDataBySubdomain } from '~/testing/mock-data/publicWorkspaceDataBySubdomain';
|
||||||
import { mockedRemoteTables } from '~/testing/mock-data/remote-tables';
|
import { mockedRemoteTables } from '~/testing/mock-data/remote-tables';
|
||||||
import { mockedUserData } from '~/testing/mock-data/users';
|
import { mockedUserData } from '~/testing/mock-data/users';
|
||||||
import { mockedViewsData } from '~/testing/mock-data/views';
|
import { mockedViewsData } from '~/testing/mock-data/views';
|
||||||
import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
|
import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
|
||||||
import { mockedPublicWorkspaceDataBySubdomain } from '~/testing/mock-data/publicWorkspaceDataBySubdomain';
|
|
||||||
|
|
||||||
import { GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN } from '@/auth/graphql/queries/getPublicWorkspaceDataByDomain';
|
import { GET_PUBLIC_WORKSPACE_DATA_BY_DOMAIN } from '@/auth/graphql/queries/getPublicWorkspaceDataByDomain';
|
||||||
import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery';
|
import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery';
|
||||||
@ -104,13 +103,6 @@ export const graphqlMocks = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
graphql.query(getOperationName(GET_CLIENT_CONFIG) ?? '', () => {
|
|
||||||
return HttpResponse.json({
|
|
||||||
data: {
|
|
||||||
clientConfig: mockedClientConfig,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
http.get(`${REACT_APP_SERVER_BASE_URL}/client-config`, () => {
|
http.get(`${REACT_APP_SERVER_BASE_URL}/client-config`, () => {
|
||||||
return HttpResponse.json(mockedClientConfig);
|
return HttpResponse.json(mockedClientConfig);
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
import {
|
import { ClientConfig } from '@/client-config/types/ClientConfig';
|
||||||
CaptchaDriverType,
|
import { CaptchaDriverType, SupportDriver } from '~/generated/graphql';
|
||||||
ClientConfig,
|
|
||||||
SupportDriver,
|
|
||||||
} from '~/generated/graphql';
|
|
||||||
|
|
||||||
export const mockedClientConfig: ClientConfig = {
|
export const mockedClientConfig: ClientConfig = {
|
||||||
aiModels: [],
|
aiModels: [],
|
||||||
|
|||||||
@ -42,10 +42,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Workflow Runs",
|
"labelPlural": "Workflow Runs",
|
||||||
"description": "A workflow run",
|
"description": "A workflow run",
|
||||||
"icon": "IconHistoryToggle",
|
"icon": "IconHistoryToggle",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -561,10 +558,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Note Targets",
|
"labelPlural": "Note Targets",
|
||||||
"description": "A note target",
|
"description": "A note target",
|
||||||
"icon": "IconCheckbox",
|
"icon": "IconCheckbox",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -1033,40 +1027,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Survey results",
|
"labelPlural": "Survey results",
|
||||||
"description": null,
|
"description": null,
|
||||||
"icon": "IconRulerMeasure",
|
"icon": "IconRulerMeasure",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "e5cc0d0e-515d-4ae4-bbd4-a2c76b394ff7",
|
|
||||||
"createdAt": "2025-06-09T18:53:52.547Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:52.547Z",
|
|
||||||
"name": "IDX_e2a25535adda4544be555d3b6d8",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "GIN",
|
|
||||||
"isUnique": false,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "d1c159f6-148a-4670-87eb-05fc7c824704",
|
|
||||||
"createdAt": "2025-06-09T18:53:52.547Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:52.547Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "a4c055c6-df30-4cdf-9622-3d3db93b4337"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -1650,10 +1611,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "API Keys",
|
"labelPlural": "API Keys",
|
||||||
"description": "An API key",
|
"description": "An API key",
|
||||||
"icon": "IconRobot",
|
"icon": "IconRobot",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -1835,10 +1793,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "View Filter Groups",
|
"labelPlural": "View Filter Groups",
|
||||||
"description": "(System) View Filter Groups",
|
"description": "(System) View Filter Groups",
|
||||||
"icon": "IconFilterBolt",
|
"icon": "IconFilterBolt",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -2092,10 +2047,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Timeline Activities",
|
"labelPlural": "Timeline Activities",
|
||||||
"description": "Aggregated / filtered event to be displayed on the timeline",
|
"description": "Aggregated / filtered event to be displayed on the timeline",
|
||||||
"icon": "IconTimelineEvent",
|
"icon": "IconTimelineEvent",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -2940,10 +2892,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Message Threads",
|
"labelPlural": "Message Threads",
|
||||||
"description": "A group of related messages (e.g. email thread, chat thread)",
|
"description": "A group of related messages (e.g. email thread, chat thread)",
|
||||||
"icon": "IconMessage",
|
"icon": "IconMessage",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -3110,10 +3059,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "View Groups",
|
"labelPlural": "View Groups",
|
||||||
"description": "(System) View Groups",
|
"description": "(System) View Groups",
|
||||||
"icon": "IconTag",
|
"icon": "IconTag",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -3366,40 +3312,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Rockets",
|
"labelPlural": "Rockets",
|
||||||
"description": "A rocket",
|
"description": "A rocket",
|
||||||
"icon": "IconRocket",
|
"icon": "IconRocket",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "960148fc-d043-415e-a71d-434cf3f614b9",
|
|
||||||
"createdAt": "2025-06-09T18:53:50.917Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:50.917Z",
|
|
||||||
"name": "IDX_530792e4278e7696c4e3e3e55f8",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "GIN",
|
|
||||||
"isUnique": false,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "54da1cce-c87c-4709-ab33-1b92e0b20e47",
|
|
||||||
"createdAt": "2025-06-09T18:53:50.917Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:50.917Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "683ab4d4-70fb-4fc9-a765-e77755a8f30e"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -3839,10 +3752,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Message Participants",
|
"labelPlural": "Message Participants",
|
||||||
"description": "Message Participants",
|
"description": "Message Participants",
|
||||||
"icon": "IconUserCircle",
|
"icon": "IconUserCircle",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -4203,10 +4113,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Views",
|
"labelPlural": "Views",
|
||||||
"description": "(System) Views",
|
"description": "(System) Views",
|
||||||
"icon": "IconLayoutCollage",
|
"icon": "IconLayoutCollage",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -4952,10 +4859,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Favorites",
|
"labelPlural": "Favorites",
|
||||||
"description": "A favorite that can be accessed from the left menu",
|
"description": "A favorite that can be accessed from the left menu",
|
||||||
"icon": "IconHeart",
|
"icon": "IconHeart",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -5795,10 +5699,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Favorite Folders",
|
"labelPlural": "Favorite Folders",
|
||||||
"description": "A Folder of favorites",
|
"description": "A Folder of favorites",
|
||||||
"icon": "IconFolder",
|
"icon": "IconFolder",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -6007,10 +5908,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Attachments",
|
"labelPlural": "Attachments",
|
||||||
"description": "An attachment",
|
"description": "An attachment",
|
||||||
"icon": "IconFileImport",
|
"icon": "IconFileImport",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -6642,10 +6540,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Message Channel Message Associations",
|
"labelPlural": "Message Channel Message Associations",
|
||||||
"description": "Message Synced with a Message Channel",
|
"description": "Message Synced with a Message Channel",
|
||||||
"icon": "IconMessage",
|
"icon": "IconMessage",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -6942,10 +6837,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "View Filters",
|
"labelPlural": "View Filters",
|
||||||
"description": "(System) View Filters",
|
"description": "(System) View Filters",
|
||||||
"icon": "IconFilterBolt",
|
"icon": "IconFilterBolt",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -7261,40 +7153,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Tasks",
|
"labelPlural": "Tasks",
|
||||||
"description": "A task",
|
"description": "A task",
|
||||||
"icon": "IconCheckbox",
|
"icon": "IconCheckbox",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "4e92884d-139a-451d-8a1f-380078369a88",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"name": "IDX_d01a000cf26e1225d894dc3d364",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "GIN",
|
|
||||||
"isUnique": false,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "0338d40f-cbb0-4ee8-b024-fe66d623444e",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "27443165-8f25-4018-b1d6-f9f44e17efba"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -7852,10 +7711,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Task Targets",
|
"labelPlural": "Task Targets",
|
||||||
"description": "A task target",
|
"description": "A task target",
|
||||||
"icon": "IconCheckbox",
|
"icon": "IconCheckbox",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -8324,10 +8180,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Calendar events",
|
"labelPlural": "Calendar events",
|
||||||
"description": "Calendar events",
|
"description": "Calendar events",
|
||||||
"icon": "IconCalendar",
|
"icon": "IconCalendar",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -8798,10 +8651,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Blocklists",
|
"labelPlural": "Blocklists",
|
||||||
"description": "Blocklist",
|
"description": "Blocklist",
|
||||||
"icon": "IconForbid2",
|
"icon": "IconForbid2",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -9002,69 +8852,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "People",
|
"labelPlural": "People",
|
||||||
"description": "A person",
|
"description": "A person",
|
||||||
"icon": "IconUser",
|
"icon": "IconUser",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "4d799743-3cb0-4112-a497-452d9c0e6cbf",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"name": "IDX_bbd7aec1976fc684a0a5e4816c9",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "GIN",
|
|
||||||
"isUnique": false,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "c8156533-4e84-44ff-a2f5-838588041df9",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "28abc54f-5d86-4d54-8e65-5b44b9249153"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "6d3629ae-2d71-4349-b94b-c2baf736a7f7",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"name": "IDX_UNIQUE_87914cd3ce963115f8cb943e2ac",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "BTREE",
|
|
||||||
"isUnique": true,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "a33a4d19-3a59-4d01-bd82-787172be4c4f",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "d49dcd4e-9565-4a11-99ac-c6e278fc028b"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -10013,80 +9801,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Opportunities",
|
"labelPlural": "Opportunities",
|
||||||
"description": "An opportunity",
|
"description": "An opportunity",
|
||||||
"icon": "IconTargetArrow",
|
"icon": "IconTargetArrow",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "517dbbfa-650d-4d6b-b7a6-35ff3c10637d",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"name": "IDX_9f96d65260c4676faac27cb6bf3",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "GIN",
|
|
||||||
"isUnique": false,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "815a1083-69db-486b-b175-22e80d0a99f2",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "e918212c-7546-4d09-a4f2-b1718976d451"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "5eaf9196-e14f-423c-97b6-5911a2ea6ef0",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"name": "IDX_4f469d3a7ee08aefdc099836364",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "BTREE",
|
|
||||||
"isUnique": false,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "d1ceb012-d59c-4965-97d4-a8c8655e6cca",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "77fccf84-50d7-40d0-a097-564ceb3e8433"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "0ebf386b-65ca-48a9-bfef-afb2d9df8c75",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"order": 1,
|
|
||||||
"fieldMetadataId": "5398e336-835f-467b-94da-4a8869456dfd"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -10735,40 +10450,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Pets",
|
"labelPlural": "Pets",
|
||||||
"description": null,
|
"description": null,
|
||||||
"icon": "IconCat",
|
"icon": "IconCat",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "42d09c88-b63a-4249-91bb-0db7fd653749",
|
|
||||||
"createdAt": "2025-06-09T18:53:51.353Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:51.353Z",
|
|
||||||
"name": "IDX_82c02a6c94da4f260020dfb54b9",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "GIN",
|
|
||||||
"isUnique": false,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "cd98958f-f90d-48b0-8697-73613103caa3",
|
|
||||||
"createdAt": "2025-06-09T18:53:51.353Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:51.353Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "0306bb8e-b9c2-4ffe-9395-60a053110018"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -11688,10 +11370,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "WorkflowAutomatedTriggers",
|
"labelPlural": "WorkflowAutomatedTriggers",
|
||||||
"description": "A workflow automated trigger",
|
"description": "A workflow automated trigger",
|
||||||
"icon": "IconSettingsAutomation",
|
"icon": "IconSettingsAutomation",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -11924,69 +11603,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Companies",
|
"labelPlural": "Companies",
|
||||||
"description": "A company",
|
"description": "A company",
|
||||||
"icon": "IconBuildingSkyscraper",
|
"icon": "IconBuildingSkyscraper",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "7471efe7-2fa4-4496-8403-cf3d6e1f4d76",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"name": "IDX_UNIQUE_2a32339058d0b6910b0834ddf81",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "BTREE",
|
|
||||||
"isUnique": true,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "1173c55d-3d5e-4c7a-9ea1-e1c2aca9c39b",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "a710c97a-565d-4868-be27-fa846be32021"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "f42775a0-8f04-4123-81f4-f080ef14ceb9",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"name": "IDX_fb1f4905546cfc6d70a971c76f7",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "GIN",
|
|
||||||
"isUnique": false,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "cbd8e127-c06a-4f2c-929f-bee3d2fe8b2c",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "d69d9ca9-df3f-400b-87c0-ef09fa250f0c"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -12860,10 +12477,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Connected Accounts",
|
"labelPlural": "Connected Accounts",
|
||||||
"description": "A connected account",
|
"description": "A connected account",
|
||||||
"icon": "IconAt",
|
"icon": "IconAt",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -13296,40 +12910,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Notes",
|
"labelPlural": "Notes",
|
||||||
"description": "A note",
|
"description": "A note",
|
||||||
"icon": "IconNotes",
|
"icon": "IconNotes",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "d7aa0d37-d4e2-455f-bbb9-201c03e876dd",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"name": "IDX_f20de8d7fc74a405e4083051275",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "GIN",
|
|
||||||
"isUnique": false,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "53699c8e-22dc-4ade-a144-189162ac63b7",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "ac77bf26-535c-4c88-8735-97702866de25"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -13773,10 +13354,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "View Fields",
|
"labelPlural": "View Fields",
|
||||||
"description": "(System) View Fields",
|
"description": "(System) View Fields",
|
||||||
"icon": "IconTag",
|
"icon": "IconTag",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -14135,10 +13713,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Workflow Versions",
|
"labelPlural": "Workflow Versions",
|
||||||
"description": "A workflow version",
|
"description": "A workflow version",
|
||||||
"icon": "IconVersions",
|
"icon": "IconVersions",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -14585,10 +14160,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Calendar event participants",
|
"labelPlural": "Calendar event participants",
|
||||||
"description": "Calendar event participants",
|
"description": "Calendar event participants",
|
||||||
"icon": "IconCalendar",
|
"icon": "IconCalendar",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -14970,10 +14542,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Workflows",
|
"labelPlural": "Workflows",
|
||||||
"description": "A workflow",
|
"description": "A workflow",
|
||||||
"icon": "IconSettingsAutomation",
|
"icon": "IconSettingsAutomation",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -15460,40 +15029,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Workspace Members",
|
"labelPlural": "Workspace Members",
|
||||||
"description": "A workspace member",
|
"description": "A workspace member",
|
||||||
"icon": "IconUserCircle",
|
"icon": "IconUserCircle",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "Index",
|
|
||||||
"id": "827348bd-da39-40d9-85c8-09370b9ec58e",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"name": "IDX_e47451872f70c8f187a6b460ac7",
|
|
||||||
"indexWhereClause": null,
|
|
||||||
"indexType": "GIN",
|
|
||||||
"isUnique": false,
|
|
||||||
"indexFieldMetadatas": {
|
|
||||||
"__typename": "IndexIndexFieldMetadatasConnection",
|
|
||||||
"edges": [
|
|
||||||
{
|
|
||||||
"__typename": "IndexFieldEdge",
|
|
||||||
"node": {
|
|
||||||
"__typename": "IndexField",
|
|
||||||
"id": "ebbf44cc-2880-4b99-b817-2851e131b13f",
|
|
||||||
"createdAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"updatedAt": "2025-06-09T18:53:47.000Z",
|
|
||||||
"order": 0,
|
|
||||||
"fieldMetadataId": "7e389f1a-c377-4846-92d9-3e38697ee198"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -16329,10 +15865,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Webhooks",
|
"labelPlural": "Webhooks",
|
||||||
"description": "A webhook",
|
"description": "A webhook",
|
||||||
"icon": "IconRobot",
|
"icon": "IconRobot",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -16537,10 +16070,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Message Folders",
|
"labelPlural": "Message Folders",
|
||||||
"description": "Folder for Message Channel",
|
"description": "Folder for Message Channel",
|
||||||
"icon": "IconFolder",
|
"icon": "IconFolder",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -16751,10 +16281,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "View Sorts",
|
"labelPlural": "View Sorts",
|
||||||
"description": "(System) View Sorts",
|
"description": "(System) View Sorts",
|
||||||
"icon": "IconArrowsSort",
|
"icon": "IconArrowsSort",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -16965,10 +16492,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Calendar Channel Event Associations",
|
"labelPlural": "Calendar Channel Event Associations",
|
||||||
"description": "Calendar Channel Event Associations",
|
"description": "Calendar Channel Event Associations",
|
||||||
"icon": "IconCalendar",
|
"icon": "IconCalendar",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -17229,10 +16753,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Calendar Channels",
|
"labelPlural": "Calendar Channels",
|
||||||
"description": "Calendar Channels",
|
"description": "Calendar Channels",
|
||||||
"icon": "IconCalendar",
|
"icon": "IconCalendar",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -17803,10 +17324,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Message Channels",
|
"labelPlural": "Message Channels",
|
||||||
"description": "Message Channels",
|
"description": "Message Channels",
|
||||||
"icon": "IconMessage",
|
"icon": "IconMessage",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
@ -18503,10 +18021,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =
|
|||||||
"labelPlural": "Messages",
|
"labelPlural": "Messages",
|
||||||
"description": "A message sent or received through a messaging channel (email, chat, etc.)",
|
"description": "A message sent or received through a messaging channel (email, chat, etc.)",
|
||||||
"icon": "IconMessage",
|
"icon": "IconMessage",
|
||||||
"indexMetadatas": {
|
"indexMetadataList": [],
|
||||||
"__typename": "ObjectIndexMetadatasConnection",
|
|
||||||
"edges": []
|
|
||||||
},
|
|
||||||
"fieldsList": [
|
"fieldsList": [
|
||||||
{
|
{
|
||||||
"__typename": "Field",
|
"__typename": "Field",
|
||||||
|
|||||||
@ -10,17 +10,16 @@ export const generatedMockObjectMetadataItems: ObjectMetadataItem[] =
|
|||||||
edge.node.labelIdentifierFieldMetadataId,
|
edge.node.labelIdentifierFieldMetadataId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { fieldsList, ...objectWithoutFieldsList } = edge.node;
|
const { fieldsList, indexMetadataList, ...objectWithoutFieldsList } =
|
||||||
|
edge.node;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...objectWithoutFieldsList,
|
...objectWithoutFieldsList,
|
||||||
fields: fieldsList,
|
fields: fieldsList,
|
||||||
labelIdentifierFieldMetadataId,
|
labelIdentifierFieldMetadataId,
|
||||||
indexMetadatas: edge.node.indexMetadatas.edges.map((index) => ({
|
indexMetadatas: indexMetadataList.map((index) => ({
|
||||||
...index.node,
|
...index,
|
||||||
indexFieldMetadatas: index.node.indexFieldMetadatas?.edges.map(
|
indexFieldMetadatas: [],
|
||||||
(indexField) => indexField.node,
|
|
||||||
),
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -140,7 +140,6 @@ export abstract class ActiveOrSuspendedWorkspacesMigrationCommandRunner<
|
|||||||
const dataSource =
|
const dataSource =
|
||||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
shouldFailIfMetadataNotFound: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.runOnWorkspace({
|
await this.runOnWorkspace({
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class RemoveRelationMetadata1750673748111 implements MigrationInterface {
|
||||||
|
name = 'RemoveRelationMetadata1750673748111';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_DATA_SOURCE_WORKSPACE_ID_CREATED_AT" ON "core"."dataSource" ("workspaceId", "createdAt") `,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."relationMetadata" DROP CONSTRAINT IF EXISTS "FK_9dea8f90d04edbbf9c541a95c3b"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."relationMetadata" DROP CONSTRAINT IF EXISTS "FK_3deb257254145a3bdde9575e7d6"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."relationMetadata" DROP CONSTRAINT IF EXISTS "FK_0f781f589e5a527b8f3d3a4b824"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."relationMetadata" DROP CONSTRAINT IF EXISTS "FK_f2a0acd3a548ee446a1a35df44d"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(`DROP TABLE IF EXISTS "core"."relationMetadata"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`DROP INDEX "core"."IDX_DATA_SOURCE_WORKSPACE_ID_CREATED_AT"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -307,22 +307,34 @@ export const objectMetadataItemMock = {
|
|||||||
|
|
||||||
export const objectMetadataMapItemMock = {
|
export const objectMetadataMapItemMock = {
|
||||||
id: 'mockObjectId',
|
id: 'mockObjectId',
|
||||||
|
icon: 'Icon123',
|
||||||
nameSingular: 'objectName',
|
nameSingular: 'objectName',
|
||||||
namePlural: 'objectsName',
|
namePlural: 'objectsName',
|
||||||
fields,
|
|
||||||
fieldsById: fields.reduce((acc, field) => {
|
fieldsById: fields.reduce((acc, field) => {
|
||||||
// @ts-expect-error legacy noImplicitAny
|
// @ts-expect-error legacy noImplicitAny
|
||||||
acc[field.id] = field;
|
acc[field.id] = field;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {}),
|
}, {}),
|
||||||
fieldsByName: fields.reduce((acc, field) => {
|
fieldIdByName: fields.reduce((acc, field) => {
|
||||||
// @ts-expect-error legacy noImplicitAny
|
// @ts-expect-error legacy noImplicitAny
|
||||||
acc[field.name] = field;
|
acc[field.name] = field;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {}),
|
}, {}),
|
||||||
} as ObjectMetadataItemWithFieldMaps;
|
fieldIdByJoinColumnName: {},
|
||||||
|
labelSingular: 'Object',
|
||||||
|
labelPlural: 'Objects',
|
||||||
|
workspaceId: 'mockWorkspaceId',
|
||||||
|
isCustom: false,
|
||||||
|
isSystem: false,
|
||||||
|
targetTableName: '',
|
||||||
|
indexMetadatas: [],
|
||||||
|
isActive: true,
|
||||||
|
isRemote: false,
|
||||||
|
isAuditLogged: false,
|
||||||
|
isSearchable: false,
|
||||||
|
} satisfies ObjectMetadataItemWithFieldMaps;
|
||||||
|
|
||||||
export const objectMetadataMapsMock = {
|
export const objectMetadataMapsMock = {
|
||||||
byId: {
|
byId: {
|
||||||
|
|||||||
@ -3,10 +3,11 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
|||||||
import { WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
|
import { WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
|
||||||
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';
|
||||||
|
|
||||||
export const mockPersonObjectMetadata = (
|
export const mockPersonObjectMetadataWithFieldMaps = (
|
||||||
duplicateCriteria: WorkspaceEntityDuplicateCriteria[],
|
duplicateCriteria: WorkspaceEntityDuplicateCriteria[],
|
||||||
): ObjectMetadataItemWithFieldMaps => ({
|
): ObjectMetadataItemWithFieldMaps => ({
|
||||||
id: '',
|
id: '',
|
||||||
|
icon: 'Icon123',
|
||||||
standardId: '',
|
standardId: '',
|
||||||
nameSingular: 'person',
|
nameSingular: 'person',
|
||||||
namePlural: 'people',
|
namePlural: 'people',
|
||||||
@ -24,12 +25,16 @@ export const mockPersonObjectMetadata = (
|
|||||||
labelIdentifierFieldMetadataId: '',
|
labelIdentifierFieldMetadataId: '',
|
||||||
imageIdentifierFieldMetadataId: '',
|
imageIdentifierFieldMetadataId: '',
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
fields: [],
|
|
||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
fieldsById: {},
|
fieldIdByName: {
|
||||||
fieldsByJoinColumnName: {},
|
name: 'name-id',
|
||||||
fieldsByName: {
|
emails: 'emails-id',
|
||||||
name: {
|
linkedinLink: 'linkedinLink-id',
|
||||||
|
jobTitle: 'jobTitle-id',
|
||||||
|
},
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
|
fieldsById: {
|
||||||
|
'name-id': {
|
||||||
id: '',
|
id: '',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.FULL_NAME,
|
type: FieldMetadataType.FULL_NAME,
|
||||||
@ -44,8 +49,11 @@ export const mockPersonObjectMetadata = (
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isUnique: false,
|
isUnique: false,
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
emails: {
|
'emails-id': {
|
||||||
id: '',
|
id: '',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.EMAILS,
|
type: FieldMetadataType.EMAILS,
|
||||||
@ -57,9 +65,13 @@ export const mockPersonObjectMetadata = (
|
|||||||
},
|
},
|
||||||
description: 'Contact’s Emails',
|
description: 'Contact’s Emails',
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
|
isNullable: true,
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
linkedinLink: {
|
'linkedinLink-id': {
|
||||||
id: '',
|
id: '',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.LINKS,
|
type: FieldMetadataType.LINKS,
|
||||||
@ -75,8 +87,11 @@ export const mockPersonObjectMetadata = (
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isUnique: false,
|
isUnique: false,
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
jobTitle: {
|
'jobTitle-id': {
|
||||||
id: '',
|
id: '',
|
||||||
objectMetadataId: '',
|
objectMetadataId: '',
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
@ -88,6 +103,9 @@ export const mockPersonObjectMetadata = (
|
|||||||
isNullable: false,
|
isNullable: false,
|
||||||
isUnique: false,
|
isUnique: false,
|
||||||
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
|
workspaceId: '20202020-1c25-4d02-bf25-6aeccf7ea419',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export enum GraphqlQueryRunnerExceptionCode {
|
|||||||
UNSUPPORTED_OPERATOR = 'UNSUPPORTED_OPERATOR',
|
UNSUPPORTED_OPERATOR = 'UNSUPPORTED_OPERATOR',
|
||||||
ARGS_CONFLICT = 'ARGS_CONFLICT',
|
ARGS_CONFLICT = 'ARGS_CONFLICT',
|
||||||
FIELD_NOT_FOUND = 'FIELD_NOT_FOUND',
|
FIELD_NOT_FOUND = 'FIELD_NOT_FOUND',
|
||||||
|
MISSING_SYSTEM_FIELD = 'MISSING_SYSTEM_FIELD',
|
||||||
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND',
|
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND',
|
||||||
RECORD_NOT_FOUND = 'RECORD_NOT_FOUND',
|
RECORD_NOT_FOUND = 'RECORD_NOT_FOUND',
|
||||||
INVALID_ARGS_FIRST = 'INVALID_ARGS_FIRST',
|
INVALID_ARGS_FIRST = 'INVALID_ARGS_FIRST',
|
||||||
|
|||||||
@ -2,25 +2,19 @@ import { Brackets, NotBrackets, WhereExpressionBuilder } from 'typeorm';
|
|||||||
|
|
||||||
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
|
|
||||||
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 { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
|
|
||||||
import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.parser';
|
import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.parser';
|
||||||
|
|
||||||
export class GraphqlQueryFilterConditionParser {
|
export class GraphqlQueryFilterConditionParser {
|
||||||
private fieldMetadataMapByName: FieldMetadataMap;
|
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||||
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;
|
|
||||||
private queryFilterFieldParser: GraphqlQueryFilterFieldParser;
|
private queryFilterFieldParser: GraphqlQueryFilterFieldParser;
|
||||||
|
|
||||||
constructor(
|
constructor(objectMetadataMapItem: ObjectMetadataItemWithFieldMaps) {
|
||||||
fieldMetadataMapByName: FieldMetadataMap,
|
this.objectMetadataMapItem = objectMetadataMapItem;
|
||||||
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
|
|
||||||
) {
|
|
||||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
|
||||||
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
|
|
||||||
this.queryFilterFieldParser = new GraphqlQueryFilterFieldParser(
|
this.queryFilterFieldParser = new GraphqlQueryFilterFieldParser(
|
||||||
this.fieldMetadataMapByName,
|
this.objectMetadataMapItem,
|
||||||
this.fieldMetadataMapByJoinColumnName,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,21 +10,16 @@ import {
|
|||||||
import { computeWhereConditionParts } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts';
|
import { computeWhereConditionParts } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts';
|
||||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
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 { 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 { 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';
|
||||||
|
|
||||||
const ARRAY_OPERATORS = ['in', 'contains', 'notContains'];
|
const ARRAY_OPERATORS = ['in', 'contains', 'notContains'];
|
||||||
|
|
||||||
export class GraphqlQueryFilterFieldParser {
|
export class GraphqlQueryFilterFieldParser {
|
||||||
private fieldMetadataMapByName: FieldMetadataMap;
|
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||||
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;
|
|
||||||
|
|
||||||
constructor(
|
constructor(objectMetadataMapItem: ObjectMetadataItemWithFieldMaps) {
|
||||||
fieldMetadataMapByName: FieldMetadataMap,
|
this.objectMetadataMapItem = objectMetadataMapItem;
|
||||||
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
|
|
||||||
) {
|
|
||||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
|
||||||
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public parse(
|
public parse(
|
||||||
@ -35,9 +30,12 @@ export class GraphqlQueryFilterFieldParser {
|
|||||||
filterValue: any,
|
filterValue: any,
|
||||||
isFirst = false,
|
isFirst = false,
|
||||||
): void {
|
): void {
|
||||||
|
const fieldMetadataId =
|
||||||
|
this.objectMetadataMapItem.fieldIdByName[`${key}`] ||
|
||||||
|
this.objectMetadataMapItem.fieldIdByJoinColumnName[`${key}`];
|
||||||
|
|
||||||
const fieldMetadata =
|
const fieldMetadata =
|
||||||
this.fieldMetadataMapByName[`${key}`] ||
|
this.objectMetadataMapItem.fieldsById[fieldMetadataId];
|
||||||
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}`);
|
||||||
|
|||||||
@ -12,14 +12,14 @@ import {
|
|||||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
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 { 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 { 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';
|
||||||
|
|
||||||
export class GraphqlQueryOrderFieldParser {
|
export class GraphqlQueryOrderFieldParser {
|
||||||
private fieldMetadataMapByName: FieldMetadataMap;
|
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||||
|
|
||||||
constructor(fieldMetadataMapByName: FieldMetadataMap) {
|
constructor(objectMetadataMapItem: ObjectMetadataItemWithFieldMaps) {
|
||||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
this.objectMetadataMapItem = objectMetadataMapItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(
|
parse(
|
||||||
@ -30,7 +30,9 @@ export class GraphqlQueryOrderFieldParser {
|
|||||||
return orderBy.reduce(
|
return orderBy.reduce(
|
||||||
(acc, item) => {
|
(acc, item) => {
|
||||||
Object.entries(item).forEach(([key, value]) => {
|
Object.entries(item).forEach(([key, value]) => {
|
||||||
const fieldMetadata = this.fieldMetadataMapByName[key];
|
const fieldMetadataId = this.objectMetadataMapItem.fieldIdByName[key];
|
||||||
|
const fieldMetadata =
|
||||||
|
this.objectMetadataMapItem.fieldsById[fieldMetadataId];
|
||||||
|
|
||||||
if (!fieldMetadata || value === undefined) {
|
if (!fieldMetadata || value === undefined) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
|
|||||||
@ -1,21 +1,20 @@
|
|||||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
|
||||||
|
|
||||||
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||||
import {
|
import {
|
||||||
AggregationField,
|
AggregationField,
|
||||||
getAvailableAggregationsFromObjectFields,
|
getAvailableAggregationsFromObjectFields,
|
||||||
} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
} from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
|
|
||||||
export class GraphqlQuerySelectedFieldsAggregateParser {
|
export class GraphqlQuerySelectedFieldsAggregateParser {
|
||||||
parse(
|
parse(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
|
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||||
accumulator: GraphqlQuerySelectedFieldsResult,
|
accumulator: GraphqlQuerySelectedFieldsResult,
|
||||||
): void {
|
): void {
|
||||||
const availableAggregations: Record<string, AggregationField> =
|
const availableAggregations: Record<string, AggregationField> =
|
||||||
getAvailableAggregationsFromObjectFields(
|
getAvailableAggregationsFromObjectFields(
|
||||||
Object.values(fieldMetadataMapByName),
|
Object.values(objectMetadataMapItem.fieldsById),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const selectedField of Object.keys(graphqlSelectedFields)) {
|
for (const selectedField of Object.keys(graphqlSelectedFields)) {
|
||||||
|
|||||||
@ -32,11 +32,13 @@ export class GraphqlQuerySelectedFieldsRelationParser {
|
|||||||
this.objectMetadataMaps,
|
this.objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetFields = targetObjectMetadata.fieldsByName;
|
|
||||||
const fieldParser = new GraphqlQuerySelectedFieldsParser(
|
const fieldParser = new GraphqlQuerySelectedFieldsParser(
|
||||||
this.objectMetadataMaps,
|
this.objectMetadataMaps,
|
||||||
);
|
);
|
||||||
const relationAccumulator = fieldParser.parse(fieldValue, targetFields);
|
const relationAccumulator = fieldParser.parse(
|
||||||
|
fieldValue,
|
||||||
|
targetObjectMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
accumulator.select[fieldKey] = {
|
accumulator.select[fieldKey] = {
|
||||||
id: true,
|
id: true,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { GraphqlQuerySelectedFieldsAggregateParser } from 'src/engine/api/graphq
|
|||||||
import { GraphqlQuerySelectedFieldsRelationParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser';
|
import { GraphqlQuerySelectedFieldsRelationParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields-relation.parser';
|
||||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
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 { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-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 { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||||
@ -32,7 +33,7 @@ export class GraphqlQuerySelectedFieldsParser {
|
|||||||
parse(
|
parse(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
|
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||||
): GraphqlQuerySelectedFieldsResult {
|
): GraphqlQuerySelectedFieldsResult {
|
||||||
const accumulator: GraphqlQuerySelectedFieldsResult = {
|
const accumulator: GraphqlQuerySelectedFieldsResult = {
|
||||||
select: {},
|
select: {},
|
||||||
@ -43,7 +44,7 @@ export class GraphqlQuerySelectedFieldsParser {
|
|||||||
if (this.isRootConnection(graphqlSelectedFields)) {
|
if (this.isRootConnection(graphqlSelectedFields)) {
|
||||||
this.parseConnectionField(
|
this.parseConnectionField(
|
||||||
graphqlSelectedFields,
|
graphqlSelectedFields,
|
||||||
fieldMetadataMapByName,
|
objectMetadataMapItem,
|
||||||
accumulator,
|
accumulator,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -52,13 +53,13 @@ export class GraphqlQuerySelectedFieldsParser {
|
|||||||
|
|
||||||
this.aggregateParser.parse(
|
this.aggregateParser.parse(
|
||||||
graphqlSelectedFields,
|
graphqlSelectedFields,
|
||||||
fieldMetadataMapByName,
|
objectMetadataMapItem,
|
||||||
accumulator,
|
accumulator,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.parseRecordField(
|
this.parseRecordField(
|
||||||
graphqlSelectedFields,
|
graphqlSelectedFields,
|
||||||
fieldMetadataMapByName,
|
objectMetadataMapItem,
|
||||||
accumulator,
|
accumulator,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -68,13 +69,16 @@ export class GraphqlQuerySelectedFieldsParser {
|
|||||||
private parseRecordField(
|
private parseRecordField(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
|
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||||
accumulator: GraphqlQuerySelectedFieldsResult,
|
accumulator: GraphqlQuerySelectedFieldsResult,
|
||||||
): void {
|
): void {
|
||||||
for (const [fieldKey, fieldValue] of Object.entries(
|
for (const [fieldKey, fieldValue] of Object.entries(
|
||||||
graphqlSelectedFields,
|
graphqlSelectedFields,
|
||||||
)) {
|
)) {
|
||||||
const fieldMetadata = fieldMetadataMapByName[fieldKey];
|
const fieldMetadata =
|
||||||
|
objectMetadataMapItem.fieldsById[
|
||||||
|
objectMetadataMapItem.fieldIdByName[fieldKey]
|
||||||
|
];
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
continue;
|
continue;
|
||||||
@ -103,18 +107,18 @@ export class GraphqlQuerySelectedFieldsParser {
|
|||||||
private parseConnectionField(
|
private parseConnectionField(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
|
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||||
accumulator: GraphqlQuerySelectedFieldsResult,
|
accumulator: GraphqlQuerySelectedFieldsResult,
|
||||||
): void {
|
): void {
|
||||||
this.aggregateParser.parse(
|
this.aggregateParser.parse(
|
||||||
graphqlSelectedFields,
|
graphqlSelectedFields,
|
||||||
fieldMetadataMapByName,
|
objectMetadataMapItem,
|
||||||
accumulator,
|
accumulator,
|
||||||
);
|
);
|
||||||
|
|
||||||
const node = graphqlSelectedFields.edges.node;
|
const node = graphqlSelectedFields.edges.node;
|
||||||
|
|
||||||
this.parseRecordField(node, fieldMetadataMapByName, accumulator);
|
this.parseRecordField(node, objectMetadataMapItem, accumulator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isRootConnection(
|
private isRootConnection(
|
||||||
|
|||||||
@ -15,33 +15,29 @@ import {
|
|||||||
GraphqlQuerySelectedFieldsParser,
|
GraphqlQuerySelectedFieldsParser,
|
||||||
GraphqlQuerySelectedFieldsResult,
|
GraphqlQuerySelectedFieldsResult,
|
||||||
} from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
} from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||||
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 { 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 { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
|
|
||||||
export class GraphqlQueryParser {
|
export class GraphqlQueryParser {
|
||||||
private fieldMetadataMapByName: FieldMetadataMap;
|
private objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||||
private fieldMetadataMapByJoinColumnName: FieldMetadataMap;
|
|
||||||
private objectMetadataMaps: ObjectMetadataMaps;
|
private objectMetadataMaps: ObjectMetadataMaps;
|
||||||
private filterConditionParser: GraphqlQueryFilterConditionParser;
|
private filterConditionParser: GraphqlQueryFilterConditionParser;
|
||||||
private orderFieldParser: GraphqlQueryOrderFieldParser;
|
private orderFieldParser: GraphqlQueryOrderFieldParser;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
fieldMetadataMapByName: FieldMetadataMap,
|
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps,
|
||||||
fieldMetadataMapByJoinColumnName: FieldMetadataMap,
|
|
||||||
objectMetadataMaps: ObjectMetadataMaps,
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
) {
|
) {
|
||||||
|
this.objectMetadataMapItem = objectMetadataMapItem;
|
||||||
this.objectMetadataMaps = objectMetadataMaps;
|
this.objectMetadataMaps = objectMetadataMaps;
|
||||||
this.fieldMetadataMapByName = fieldMetadataMapByName;
|
|
||||||
this.fieldMetadataMapByJoinColumnName = fieldMetadataMapByJoinColumnName;
|
|
||||||
this.filterConditionParser = new GraphqlQueryFilterConditionParser(
|
this.filterConditionParser = new GraphqlQueryFilterConditionParser(
|
||||||
this.fieldMetadataMapByName,
|
this.objectMetadataMapItem,
|
||||||
this.fieldMetadataMapByJoinColumnName,
|
|
||||||
);
|
);
|
||||||
this.orderFieldParser = new GraphqlQueryOrderFieldParser(
|
this.orderFieldParser = new GraphqlQueryOrderFieldParser(
|
||||||
this.fieldMetadataMapByName,
|
this.objectMetadataMapItem,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,12 +116,12 @@ export class GraphqlQueryParser {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
graphqlSelectedFields: Partial<Record<string, any>>,
|
graphqlSelectedFields: Partial<Record<string, any>>,
|
||||||
): GraphqlQuerySelectedFieldsResult {
|
): GraphqlQuerySelectedFieldsResult {
|
||||||
const parentFields = getObjectMetadataMapItemByNameSingular(
|
const objectMetadataMapItem = getObjectMetadataMapItemByNameSingular(
|
||||||
this.objectMetadataMaps,
|
this.objectMetadataMaps,
|
||||||
parentObjectMetadata.nameSingular,
|
parentObjectMetadata.nameSingular,
|
||||||
)?.fieldsByName;
|
);
|
||||||
|
|
||||||
if (!parentFields) {
|
if (!objectMetadataMapItem) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
`Could not find object metadata for ${parentObjectMetadata.nameSingular}`,
|
`Could not find object metadata for ${parentObjectMetadata.nameSingular}`,
|
||||||
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||||
@ -136,6 +132,9 @@ export class GraphqlQueryParser {
|
|||||||
this.objectMetadataMaps,
|
this.objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
return selectedFieldsParser.parse(graphqlSelectedFields, parentFields);
|
return selectedFieldsParser.parse(
|
||||||
|
graphqlSelectedFields,
|
||||||
|
objectMetadataMapItem,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -168,7 +168,8 @@ export class ObjectRecordsToGraphqlConnectionHelper {
|
|||||||
const processedObjectRecord: Record<string, any> = {};
|
const processedObjectRecord: Record<string, any> = {};
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(objectRecord)) {
|
for (const [key, value] of Object.entries(objectRecord)) {
|
||||||
const fieldMetadata = objectMetadata.fieldsByName[key];
|
const fieldMetadataId = objectMetadata.fieldIdByName[key];
|
||||||
|
const fieldMetadata = objectMetadata.fieldsById[fieldMetadataId];
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
processedObjectRecord[key] = value;
|
processedObjectRecord[key] = value;
|
||||||
|
|||||||
@ -103,8 +103,10 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
shouldBypassPermissionChecks: boolean;
|
shouldBypassPermissionChecks: boolean;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
|
const sourceFieldMetadataId =
|
||||||
|
parentObjectMetadataItem.fieldIdByName[sourceFieldName];
|
||||||
const sourceFieldMetadata =
|
const sourceFieldMetadata =
|
||||||
parentObjectMetadataItem.fieldsByName[sourceFieldName];
|
parentObjectMetadataItem.fieldsById[sourceFieldMetadataId];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isFieldMetadataInterfaceOfType(
|
!isFieldMetadataInterfaceOfType(
|
||||||
@ -219,8 +221,11 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||||
sourceFieldName: string;
|
sourceFieldName: string;
|
||||||
}) {
|
}) {
|
||||||
|
const targetFieldMetadataId =
|
||||||
|
parentObjectMetadataItem.fieldIdByName[sourceFieldName];
|
||||||
const targetFieldMetadata =
|
const targetFieldMetadata =
|
||||||
parentObjectMetadataItem.fieldsByName[sourceFieldName];
|
parentObjectMetadataItem.fieldsById[targetFieldMetadataId];
|
||||||
|
|
||||||
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
const targetObjectMetadata = getTargetObjectMetadataOrThrow(
|
||||||
targetFieldMetadata,
|
targetFieldMetadata,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
|
|||||||
@ -93,7 +93,6 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
const workspaceDataSource =
|
const workspaceDataSource =
|
||||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
shouldFailIfMetadataNotFound: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const featureFlagsMap = workspaceDataSource.featureFlagMap;
|
const featureFlagsMap = workspaceDataSource.featureFlagMap;
|
||||||
@ -132,8 +131,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
);
|
);
|
||||||
|
|
||||||
const graphqlQueryParser = new GraphqlQueryParser(
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataItemWithFieldMaps.fieldsByJoinColumnName,
|
|
||||||
options.objectMetadataMaps,
|
options.objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,10 @@ import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/int
|
|||||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||||
import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GraphqlQueryRunnerException,
|
||||||
|
GraphqlQueryRunnerExceptionCode,
|
||||||
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
@ -19,6 +23,7 @@ import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-meta
|
|||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.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 { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
@ -128,7 +133,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
fullPath: string;
|
fullPath: string;
|
||||||
column: string;
|
column: string;
|
||||||
}[] {
|
}[] {
|
||||||
return objectMetadataItemWithFieldMaps.fields
|
return Object.values(objectMetadataItemWithFieldMaps.fieldsById)
|
||||||
.filter((field) => field.isUnique || field.name === 'id')
|
.filter((field) => field.isUnique || field.name === 'id')
|
||||||
.flatMap((field) => {
|
.flatMap((field) => {
|
||||||
const compositeType = compositeTypeDefinitions.get(field.type);
|
const compositeType = compositeTypeDefinitions.get(field.type);
|
||||||
@ -330,7 +335,10 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
records: structuredClone([record]),
|
records: structuredClone([record]),
|
||||||
updatedFields: Object.keys(formattedPartialRecordToUpdate),
|
updatedFields: Object.keys(formattedPartialRecordToUpdate),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem:
|
||||||
|
getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -373,7 +381,9 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
this.apiEventEmitterService.emitCreateEvents({
|
this.apiEventEmitterService.emitCreateEvents({
|
||||||
records: structuredClone(formattedInsertedRecords),
|
records: structuredClone(formattedInsertedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,11 +460,19 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
) {
|
) {
|
||||||
let recordWithoutCreatedByUpdate = record;
|
let recordWithoutCreatedByUpdate = record;
|
||||||
|
|
||||||
if (
|
const createdByFieldMetadataId =
|
||||||
'createdBy' in record &&
|
objectMetadataItemWithFieldMaps.fieldIdByName['createdBy'];
|
||||||
objectMetadataItemWithFieldMaps.fieldsByName['createdBy']?.isCustom ===
|
const createdByFieldMetadata =
|
||||||
false
|
objectMetadataItemWithFieldMaps.fieldsById[createdByFieldMetadataId];
|
||||||
) {
|
|
||||||
|
if (!isDefined(createdByFieldMetadata)) {
|
||||||
|
throw new GraphqlQueryRunnerException(
|
||||||
|
`Missing createdBy field metadata for object ${objectMetadataItemWithFieldMaps.nameSingular}`,
|
||||||
|
GraphqlQueryRunnerExceptionCode.MISSING_SYSTEM_FIELD,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('createdBy' in record && createdByFieldMetadata.isCustom === false) {
|
||||||
const { createdBy: _createdBy, ...recordWithoutCreatedBy } = record;
|
const { createdBy: _createdBy, ...recordWithoutCreatedBy } = record;
|
||||||
|
|
||||||
recordWithoutCreatedByUpdate = recordWithoutCreatedBy;
|
recordWithoutCreatedByUpdate = recordWithoutCreatedBy;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver
|
|||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -56,7 +57,9 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
this.apiEventEmitterService.emitCreateEvents({
|
this.apiEventEmitterService.emitCreateEvents({
|
||||||
records: structuredClone(upsertedRecords),
|
records: structuredClone(upsertedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { DeleteManyResolverArgs } from 'src/engine/api/graphql/workspace-resolve
|
|||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
|
||||||
@ -58,7 +59,9 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
|||||||
this.apiEventEmitterService.emitDeletedEvents({
|
this.apiEventEmitterService.emitDeletedEvents({
|
||||||
records: structuredClone(formattedDeletedRecords),
|
records: structuredClone(formattedDeletedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
|
|||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -60,7 +61,9 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
this.apiEventEmitterService.emitDeletedEvents({
|
this.apiEventEmitterService.emitDeletedEvents({
|
||||||
records: structuredClone(formattedDeletedRecords),
|
records: structuredClone(formattedDeletedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-qu
|
|||||||
import { DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
import { DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
|
||||||
@ -56,7 +57,9 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
|||||||
this.apiEventEmitterService.emitDestroyEvents({
|
this.apiEventEmitterService.emitDestroyEvents({
|
||||||
records: structuredClone(deletedRecords),
|
records: structuredClone(deletedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
GraphqlQueryRunnerExceptionCode,
|
GraphqlQueryRunnerExceptionCode,
|
||||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -56,7 +57,9 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
|||||||
this.apiEventEmitterService.emitDestroyEvents({
|
this.apiEventEmitterService.emitDestroyEvents({
|
||||||
records: structuredClone(deletedRecords),
|
records: structuredClone(deletedRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
|
|||||||
@ -21,10 +21,10 @@ import {
|
|||||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
|
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
||||||
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 { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -56,8 +56,7 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
|
|||||||
}
|
}
|
||||||
|
|
||||||
const graphqlQueryParser = new GraphqlQueryParser(
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
objectMetadataItemWithFieldsMaps?.fieldsByName,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataItemWithFieldsMaps?.fieldsByJoinColumnName,
|
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -80,7 +80,7 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
|
|||||||
const cursorArgFilter = computeCursorArgFilter(
|
const cursorArgFilter = computeCursorArgFilter(
|
||||||
cursor,
|
cursor,
|
||||||
orderByWithIdCondition,
|
orderByWithIdCondition,
|
||||||
objectMetadataItemWithFieldMaps.fieldsByName,
|
objectMetadataItemWithFieldMaps,
|
||||||
isForwardPagination,
|
isForwardPagination,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner
|
|||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -58,7 +59,9 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
|||||||
this.apiEventEmitterService.emitRestoreEvents({
|
this.apiEventEmitterService.emitRestoreEvents({
|
||||||
records: structuredClone(formattedRestoredRecords),
|
records: structuredClone(formattedRestoredRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -60,7 +61,9 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
|
|||||||
this.apiEventEmitterService.emitRestoreEvents({
|
this.apiEventEmitterService.emitRestoreEvents({
|
||||||
records: structuredClone(formattedRestoredRecords),
|
records: structuredClone(formattedRestoredRecords),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/obj
|
|||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -94,7 +95,9 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
records: structuredClone(formattedUpdatedRecords),
|
records: structuredClone(formattedUpdatedRecords),
|
||||||
updatedFields: Object.keys(executionArgs.args.data),
|
updatedFields: Object.keys(executionArgs.args.data),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
@ -89,7 +90,9 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
records: structuredClone(formattedUpdatedRecords),
|
records: structuredClone(formattedUpdatedRecords),
|
||||||
updatedFields: Object.keys(executionArgs.args.data),
|
updatedFields: Object.keys(executionArgs.args.data),
|
||||||
authContext,
|
authContext,
|
||||||
objectMetadataItem: objectMetadataItemWithFieldMaps,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
|
|||||||
@ -23,40 +23,29 @@ describe('QueryRunnerArgsFactory', () => {
|
|||||||
objectMetadataItemWithFieldMaps: {
|
objectMetadataItemWithFieldMaps: {
|
||||||
isCustom: true,
|
isCustom: true,
|
||||||
nameSingular: 'testNumber',
|
nameSingular: 'testNumber',
|
||||||
fields: [
|
fieldsById: {
|
||||||
{
|
'position-id': {
|
||||||
type: FieldMetadataType.POSITION,
|
type: FieldMetadataType.POSITION,
|
||||||
isCustom: true,
|
isCustom: true,
|
||||||
name: 'position',
|
name: 'position',
|
||||||
},
|
},
|
||||||
{
|
'testNumber-id': {
|
||||||
type: FieldMetadataType.NUMBER,
|
type: FieldMetadataType.NUMBER,
|
||||||
isCustom: true,
|
isCustom: true,
|
||||||
name: 'testNumber',
|
name: 'testNumber',
|
||||||
},
|
},
|
||||||
{
|
'otherField-id': {
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
isCustom: true,
|
|
||||||
name: 'otherField',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
fieldsByName: {
|
|
||||||
position: {
|
|
||||||
type: FieldMetadataType.POSITION,
|
|
||||||
isCustom: true,
|
|
||||||
name: 'position',
|
|
||||||
},
|
|
||||||
testNumber: {
|
|
||||||
type: FieldMetadataType.NUMBER,
|
|
||||||
isCustom: true,
|
|
||||||
name: 'testNumber',
|
|
||||||
},
|
|
||||||
otherField: {
|
|
||||||
type: FieldMetadataType.TEXT,
|
type: FieldMetadataType.TEXT,
|
||||||
isCustom: true,
|
isCustom: true,
|
||||||
name: 'otherField',
|
name: 'otherField',
|
||||||
},
|
},
|
||||||
} as unknown as FieldMetadataMap,
|
} as unknown as FieldMetadataMap,
|
||||||
|
fieldIdByName: {
|
||||||
|
position: 'position-id',
|
||||||
|
testNumber: 'testNumber-id',
|
||||||
|
otherField: 'otherField-id',
|
||||||
|
},
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
},
|
},
|
||||||
} as unknown as WorkspaceQueryRunnerOptions;
|
} as unknown as WorkspaceQueryRunnerOptions;
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-ru
|
|||||||
import { QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface';
|
import { QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface';
|
||||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||||
import { IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface';
|
import { IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface';
|
||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
|
||||||
|
|
||||||
import { isQueryResultFieldValueAConnection } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/guards/is-query-result-field-value-a-connection.guard';
|
import { isQueryResultFieldValueAConnection } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/guards/is-query-result-field-value-a-connection.guard';
|
||||||
import { isQueryResultFieldValueANestedRecordArray } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/guards/is-query-result-field-value-a-nested-record-array.guard';
|
import { isQueryResultFieldValueANestedRecordArray } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/guards/is-query-result-field-value-a-nested-record-array.guard';
|
||||||
@ -22,6 +21,7 @@ import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/work
|
|||||||
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 { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
|
|
||||||
// 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
|
||||||
@ -126,7 +126,9 @@ export class QueryResultGettersFactory {
|
|||||||
const relationFields = Object.keys(record)
|
const relationFields = Object.keys(record)
|
||||||
.map(
|
.map(
|
||||||
(recordFieldName) =>
|
(recordFieldName) =>
|
||||||
objectMetadataMapItem.fieldsByName[recordFieldName],
|
objectMetadataMapItem.fieldsById[
|
||||||
|
objectMetadataMapItem.fieldIdByName[recordFieldName]
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.filter(isDefined)
|
.filter(isDefined)
|
||||||
.filter((fieldMetadata) =>
|
.filter((fieldMetadata) =>
|
||||||
@ -214,7 +216,7 @@ export class QueryResultGettersFactory {
|
|||||||
|
|
||||||
async create(
|
async create(
|
||||||
result: QueryResultFieldValue,
|
result: QueryResultFieldValue,
|
||||||
objectMetadataItem: ObjectMetadataInterface,
|
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
objectMetadataMaps: ObjectMetadataMaps,
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|||||||
@ -19,12 +19,12 @@ import {
|
|||||||
UpdateManyResolverArgs,
|
UpdateManyResolverArgs,
|
||||||
UpdateOneResolverArgs,
|
UpdateOneResolverArgs,
|
||||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
|
||||||
|
|
||||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
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';
|
||||||
|
|
||||||
type ArgPositionBackfillInput = {
|
type ArgPositionBackfillInput = {
|
||||||
argIndex?: number;
|
argIndex?: number;
|
||||||
@ -44,14 +44,14 @@ export class QueryRunnerArgsFactory {
|
|||||||
resolverArgsType: ResolverArgsType,
|
resolverArgsType: ResolverArgsType,
|
||||||
) {
|
) {
|
||||||
const fieldMetadataMapByNameByName =
|
const fieldMetadataMapByNameByName =
|
||||||
options.objectMetadataItemWithFieldMaps.fieldsByName;
|
options.objectMetadataItemWithFieldMaps.fieldsById;
|
||||||
|
|
||||||
const shouldBackfillPosition =
|
const shouldBackfillPosition = Object.values(
|
||||||
options.objectMetadataItemWithFieldMaps.fields.some(
|
options.objectMetadataItemWithFieldMaps.fieldsById,
|
||||||
(field) =>
|
).some(
|
||||||
field.type === FieldMetadataType.POSITION &&
|
(field) =>
|
||||||
field.name === 'position',
|
field.type === FieldMetadataType.POSITION && field.name === 'position',
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (resolverArgsType) {
|
switch (resolverArgsType) {
|
||||||
case ResolverArgsType.CreateOne:
|
case ResolverArgsType.CreateOne:
|
||||||
@ -60,7 +60,6 @@ export class QueryRunnerArgsFactory {
|
|||||||
data: await this.overrideDataByFieldMetadata(
|
data: await this.overrideDataByFieldMetadata(
|
||||||
(args as CreateOneResolverArgs).data,
|
(args as CreateOneResolverArgs).data,
|
||||||
options,
|
options,
|
||||||
fieldMetadataMapByNameByName,
|
|
||||||
{
|
{
|
||||||
argIndex: 0,
|
argIndex: 0,
|
||||||
shouldBackfillPosition,
|
shouldBackfillPosition,
|
||||||
@ -72,15 +71,10 @@ export class QueryRunnerArgsFactory {
|
|||||||
...args,
|
...args,
|
||||||
data: await Promise.all(
|
data: await Promise.all(
|
||||||
(args as CreateManyResolverArgs).data?.map((arg, index) =>
|
(args as CreateManyResolverArgs).data?.map((arg, index) =>
|
||||||
this.overrideDataByFieldMetadata(
|
this.overrideDataByFieldMetadata(arg, options, {
|
||||||
arg,
|
argIndex: index,
|
||||||
options,
|
shouldBackfillPosition,
|
||||||
fieldMetadataMapByNameByName,
|
}),
|
||||||
{
|
|
||||||
argIndex: index,
|
|
||||||
shouldBackfillPosition,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
) ?? [],
|
) ?? [],
|
||||||
),
|
),
|
||||||
} satisfies CreateManyResolverArgs;
|
} satisfies CreateManyResolverArgs;
|
||||||
@ -91,7 +85,6 @@ export class QueryRunnerArgsFactory {
|
|||||||
data: await this.overrideDataByFieldMetadata(
|
data: await this.overrideDataByFieldMetadata(
|
||||||
(args as UpdateOneResolverArgs).data,
|
(args as UpdateOneResolverArgs).data,
|
||||||
options,
|
options,
|
||||||
fieldMetadataMapByNameByName,
|
|
||||||
{
|
{
|
||||||
argIndex: 0,
|
argIndex: 0,
|
||||||
shouldBackfillPosition: false,
|
shouldBackfillPosition: false,
|
||||||
@ -103,12 +96,11 @@ export class QueryRunnerArgsFactory {
|
|||||||
...args,
|
...args,
|
||||||
filter: this.overrideFilterByFieldMetadata(
|
filter: this.overrideFilterByFieldMetadata(
|
||||||
(args as UpdateManyResolverArgs).filter,
|
(args as UpdateManyResolverArgs).filter,
|
||||||
fieldMetadataMapByNameByName,
|
options.objectMetadataItemWithFieldMaps,
|
||||||
),
|
),
|
||||||
data: await this.overrideDataByFieldMetadata(
|
data: await this.overrideDataByFieldMetadata(
|
||||||
(args as UpdateManyResolverArgs).data,
|
(args as UpdateManyResolverArgs).data,
|
||||||
options,
|
options,
|
||||||
fieldMetadataMapByNameByName,
|
|
||||||
{
|
{
|
||||||
argIndex: 0,
|
argIndex: 0,
|
||||||
shouldBackfillPosition: false,
|
shouldBackfillPosition: false,
|
||||||
@ -120,7 +112,7 @@ export class QueryRunnerArgsFactory {
|
|||||||
...args,
|
...args,
|
||||||
filter: this.overrideFilterByFieldMetadata(
|
filter: this.overrideFilterByFieldMetadata(
|
||||||
(args as FindOneResolverArgs).filter,
|
(args as FindOneResolverArgs).filter,
|
||||||
fieldMetadataMapByNameByName,
|
options.objectMetadataItemWithFieldMaps,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
case ResolverArgsType.FindMany:
|
case ResolverArgsType.FindMany:
|
||||||
@ -128,7 +120,7 @@ export class QueryRunnerArgsFactory {
|
|||||||
...args,
|
...args,
|
||||||
filter: this.overrideFilterByFieldMetadata(
|
filter: this.overrideFilterByFieldMetadata(
|
||||||
(args as FindManyResolverArgs).filter,
|
(args as FindManyResolverArgs).filter,
|
||||||
fieldMetadataMapByNameByName,
|
options.objectMetadataItemWithFieldMaps,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -146,15 +138,10 @@ export class QueryRunnerArgsFactory {
|
|||||||
)) as string[],
|
)) as string[],
|
||||||
data: await Promise.all(
|
data: await Promise.all(
|
||||||
(args as FindDuplicatesResolverArgs).data?.map((arg, index) =>
|
(args as FindDuplicatesResolverArgs).data?.map((arg, index) =>
|
||||||
this.overrideDataByFieldMetadata(
|
this.overrideDataByFieldMetadata(arg, options, {
|
||||||
arg,
|
argIndex: index,
|
||||||
options,
|
shouldBackfillPosition,
|
||||||
fieldMetadataMapByNameByName,
|
}),
|
||||||
{
|
|
||||||
argIndex: index,
|
|
||||||
shouldBackfillPosition,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
) ?? [],
|
) ?? [],
|
||||||
),
|
),
|
||||||
} satisfies FindDuplicatesResolverArgs;
|
} satisfies FindDuplicatesResolverArgs;
|
||||||
@ -166,7 +153,6 @@ export class QueryRunnerArgsFactory {
|
|||||||
private async overrideDataByFieldMetadata(
|
private async overrideDataByFieldMetadata(
|
||||||
data: Partial<ObjectRecord> | undefined,
|
data: Partial<ObjectRecord> | undefined,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
fieldMetadataMapByNameByName: Record<string, FieldMetadataInterface>,
|
|
||||||
argPositionBackfillInput: ArgPositionBackfillInput,
|
argPositionBackfillInput: ArgPositionBackfillInput,
|
||||||
): Promise<Partial<ObjectRecord>> {
|
): Promise<Partial<ObjectRecord>> {
|
||||||
if (!isDefined(data)) {
|
if (!isDefined(data)) {
|
||||||
@ -184,7 +170,10 @@ export class QueryRunnerArgsFactory {
|
|||||||
data,
|
data,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
).map(async ([key, value]): Promise<[string, any]> => {
|
).map(async ([key, value]): Promise<[string, any]> => {
|
||||||
const fieldMetadata = fieldMetadataMapByNameByName[key];
|
const fieldMetadataId =
|
||||||
|
options.objectMetadataItemWithFieldMaps.fieldIdByName[key];
|
||||||
|
const fieldMetadata =
|
||||||
|
options.objectMetadataItemWithFieldMaps.fieldsById[fieldMetadataId];
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
return [key, value];
|
return [key, value];
|
||||||
@ -257,7 +246,7 @@ export class QueryRunnerArgsFactory {
|
|||||||
|
|
||||||
private overrideFilterByFieldMetadata(
|
private overrideFilterByFieldMetadata(
|
||||||
filter: ObjectRecordFilter | undefined,
|
filter: ObjectRecordFilter | undefined,
|
||||||
fieldMetadataMapByName: Record<string, FieldMetadataInterface>,
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||||
) {
|
) {
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
return;
|
return;
|
||||||
@ -278,7 +267,7 @@ export class QueryRunnerArgsFactory {
|
|||||||
acc[key] = this.transformFilterValueByType(
|
acc[key] = this.transformFilterValueByType(
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
fieldMetadataMapByName,
|
objectMetadataItemWithFieldMaps,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,9 +282,11 @@ export class QueryRunnerArgsFactory {
|
|||||||
key: string,
|
key: string,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
value: any,
|
value: any,
|
||||||
fieldMetadataMapByName: FieldMetadataMap,
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||||
) {
|
) {
|
||||||
const fieldMetadata = fieldMetadataMapByName[key];
|
const fieldMetadataId = objectMetadataItemWithFieldMaps.fieldIdByName[key];
|
||||||
|
const fieldMetadata =
|
||||||
|
objectMetadataItemWithFieldMaps.fieldsById[fieldMetadataId];
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export const graphqlQueryRunnerExceptionHandler = (
|
|||||||
case GraphqlQueryRunnerExceptionCode.RELATION_SETTINGS_NOT_FOUND:
|
case GraphqlQueryRunnerExceptionCode.RELATION_SETTINGS_NOT_FOUND:
|
||||||
case GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND:
|
case GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND:
|
||||||
case GraphqlQueryRunnerExceptionCode.INVALID_POST_HOOK_PAYLOAD:
|
case GraphqlQueryRunnerExceptionCode.INVALID_POST_HOOK_PAYLOAD:
|
||||||
|
case GraphqlQueryRunnerExceptionCode.MISSING_SYSTEM_FIELD:
|
||||||
throw error;
|
throw error;
|
||||||
default: {
|
default: {
|
||||||
const _exhaustiveCheck: never = error.code;
|
const _exhaustiveCheck: never = error.code;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { QueryFailedError } from 'typeorm';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { QueryFailedError } from 'typeorm';
|
||||||
|
|
||||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||||
|
|
||||||
@ -14,14 +14,14 @@ export const handleDuplicateKeyError = (
|
|||||||
if (indexNameMatch) {
|
if (indexNameMatch) {
|
||||||
const indexName = indexNameMatch[1];
|
const indexName = indexNameMatch[1];
|
||||||
|
|
||||||
const deletedAtFieldMetadata =
|
const deletedAtFieldMetadataId =
|
||||||
context.objectMetadataItemWithFieldMaps.fieldsByName['deletedAt'];
|
context.objectMetadataItemWithFieldMaps.fieldIdByName['deletedAt'];
|
||||||
|
|
||||||
const affectedColumns =
|
const affectedColumns =
|
||||||
context.objectMetadataItemWithFieldMaps.indexMetadatas
|
context.objectMetadataItemWithFieldMaps.indexMetadatas
|
||||||
.find((index) => index.name === indexName)
|
.find((index) => index.name === indexName)
|
||||||
?.indexFieldMetadatas?.filter(
|
?.indexFieldMetadatas?.filter(
|
||||||
(field) => field.fieldMetadataId !== deletedAtFieldMetadata?.id,
|
(field) => field.fieldMetadataId !== deletedAtFieldMetadataId,
|
||||||
)
|
)
|
||||||
.map((indexField) => {
|
.map((indexField) => {
|
||||||
const fieldMetadata =
|
const fieldMetadata =
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export class WorkspaceResolverBuilderService {
|
|||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
shouldBuildResolver(
|
shouldBuildResolver(
|
||||||
objectMetadata: ObjectMetadataInterface,
|
objectMetadata: Pick<ObjectMetadataInterface, 'duplicateCriteria'>,
|
||||||
methodName: WorkspaceResolverBuilderMethodNames,
|
methodName: WorkspaceResolverBuilderMethodNames,
|
||||||
) {
|
) {
|
||||||
switch (methodName) {
|
switch (methodName) {
|
||||||
|
|||||||
@ -3,26 +3,18 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { makeExecutableSchema } from '@graphql-tools/schema';
|
import { makeExecutableSchema } from '@graphql-tools/schema';
|
||||||
import { GraphQLSchema, printSchema } from 'graphql';
|
import { GraphQLSchema, printSchema } from 'graphql';
|
||||||
import { gql } from 'graphql-tag';
|
import { gql } from 'graphql-tag';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
|
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
|
||||||
import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories';
|
import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories';
|
||||||
import { WorkspaceResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory';
|
import { WorkspaceResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory';
|
||||||
import { WorkspaceGraphQLSchemaFactory } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
|
import { WorkspaceGraphQLSchemaFactory } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
|
||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
|
||||||
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 { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
|
||||||
import {
|
import {
|
||||||
WorkspaceMetadataCacheException,
|
WorkspaceMetadataCacheException,
|
||||||
WorkspaceMetadataCacheExceptionCode,
|
WorkspaceMetadataCacheExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception';
|
} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception';
|
||||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||||
import {
|
|
||||||
WorkspaceMetadataVersionException,
|
|
||||||
WorkspaceMetadataVersionExceptionCode,
|
|
||||||
} from 'src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception';
|
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -34,8 +26,6 @@ export class WorkspaceSchemaFactory {
|
|||||||
private readonly workspaceResolverFactory: WorkspaceResolverFactory,
|
private readonly workspaceResolverFactory: WorkspaceResolverFactory,
|
||||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||||
private readonly featureFlagService: FeatureFlagService,
|
|
||||||
private readonly twentyConfigService: TwentyConfigService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createGraphQLSchema(authContext: AuthContext): Promise<GraphQLSchema> {
|
async createGraphQLSchema(authContext: AuthContext): Promise<GraphQLSchema> {
|
||||||
@ -52,38 +42,12 @@ export class WorkspaceSchemaFactory {
|
|||||||
return new GraphQLSchema({});
|
return new GraphQLSchema({});
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentCacheVersion =
|
const { objectMetadataMaps, metadataVersion } =
|
||||||
await this.workspaceCacheStorageService.getMetadataVersion(
|
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||||
authContext.workspace.id,
|
{
|
||||||
);
|
|
||||||
|
|
||||||
let objectMetadataMaps: ObjectMetadataMaps | undefined;
|
|
||||||
|
|
||||||
if (currentCacheVersion === undefined) {
|
|
||||||
const recomputed =
|
|
||||||
await this.workspaceMetadataCacheService.recomputeMetadataCache({
|
|
||||||
workspaceId: authContext.workspace.id,
|
workspaceId: authContext.workspace.id,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
objectMetadataMaps = recomputed?.recomputedObjectMetadataMaps;
|
|
||||||
currentCacheVersion = recomputed?.recomputedMetadataVersion;
|
|
||||||
} else {
|
|
||||||
objectMetadataMaps =
|
|
||||||
await this.workspaceCacheStorageService.getObjectMetadataMaps(
|
|
||||||
authContext.workspace.id,
|
|
||||||
currentCacheVersion,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isDefined(objectMetadataMaps)) {
|
|
||||||
const recomputed =
|
|
||||||
await this.workspaceMetadataCacheService.recomputeMetadataCache({
|
|
||||||
workspaceId: authContext.workspace.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
objectMetadataMaps = recomputed?.recomputedObjectMetadataMaps;
|
|
||||||
currentCacheVersion = recomputed?.recomputedMetadataVersion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!objectMetadataMaps) {
|
if (!objectMetadataMaps) {
|
||||||
throw new WorkspaceMetadataCacheException(
|
throw new WorkspaceMetadataCacheException(
|
||||||
@ -92,17 +56,10 @@ export class WorkspaceSchemaFactory {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentCacheVersion) {
|
|
||||||
throw new WorkspaceMetadataVersionException(
|
|
||||||
'Metadata cache version not found',
|
|
||||||
WorkspaceMetadataVersionExceptionCode.METADATA_VERSION_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const objectMetadataCollection = Object.values(objectMetadataMaps.byId).map(
|
const objectMetadataCollection = Object.values(objectMetadataMaps.byId).map(
|
||||||
(objectMetadataItem) => ({
|
(objectMetadataItem) => ({
|
||||||
...objectMetadataItem,
|
...objectMetadataItem,
|
||||||
fields: objectMetadataItem.fields,
|
fields: Object.values(objectMetadataItem.fieldsById),
|
||||||
indexes: objectMetadataItem.indexMetadatas,
|
indexes: objectMetadataItem.indexMetadatas,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -110,12 +67,12 @@ export class WorkspaceSchemaFactory {
|
|||||||
// Get typeDefs from cache
|
// Get typeDefs from cache
|
||||||
let typeDefs = await this.workspaceCacheStorageService.getGraphQLTypeDefs(
|
let typeDefs = await this.workspaceCacheStorageService.getGraphQLTypeDefs(
|
||||||
authContext.workspace.id,
|
authContext.workspace.id,
|
||||||
currentCacheVersion,
|
metadataVersion,
|
||||||
);
|
);
|
||||||
let usedScalarNames =
|
let usedScalarNames =
|
||||||
await this.workspaceCacheStorageService.getGraphQLUsedScalarNames(
|
await this.workspaceCacheStorageService.getGraphQLUsedScalarNames(
|
||||||
authContext.workspace.id,
|
authContext.workspace.id,
|
||||||
currentCacheVersion,
|
metadataVersion,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If typeDefs are not cached, generate them
|
// If typeDefs are not cached, generate them
|
||||||
@ -133,12 +90,12 @@ export class WorkspaceSchemaFactory {
|
|||||||
|
|
||||||
await this.workspaceCacheStorageService.setGraphQLTypeDefs(
|
await this.workspaceCacheStorageService.setGraphQLTypeDefs(
|
||||||
authContext.workspace.id,
|
authContext.workspace.id,
|
||||||
currentCacheVersion,
|
metadataVersion,
|
||||||
typeDefs,
|
typeDefs,
|
||||||
);
|
);
|
||||||
await this.workspaceCacheStorageService.setGraphQLUsedScalarNames(
|
await this.workspaceCacheStorageService.setGraphQLUsedScalarNames(
|
||||||
authContext.workspace.id,
|
authContext.workspace.id,
|
||||||
currentCacheVersion,
|
metadataVersion,
|
||||||
usedScalarNames,
|
usedScalarNames,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
|
|
||||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||||
|
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiCreateManyHandler extends RestApiBaseHandler {
|
export class RestApiCreateManyHandler extends RestApiBaseHandler {
|
||||||
async handle(request: Request) {
|
async handle(request: Request) {
|
||||||
@ -57,7 +59,9 @@ export class RestApiCreateManyHandler extends RestApiBaseHandler {
|
|||||||
this.apiEventEmitterService.emitCreateEvents({
|
this.apiEventEmitterService.emitCreateEvents({
|
||||||
records: createdRecords,
|
records: createdRecords,
|
||||||
authContext: this.getAuthContextFromRequest(request),
|
authContext: this.getAuthContextFromRequest(request),
|
||||||
objectMetadataItem: objectMetadata.objectMetadataMapItem,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadata.objectMetadataMapItem,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const records = await this.getRecord({
|
const records = await this.getRecord({
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
|
|
||||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||||
|
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiCreateOneHandler extends RestApiBaseHandler {
|
export class RestApiCreateOneHandler extends RestApiBaseHandler {
|
||||||
async handle(request: Request) {
|
async handle(request: Request) {
|
||||||
@ -40,7 +42,9 @@ export class RestApiCreateOneHandler extends RestApiBaseHandler {
|
|||||||
this.apiEventEmitterService.emitCreateEvents({
|
this.apiEventEmitterService.emitCreateEvents({
|
||||||
records: [createdRecord],
|
records: [createdRecord],
|
||||||
authContext: this.getAuthContextFromRequest(request),
|
authContext: this.getAuthContextFromRequest(request),
|
||||||
objectMetadataItem: objectMetadata.objectMetadataMapItem,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadata.objectMetadataMapItem,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const records = await this.getRecord({
|
const records = await this.getRecord({
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { Request } from 'express';
|
|||||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||||
|
|
||||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiDeleteOneHandler extends RestApiBaseHandler {
|
export class RestApiDeleteOneHandler extends RestApiBaseHandler {
|
||||||
@ -26,7 +27,9 @@ export class RestApiDeleteOneHandler extends RestApiBaseHandler {
|
|||||||
this.apiEventEmitterService.emitDestroyEvents({
|
this.apiEventEmitterService.emitDestroyEvents({
|
||||||
records: [recordToDelete],
|
records: [recordToDelete],
|
||||||
authContext: this.getAuthContextFromRequest(request),
|
authContext: this.getAuthContextFromRequest(request),
|
||||||
objectMetadataItem: objectMetadata.objectMetadataMapItem,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadata.objectMetadataMapItem,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.formatResult({
|
return this.formatResult({
|
||||||
|
|||||||
@ -4,14 +4,14 @@ import { Request } from 'express';
|
|||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
|
|
||||||
|
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
import {
|
import {
|
||||||
FormatResult,
|
FormatResult,
|
||||||
RestApiBaseHandler,
|
RestApiBaseHandler,
|
||||||
} from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
} from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
|
||||||
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiFindDuplicatesHandler extends RestApiBaseHandler {
|
export class RestApiFindDuplicatesHandler extends RestApiBaseHandler {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||||
|
|
||||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||||
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiUpdateOneHandler extends RestApiBaseHandler {
|
export class RestApiUpdateOneHandler extends RestApiBaseHandler {
|
||||||
@ -38,7 +39,9 @@ export class RestApiUpdateOneHandler extends RestApiBaseHandler {
|
|||||||
records: [updatedRecord],
|
records: [updatedRecord],
|
||||||
updatedFields: Object.keys(request.body),
|
updatedFields: Object.keys(request.body),
|
||||||
authContext: this.getAuthContextFromRequest(request),
|
authContext: this.getAuthContextFromRequest(request),
|
||||||
objectMetadataItem: objectMetadata.objectMetadataMapItem,
|
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
|
objectMetadata.objectMetadataMapItem,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const records = await this.getRecord({
|
const records = await this.getRecord({
|
||||||
|
|||||||
@ -160,32 +160,34 @@ export abstract class RestApiBaseHandler {
|
|||||||
|
|
||||||
const relations: string[] = [];
|
const relations: string[] = [];
|
||||||
|
|
||||||
objectMetadata.objectMetadataMapItem.fields.forEach((field) => {
|
Object.values(objectMetadata.objectMetadataMapItem.fieldsById).forEach(
|
||||||
if (field.type === FieldMetadataType.RELATION) {
|
(field) => {
|
||||||
if (
|
if (field.type === FieldMetadataType.RELATION) {
|
||||||
depth === MAX_DEPTH &&
|
if (
|
||||||
isDefined(field.relationTargetObjectMetadataId)
|
depth === MAX_DEPTH &&
|
||||||
) {
|
isDefined(field.relationTargetObjectMetadataId)
|
||||||
const relationTargetObjectMetadata =
|
) {
|
||||||
objectMetadata.objectMetadataMaps.byId[
|
const relationTargetObjectMetadata =
|
||||||
field.relationTargetObjectMetadataId
|
objectMetadata.objectMetadataMaps.byId[
|
||||||
];
|
field.relationTargetObjectMetadataId
|
||||||
const depth2Relations = this.getRelations({
|
];
|
||||||
objectMetadata: {
|
const depth2Relations = this.getRelations({
|
||||||
objectMetadataMaps: objectMetadata.objectMetadataMaps,
|
objectMetadata: {
|
||||||
objectMetadataMapItem: relationTargetObjectMetadata,
|
objectMetadataMaps: objectMetadata.objectMetadataMaps,
|
||||||
},
|
objectMetadataMapItem: relationTargetObjectMetadata,
|
||||||
depth: 1,
|
},
|
||||||
});
|
depth: 1,
|
||||||
|
});
|
||||||
|
|
||||||
depth2Relations.forEach((depth2Relation) => {
|
depth2Relations.forEach((depth2Relation) => {
|
||||||
relations.push(`${field.name}.${depth2Relation}`);
|
relations.push(`${field.name}.${depth2Relation}`);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
relations.push(`${field.name}`);
|
relations.push(`${field.name}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
return relations;
|
return relations;
|
||||||
}
|
}
|
||||||
@ -305,9 +307,7 @@ export abstract class RestApiBaseHandler {
|
|||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
|
||||||
};
|
};
|
||||||
objectMetadataItemWithFieldsMaps:
|
objectMetadataItemWithFieldsMaps: ObjectMetadataItemWithFieldMaps;
|
||||||
| ObjectMetadataItemWithFieldMaps
|
|
||||||
| undefined;
|
|
||||||
extraFilters?: Partial<ObjectRecordFilter>;
|
extraFilters?: Partial<ObjectRecordFilter>;
|
||||||
}) {
|
}) {
|
||||||
const objectMetadataNameSingular =
|
const objectMetadataNameSingular =
|
||||||
@ -321,17 +321,10 @@ export abstract class RestApiBaseHandler {
|
|||||||
objectMetadata,
|
objectMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
const fieldMetadataMapByName =
|
|
||||||
objectMetadataItemWithFieldsMaps?.fieldsByName || {};
|
|
||||||
|
|
||||||
const fieldMetadataMapByJoinColumnName =
|
|
||||||
objectMetadataItemWithFieldsMaps?.fieldsByJoinColumnName || {};
|
|
||||||
|
|
||||||
const isForwardPagination = !inputs.endingBefore;
|
const isForwardPagination = !inputs.endingBefore;
|
||||||
|
|
||||||
const graphqlQueryParser = new GraphqlQueryParser(
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
fieldMetadataMapByName,
|
objectMetadataItemWithFieldsMaps,
|
||||||
fieldMetadataMapByJoinColumnName,
|
|
||||||
objectMetadata.objectMetadataMaps,
|
objectMetadata.objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -442,7 +435,7 @@ export abstract class RestApiBaseHandler {
|
|||||||
const cursorArgFilter = computeCursorArgFilter(
|
const cursorArgFilter = computeCursorArgFilter(
|
||||||
this.parseCursor(cursor),
|
this.parseCursor(cursor),
|
||||||
inputs.orderBy || [],
|
inputs.orderBy || [],
|
||||||
objectMetadata.objectMetadataMapItem.fieldsByName,
|
objectMetadata.objectMetadataMapItem,
|
||||||
isForwardPagination,
|
isForwardPagination,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export class CreateManyQueryFactory {
|
|||||||
mutation Create${objectNamePlural}($data: [${objectNameSingular}CreateInput!]) {
|
mutation Create${objectNamePlural}($data: [${objectNameSingular}CreateInput!]) {
|
||||||
create${objectNamePlural}(data: $data) {
|
create${objectNamePlural}(data: $data) {
|
||||||
id
|
id
|
||||||
${objectMetadata.objectMetadataMapItem.fields
|
${Object.values(objectMetadata.objectMetadataMapItem.fieldsById)
|
||||||
.map((field) =>
|
.map((field) =>
|
||||||
mapFieldMetadataToGraphqlQuery(
|
mapFieldMetadataToGraphqlQuery(
|
||||||
objectMetadata.objectMetadataMaps,
|
objectMetadata.objectMetadataMaps,
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export class FindDuplicatesQueryFactory {
|
|||||||
}
|
}
|
||||||
edges{
|
edges{
|
||||||
node {
|
node {
|
||||||
${objectMetadata.objectMetadataMapItem.fields
|
${Object.values(objectMetadata.objectMetadataMapItem.fieldsById)
|
||||||
.map((field) =>
|
.map((field) =>
|
||||||
mapFieldMetadataToGraphqlQuery(
|
mapFieldMetadataToGraphqlQuery(
|
||||||
objectMetadata.objectMetadataMaps,
|
objectMetadata.objectMetadataMaps,
|
||||||
|
|||||||
@ -17,21 +17,23 @@ describe('checkFields', () => {
|
|||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
isNullable: fieldNumberMock.isNullable,
|
isNullable: fieldNumberMock.isNullable,
|
||||||
defaultValue: fieldNumberMock.defaultValue,
|
defaultValue: fieldNumberMock.defaultValue,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsById: FieldMetadataMap = {
|
const fieldsById: FieldMetadataMap = {
|
||||||
'field-number-id': completeFieldNumberMock,
|
'field-number-id': completeFieldNumberMock,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsByName: FieldMetadataMap = {
|
|
||||||
[completeFieldNumberMock.name]: completeFieldNumberMock,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockObjectMetadataWithFieldMaps = {
|
const mockObjectMetadataWithFieldMaps = {
|
||||||
...objectMetadataItemMock,
|
...objectMetadataItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldIdByName: {
|
||||||
fieldsByJoinColumnName: {},
|
[completeFieldNumberMock.name]: completeFieldNumberMock.id,
|
||||||
|
},
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
|
indexMetadatas: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should check field types', () => {
|
it('should check field types', () => {
|
||||||
|
|||||||
@ -18,21 +18,23 @@ describe('getFieldType', () => {
|
|||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
isNullable: fieldNumberMock.isNullable,
|
isNullable: fieldNumberMock.isNullable,
|
||||||
defaultValue: fieldNumberMock.defaultValue,
|
defaultValue: fieldNumberMock.defaultValue,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsById: FieldMetadataMap = {
|
const fieldsById: FieldMetadataMap = {
|
||||||
'field-number-id': completeFieldNumberMock,
|
'field-number-id': completeFieldNumberMock,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsByName: FieldMetadataMap = {
|
|
||||||
[completeFieldNumberMock.name]: completeFieldNumberMock,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockObjectMetadataWithFieldMaps = {
|
const mockObjectMetadataWithFieldMaps = {
|
||||||
...objectMetadataItemMock,
|
...objectMetadataItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldIdByName: {
|
||||||
fieldsByJoinColumnName: {},
|
[completeFieldNumberMock.name]: completeFieldNumberMock.id,
|
||||||
|
},
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
|
indexMetadatas: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should get field type', () => {
|
it('should get field type', () => {
|
||||||
|
|||||||
@ -24,6 +24,9 @@ describe('mapFieldMetadataToGraphqlQuery', () => {
|
|||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
isNullable: fieldNumberMock.isNullable,
|
isNullable: fieldNumberMock.isNullable,
|
||||||
defaultValue: fieldNumberMock.defaultValue,
|
defaultValue: fieldNumberMock.defaultValue,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const typedFieldTextMock: FieldMetadataInterface = {
|
const typedFieldTextMock: FieldMetadataInterface = {
|
||||||
@ -34,6 +37,9 @@ describe('mapFieldMetadataToGraphqlQuery', () => {
|
|||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
isNullable: fieldTextMock.isNullable,
|
isNullable: fieldTextMock.isNullable,
|
||||||
defaultValue: fieldTextMock.defaultValue,
|
defaultValue: fieldTextMock.defaultValue,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const typedFieldCurrencyMock: FieldMetadataInterface = {
|
const typedFieldCurrencyMock: FieldMetadataInterface = {
|
||||||
@ -44,6 +50,9 @@ describe('mapFieldMetadataToGraphqlQuery', () => {
|
|||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
isNullable: fieldCurrencyMock.isNullable,
|
isNullable: fieldCurrencyMock.isNullable,
|
||||||
defaultValue: fieldCurrencyMock.defaultValue,
|
defaultValue: fieldCurrencyMock.defaultValue,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsById: FieldMetadataMap = {
|
const fieldsById: FieldMetadataMap = {
|
||||||
@ -52,17 +61,16 @@ describe('mapFieldMetadataToGraphqlQuery', () => {
|
|||||||
'field-currency-id': typedFieldCurrencyMock,
|
'field-currency-id': typedFieldCurrencyMock,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsByName: FieldMetadataMap = {
|
|
||||||
[typedFieldNumberMock.name]: typedFieldNumberMock,
|
|
||||||
[typedFieldTextMock.name]: typedFieldTextMock,
|
|
||||||
[typedFieldCurrencyMock.name]: typedFieldCurrencyMock,
|
|
||||||
};
|
|
||||||
|
|
||||||
const typedObjectMetadataItem: ObjectMetadataItemWithFieldMaps = {
|
const typedObjectMetadataItem: ObjectMetadataItemWithFieldMaps = {
|
||||||
...objectMetadataItemMock,
|
...objectMetadataItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldIdByName: {
|
||||||
fieldsByJoinColumnName: {},
|
[typedFieldNumberMock.name]: typedFieldNumberMock.id,
|
||||||
|
[typedFieldTextMock.name]: typedFieldTextMock.id,
|
||||||
|
[typedFieldCurrencyMock.name]: typedFieldCurrencyMock.id,
|
||||||
|
},
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
|
indexMetadatas: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const objectMetadataMapsMock: ObjectMetadataMaps = {
|
const objectMetadataMapsMock: ObjectMetadataMaps = {
|
||||||
@ -110,6 +118,10 @@ describe('mapFieldMetadataToGraphqlQuery', () => {
|
|||||||
name: 'toObjectMetadataName',
|
name: 'toObjectMetadataName',
|
||||||
label: 'Test Field',
|
label: 'Test Field',
|
||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
|
isNullable: true,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fieldMetadataType === FieldMetadataType.RELATION) {
|
if (fieldMetadataType === FieldMetadataType.RELATION) {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export const checkFields = (
|
|||||||
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
|
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
|
||||||
fieldNames: string[],
|
fieldNames: string[],
|
||||||
): void => {
|
): void => {
|
||||||
const fieldMetadataNames = objectMetadataItem.fields
|
const fieldMetadataNames = Object.values(objectMetadataItem.fieldsById)
|
||||||
.map((field) => {
|
.map((field) => {
|
||||||
if (isCompositeFieldMetadataType(field.type)) {
|
if (isCompositeFieldMetadataType(field.type)) {
|
||||||
const compositeType = compositeTypeDefinitions.get(field.type);
|
const compositeType = compositeTypeDefinitions.get(field.type);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export const checkArrayFields = (
|
|||||||
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
|
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
|
||||||
fields: Array<Partial<ObjectRecord>>,
|
fields: Array<Partial<ObjectRecord>>,
|
||||||
): void => {
|
): void => {
|
||||||
const fieldMetadataNames = objectMetadataItem.fields
|
const fieldMetadataNames = Object.values(objectMetadataItem.fieldsById)
|
||||||
.map((field) => {
|
.map((field) => {
|
||||||
if (isCompositeFieldMetadataType(field.type)) {
|
if (isCompositeFieldMetadataType(field.type)) {
|
||||||
const compositeType = compositeTypeDefinitions.get(field.type);
|
const compositeType = compositeTypeDefinitions.get(field.type);
|
||||||
|
|||||||
@ -19,21 +19,23 @@ describe('checkFilterEnumValues', () => {
|
|||||||
isNullable: fieldSelectMock.isNullable,
|
isNullable: fieldSelectMock.isNullable,
|
||||||
defaultValue: fieldSelectMock.defaultValue,
|
defaultValue: fieldSelectMock.defaultValue,
|
||||||
options: fieldSelectMock.options,
|
options: fieldSelectMock.options,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsById: FieldMetadataMap = {
|
const fieldsById: FieldMetadataMap = {
|
||||||
'field-select-id': completeFieldSelectMock,
|
'field-select-id': completeFieldSelectMock,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsByName: FieldMetadataMap = {
|
|
||||||
[completeFieldSelectMock.name]: completeFieldSelectMock,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockObjectMetadataWithFieldMaps = {
|
const mockObjectMetadataWithFieldMaps = {
|
||||||
...objectMetadataItemMock,
|
...objectMetadataItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldIdByName: {
|
||||||
fieldsByJoinColumnName: {},
|
[completeFieldSelectMock.name]: completeFieldSelectMock.id,
|
||||||
|
},
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
|
indexMetadatas: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should check properly', () => {
|
it('should check properly', () => {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
||||||
import { parseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils';
|
import { parseFilter } from 'src/engine/api/rest/core/query-builder/utils/filter-utils/parse-filter.utils';
|
||||||
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';
|
||||||
|
|
||||||
describe('parseFilter', () => {
|
describe('parseFilter', () => {
|
||||||
const completeFieldNumberMock: FieldMetadataInterface = {
|
const completeFieldNumberMock: FieldMetadataInterface = {
|
||||||
@ -17,6 +18,9 @@ describe('parseFilter', () => {
|
|||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
isNullable: fieldNumberMock.isNullable,
|
isNullable: fieldNumberMock.isNullable,
|
||||||
defaultValue: fieldNumberMock.defaultValue,
|
defaultValue: fieldNumberMock.defaultValue,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const completeFieldTextMock: FieldMetadataInterface = {
|
const completeFieldTextMock: FieldMetadataInterface = {
|
||||||
@ -27,6 +31,9 @@ describe('parseFilter', () => {
|
|||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
isNullable: fieldTextMock.isNullable,
|
isNullable: fieldTextMock.isNullable,
|
||||||
defaultValue: fieldTextMock.defaultValue,
|
defaultValue: fieldTextMock.defaultValue,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsById: FieldMetadataMap = {
|
const fieldsById: FieldMetadataMap = {
|
||||||
@ -34,16 +41,15 @@ describe('parseFilter', () => {
|
|||||||
'field-text-id': completeFieldTextMock,
|
'field-text-id': completeFieldTextMock,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsByName: FieldMetadataMap = {
|
const mockObjectMetadataWithFieldMaps: ObjectMetadataItemWithFieldMaps = {
|
||||||
[completeFieldNumberMock.name]: completeFieldNumberMock,
|
|
||||||
[completeFieldTextMock.name]: completeFieldTextMock,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockObjectMetadataWithFieldMaps = {
|
|
||||||
...objectMetadataItemMock,
|
...objectMetadataItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldIdByName: {
|
||||||
fieldsByJoinColumnName: {},
|
[completeFieldNumberMock.name]: completeFieldNumberMock.id,
|
||||||
|
[completeFieldTextMock.name]: completeFieldTextMock.id,
|
||||||
|
},
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
|
indexMetadatas: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should parse string filter test 1', () => {
|
it('should parse string filter test 1', () => {
|
||||||
|
|||||||
@ -18,7 +18,8 @@ export const checkFilterEnumValues = (
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const field = objectMetadataItem.fieldsByName[fieldName];
|
const fieldMetadataId = objectMetadataItem.fieldIdByName[fieldName];
|
||||||
|
const field = objectMetadataItem.fieldsById[fieldMetadataId];
|
||||||
|
|
||||||
const values = /^\[.*\]$/.test(value)
|
const values = /^\[.*\]$/.test(value)
|
||||||
? value.slice(1, -1).split(',')
|
? value.slice(1, -1).split(',')
|
||||||
|
|||||||
@ -6,5 +6,8 @@ export const getFieldType = (
|
|||||||
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
|
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
|
||||||
fieldName: string,
|
fieldName: string,
|
||||||
): FieldMetadataType | undefined => {
|
): FieldMetadataType | undefined => {
|
||||||
return objectMetadataItem.fieldsByName[fieldName]?.type;
|
const fieldMetadataId = objectMetadataItem.fieldIdByName[fieldName];
|
||||||
|
const field = objectMetadataItem.fieldsById[fieldMetadataId];
|
||||||
|
|
||||||
|
return field?.type;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
||||||
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
|
import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-input.factory';
|
||||||
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';
|
||||||
|
|
||||||
describe('FilterInputFactory', () => {
|
describe('FilterInputFactory', () => {
|
||||||
const completeFieldNumberMock: FieldMetadataInterface = {
|
const completeFieldNumberMock: FieldMetadataInterface = {
|
||||||
@ -20,6 +21,9 @@ describe('FilterInputFactory', () => {
|
|||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
isNullable: fieldNumberMock.isNullable,
|
isNullable: fieldNumberMock.isNullable,
|
||||||
defaultValue: fieldNumberMock.defaultValue,
|
defaultValue: fieldNumberMock.defaultValue,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const completeFieldTextMock: FieldMetadataInterface = {
|
const completeFieldTextMock: FieldMetadataInterface = {
|
||||||
@ -30,6 +34,9 @@ describe('FilterInputFactory', () => {
|
|||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
isNullable: fieldTextMock.isNullable,
|
isNullable: fieldTextMock.isNullable,
|
||||||
defaultValue: fieldTextMock.defaultValue,
|
defaultValue: fieldTextMock.defaultValue,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const completeFieldCurrencyMock: FieldMetadataInterface = {
|
const completeFieldCurrencyMock: FieldMetadataInterface = {
|
||||||
@ -40,6 +47,9 @@ describe('FilterInputFactory', () => {
|
|||||||
objectMetadataId: 'object-metadata-id',
|
objectMetadataId: 'object-metadata-id',
|
||||||
isNullable: fieldCurrencyMock.isNullable,
|
isNullable: fieldCurrencyMock.isNullable,
|
||||||
defaultValue: fieldCurrencyMock.defaultValue,
|
defaultValue: fieldCurrencyMock.defaultValue,
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsById: FieldMetadataMap = {
|
const fieldsById: FieldMetadataMap = {
|
||||||
@ -48,16 +58,15 @@ describe('FilterInputFactory', () => {
|
|||||||
'field-currency-id': completeFieldCurrencyMock,
|
'field-currency-id': completeFieldCurrencyMock,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fieldsByName: FieldMetadataMap = {
|
const objectMetadataMapItem: ObjectMetadataItemWithFieldMaps = {
|
||||||
[completeFieldNumberMock.name]: completeFieldNumberMock,
|
|
||||||
[completeFieldTextMock.name]: completeFieldTextMock,
|
|
||||||
[completeFieldCurrencyMock.name]: completeFieldCurrencyMock,
|
|
||||||
};
|
|
||||||
|
|
||||||
const objectMetadataMapItem = {
|
|
||||||
...objectMetadataMapItemMock,
|
...objectMetadataMapItemMock,
|
||||||
fieldsById,
|
fieldsById,
|
||||||
fieldsByName,
|
fieldIdByName: {
|
||||||
|
[completeFieldNumberMock.name]: completeFieldNumberMock.id,
|
||||||
|
[completeFieldTextMock.name]: completeFieldTextMock.id,
|
||||||
|
[completeFieldCurrencyMock.name]: completeFieldCurrencyMock.id,
|
||||||
|
},
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const objectMetadataMaps = {
|
const objectMetadataMaps = {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { mockPersonObjectMetadata } from 'src/engine/api/graphql/graphql-query-runner/__mocks__/mockPersonObjectMetadata';
|
import { mockPersonObjectMetadataWithFieldMaps } from 'src/engine/api/graphql/graphql-query-runner/__mocks__/mockPersonObjectMetadata';
|
||||||
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
|
||||||
import { mockPersonRecords } from 'src/engine/api/graphql/graphql-query-runner/__mocks__/mockPersonRecords';
|
import { mockPersonRecords } from 'src/engine/api/graphql/graphql-query-runner/__mocks__/mockPersonRecords';
|
||||||
|
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
||||||
|
|
||||||
describe('buildDuplicateConditions', () => {
|
describe('buildDuplicateConditions', () => {
|
||||||
it('should build conditions based on duplicate criteria from composite field', () => {
|
it('should build conditions based on duplicate criteria from composite field', () => {
|
||||||
const duplicateConditons = buildDuplicateConditions(
|
const duplicateConditons = buildDuplicateConditions(
|
||||||
mockPersonObjectMetadata([['emailsPrimaryEmail']]),
|
mockPersonObjectMetadataWithFieldMaps([['emailsPrimaryEmail']]),
|
||||||
mockPersonRecords,
|
mockPersonRecords,
|
||||||
'recordId',
|
'recordId',
|
||||||
);
|
);
|
||||||
@ -13,8 +13,10 @@ describe('buildDuplicateConditions', () => {
|
|||||||
expect(duplicateConditons).toEqual({
|
expect(duplicateConditons).toEqual({
|
||||||
or: [
|
or: [
|
||||||
{
|
{
|
||||||
emailsPrimaryEmail: {
|
emails: {
|
||||||
eq: 'test@test.fr',
|
primaryEmail: {
|
||||||
|
eq: 'test@test.fr',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -26,7 +28,7 @@ describe('buildDuplicateConditions', () => {
|
|||||||
|
|
||||||
it('should build conditions based on duplicate criteria from basic field', () => {
|
it('should build conditions based on duplicate criteria from basic field', () => {
|
||||||
const duplicateConditons = buildDuplicateConditions(
|
const duplicateConditons = buildDuplicateConditions(
|
||||||
mockPersonObjectMetadata([['jobTitle']]),
|
mockPersonObjectMetadataWithFieldMaps([['jobTitle']]),
|
||||||
mockPersonRecords,
|
mockPersonRecords,
|
||||||
'recordId',
|
'recordId',
|
||||||
);
|
);
|
||||||
@ -47,7 +49,7 @@ describe('buildDuplicateConditions', () => {
|
|||||||
|
|
||||||
it('should not build conditions based on duplicate criteria if record value is null or too small', () => {
|
it('should not build conditions based on duplicate criteria if record value is null or too small', () => {
|
||||||
const duplicateConditons = buildDuplicateConditions(
|
const duplicateConditons = buildDuplicateConditions(
|
||||||
mockPersonObjectMetadata([['linkedinLinkPrimaryLinkUrl']]),
|
mockPersonObjectMetadataWithFieldMaps([['linkedinLinkPrimaryLinkUrl']]),
|
||||||
mockPersonRecords,
|
mockPersonRecords,
|
||||||
'recordId',
|
'recordId',
|
||||||
);
|
);
|
||||||
@ -57,7 +59,7 @@ describe('buildDuplicateConditions', () => {
|
|||||||
|
|
||||||
it('should build conditions based on duplicate criteria and without recordId filter', () => {
|
it('should build conditions based on duplicate criteria and without recordId filter', () => {
|
||||||
const duplicateConditons = buildDuplicateConditions(
|
const duplicateConditons = buildDuplicateConditions(
|
||||||
mockPersonObjectMetadata([['jobTitle']]),
|
mockPersonObjectMetadataWithFieldMaps([['jobTitle']]),
|
||||||
mockPersonRecords,
|
mockPersonRecords,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -4,35 +4,76 @@ import { OrderByDirection } from 'src/engine/api/graphql/workspace-query-builder
|
|||||||
|
|
||||||
import { GraphqlQueryRunnerException } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
import { GraphqlQueryRunnerException } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
import { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils';
|
import { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils';
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
|
|
||||||
describe('computeCursorArgFilter', () => {
|
describe('computeCursorArgFilter', () => {
|
||||||
const mockFieldMetadataMap = {
|
const objectMetadataItemWithFieldMaps = {
|
||||||
name: {
|
id: 'object-id',
|
||||||
type: FieldMetadataType.TEXT,
|
workspaceId: 'workspace-id',
|
||||||
id: 'name-id',
|
nameSingular: 'person',
|
||||||
name: 'name',
|
namePlural: 'people',
|
||||||
label: 'Name',
|
isCustom: false,
|
||||||
objectMetadataId: 'object-id',
|
isRemote: false,
|
||||||
|
labelSingular: 'Person',
|
||||||
|
labelPlural: 'People',
|
||||||
|
targetTableName: 'person',
|
||||||
|
indexMetadatas: [],
|
||||||
|
isSystem: false,
|
||||||
|
isActive: true,
|
||||||
|
isAuditLogged: false,
|
||||||
|
isSearchable: false,
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
|
icon: 'Icon123',
|
||||||
|
fieldIdByName: {
|
||||||
|
name: 'name-id',
|
||||||
|
age: 'age-id',
|
||||||
|
fullName: 'fullname-id',
|
||||||
},
|
},
|
||||||
age: {
|
fieldsById: {
|
||||||
type: FieldMetadataType.NUMBER,
|
'name-id': {
|
||||||
id: 'age-id',
|
type: FieldMetadataType.TEXT,
|
||||||
name: 'age',
|
id: 'name-id',
|
||||||
label: 'Age',
|
name: 'name',
|
||||||
objectMetadataId: 'object-id',
|
label: 'Name',
|
||||||
|
objectMetadataId: 'object-id',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
isNullable: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
'age-id': {
|
||||||
|
type: FieldMetadataType.NUMBER,
|
||||||
|
id: 'age-id',
|
||||||
|
name: 'age',
|
||||||
|
label: 'Age',
|
||||||
|
objectMetadataId: 'object-id',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
isNullable: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
'fullname-id': {
|
||||||
|
type: FieldMetadataType.FULL_NAME,
|
||||||
|
id: 'fullname-id',
|
||||||
|
name: 'fullName',
|
||||||
|
label: 'Full Name',
|
||||||
|
objectMetadataId: 'object-id',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
isNullable: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fullName: {
|
} satisfies ObjectMetadataItemWithFieldMaps;
|
||||||
type: FieldMetadataType.FULL_NAME,
|
|
||||||
id: 'fullname-id',
|
|
||||||
name: 'fullName',
|
|
||||||
label: 'Full Name',
|
|
||||||
objectMetadataId: 'object-id',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('basic cursor filtering', () => {
|
describe('basic cursor filtering', () => {
|
||||||
it('should return empty array when cursor is empty', () => {
|
it('should return empty array when cursor is empty', () => {
|
||||||
const result = computeCursorArgFilter({}, [], mockFieldMetadataMap, true);
|
const result = computeCursorArgFilter(
|
||||||
|
{},
|
||||||
|
[],
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
expect(result).toEqual([]);
|
||||||
});
|
});
|
||||||
@ -44,7 +85,7 @@ describe('computeCursorArgFilter', () => {
|
|||||||
const result = computeCursorArgFilter(
|
const result = computeCursorArgFilter(
|
||||||
cursor,
|
cursor,
|
||||||
orderBy,
|
orderBy,
|
||||||
mockFieldMetadataMap,
|
objectMetadataItemWithFieldMaps,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -58,7 +99,7 @@ describe('computeCursorArgFilter', () => {
|
|||||||
const result = computeCursorArgFilter(
|
const result = computeCursorArgFilter(
|
||||||
cursor,
|
cursor,
|
||||||
orderBy,
|
orderBy,
|
||||||
mockFieldMetadataMap,
|
objectMetadataItemWithFieldMaps,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -77,7 +118,7 @@ describe('computeCursorArgFilter', () => {
|
|||||||
const result = computeCursorArgFilter(
|
const result = computeCursorArgFilter(
|
||||||
cursor,
|
cursor,
|
||||||
orderBy,
|
orderBy,
|
||||||
mockFieldMetadataMap,
|
objectMetadataItemWithFieldMaps,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -105,7 +146,7 @@ describe('computeCursorArgFilter', () => {
|
|||||||
const result = computeCursorArgFilter(
|
const result = computeCursorArgFilter(
|
||||||
cursor,
|
cursor,
|
||||||
orderBy,
|
orderBy,
|
||||||
mockFieldMetadataMap,
|
objectMetadataItemWithFieldMaps,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -151,7 +192,7 @@ describe('computeCursorArgFilter', () => {
|
|||||||
const result = computeCursorArgFilter(
|
const result = computeCursorArgFilter(
|
||||||
cursor,
|
cursor,
|
||||||
orderBy,
|
orderBy,
|
||||||
mockFieldMetadataMap,
|
objectMetadataItemWithFieldMaps,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -180,7 +221,7 @@ describe('computeCursorArgFilter', () => {
|
|||||||
const result = computeCursorArgFilter(
|
const result = computeCursorArgFilter(
|
||||||
cursor,
|
cursor,
|
||||||
orderBy,
|
orderBy,
|
||||||
mockFieldMetadataMap,
|
objectMetadataItemWithFieldMaps,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -218,7 +259,12 @@ describe('computeCursorArgFilter', () => {
|
|||||||
const orderBy = [{ invalidField: OrderByDirection.AscNullsLast }];
|
const orderBy = [{ invalidField: OrderByDirection.AscNullsLast }];
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
computeCursorArgFilter(cursor, orderBy, mockFieldMetadataMap, true),
|
computeCursorArgFilter(
|
||||||
|
cursor,
|
||||||
|
orderBy,
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
true,
|
||||||
|
),
|
||||||
).toThrow(GraphqlQueryRunnerException);
|
).toThrow(GraphqlQueryRunnerException);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -227,7 +273,12 @@ describe('computeCursorArgFilter', () => {
|
|||||||
const orderBy = [{ age: OrderByDirection.AscNullsLast }];
|
const orderBy = [{ age: OrderByDirection.AscNullsLast }];
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
computeCursorArgFilter(cursor, orderBy, mockFieldMetadataMap, true),
|
computeCursorArgFilter(
|
||||||
|
cursor,
|
||||||
|
orderBy,
|
||||||
|
objectMetadataItemWithFieldMaps,
|
||||||
|
true,
|
||||||
|
),
|
||||||
).toThrow(GraphqlQueryRunnerException);
|
).toThrow(GraphqlQueryRunnerException);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,19 +11,19 @@ import {
|
|||||||
GraphqlQueryRunnerException,
|
GraphqlQueryRunnerException,
|
||||||
GraphqlQueryRunnerExceptionCode,
|
GraphqlQueryRunnerExceptionCode,
|
||||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
|
import { buildCursorCompositeFieldWhereCondition } from 'src/engine/api/utils/build-cursor-composite-field-where-condition.utils';
|
||||||
import { computeOperator } from 'src/engine/api/utils/compute-operator.utils';
|
import { computeOperator } from 'src/engine/api/utils/compute-operator.utils';
|
||||||
import { isAscendingOrder } from 'src/engine/api/utils/is-ascending-order.utils';
|
import { isAscendingOrder } from 'src/engine/api/utils/is-ascending-order.utils';
|
||||||
import { validateAndGetOrderByForScalarField } from 'src/engine/api/utils/validate-and-get-order-by.utils';
|
import { validateAndGetOrderByForScalarField } from 'src/engine/api/utils/validate-and-get-order-by.utils';
|
||||||
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 { 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 { buildCursorCompositeFieldWhereCondition } from 'src/engine/api/utils/build-cursor-composite-field-where-condition.utils';
|
|
||||||
|
|
||||||
type BuildCursorWhereConditionParams = {
|
type BuildCursorWhereConditionParams = {
|
||||||
cursorKey: keyof ObjectRecord;
|
cursorKey: keyof ObjectRecord;
|
||||||
cursorValue:
|
cursorValue:
|
||||||
| ObjectRecordCursorLeafScalarValue
|
| ObjectRecordCursorLeafScalarValue
|
||||||
| ObjectRecordCursorLeafCompositeValue;
|
| ObjectRecordCursorLeafCompositeValue;
|
||||||
fieldMetadataMapByName: FieldMetadataMap;
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||||
orderBy: ObjectRecordOrderBy;
|
orderBy: ObjectRecordOrderBy;
|
||||||
isForwardPagination: boolean;
|
isForwardPagination: boolean;
|
||||||
isEqualityCondition?: boolean;
|
isEqualityCondition?: boolean;
|
||||||
@ -32,12 +32,15 @@ type BuildCursorWhereConditionParams = {
|
|||||||
export const buildCursorWhereCondition = ({
|
export const buildCursorWhereCondition = ({
|
||||||
cursorKey,
|
cursorKey,
|
||||||
cursorValue,
|
cursorValue,
|
||||||
fieldMetadataMapByName,
|
objectMetadataItemWithFieldMaps,
|
||||||
orderBy,
|
orderBy,
|
||||||
isForwardPagination,
|
isForwardPagination,
|
||||||
isEqualityCondition = false,
|
isEqualityCondition = false,
|
||||||
}: BuildCursorWhereConditionParams): Record<string, unknown> => {
|
}: BuildCursorWhereConditionParams): Record<string, unknown> => {
|
||||||
const fieldMetadata = fieldMetadataMapByName[cursorKey];
|
const fieldMetadataId =
|
||||||
|
objectMetadataItemWithFieldMaps.fieldIdByName[cursorKey];
|
||||||
|
const fieldMetadata =
|
||||||
|
objectMetadataItemWithFieldMaps.fieldsById[fieldMetadataId];
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
|
|||||||
@ -9,13 +9,13 @@ import {
|
|||||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
|
|
||||||
import { buildCursorCumulativeWhereCondition } from 'src/engine/api/utils/build-cursor-cumulative-where-conditions.utils';
|
import { buildCursorCumulativeWhereCondition } from 'src/engine/api/utils/build-cursor-cumulative-where-conditions.utils';
|
||||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
|
||||||
import { buildCursorWhereCondition } from 'src/engine/api/utils/build-cursor-where-condition.utils';
|
import { buildCursorWhereCondition } from 'src/engine/api/utils/build-cursor-where-condition.utils';
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
|
|
||||||
export const computeCursorArgFilter = (
|
export const computeCursorArgFilter = (
|
||||||
cursor: ObjectRecordCursor,
|
cursor: ObjectRecordCursor,
|
||||||
orderBy: ObjectRecordOrderBy,
|
orderBy: ObjectRecordOrderBy,
|
||||||
fieldMetadataMapByName: FieldMetadataMap,
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||||
isForwardPagination = true,
|
isForwardPagination = true,
|
||||||
): ObjectRecordFilter[] => {
|
): ObjectRecordFilter[] => {
|
||||||
const cursorEntries = Object.entries(cursor)
|
const cursorEntries = Object.entries(cursor)
|
||||||
@ -42,7 +42,7 @@ export const computeCursorArgFilter = (
|
|||||||
buildCursorWhereCondition({
|
buildCursorWhereCondition({
|
||||||
cursorKey,
|
cursorKey,
|
||||||
cursorValue,
|
cursorValue,
|
||||||
fieldMetadataMapByName,
|
objectMetadataItemWithFieldMaps,
|
||||||
orderBy,
|
orderBy,
|
||||||
isForwardPagination: true,
|
isForwardPagination: true,
|
||||||
isEqualityCondition: true,
|
isEqualityCondition: true,
|
||||||
@ -51,7 +51,7 @@ export const computeCursorArgFilter = (
|
|||||||
buildCursorWhereCondition({
|
buildCursorWhereCondition({
|
||||||
cursorKey,
|
cursorKey,
|
||||||
cursorValue,
|
cursorValue,
|
||||||
fieldMetadataMapByName,
|
objectMetadataItemWithFieldMaps,
|
||||||
orderBy,
|
orderBy,
|
||||||
isForwardPagination,
|
isForwardPagination,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -23,25 +23,6 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
||||||
imageIdentifierFieldMetadataId: '',
|
imageIdentifierFieldMetadataId: '',
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
id: 'nameFieldMetadataId',
|
|
||||||
objectMetadataId: '',
|
|
||||||
type: FieldMetadataType.FULL_NAME,
|
|
||||||
icon: 'test-field-icon',
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
defaultValue: {
|
|
||||||
lastName: "''",
|
|
||||||
firstName: "''",
|
|
||||||
},
|
|
||||||
description: 'Contact’s name',
|
|
||||||
isCustom: false,
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
workspaceId: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
fieldsById: {
|
fieldsById: {
|
||||||
nameFieldMetadataId: {
|
nameFieldMetadataId: {
|
||||||
@ -60,28 +41,15 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isUnique: false,
|
isUnique: false,
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fieldsByName: {
|
fieldIdByName: {
|
||||||
name: {
|
name: 'nameFieldMetadataId',
|
||||||
id: 'nameFieldMetadataId',
|
|
||||||
objectMetadataId: '',
|
|
||||||
type: FieldMetadataType.FULL_NAME,
|
|
||||||
icon: 'test-field-icon',
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
defaultValue: {
|
|
||||||
lastName: "''",
|
|
||||||
firstName: "''",
|
|
||||||
},
|
|
||||||
description: 'Contact’s name',
|
|
||||||
isCustom: false,
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
workspaceId: '',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
fieldsByJoinColumnName: {},
|
fieldIdByJoinColumnName: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '',
|
id: '',
|
||||||
@ -102,34 +70,6 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
||||||
imageIdentifierFieldMetadataId: '',
|
imageIdentifierFieldMetadataId: '',
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
id: 'nameFieldMetadataId',
|
|
||||||
objectMetadataId: '',
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
icon: 'test-field-icon',
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
defaultValue: '',
|
|
||||||
isCustom: false,
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
workspaceId: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'domainNameFieldMetadataId',
|
|
||||||
objectMetadataId: '',
|
|
||||||
type: FieldMetadataType.LINKS,
|
|
||||||
icon: 'test-field-icon',
|
|
||||||
name: 'domainName',
|
|
||||||
label: 'Domain Name',
|
|
||||||
defaultValue: '',
|
|
||||||
isCustom: false,
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
workspaceId: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
fieldsById: {
|
fieldsById: {
|
||||||
nameFieldMetadataId: {
|
nameFieldMetadataId: {
|
||||||
@ -144,6 +84,9 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isUnique: false,
|
isUnique: false,
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
domainNameFieldMetadataId: {
|
domainNameFieldMetadataId: {
|
||||||
id: 'domainNameFieldMetadataId',
|
id: 'domainNameFieldMetadataId',
|
||||||
@ -157,40 +100,16 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isUnique: false,
|
isUnique: false,
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fieldsByName: {
|
fieldIdByName: {
|
||||||
name: {
|
name: 'nameFieldMetadataId',
|
||||||
id: 'nameFieldMetadataId',
|
domainName: 'domainNameFieldMetadataId',
|
||||||
objectMetadataId: '',
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
icon: 'test-field-icon',
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
defaultValue: {
|
|
||||||
lastName: "''",
|
|
||||||
firstName: "''",
|
|
||||||
},
|
|
||||||
isCustom: false,
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
workspaceId: '',
|
|
||||||
},
|
|
||||||
domainName: {
|
|
||||||
id: 'domainNameFieldMetadataId',
|
|
||||||
objectMetadataId: '',
|
|
||||||
type: FieldMetadataType.LINKS,
|
|
||||||
icon: 'test-field-icon',
|
|
||||||
name: 'domainName',
|
|
||||||
label: 'Domain Name',
|
|
||||||
defaultValue: '',
|
|
||||||
isCustom: false,
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
workspaceId: '',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
fieldsByJoinColumnName: {},
|
fieldIdByJoinColumnName: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '',
|
id: '',
|
||||||
@ -211,34 +130,6 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
labelIdentifierFieldMetadataId: 'nameFieldMetadataId',
|
||||||
imageIdentifierFieldMetadataId: 'imageIdentifierFieldMetadataId',
|
imageIdentifierFieldMetadataId: 'imageIdentifierFieldMetadataId',
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
id: 'nameFieldMetadataId',
|
|
||||||
objectMetadataId: '',
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
icon: 'test-field-icon',
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
defaultValue: '',
|
|
||||||
isCustom: false,
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
workspaceId: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'imageIdentifierFieldMetadataId',
|
|
||||||
objectMetadataId: '',
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
icon: 'test-field-icon',
|
|
||||||
name: 'imageIdentifierFieldName',
|
|
||||||
label: 'Image Identifier Field Name',
|
|
||||||
defaultValue: '',
|
|
||||||
isCustom: false,
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
workspaceId: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
fieldsById: {
|
fieldsById: {
|
||||||
nameFieldMetadataId: {
|
nameFieldMetadataId: {
|
||||||
@ -253,6 +144,9 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isUnique: false,
|
isUnique: false,
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
imageIdentifierFieldMetadataId: {
|
imageIdentifierFieldMetadataId: {
|
||||||
id: 'imageIdentifierFieldMetadataId',
|
id: 'imageIdentifierFieldMetadataId',
|
||||||
@ -266,40 +160,16 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
isNullable: true,
|
isNullable: true,
|
||||||
isUnique: false,
|
isUnique: false,
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
|
isLabelSyncedWithName: true,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fieldsByName: {
|
fieldIdByName: {
|
||||||
name: {
|
name: 'nameFieldMetadataId',
|
||||||
id: 'nameFieldMetadataId',
|
imageIdentifierFieldName: 'imageIdentifierFieldMetadataId',
|
||||||
objectMetadataId: '',
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
icon: 'test-field-icon',
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
defaultValue: {
|
|
||||||
lastName: "''",
|
|
||||||
firstName: "''",
|
|
||||||
},
|
|
||||||
isCustom: false,
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
workspaceId: '',
|
|
||||||
},
|
|
||||||
imageIdentifierFieldName: {
|
|
||||||
id: 'imageIdentifierFieldMetadataId',
|
|
||||||
objectMetadataId: '',
|
|
||||||
type: FieldMetadataType.TEXT,
|
|
||||||
icon: 'test-field-icon',
|
|
||||||
name: 'imageIdentifierFieldName',
|
|
||||||
label: 'Image Identifier Field Name',
|
|
||||||
defaultValue: '',
|
|
||||||
isCustom: false,
|
|
||||||
isNullable: true,
|
|
||||||
isUnique: false,
|
|
||||||
workspaceId: '',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
fieldsByJoinColumnName: {},
|
fieldIdByJoinColumnName: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '',
|
id: '',
|
||||||
@ -320,10 +190,9 @@ export const mockObjectMetadataItemsWithFieldMaps: ObjectMetadataItemWithFieldMa
|
|||||||
labelIdentifierFieldMetadataId: '',
|
labelIdentifierFieldMetadataId: '',
|
||||||
imageIdentifierFieldMetadataId: '',
|
imageIdentifierFieldMetadataId: '',
|
||||||
workspaceId: '',
|
workspaceId: '',
|
||||||
fields: [],
|
|
||||||
indexMetadatas: [],
|
indexMetadatas: [],
|
||||||
fieldsById: {},
|
fieldsById: {},
|
||||||
fieldsByName: {},
|
fieldIdByName: {},
|
||||||
fieldsByJoinColumnName: {},
|
fieldIdByJoinColumnName: {},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -21,13 +21,13 @@ import {
|
|||||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||||
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 { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
|
import { userWorkspaceValidator } from 'src/engine/core-modules/user-workspace/user-workspace.validate';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
import { userWorkspaceValidator } from 'src/engine/core-modules/user-workspace/user-workspace.validate';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccessTokenService {
|
export class AccessTokenService {
|
||||||
@ -78,9 +78,6 @@ export class AccessTokenService {
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
'workspaceMember',
|
'workspaceMember',
|
||||||
{
|
|
||||||
shouldFailIfMetadataNotFound: false,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||||
|
|||||||
@ -13,6 +13,6 @@ export class ObjectRecordBaseEvent<T = object> {
|
|||||||
recordId: string;
|
recordId: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
workspaceMemberId?: string;
|
workspaceMemberId?: string;
|
||||||
objectMetadata: ObjectMetadataInterface;
|
objectMetadata: Omit<ObjectMetadataInterface, 'indexMetadatas'>;
|
||||||
properties: Properties<T>;
|
properties: Properties<T>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter
|
|||||||
|
|
||||||
const mockObjectMetadata: ObjectMetadataInterface = {
|
const mockObjectMetadata: ObjectMetadataInterface = {
|
||||||
id: '1',
|
id: '1',
|
||||||
|
icon: 'Icon123',
|
||||||
nameSingular: 'Object',
|
nameSingular: 'Object',
|
||||||
namePlural: 'Objects',
|
namePlural: 'Objects',
|
||||||
labelSingular: 'Object',
|
labelSingular: 'Object',
|
||||||
|
|||||||
@ -74,10 +74,7 @@ export class FeatureFlagService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await this.workspaceFeatureFlagsMapCacheService.recomputeFeatureFlagsMapCache(
|
await this.workspaceFeatureFlagsMapCacheService.recomputeFeatureFlagsMapCache(
|
||||||
{
|
{ workspaceId },
|
||||||
workspaceId,
|
|
||||||
ignoreLock: true,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,10 +129,7 @@ export class FeatureFlagService {
|
|||||||
const result = await this.featureFlagRepository.save(featureFlagToSave);
|
const result = await this.featureFlagRepository.save(featureFlagToSave);
|
||||||
|
|
||||||
await this.workspaceFeatureFlagsMapCacheService.recomputeFeatureFlagsMapCache(
|
await this.workspaceFeatureFlagsMapCacheService.recomputeFeatureFlagsMapCache(
|
||||||
{
|
{ workspaceId },
|
||||||
workspaceId,
|
|
||||||
ignoreLock: true,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@ -3,8 +3,6 @@ import { Injectable } from '@nestjs/common';
|
|||||||
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 { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
|
||||||
|
|
||||||
import { transformLinksValue } from 'src/engine/core-modules/record-transformer/utils/transform-links-value.util';
|
import { transformLinksValue } from 'src/engine/core-modules/record-transformer/utils/transform-links-value.util';
|
||||||
import { transformPhonesValue } from 'src/engine/core-modules/record-transformer/utils/transform-phones-value.util';
|
import { transformPhonesValue } from 'src/engine/core-modules/record-transformer/utils/transform-phones-value.util';
|
||||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
@ -29,19 +27,11 @@ export class RecordInputTransformerService {
|
|||||||
return recordInput;
|
return recordInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldMetadataByFieldName = objectMetadataMapItem.fields.reduce(
|
|
||||||
(acc, field) => {
|
|
||||||
acc[field.name] = field;
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as Record<string, FieldMetadataInterface>,
|
|
||||||
);
|
|
||||||
|
|
||||||
let transformedEntries = {};
|
let transformedEntries = {};
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(recordInput)) {
|
for (const [key, value] of Object.entries(recordInput)) {
|
||||||
const fieldMetadata = fieldMetadataByFieldName[key];
|
const fieldMetadataId = objectMetadataMapItem.fieldIdByName[key];
|
||||||
|
const fieldMetadata = objectMetadataMapItem.fieldsById[fieldMetadataId];
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
if (!fieldMetadata) {
|
||||||
transformedEntries = { ...transformedEntries, [key]: value };
|
transformedEntries = { ...transformedEntries, [key]: value };
|
||||||
|
|||||||
@ -163,9 +163,13 @@ export class SearchService {
|
|||||||
const queryBuilder = entityManager.createQueryBuilder();
|
const queryBuilder = entityManager.createQueryBuilder();
|
||||||
|
|
||||||
const queryParser = new GraphqlQueryParser(
|
const queryParser = new GraphqlQueryParser(
|
||||||
objectMetadataItem.fieldsByName,
|
objectMetadataItem,
|
||||||
objectMetadataItem.fieldsByJoinColumnName,
|
generateObjectMetadataMaps([
|
||||||
generateObjectMetadataMaps([objectMetadataItem]),
|
{
|
||||||
|
...objectMetadataItem,
|
||||||
|
fields: Object.values(objectMetadataItem.fieldsById),
|
||||||
|
},
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
queryParser.applyFilterToBuilder(
|
queryParser.applyFilterToBuilder(
|
||||||
|
|||||||
@ -2,9 +2,12 @@ import DataLoader from 'dataloader';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
FieldMetadataLoaderPayload,
|
FieldMetadataLoaderPayload,
|
||||||
|
IndexMetadataLoaderPayload,
|
||||||
RelationLoaderPayload,
|
RelationLoaderPayload,
|
||||||
} from 'src/engine/dataloaders/dataloader.service';
|
} from 'src/engine/dataloaders/dataloader.service';
|
||||||
|
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||||
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 { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
|
||||||
export interface IDataloaders {
|
export interface IDataloaders {
|
||||||
@ -20,6 +23,11 @@ export interface IDataloaders {
|
|||||||
|
|
||||||
fieldMetadataLoader: DataLoader<
|
fieldMetadataLoader: DataLoader<
|
||||||
FieldMetadataLoaderPayload,
|
FieldMetadataLoaderPayload,
|
||||||
FieldMetadataEntity[]
|
FieldMetadataDTO[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
indexMetadataLoader: DataLoader<
|
||||||
|
IndexMetadataLoaderPayload,
|
||||||
|
IndexMetadataDTO[]
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
|||||||
|
|
||||||
import { DataloaderService } from 'src/engine/dataloaders/dataloader.service';
|
import { DataloaderService } from 'src/engine/dataloaders/dataloader.service';
|
||||||
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||||
|
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [FieldMetadataModule],
|
imports: [FieldMetadataModule, WorkspaceMetadataCacheModule],
|
||||||
providers: [DataloaderService],
|
providers: [DataloaderService],
|
||||||
exports: [DataloaderService],
|
exports: [DataloaderService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,10 +6,13 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada
|
|||||||
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 { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||||
|
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||||
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 { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service';
|
import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service';
|
||||||
|
import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
|
||||||
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 { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||||
|
|
||||||
export type RelationMetadataLoaderPayload = {
|
export type RelationMetadataLoaderPayload = {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@ -36,20 +39,28 @@ export type FieldMetadataLoaderPayload = {
|
|||||||
objectMetadata: Pick<ObjectMetadataInterface, 'id'>;
|
objectMetadata: Pick<ObjectMetadataInterface, 'id'>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IndexMetadataLoaderPayload = {
|
||||||
|
workspaceId: string;
|
||||||
|
objectMetadata: Pick<ObjectMetadataInterface, 'id'>;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DataloaderService {
|
export class DataloaderService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly fieldMetadataRelationService: FieldMetadataRelationService,
|
private readonly fieldMetadataRelationService: FieldMetadataRelationService,
|
||||||
private readonly fieldMetadataService: FieldMetadataService,
|
private readonly fieldMetadataService: FieldMetadataService,
|
||||||
|
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
createLoaders(): IDataloaders {
|
createLoaders(): IDataloaders {
|
||||||
const relationLoader = this.createRelationLoader();
|
const relationLoader = this.createRelationLoader();
|
||||||
const fieldMetadataLoader = this.createFieldMetadataLoader();
|
const fieldMetadataLoader = this.createFieldMetadataLoader();
|
||||||
|
const indexMetadataLoader = this.createIndexMetadataLoader();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
relationLoader,
|
relationLoader,
|
||||||
fieldMetadataLoader,
|
fieldMetadataLoader,
|
||||||
|
indexMetadataLoader,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,20 +89,67 @@ export class DataloaderService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private createFieldMetadataLoader() {
|
private createIndexMetadataLoader() {
|
||||||
return new DataLoader<FieldMetadataLoaderPayload, FieldMetadataEntity[]>(
|
return new DataLoader<IndexMetadataLoaderPayload, IndexMetadataDTO[]>(
|
||||||
async (dataLoaderParams: FieldMetadataLoaderPayload[]) => {
|
async (dataLoaderParams: IndexMetadataLoaderPayload[]) => {
|
||||||
const workspaceId = dataLoaderParams[0].workspaceId;
|
const workspaceId = dataLoaderParams[0].workspaceId;
|
||||||
const objectMetadataItems = dataLoaderParams.map(
|
const objectMetadataIds = dataLoaderParams.map(
|
||||||
(dataLoaderParam) => dataLoaderParam.objectMetadata,
|
(dataLoaderParam) => dataLoaderParam.objectMetadata.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const fieldMetadataCollection =
|
const { objectMetadataMaps } =
|
||||||
await this.fieldMetadataService.getFieldMetadataItemsByBatch(
|
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||||
objectMetadataItems.map((item) => item.id),
|
{ workspaceId },
|
||||||
workspaceId,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const indexMetadataCollection = objectMetadataIds.map((id) =>
|
||||||
|
Object.values(objectMetadataMaps.byId[id].indexMetadatas).map(
|
||||||
|
(indexMetadata) => {
|
||||||
|
return {
|
||||||
|
...indexMetadata,
|
||||||
|
createdAt: new Date(indexMetadata.createdAt),
|
||||||
|
updatedAt: new Date(indexMetadata.updatedAt),
|
||||||
|
id: indexMetadata.id,
|
||||||
|
indexWhereClause: indexMetadata.indexWhereClause ?? undefined,
|
||||||
|
objectMetadataId: id,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return indexMetadataCollection;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createFieldMetadataLoader() {
|
||||||
|
return new DataLoader<FieldMetadataLoaderPayload, FieldMetadataDTO[]>(
|
||||||
|
async (dataLoaderParams: FieldMetadataLoaderPayload[]) => {
|
||||||
|
const workspaceId = dataLoaderParams[0].workspaceId;
|
||||||
|
const objectMetadataIds = dataLoaderParams.map(
|
||||||
|
(dataLoaderParam) => dataLoaderParam.objectMetadata.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { objectMetadataMaps } =
|
||||||
|
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||||
|
{ workspaceId },
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldMetadataCollection = objectMetadataIds.map((id) =>
|
||||||
|
Object.values(objectMetadataMaps.byId[id].fieldsById).map(
|
||||||
|
// TODO: fix this as we should merge FieldMetadataEntity and FieldMetadataInterface
|
||||||
|
(fieldMetadata) => {
|
||||||
|
return {
|
||||||
|
...fieldMetadata,
|
||||||
|
createdAt: new Date(fieldMetadata.createdAt),
|
||||||
|
updatedAt: new Date(fieldMetadata.updatedAt),
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return fieldMetadataCollection;
|
return fieldMetadataCollection;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
DataSourceOptions,
|
||||||
Entity,
|
Entity,
|
||||||
|
Index,
|
||||||
|
OneToMany,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
DataSourceOptions,
|
|
||||||
OneToMany,
|
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
@ -13,6 +14,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
export type DataSourceType = DataSourceOptions['type'];
|
export type DataSourceType = DataSourceOptions['type'];
|
||||||
|
|
||||||
@Entity('dataSource')
|
@Entity('dataSource')
|
||||||
|
@Index('IDX_DATA_SOURCE_WORKSPACE_ID_CREATED_AT', ['workspaceId', 'createdAt'])
|
||||||
export class DataSourceEntity {
|
export class DataSourceEntity {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import {
|
|||||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||||
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
import { FieldMetadataSettings } 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 { FieldStandardOverridesDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-standard-overrides.dto';
|
import { FieldStandardOverridesDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-standard-overrides.dto';
|
||||||
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
|
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
|
||||||
@ -41,8 +40,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
])
|
])
|
||||||
export class FieldMetadataEntity<
|
export class FieldMetadataEntity<
|
||||||
T extends FieldMetadataType = FieldMetadataType,
|
T extends FieldMetadataType = FieldMetadataType,
|
||||||
> implements FieldMetadataInterface<T>
|
> {
|
||||||
{
|
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metada
|
|||||||
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 { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
||||||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.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 { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
@ -54,6 +55,7 @@ import { UpdateFieldInput } from './dtos/update-field.input';
|
|||||||
ActorModule,
|
ActorModule,
|
||||||
ViewModule,
|
ViewModule,
|
||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
|
WorkspaceMetadataCacheModule,
|
||||||
],
|
],
|
||||||
services: [
|
services: [
|
||||||
IsFieldMetadataDefaultValue,
|
IsFieldMetadataDefaultValue,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
import { DataSource, FindOneOptions, In, Repository } from 'typeorm';
|
import { DataSource, FindOneOptions, In, Repository } from 'typeorm';
|
||||||
import { v4 as uuidV4, v4 } from 'uuid';
|
import { v4 as uuidV4, v4 } from 'uuid';
|
||||||
|
|
||||||
|
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 { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
import { settings } from 'src/engine/constants/settings';
|
import { settings } from 'src/engine/constants/settings';
|
||||||
@ -40,9 +41,9 @@ import { generateNullable } from 'src/engine/metadata-modules/field-metadata/uti
|
|||||||
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 { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util';
|
||||||
import { isSelectOrMultiSelectFieldMetadata } from 'src/engine/metadata-modules/field-metadata/utils/is-select-or-multi-select-field-metadata.util';
|
import { isSelectOrMultiSelectFieldMetadata } from 'src/engine/metadata-modules/field-metadata/utils/is-select-or-multi-select-field-metadata.util';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
|
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.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';
|
||||||
@ -50,6 +51,7 @@ import {
|
|||||||
computeMetadataNameFromLabel,
|
computeMetadataNameFromLabel,
|
||||||
validateNameAndLabelAreSyncOrThrow,
|
validateNameAndLabelAreSyncOrThrow,
|
||||||
} from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
} from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||||
|
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||||
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';
|
||||||
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';
|
||||||
import {
|
import {
|
||||||
@ -78,8 +80,8 @@ type ValidateFieldMetadataArgs<T extends UpdateFieldInput | CreateFieldInput> =
|
|||||||
{
|
{
|
||||||
fieldMetadataType: FieldMetadataType;
|
fieldMetadataType: FieldMetadataType;
|
||||||
fieldMetadataInput: T;
|
fieldMetadataInput: T;
|
||||||
objectMetadata: ObjectMetadataEntity;
|
objectMetadata: ObjectMetadataItemWithFieldMaps;
|
||||||
existingFieldMetadata?: FieldMetadataEntity;
|
existingFieldMetadata?: FieldMetadataInterface;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -89,8 +91,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
private readonly coreDataSource: DataSource,
|
private readonly coreDataSource: DataSource,
|
||||||
@InjectRepository(FieldMetadataEntity, 'core')
|
@InjectRepository(FieldMetadataEntity, 'core')
|
||||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
|
||||||
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
private readonly workspaceMigrationService: WorkspaceMigrationService,
|
||||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||||
@ -100,6 +100,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
private readonly fieldMetadataValidationService: FieldMetadataValidationService,
|
private readonly fieldMetadataValidationService: FieldMetadataValidationService,
|
||||||
private readonly fieldMetadataRelatedRecordsService: FieldMetadataRelatedRecordsService,
|
private readonly fieldMetadataRelatedRecordsService: FieldMetadataRelatedRecordsService,
|
||||||
private readonly viewService: ViewService,
|
private readonly viewService: ViewService,
|
||||||
|
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||||
) {
|
) {
|
||||||
super(fieldMetadataRepository);
|
super(fieldMetadataRepository);
|
||||||
}
|
}
|
||||||
@ -123,54 +124,49 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
id: string,
|
id: string,
|
||||||
fieldMetadataInput: UpdateFieldInput,
|
fieldMetadataInput: UpdateFieldInput,
|
||||||
): Promise<FieldMetadataEntity> {
|
): Promise<FieldMetadataEntity> {
|
||||||
|
const { objectMetadataMaps } =
|
||||||
|
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||||
|
{ workspaceId: fieldMetadataInput.workspaceId },
|
||||||
|
);
|
||||||
|
|
||||||
|
let existingFieldMetadata: FieldMetadataInterface | undefined;
|
||||||
|
|
||||||
|
for (const objectMetadataItem of Object.values(objectMetadataMaps.byId)) {
|
||||||
|
const fieldMetadata = objectMetadataItem.fieldsById[id];
|
||||||
|
|
||||||
|
if (fieldMetadata) {
|
||||||
|
existingFieldMetadata = fieldMetadata;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDefined(existingFieldMetadata)) {
|
||||||
|
throw new FieldMetadataException(
|
||||||
|
'Field does not exist',
|
||||||
|
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectMetadataItemWithFieldMaps =
|
||||||
|
objectMetadataMaps.byId[existingFieldMetadata.objectMetadataId];
|
||||||
|
|
||||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||||
|
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
await queryRunner.startTransaction();
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fieldMetadataRepository =
|
if (
|
||||||
queryRunner.manager.getRepository<FieldMetadataEntity>(
|
!isDefined(
|
||||||
FieldMetadataEntity,
|
objectMetadataItemWithFieldMaps.labelIdentifierFieldMetadataId,
|
||||||
);
|
)
|
||||||
|
) {
|
||||||
const [existingFieldMetadata] = await fieldMetadataRepository.find({
|
|
||||||
where: {
|
|
||||||
id,
|
|
||||||
workspaceId: fieldMetadataInput.workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDefined(existingFieldMetadata)) {
|
|
||||||
throw new FieldMetadataException(
|
|
||||||
'Field does not exist',
|
|
||||||
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [objectMetadata] = await this.objectMetadataRepository.find({
|
|
||||||
where: {
|
|
||||||
id: existingFieldMetadata.objectMetadataId,
|
|
||||||
workspaceId: fieldMetadataInput.workspaceId,
|
|
||||||
},
|
|
||||||
relations: ['fields'],
|
|
||||||
order: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDefined(objectMetadata)) {
|
|
||||||
throw new FieldMetadataException(
|
|
||||||
'Object metadata does not exist',
|
|
||||||
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDefined(objectMetadata.labelIdentifierFieldMetadataId)) {
|
|
||||||
throw new FieldMetadataException(
|
throw new FieldMetadataException(
|
||||||
'Label identifier field metadata id does not exist',
|
'Label identifier field metadata id does not exist',
|
||||||
FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND,
|
FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
assertMutationNotOnRemoteObject(objectMetadata);
|
assertMutationNotOnRemoteObject(objectMetadataItemWithFieldMaps);
|
||||||
|
|
||||||
assertDoesNotNullifyDefaultValueForNonNullableField({
|
assertDoesNotNullifyDefaultValueForNonNullableField({
|
||||||
isNullable: existingFieldMetadata.isNullable,
|
isNullable: existingFieldMetadata.isNullable,
|
||||||
@ -180,19 +176,9 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
if (fieldMetadataInput.isActive === false) {
|
if (fieldMetadataInput.isActive === false) {
|
||||||
checkCanDeactivateFieldOrThrow({
|
checkCanDeactivateFieldOrThrow({
|
||||||
labelIdentifierFieldMetadataId:
|
labelIdentifierFieldMetadataId:
|
||||||
objectMetadata.labelIdentifierFieldMetadataId,
|
objectMetadataItemWithFieldMaps.labelIdentifierFieldMetadataId,
|
||||||
existingFieldMetadata,
|
existingFieldMetadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
const viewsRepository =
|
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
|
||||||
fieldMetadataInput.workspaceId,
|
|
||||||
'view',
|
|
||||||
);
|
|
||||||
|
|
||||||
await viewsRepository.delete({
|
|
||||||
kanbanFieldMetadataId: id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatableFieldInput =
|
const updatableFieldInput =
|
||||||
@ -221,7 +207,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
fieldMetadataType: existingFieldMetadata.type,
|
fieldMetadataType: existingFieldMetadata.type,
|
||||||
existingFieldMetadata,
|
existingFieldMetadata,
|
||||||
fieldMetadataInput: fieldMetadataForUpdate,
|
fieldMetadataInput: fieldMetadataForUpdate,
|
||||||
objectMetadata,
|
objectMetadata: objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLabelSyncedWithName =
|
const isLabelSyncedWithName =
|
||||||
@ -236,9 +222,9 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We're running field update under a transaction, so we can rollback if migration fails
|
// We're running field update under a transaction, so we can rollback if migration fails
|
||||||
await fieldMetadataRepository.update(id, fieldMetadataForUpdate);
|
await this.fieldMetadataRepository.update(id, fieldMetadataForUpdate);
|
||||||
|
|
||||||
const [updatedFieldMetadata] = await fieldMetadataRepository.find({
|
const [updatedFieldMetadata] = await this.fieldMetadataRepository.find({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -249,6 +235,18 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fieldMetadataInput.isActive === false) {
|
||||||
|
const viewsRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
fieldMetadataInput.workspaceId,
|
||||||
|
'view',
|
||||||
|
);
|
||||||
|
|
||||||
|
await viewsRepository.delete({
|
||||||
|
kanbanFieldMetadataId: id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
updatedFieldMetadata.isActive &&
|
updatedFieldMetadata.isActive &&
|
||||||
isSelectOrMultiSelectFieldMetadata(updatedFieldMetadata) &&
|
isSelectOrMultiSelectFieldMetadata(updatedFieldMetadata) &&
|
||||||
@ -272,10 +270,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
) {
|
) {
|
||||||
await this.workspaceMigrationService.createCustomMigration(
|
await this.workspaceMigrationService.createCustomMigration(
|
||||||
generateMigrationName(`update-${updatedFieldMetadata.name}`),
|
generateMigrationName(`update-${updatedFieldMetadata.name}`),
|
||||||
existingFieldMetadata.workspaceId,
|
fieldMetadataInput.workspaceId,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: computeObjectTargetTable(objectMetadata),
|
name: computeObjectTargetTable(objectMetadataItemWithFieldMaps),
|
||||||
action: WorkspaceMigrationTableActionType.ALTER,
|
action: WorkspaceMigrationTableActionType.ALTER,
|
||||||
columns: this.workspaceMigrationFactory.createColumnActions(
|
columns: this.workspaceMigrationFactory.createColumnActions(
|
||||||
WorkspaceMigrationColumnActionType.ALTER,
|
WorkspaceMigrationColumnActionType.ALTER,
|
||||||
@ -299,6 +297,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release();
|
await queryRunner.release();
|
||||||
|
|
||||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||||
fieldMetadataInput.workspaceId,
|
fieldMetadataInput.workspaceId,
|
||||||
);
|
);
|
||||||
@ -470,28 +469,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findOneOrFail(
|
|
||||||
id: string,
|
|
||||||
options?: FindOneOptions<FieldMetadataEntity>,
|
|
||||||
) {
|
|
||||||
const [fieldMetadata] = await this.fieldMetadataRepository.find({
|
|
||||||
...options,
|
|
||||||
where: {
|
|
||||||
...options?.where,
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!fieldMetadata) {
|
|
||||||
throw new FieldMetadataException(
|
|
||||||
'Field does not exist',
|
|
||||||
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fieldMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async findOneWithinWorkspace(
|
public async findOneWithinWorkspace(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
options: FindOneOptions<FieldMetadataEntity>,
|
options: FindOneOptions<FieldMetadataEntity>,
|
||||||
@ -509,7 +486,10 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
|
|
||||||
private buildUpdatableStandardFieldInput(
|
private buildUpdatableStandardFieldInput(
|
||||||
fieldMetadataInput: UpdateFieldInput,
|
fieldMetadataInput: UpdateFieldInput,
|
||||||
existingFieldMetadata: FieldMetadataEntity,
|
existingFieldMetadata: Pick<
|
||||||
|
FieldMetadataInterface,
|
||||||
|
'type' | 'isNullable' | 'defaultValue' | 'options'
|
||||||
|
>,
|
||||||
) {
|
) {
|
||||||
const updatableStandardFieldInput: UpdateFieldInput & {
|
const updatableStandardFieldInput: UpdateFieldInput & {
|
||||||
standardOverrides?: FieldStandardOverridesDTO;
|
standardOverrides?: FieldStandardOverridesDTO;
|
||||||
@ -754,7 +734,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
|
|
||||||
private async validateAndCreateFieldMetadataItems(
|
private async validateAndCreateFieldMetadataItems(
|
||||||
fieldMetadataInput: CreateFieldInput,
|
fieldMetadataInput: CreateFieldInput,
|
||||||
objectMetadata: ObjectMetadataEntity,
|
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||||
fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||||
): Promise<FieldMetadataEntity[]> {
|
): Promise<FieldMetadataEntity[]> {
|
||||||
if (!fieldMetadataInput.isRemoteCreation) {
|
if (!fieldMetadataInput.isRemoteCreation) {
|
||||||
@ -841,7 +821,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
isRemoteCreation,
|
isRemoteCreation,
|
||||||
}: {
|
}: {
|
||||||
createdFieldMetadataItems: FieldMetadataEntity[];
|
createdFieldMetadataItems: FieldMetadataEntity[];
|
||||||
objectMetadataMap: Record<string, ObjectMetadataEntity>;
|
objectMetadataMap: Record<string, ObjectMetadataItemWithFieldMaps>;
|
||||||
isRemoteCreation: boolean;
|
isRemoteCreation: boolean;
|
||||||
}): Promise<WorkspaceMigrationTableAction[]> {
|
}): Promise<WorkspaceMigrationTableAction[]> {
|
||||||
if (isRemoteCreation) {
|
if (isRemoteCreation) {
|
||||||
@ -886,6 +866,11 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { objectMetadataMaps } =
|
||||||
|
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||||
|
{ workspaceId: fieldMetadataInputs[0].workspaceId },
|
||||||
|
);
|
||||||
|
|
||||||
const workspaceId = fieldMetadataInputs[0].workspaceId;
|
const workspaceId = fieldMetadataInputs[0].workspaceId;
|
||||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||||
|
|
||||||
@ -902,23 +887,11 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
this.groupFieldInputsByObjectId(fieldMetadataInputs);
|
this.groupFieldInputsByObjectId(fieldMetadataInputs);
|
||||||
const objectMetadataIds = Object.keys(inputsByObjectId);
|
const objectMetadataIds = Object.keys(inputsByObjectId);
|
||||||
|
|
||||||
const objectMetadatas = await this.objectMetadataRepository.find({
|
|
||||||
where: {
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
relations: ['fields'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectMetadataMap = objectMetadatas.reduce(
|
|
||||||
(acc, obj) => ({ ...acc, [obj.id]: obj }),
|
|
||||||
{} as Record<string, ObjectMetadataEntity>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const createdFieldMetadatas: FieldMetadataEntity[] = [];
|
const createdFieldMetadatas: FieldMetadataEntity[] = [];
|
||||||
const migrationActions: WorkspaceMigrationTableAction[] = [];
|
const migrationActions: WorkspaceMigrationTableAction[] = [];
|
||||||
|
|
||||||
for (const objectMetadataId of objectMetadataIds) {
|
for (const objectMetadataId of objectMetadataIds) {
|
||||||
const objectMetadata = objectMetadataMap[objectMetadataId];
|
const objectMetadata = objectMetadataMaps.byId[objectMetadataId];
|
||||||
|
|
||||||
if (!isDefined(objectMetadata)) {
|
if (!isDefined(objectMetadata)) {
|
||||||
throw new FieldMetadataException(
|
throw new FieldMetadataException(
|
||||||
@ -941,7 +914,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
|
|
||||||
const fieldMigrationActions = await this.createMigrationActions({
|
const fieldMigrationActions = await this.createMigrationActions({
|
||||||
createdFieldMetadataItems,
|
createdFieldMetadataItems,
|
||||||
objectMetadataMap,
|
objectMetadataMap: objectMetadataMaps.byId,
|
||||||
isRemoteCreation: fieldMetadataInput.isRemoteCreation ?? false,
|
isRemoteCreation: fieldMetadataInput.isRemoteCreation ?? false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export interface FieldMetadataInterface<
|
|||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
isNullable?: boolean;
|
isNullable: boolean;
|
||||||
isUnique?: boolean;
|
isUnique?: boolean;
|
||||||
relationTargetFieldMetadataId?: string;
|
relationTargetFieldMetadataId?: string;
|
||||||
relationTargetFieldMetadata?: FieldMetadataInterface;
|
relationTargetFieldMetadata?: FieldMetadataInterface;
|
||||||
@ -30,4 +30,7 @@ export interface FieldMetadataInterface<
|
|||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
generatedType?: 'STORED' | 'VIRTUAL';
|
generatedType?: 'STORED' | 'VIRTUAL';
|
||||||
asExpression?: string;
|
asExpression?: string;
|
||||||
|
isLabelSyncedWithName: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export interface ObjectMetadataInterface {
|
|||||||
labelSingular: string;
|
labelSingular: string;
|
||||||
labelPlural: string;
|
labelPlural: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
icon?: string;
|
icon: string;
|
||||||
targetTableName: string;
|
targetTableName: string;
|
||||||
fields: FieldMetadataInterface[];
|
fields: FieldMetadataInterface[];
|
||||||
indexMetadatas: IndexMetadataInterface[];
|
indexMetadatas: IndexMetadataInterface[];
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
FieldMetadataExceptionCode,
|
FieldMetadataExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
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 { removeFieldMapsFromObjectMetadata } from 'src/engine/metadata-modules/utils/remove-field-maps-from-object-metadata.util';
|
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -77,13 +77,15 @@ export class FieldMetadataRelationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sourceObjectMetadata: removeFieldMapsFromObjectMetadata(
|
sourceObjectMetadata:
|
||||||
sourceObjectMetadata,
|
getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
) as ObjectMetadataEntity,
|
sourceObjectMetadata,
|
||||||
|
) as ObjectMetadataEntity,
|
||||||
sourceFieldMetadata: sourceFieldMetadata as FieldMetadataEntity,
|
sourceFieldMetadata: sourceFieldMetadata as FieldMetadataEntity,
|
||||||
targetObjectMetadata: removeFieldMapsFromObjectMetadata(
|
targetObjectMetadata:
|
||||||
targetObjectMetadata,
|
getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||||
) as ObjectMetadataEntity,
|
targetObjectMetadata,
|
||||||
|
) as ObjectMetadataEntity,
|
||||||
targetFieldMetadata: targetFieldMetadata as FieldMetadataEntity,
|
targetFieldMetadata: targetFieldMetadata as FieldMetadataEntity,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||||
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
|
||||||
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||||
import {
|
import {
|
||||||
@ -13,7 +14,6 @@ import {
|
|||||||
FieldMetadataDefaultOption,
|
FieldMetadataDefaultOption,
|
||||||
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
} from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||||
import { UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
import { UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
||||||
import {
|
import {
|
||||||
FieldMetadataException,
|
FieldMetadataException,
|
||||||
FieldMetadataExceptionCode,
|
FieldMetadataExceptionCode,
|
||||||
@ -31,7 +31,10 @@ type Validator<T> = { validator: (str: T) => boolean; message: string };
|
|||||||
type FieldMetadataUpdateCreateInput = CreateFieldInput | UpdateFieldInput;
|
type FieldMetadataUpdateCreateInput = CreateFieldInput | UpdateFieldInput;
|
||||||
|
|
||||||
type ValidateEnumFieldMetadataArgs = {
|
type ValidateEnumFieldMetadataArgs = {
|
||||||
existingFieldMetadata?: FieldMetadataEntity;
|
existingFieldMetadata?: Pick<
|
||||||
|
FieldMetadataInterface,
|
||||||
|
'type' | 'isNullable' | 'defaultValue' | 'options'
|
||||||
|
>;
|
||||||
fieldMetadataInput: FieldMetadataUpdateCreateInput;
|
fieldMetadataInput: FieldMetadataUpdateCreateInput;
|
||||||
fieldMetadataType: EnumFieldMetadataUnionType;
|
fieldMetadataType: EnumFieldMetadataUnionType;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,17 +1,15 @@
|
|||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.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';
|
||||||
|
|
||||||
export type SelectOrMultiSelectFieldMetadataEntity = FieldMetadataEntity<
|
export type SelectOrMultiSelectFieldMetadataEntity = FieldMetadataEntity<
|
||||||
FieldMetadataType.SELECT | FieldMetadataType.MULTI_SELECT
|
FieldMetadataType.SELECT | FieldMetadataType.MULTI_SELECT
|
||||||
>;
|
>;
|
||||||
export const isSelectOrMultiSelectFieldMetadata = (
|
export const isSelectOrMultiSelectFieldMetadata = (
|
||||||
fieldMetadata: unknown,
|
fieldMetadata: FieldMetadataInterface,
|
||||||
): fieldMetadata is SelectOrMultiSelectFieldMetadataEntity => {
|
): fieldMetadata is SelectOrMultiSelectFieldMetadataEntity => {
|
||||||
if (!(fieldMetadata instanceof FieldMetadataEntity)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [FieldMetadataType.SELECT, FieldMetadataType.MULTI_SELECT].includes(
|
return [FieldMetadataType.SELECT, FieldMetadataType.MULTI_SELECT].includes(
|
||||||
fieldMetadata.type,
|
fieldMetadata.type,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -80,5 +80,5 @@ export class IndexMetadataEntity {
|
|||||||
default: IndexType.BTREE,
|
default: IndexType.BTREE,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
indexType?: IndexType;
|
indexType: IndexType;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -106,7 +106,10 @@ export class IndexMetadataService {
|
|||||||
|
|
||||||
async recomputeIndexMetadataForObject(
|
async recomputeIndexMetadataForObject(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
updatedObjectMetadata: ObjectMetadataEntity,
|
updatedObjectMetadata: Pick<
|
||||||
|
ObjectMetadataEntity,
|
||||||
|
'nameSingular' | 'isCustom' | 'id'
|
||||||
|
>,
|
||||||
) {
|
) {
|
||||||
const indexesToRecompute = await this.indexMetadataRepository.find({
|
const indexesToRecompute = await this.indexMetadataRepository.find({
|
||||||
where: {
|
where: {
|
||||||
@ -232,7 +235,10 @@ export class IndexMetadataService {
|
|||||||
|
|
||||||
async createIndexRecomputeMigrations(
|
async createIndexRecomputeMigrations(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
objectMetadata: ObjectMetadataEntity,
|
objectMetadata: Pick<
|
||||||
|
ObjectMetadataEntity,
|
||||||
|
'nameSingular' | 'isCustom' | 'id'
|
||||||
|
>,
|
||||||
recomputedIndexes: {
|
recomputedIndexes: {
|
||||||
indexMetadata: IndexMetadataEntity;
|
indexMetadata: IndexMetadataEntity;
|
||||||
previousName: string;
|
previousName: string;
|
||||||
|
|||||||
@ -8,4 +8,6 @@ export interface IndexFieldMetadataInterface {
|
|||||||
fieldMetadata: FieldMetadataInterface;
|
fieldMetadata: FieldMetadataInterface;
|
||||||
indexMetadata: IndexMetadataInterface;
|
indexMetadata: IndexMetadataInterface;
|
||||||
order: number;
|
order: number;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,14 @@
|
|||||||
import { IndexFieldMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-field-metadata.interface';
|
import { IndexFieldMetadataInterface } from 'src/engine/metadata-modules/index-metadata/interfaces/index-field-metadata.interface';
|
||||||
|
|
||||||
|
import { IndexType } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
|
||||||
|
|
||||||
export interface IndexMetadataInterface {
|
export interface IndexMetadataInterface {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
isUnique: boolean;
|
isUnique: boolean;
|
||||||
indexFieldMetadatas: IndexFieldMetadataInterface[];
|
indexFieldMetadatas: IndexFieldMetadataInterface[];
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
indexWhereClause: string | null;
|
||||||
|
indexType: IndexType;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
|||||||
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 { RemoteTableRelationsModule } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module';
|
import { RemoteTableRelationsModule } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module';
|
||||||
import { SearchVectorModule } from 'src/engine/metadata-modules/search-vector/search-vector.module';
|
import { SearchVectorModule } from 'src/engine/metadata-modules/search-vector/search-vector.module';
|
||||||
|
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
||||||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.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 { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||||
@ -59,6 +60,7 @@ import { UpdateObjectPayload } from './dtos/update-object.input';
|
|||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
WorkspacePermissionsCacheModule,
|
WorkspacePermissionsCacheModule,
|
||||||
WorkspaceCacheStorageModule,
|
WorkspaceCacheStorageModule,
|
||||||
|
WorkspaceMetadataCacheModule,
|
||||||
],
|
],
|
||||||
services: [
|
services: [
|
||||||
ObjectMetadataService,
|
ObjectMetadataService,
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
|||||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
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 { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
|
||||||
import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
|
import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
|
||||||
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
|
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
|
||||||
import {
|
import {
|
||||||
@ -150,4 +151,26 @@ export class ObjectMetadataResolver {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResolveField(() => [IndexMetadataDTO], { nullable: false })
|
||||||
|
async indexMetadataList(
|
||||||
|
@AuthWorkspace() workspace: Workspace,
|
||||||
|
@Parent() objectMetadata: ObjectMetadataDTO,
|
||||||
|
@Context() context: { loaders: IDataloaders },
|
||||||
|
): Promise<IndexMetadataDTO[]> {
|
||||||
|
try {
|
||||||
|
const indexMetadataItems = await context.loaders.indexMetadataLoader.load(
|
||||||
|
{
|
||||||
|
objectMetadata,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return indexMetadataItems;
|
||||||
|
} catch (error) {
|
||||||
|
objectMetadataGraphqlApiExceptionHandler(error);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { i18n } from '@lingui/core';
|
import { i18n } from '@lingui/core';
|
||||||
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
|
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||||
import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
|
import { APP_LOCALES } from 'twenty-shared/translations';
|
||||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||||
import { FindManyOptions, FindOneOptions, In, Not, Repository } from 'typeorm';
|
import { FindManyOptions, FindOneOptions, In, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';
|
|
||||||
|
|
||||||
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';
|
||||||
@ -35,7 +33,10 @@ import {
|
|||||||
} from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
} from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
||||||
import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service';
|
import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service';
|
||||||
import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
|
import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||||
|
import { validatesNoOtherObjectWithSameNameExistsOrThrows } from 'src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util';
|
||||||
|
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||||
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';
|
||||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||||
@ -58,6 +59,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
|
|
||||||
private readonly remoteTableRelationsService: RemoteTableRelationsService,
|
private readonly remoteTableRelationsService: RemoteTableRelationsService,
|
||||||
private readonly dataSourceService: DataSourceService,
|
private readonly dataSourceService: DataSourceService,
|
||||||
|
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
private readonly searchVectorService: SearchVectorService,
|
private readonly searchVectorService: SearchVectorService,
|
||||||
@ -89,6 +91,13 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
override async createOne(
|
override async createOne(
|
||||||
objectMetadataInput: CreateObjectInput,
|
objectMetadataInput: CreateObjectInput,
|
||||||
): Promise<ObjectMetadataEntity> {
|
): Promise<ObjectMetadataEntity> {
|
||||||
|
const { objectMetadataMaps } =
|
||||||
|
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||||
|
{
|
||||||
|
workspaceId: objectMetadataInput.workspaceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const lastDataSourceMetadata =
|
const lastDataSourceMetadata =
|
||||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||||
objectMetadataInput.workspaceId,
|
objectMetadataInput.workspaceId,
|
||||||
@ -131,13 +140,28 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.validatesNoOtherObjectWithSameNameExistsOrThrows({
|
validatesNoOtherObjectWithSameNameExistsOrThrows({
|
||||||
objectMetadataNamePlural: objectMetadataInput.namePlural,
|
objectMetadataNamePlural: objectMetadataInput.namePlural,
|
||||||
objectMetadataNameSingular: objectMetadataInput.nameSingular,
|
objectMetadataNameSingular: objectMetadataInput.nameSingular,
|
||||||
workspaceId: objectMetadataInput.workspaceId,
|
objectMetadataMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const createdObjectMetadata = await super.createOne({
|
const baseCustomFields = buildDefaultFieldsForCustomObject(
|
||||||
|
objectMetadataInput.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const labelIdentifierFieldMetadataId = baseCustomFields.find(
|
||||||
|
(field) => field.standardId === CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
|
||||||
|
)?.id;
|
||||||
|
|
||||||
|
if (!labelIdentifierFieldMetadataId) {
|
||||||
|
throw new ObjectMetadataException(
|
||||||
|
'Label identifier field metadata not created properly',
|
||||||
|
ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdObjectMetadata = await this.objectMetadataRepository.save({
|
||||||
...objectMetadataInput,
|
...objectMetadataInput,
|
||||||
dataSourceId: lastDataSourceMetadata.id,
|
dataSourceId: lastDataSourceMetadata.id,
|
||||||
targetTableName: 'DEPRECATED',
|
targetTableName: 'DEPRECATED',
|
||||||
@ -146,24 +170,8 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
isSystem: false,
|
isSystem: false,
|
||||||
isRemote: objectMetadataInput.isRemote,
|
isRemote: objectMetadataInput.isRemote,
|
||||||
isSearchable: !objectMetadataInput.isRemote,
|
isSearchable: !objectMetadataInput.isRemote,
|
||||||
fields: objectMetadataInput.isRemote
|
fields: objectMetadataInput.isRemote ? [] : baseCustomFields,
|
||||||
? []
|
labelIdentifierFieldMetadataId,
|
||||||
: buildDefaultFieldsForCustomObject(objectMetadataInput.workspaceId),
|
|
||||||
});
|
|
||||||
|
|
||||||
const labelIdentifierFieldMetadata = createdObjectMetadata.fields.find(
|
|
||||||
(field) => field.standardId === CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!labelIdentifierFieldMetadata) {
|
|
||||||
throw new ObjectMetadataException(
|
|
||||||
'Label identifier field metadata not created properly',
|
|
||||||
ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.objectMetadataRepository.update(createdObjectMetadata.id, {
|
|
||||||
labelIdentifierFieldMetadataId: labelIdentifierFieldMetadata.id,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (objectMetadataInput.isRemote) {
|
if (objectMetadataInput.isRemote) {
|
||||||
@ -174,6 +182,13 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
objectMetadataInput.primaryKeyColumnType,
|
objectMetadataInput.primaryKeyColumnType,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const createdRelatedObjectMetadataCollection =
|
||||||
|
await this.objectMetadataFieldRelationService.createRelationsAndForeignKeysMetadata(
|
||||||
|
objectMetadataInput.workspaceId,
|
||||||
|
createdObjectMetadata,
|
||||||
|
objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
await this.objectMetadataMigrationService.createTableMigration(
|
await this.objectMetadataMigrationService.createTableMigration(
|
||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
);
|
);
|
||||||
@ -183,12 +198,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
createdObjectMetadata.fields,
|
createdObjectMetadata.fields,
|
||||||
);
|
);
|
||||||
|
|
||||||
const createdRelatedObjectMetadataCollection =
|
|
||||||
await this.objectMetadataFieldRelationService.createRelationsAndForeignKeysMetadata(
|
|
||||||
objectMetadataInput.workspaceId,
|
|
||||||
createdObjectMetadata,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.objectMetadataMigrationService.createRelationMigrations(
|
await this.objectMetadataMigrationService.createRelationMigrations(
|
||||||
createdObjectMetadata,
|
createdObjectMetadata,
|
||||||
createdRelatedObjectMetadataCollection,
|
createdRelatedObjectMetadataCollection,
|
||||||
@ -214,7 +223,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
|
|
||||||
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
|
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
|
||||||
workspaceId: objectMetadataInput.workspaceId,
|
workspaceId: objectMetadataInput.workspaceId,
|
||||||
ignoreLock: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return createdObjectMetadata;
|
return createdObjectMetadata;
|
||||||
@ -224,6 +232,11 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
input: UpdateOneObjectInput,
|
input: UpdateOneObjectInput,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<ObjectMetadataEntity> {
|
): Promise<ObjectMetadataEntity> {
|
||||||
|
const { objectMetadataMaps } =
|
||||||
|
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||||
|
{ workspaceId },
|
||||||
|
);
|
||||||
|
|
||||||
const inputId = input.id;
|
const inputId = input.id;
|
||||||
|
|
||||||
const inputPayload = {
|
const inputPayload = {
|
||||||
@ -238,9 +251,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
|
|
||||||
validateObjectMetadataInputNamesOrThrow(inputPayload);
|
validateObjectMetadataInputNamesOrThrow(inputPayload);
|
||||||
|
|
||||||
const existingObjectMetadata = await this.objectMetadataRepository.findOne({
|
const existingObjectMetadata = objectMetadataMaps.byId[inputId];
|
||||||
where: { id: inputId, workspaceId: workspaceId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!existingObjectMetadata) {
|
if (!existingObjectMetadata) {
|
||||||
throw new ObjectMetadataException(
|
throw new ObjectMetadataException(
|
||||||
@ -254,14 +265,14 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
...inputPayload,
|
...inputPayload,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.validatesNoOtherObjectWithSameNameExistsOrThrows({
|
validatesNoOtherObjectWithSameNameExistsOrThrows({
|
||||||
objectMetadataNameSingular:
|
objectMetadataNameSingular:
|
||||||
existingObjectMetadataCombinedWithUpdateInput.nameSingular,
|
existingObjectMetadataCombinedWithUpdateInput.nameSingular,
|
||||||
objectMetadataNamePlural:
|
objectMetadataNamePlural:
|
||||||
existingObjectMetadataCombinedWithUpdateInput.namePlural,
|
existingObjectMetadataCombinedWithUpdateInput.namePlural,
|
||||||
workspaceId: workspaceId,
|
|
||||||
existingObjectMetadataId:
|
existingObjectMetadataId:
|
||||||
existingObjectMetadataCombinedWithUpdateInput.id,
|
existingObjectMetadataCombinedWithUpdateInput.id,
|
||||||
|
objectMetadataMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingObjectMetadataCombinedWithUpdateInput.isLabelSyncedWithName) {
|
if (existingObjectMetadataCombinedWithUpdateInput.isLabelSyncedWithName) {
|
||||||
@ -395,7 +406,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
|
|
||||||
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
|
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
ignoreLock: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return objectMetadata;
|
return objectMetadata;
|
||||||
@ -436,44 +446,26 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findMany(options?: FindManyOptions<ObjectMetadataEntity>) {
|
|
||||||
return this.objectMetadataRepository.find({
|
|
||||||
relations: ['fields'],
|
|
||||||
...options,
|
|
||||||
where: {
|
|
||||||
...options?.where,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteObjectsMetadata(workspaceId: string) {
|
public async deleteObjectsMetadata(workspaceId: string) {
|
||||||
await this.objectMetadataRepository.delete({ workspaceId });
|
await this.objectMetadataRepository.delete({ workspaceId });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getObjectMetadataStandardIdToIdMap(workspaceId: string) {
|
|
||||||
const objectMetadata = await this.findManyWithinWorkspace(workspaceId);
|
|
||||||
|
|
||||||
const objectMetadataStandardIdToIdMap =
|
|
||||||
objectMetadata.reduce<ObjectMetadataStandardIdToIdMap>((acc, object) => {
|
|
||||||
acc[object.standardId ?? ''] = {
|
|
||||||
id: object.id,
|
|
||||||
fields: object.fields.reduce((acc, field) => {
|
|
||||||
// @ts-expect-error legacy noImplicitAny
|
|
||||||
acc[field.standardId ?? ''] = field.id;
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return { objectMetadataStandardIdToIdMap };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleObjectNameAndLabelUpdates(
|
private async handleObjectNameAndLabelUpdates(
|
||||||
existingObjectMetadata: ObjectMetadataEntity,
|
existingObjectMetadata: Pick<
|
||||||
objectMetadataForUpdate: ObjectMetadataEntity,
|
ObjectMetadataItemWithFieldMaps,
|
||||||
|
'nameSingular' | 'isCustom' | 'id' | 'labelPlural' | 'icon' | 'fieldsById'
|
||||||
|
>,
|
||||||
|
objectMetadataForUpdate: Pick<
|
||||||
|
ObjectMetadataItemWithFieldMaps,
|
||||||
|
| 'nameSingular'
|
||||||
|
| 'isCustom'
|
||||||
|
| 'workspaceId'
|
||||||
|
| 'id'
|
||||||
|
| 'labelSingular'
|
||||||
|
| 'labelPlural'
|
||||||
|
| 'icon'
|
||||||
|
| 'fieldsById'
|
||||||
|
>,
|
||||||
inputPayload: UpdateObjectPayload,
|
inputPayload: UpdateObjectPayload,
|
||||||
) {
|
) {
|
||||||
const newTargetTableName = computeObjectTargetTable(
|
const newTargetTableName = computeObjectTargetTable(
|
||||||
@ -533,45 +525,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private validatesNoOtherObjectWithSameNameExistsOrThrows = async ({
|
|
||||||
objectMetadataNameSingular,
|
|
||||||
objectMetadataNamePlural,
|
|
||||||
workspaceId,
|
|
||||||
existingObjectMetadataId,
|
|
||||||
}: {
|
|
||||||
objectMetadataNameSingular: string;
|
|
||||||
objectMetadataNamePlural: string;
|
|
||||||
workspaceId: string;
|
|
||||||
existingObjectMetadataId?: string;
|
|
||||||
}): Promise<void> => {
|
|
||||||
const baseWhereConditions = [
|
|
||||||
{ nameSingular: objectMetadataNameSingular, workspaceId },
|
|
||||||
{ nameSingular: objectMetadataNamePlural, workspaceId },
|
|
||||||
{ namePlural: objectMetadataNameSingular, workspaceId },
|
|
||||||
{ namePlural: objectMetadataNamePlural, workspaceId },
|
|
||||||
];
|
|
||||||
|
|
||||||
const whereConditions = baseWhereConditions.map((condition) => {
|
|
||||||
return {
|
|
||||||
...condition,
|
|
||||||
...(isDefined(existingObjectMetadataId)
|
|
||||||
? { id: Not(In([existingObjectMetadataId])) }
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectAlreadyExists = await this.objectMetadataRepository.findOne({
|
|
||||||
where: whereConditions,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (objectAlreadyExists) {
|
|
||||||
throw new ObjectMetadataException(
|
|
||||||
'Object already exists',
|
|
||||||
ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async resolveOverridableString(
|
async resolveOverridableString(
|
||||||
objectMetadata: ObjectMetadataDTO,
|
objectMetadata: ObjectMetadataDTO,
|
||||||
labelKey: 'labelPlural' | 'labelSingular' | 'description' | 'icon',
|
labelKey: 'labelPlural' | 'labelSingular' | 'description' | 'icon',
|
||||||
@ -581,17 +534,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
return objectMetadata[labelKey];
|
return objectMetadata[labelKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!locale || locale === SOURCE_LOCALE) {
|
|
||||||
if (
|
|
||||||
objectMetadata.standardOverrides &&
|
|
||||||
isDefined(objectMetadata.standardOverrides[labelKey])
|
|
||||||
) {
|
|
||||||
return objectMetadata.standardOverrides[labelKey] as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
return objectMetadata[labelKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
const translationValue =
|
const translationValue =
|
||||||
// @ts-expect-error legacy noImplicitAny
|
// @ts-expect-error legacy noImplicitAny
|
||||||
objectMetadata.standardOverrides?.translations?.[locale]?.[labelKey];
|
objectMetadata.standardOverrides?.translations?.[locale]?.[labelKey];
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import { buildDescriptionForRelationFieldMetadataOnFromField } from 'src/engine/metadata-modules/object-metadata/utils/build-description-for-relation-field-on-from-field.util';
|
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 { 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-on-delete-action.type';
|
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
|
||||||
|
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 {
|
import {
|
||||||
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
|
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
|
||||||
STANDARD_OBJECT_FIELD_IDS,
|
STANDARD_OBJECT_FIELD_IDS,
|
||||||
@ -41,33 +43,51 @@ export class ObjectMetadataFieldRelationService {
|
|||||||
|
|
||||||
public async createRelationsAndForeignKeysMetadata(
|
public async createRelationsAndForeignKeysMetadata(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
sourceObjectMetadata: ObjectMetadataEntity,
|
sourceObjectMetadata: Pick<
|
||||||
|
ObjectMetadataItemWithFieldMaps,
|
||||||
|
'id' | 'nameSingular' | 'labelSingular'
|
||||||
|
>,
|
||||||
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
) {
|
) {
|
||||||
const relatedObjectMetadataCollection = await Promise.all(
|
const relatedObjectMetadataCollection = await Promise.all(
|
||||||
DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS.map(
|
DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS.map(
|
||||||
async (relationObjectMetadataStandardId) =>
|
async (relationObjectMetadataStandardId) =>
|
||||||
this.createRelationAndForeignKeyMetadata(
|
this.createRelationAndForeignKeyMetadata({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
sourceObjectMetadata,
|
sourceObjectMetadata,
|
||||||
relationObjectMetadataStandardId,
|
relationObjectMetadataStandardId,
|
||||||
),
|
objectMetadataMaps,
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return relatedObjectMetadataCollection;
|
return relatedObjectMetadataCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createRelationAndForeignKeyMetadata(
|
private async createRelationAndForeignKeyMetadata({
|
||||||
workspaceId: string,
|
workspaceId,
|
||||||
sourceObjectMetadata: ObjectMetadataEntity,
|
sourceObjectMetadata,
|
||||||
relationObjectMetadataStandardId: string,
|
relationObjectMetadataStandardId,
|
||||||
) {
|
objectMetadataMaps,
|
||||||
const targetObjectMetadata =
|
}: {
|
||||||
await this.objectMetadataRepository.findOneByOrFail({
|
workspaceId: string;
|
||||||
standardId: relationObjectMetadataStandardId,
|
sourceObjectMetadata: Pick<
|
||||||
workspaceId: workspaceId,
|
ObjectMetadataItemWithFieldMaps,
|
||||||
isCustom: false,
|
'id' | 'nameSingular' | 'labelSingular'
|
||||||
});
|
>;
|
||||||
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
|
relationObjectMetadataStandardId: string;
|
||||||
|
}) {
|
||||||
|
const targetObjectMetadata = Object.values(objectMetadataMaps.byId).find(
|
||||||
|
(objectMetadata) =>
|
||||||
|
objectMetadata.standardId === relationObjectMetadataStandardId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!targetObjectMetadata) {
|
||||||
|
throw new Error(
|
||||||
|
`Target object metadata not found for standard ID: ${relationObjectMetadataStandardId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.createFieldMetadataRelation(
|
await this.createFieldMetadataRelation(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -80,8 +100,11 @@ export class ObjectMetadataFieldRelationService {
|
|||||||
|
|
||||||
private async createFieldMetadataRelation(
|
private async createFieldMetadataRelation(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
sourceObjectMetadata: ObjectMetadataEntity,
|
sourceObjectMetadata: Pick<
|
||||||
targetObjectMetadata: ObjectMetadataEntity,
|
ObjectMetadataItemWithFieldMaps,
|
||||||
|
'id' | 'nameSingular' | 'labelSingular'
|
||||||
|
>,
|
||||||
|
targetObjectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||||
): Promise<FieldMetadataEntity<FieldMetadataType.RELATION>[]> {
|
): Promise<FieldMetadataEntity<FieldMetadataType.RELATION>[]> {
|
||||||
const sourceFieldMetadata = this.createSourceFieldMetadata(
|
const sourceFieldMetadata = this.createSourceFieldMetadata(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -119,7 +142,10 @@ export class ObjectMetadataFieldRelationService {
|
|||||||
|
|
||||||
public async updateRelationsAndForeignKeysMetadata(
|
public async updateRelationsAndForeignKeysMetadata(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
updatedObjectMetadata: ObjectMetadataEntity,
|
updatedObjectMetadata: Pick<
|
||||||
|
ObjectMetadataEntity,
|
||||||
|
'nameSingular' | 'isCustom' | 'id' | 'labelSingular'
|
||||||
|
>,
|
||||||
): Promise<
|
): Promise<
|
||||||
{
|
{
|
||||||
targetObjectMetadata: ObjectMetadataEntity;
|
targetObjectMetadata: ObjectMetadataEntity;
|
||||||
@ -141,7 +167,10 @@ export class ObjectMetadataFieldRelationService {
|
|||||||
|
|
||||||
private async updateRelationAndForeignKeyMetadata(
|
private async updateRelationAndForeignKeyMetadata(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
sourceObjectMetadata: ObjectMetadataEntity,
|
sourceObjectMetadata: Pick<
|
||||||
|
ObjectMetadataEntity,
|
||||||
|
'nameSingular' | 'id' | 'isCustom' | 'labelSingular'
|
||||||
|
>,
|
||||||
targetObjectMetadataStandardId: string,
|
targetObjectMetadataStandardId: string,
|
||||||
) {
|
) {
|
||||||
const targetObjectMetadata =
|
const targetObjectMetadata =
|
||||||
@ -226,8 +255,14 @@ export class ObjectMetadataFieldRelationService {
|
|||||||
|
|
||||||
private createSourceFieldMetadata(
|
private createSourceFieldMetadata(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
sourceObjectMetadata: ObjectMetadataEntity,
|
sourceObjectMetadata: Pick<
|
||||||
targetObjectMetadata: ObjectMetadataEntity,
|
ObjectMetadataItemWithFieldMaps,
|
||||||
|
'labelSingular' | 'id'
|
||||||
|
>,
|
||||||
|
targetObjectMetadata: Pick<
|
||||||
|
ObjectMetadataItemWithFieldMaps,
|
||||||
|
'namePlural' | 'labelSingular'
|
||||||
|
>,
|
||||||
): Partial<FieldMetadataEntity<FieldMetadataType.RELATION>> {
|
): Partial<FieldMetadataEntity<FieldMetadataType.RELATION>> {
|
||||||
const relationObjectMetadataNamePlural = targetObjectMetadata.namePlural;
|
const relationObjectMetadataNamePlural = targetObjectMetadata.namePlural;
|
||||||
|
|
||||||
@ -261,8 +296,8 @@ export class ObjectMetadataFieldRelationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateSourceFieldMetadata(
|
private updateSourceFieldMetadata(
|
||||||
sourceObjectMetadata: ObjectMetadataEntity,
|
sourceObjectMetadata: Pick<ObjectMetadataEntity, 'labelSingular'>,
|
||||||
targetObjectMetadata: ObjectMetadataEntity,
|
targetObjectMetadata: Pick<ObjectMetadataEntity, 'namePlural'>,
|
||||||
) {
|
) {
|
||||||
const relationObjectMetadataNamePlural = targetObjectMetadata.namePlural;
|
const relationObjectMetadataNamePlural = targetObjectMetadata.namePlural;
|
||||||
|
|
||||||
@ -280,8 +315,14 @@ export class ObjectMetadataFieldRelationService {
|
|||||||
|
|
||||||
private createTargetFieldMetadata(
|
private createTargetFieldMetadata(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
sourceObjectMetadata: ObjectMetadataEntity,
|
sourceObjectMetadata: Pick<
|
||||||
targetObjectMetadata: ObjectMetadataEntity,
|
ObjectMetadataItemWithFieldMaps,
|
||||||
|
'labelSingular' | 'id' | 'nameSingular'
|
||||||
|
>,
|
||||||
|
targetObjectMetadata: Pick<
|
||||||
|
ObjectMetadataItemWithFieldMaps,
|
||||||
|
'namePlural' | 'labelSingular' | 'id' | 'nameSingular'
|
||||||
|
>,
|
||||||
): Partial<FieldMetadataEntity<FieldMetadataType.RELATION>> {
|
): Partial<FieldMetadataEntity<FieldMetadataType.RELATION>> {
|
||||||
const customStandardFieldId =
|
const customStandardFieldId =
|
||||||
// @ts-expect-error legacy noImplicitAny
|
// @ts-expect-error legacy noImplicitAny
|
||||||
@ -319,8 +360,14 @@ export class ObjectMetadataFieldRelationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateTargetFieldMetadata(
|
private updateTargetFieldMetadata(
|
||||||
sourceObjectMetadata: ObjectMetadataEntity,
|
sourceObjectMetadata: Pick<
|
||||||
targetObjectMetadata: ObjectMetadataEntity,
|
ObjectMetadataEntity,
|
||||||
|
'nameSingular' | 'labelSingular'
|
||||||
|
>,
|
||||||
|
targetObjectMetadata: Pick<
|
||||||
|
ObjectMetadataEntity,
|
||||||
|
'nameSingular' | 'namePlural'
|
||||||
|
>,
|
||||||
) {
|
) {
|
||||||
const customStandardFieldId =
|
const customStandardFieldId =
|
||||||
// @ts-expect-error legacy noImplicitAny
|
// @ts-expect-error legacy noImplicitAny
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user