Introduce npx nx mock:generate twenty-front (#18237)

## Add codegen script for frontend test mock data

### Summary

- Adds a new `npx nx mock:generate twenty-front` and
`generate-mock-data.ts` script that fetches object metadata from a
running server's `/metadata` endpoint, authenticates with default seeds,
and writes the result to a generated TypeScript file
(`src/testing/mock-data/generated/mock-metadata-query-result.ts`). This
replaces hand-maintained mock metadata with server-sourced data,
ensuring tests always reflect the real schema.
- Updates all frontend tests to be compatible with the newly generated
metadata, fixing hard-coded GraphQL queries, Zod validation schemas,
snapshot expectations, and Apollo mock mismatches.

### What changed

**New files**
- `scripts/generate-mock-data.ts` — codegen script that authenticates
against the server, queries `/metadata` for all object metadata (with
explicit `__typename` at every level), and writes a typed `.ts` file.
- `project.json` — added `mock:generate` Nx target (`dotenv npx tsx
scripts/generate-mock-data.ts`).

**Schema validation updates**
- `objectMetadataItemSchema.ts` — added `universalIdentifier`, made
`duplicateCriteria` nullable.
- `fieldMetadataItemSchema.ts` — added `universalIdentifier`, `morphId`,
`morphRelations`, restructured relation schema.
- `indexMetadataItemSchema.ts` — added optional `isCustom` field.
This commit is contained in:
Charles Bochet
2026-02-25 21:40:05 +01:00
committed by GitHub
parent 50be97422d
commit e01b641a05
25 changed files with 24997 additions and 18246 deletions
+7
View File
@@ -255,6 +255,13 @@
}
}
},
"mock:generate": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"command": "dotenv npx tsx scripts/generate-mock-data.ts"
}
},
"chromatic": {
"configurations": {
"ci": {}
@@ -0,0 +1,284 @@
/* eslint-disable no-console */
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const SERVER_BASE_URL =
process.env.REACT_APP_SERVER_BASE_URL ?? 'http://localhost:3000';
const AUTH_EMAIL = 'tim@apple.dev';
const AUTH_PASSWORD = 'tim@apple.dev';
const WORKSPACE_SUBDOMAIN = 'apple';
const serverUrl = new URL(SERVER_BASE_URL);
const WORKSPACE_ORIGIN = `${serverUrl.protocol}//${WORKSPACE_SUBDOMAIN}.${serverUrl.host}`;
const currentDir = path.dirname(fileURLToPath(import.meta.url));
const OUTPUT_DIR = path.resolve(
currentDir,
'../src/testing/mock-data/generated',
);
const graphqlRequest = async (
endpoint: string,
query: string,
token?: string,
): Promise<unknown> => {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Origin: WORKSPACE_ORIGIN,
};
if (token !== undefined) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${SERVER_BASE_URL}${endpoint}`, {
method: 'POST',
headers,
body: JSON.stringify({ query }),
});
const json = (await response.json()) as {
data?: unknown;
errors?: { message: string }[];
};
if (
json.errors !== undefined &&
json.errors !== null &&
json.errors.length > 0
) {
const errorDetails = json.errors.map((error) => error.message).join(', ');
throw new Error(`GraphQL error on ${endpoint}: ${errorDetails}`);
}
return json.data;
};
const authenticate = async (): Promise<string> => {
console.log(
`Authenticating as ${AUTH_EMAIL} on workspace ${WORKSPACE_SUBDOMAIN}...`,
);
const loginData = (await graphqlRequest(
'/metadata',
`mutation GetLoginTokenFromCredentials {
getLoginTokenFromCredentials(
email: "${AUTH_EMAIL}",
password: "${AUTH_PASSWORD}",
origin: "${WORKSPACE_ORIGIN}"
) {
loginToken { token }
}
}`,
)) as {
getLoginTokenFromCredentials: { loginToken: { token: string } };
};
const loginToken = loginData.getLoginTokenFromCredentials.loginToken.token;
const authData = (await graphqlRequest(
'/metadata',
`mutation GetAuthTokensFromLoginToken {
getAuthTokensFromLoginToken(
loginToken: "${loginToken}",
origin: "${WORKSPACE_ORIGIN}"
) {
tokens {
accessOrWorkspaceAgnosticToken { token }
}
}
}`,
)) as {
getAuthTokensFromLoginToken: {
tokens: { accessOrWorkspaceAgnosticToken: { token: string } };
};
};
const accessToken =
authData.getAuthTokensFromLoginToken.tokens.accessOrWorkspaceAgnosticToken
.token;
console.log('Authenticated successfully.');
return accessToken;
};
// Apollo Client automatically adds __typename to every object level;
// raw fetch does not, so we include it explicitly here.
const METADATA_QUERY = `
query ObjectMetadataItems {
objects(paging: { first: 1000 }) {
__typename
edges {
__typename
node {
__typename
id
universalIdentifier
nameSingular
namePlural
labelSingular
labelPlural
description
icon
isCustom
isRemote
isActive
isSystem
isUIReadOnly
createdAt
updatedAt
labelIdentifierFieldMetadataId
imageIdentifierFieldMetadataId
applicationId
shortcut
isLabelSyncedWithName
isSearchable
duplicateCriteria
indexMetadataList {
__typename
id
createdAt
updatedAt
name
indexWhereClause
indexType
isUnique
isCustom
indexFieldMetadataList {
__typename
id
fieldMetadataId
createdAt
updatedAt
order
}
}
fieldsList {
__typename
id
universalIdentifier
type
name
label
description
icon
isCustom
isActive
isSystem
isUIReadOnly
isNullable
isUnique
createdAt
updatedAt
defaultValue
options
settings
isLabelSyncedWithName
morphId
applicationId
relation {
__typename
type
sourceObjectMetadata {
__typename
id
nameSingular
namePlural
}
targetObjectMetadata {
__typename
id
nameSingular
namePlural
}
sourceFieldMetadata {
__typename
id
name
}
targetFieldMetadata {
__typename
id
name
}
}
morphRelations {
__typename
type
sourceObjectMetadata {
__typename
id
nameSingular
namePlural
}
targetObjectMetadata {
__typename
id
nameSingular
namePlural
}
sourceFieldMetadata {
__typename
id
name
}
targetFieldMetadata {
__typename
id
name
}
}
}
}
}
pageInfo {
__typename
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`;
const main = async () => {
console.log(`Server: ${SERVER_BASE_URL}`);
console.log(`Output: ${OUTPUT_DIR}`);
console.log('');
const token = await authenticate();
console.log('Fetching object metadata from /metadata ...');
const metadata = await graphqlRequest('/metadata', METADATA_QUERY, token);
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
const filePath = path.join(OUTPUT_DIR, 'mock-metadata-query-result.ts');
const content = [
'/* eslint-disable */',
'// @ts-nocheck',
"import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';",
'',
'// This file was automatically generated by scripts/generate-mock-data.ts',
'// Do not edit this file manually.',
'',
'// prettier-ignore',
'export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =',
JSON.stringify(metadata, null, 2) + ';',
'',
].join('\n');
fs.writeFileSync(filePath, content, 'utf-8');
console.log(`Written: ${filePath}`);
console.log('Done!');
};
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});
@@ -5527,7 +5527,7 @@ export type UpdateOneApplicationVariableMutationVariables = Exact<{
export type UpdateOneApplicationVariableMutation = { __typename?: 'Mutation', updateOneApplicationVariable: boolean };
export type ApplicationFieldsFragment = { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, defaultRoleId?: string | null, availablePackages: any, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string }> };
export type ApplicationFieldsFragment = { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, defaultRoleId?: string | null, availablePackages: any, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, universalIdentifier: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string }> };
export type FindManyApplicationsQueryVariables = Exact<{ [key: string]: never; }>;
@@ -5539,7 +5539,7 @@ export type FindOneApplicationQueryVariables = Exact<{
}>;
export type FindOneApplicationQuery = { __typename?: 'Query', findOneApplication: { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, defaultRoleId?: string | null, availablePackages: any, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string }> } };
export type FindOneApplicationQuery = { __typename?: 'Query', findOneApplication: { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, defaultRoleId?: string | null, availablePackages: any, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, universalIdentifier: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string }> } };
export type UploadFileMutationVariables = Exact<{
file: Scalars['Upload'];
@@ -6008,7 +6008,7 @@ export type FindOneNavigationMenuItemQueryVariables = Exact<{
export type FindOneNavigationMenuItemQuery = { __typename?: 'Query', navigationMenuItem?: { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string, targetRecordIdentifier?: { __typename?: 'RecordIdentifier', id: string, labelIdentifier: string, imageIdentifier?: string | null } | null } | null };
export type ObjectMetadataFieldsFragment = { __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> };
export type ObjectMetadataFieldsFragment = { __typename?: 'Object', id: string, universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, universalIdentifier: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> };
export type CreateOneObjectMetadataItemMutationVariables = Exact<{
input: CreateOneObjectInput;
@@ -6057,7 +6057,7 @@ export type DeleteOneFieldMetadataItemMutation = { __typename?: 'Mutation', dele
export type ObjectMetadataItemsQueryVariables = Exact<{ [key: string]: never; }>;
export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', edges: Array<{ __typename?: 'ObjectEdge', node: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } };
export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', edges: Array<{ __typename?: 'ObjectEdge', node: { __typename?: 'Object', id: string, universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, universalIdentifier: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } };
export type ObjectRecordCountsQueryVariables = Exact<{ [key: string]: never; }>;
@@ -7034,6 +7034,7 @@ export const AgentFieldsFragmentDoc = gql`
export const ObjectMetadataFieldsFragmentDoc = gql`
fragment ObjectMetadataFields on Object {
id
universalIdentifier
nameSingular
namePlural
labelSingular
@@ -7073,6 +7074,7 @@ export const ObjectMetadataFieldsFragmentDoc = gql`
}
fieldsList {
id
universalIdentifier
type
name
label
@@ -21,10 +21,10 @@ const taskTarget = {
id: '89bb825c-171e-4bcc-9cf7-43448d6fb300',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
companyId: null,
company: null,
personId: '89bb825c-171e-4bcc-9cf7-43448d6fb280',
person: {
targetCompanyId: null,
targetCompany: null,
targetPersonId: '89bb825c-171e-4bcc-9cf7-43448d6fb280',
targetPerson: {
id: '89bb825c-171e-4bcc-9cf7-43448d6fb280',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
@@ -58,21 +58,24 @@ cache.writeFragment({
__typename
updatedAt
createdAt
personId
targetPersonId
taskId
companyId
targetCompanyId
id
task {
__typename
createdAt
title
updatedAt
body
bodyV2 {
blocknote
markdown
}
dueAt
id
assigneeId
}
person {
targetPerson {
__typename
id
createdAt
@@ -83,7 +86,7 @@ cache.writeFragment({
lastName
}
}
company {
targetCompany {
__typename
id
createdAt
@@ -164,7 +167,7 @@ describe('useActivityTargetObjectRecords', () => {
expect(activityTargetObjectRecords).toHaveLength(1);
expect(activityTargetObjectRecords[0].activityTarget).toEqual(taskTarget);
expect(activityTargetObjectRecords[0].targetObject).toEqual(
taskTarget.person,
taskTarget.targetPerson,
);
expect(
activityTargetObjectRecords[0].targetObjectMetadataItem.nameSingular,
@@ -1,11 +1,14 @@
import { type MockedResponse } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react';
import gql from 'graphql-tag';
import { createOneActivityOperationSignatureFactory } from '@/activities/graphql/operation-signatures/factories/createOneActivityOperationSignatureFactory';
import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { generateCreateOneRecordMutation } from '@/object-metadata/utils/generateCreateOneRecordMutation';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { mockedTasks } from '~/testing/mock-data/tasks';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
const mockedDate = '2024-03-15T12:00:00.000Z';
const toISOStringMock = jest.fn(() => mockedDate);
@@ -21,74 +24,41 @@ const mockedActivity = {
updatedAt: mockedDate,
};
const taskMetadataItem = getMockObjectMetadataItemOrThrow('task');
const operationSignature = createOneActivityOperationSignatureFactory({
objectNameSingular: CoreObjectNameSingular.Task,
});
const createOneTaskMutation = generateCreateOneRecordMutation({
objectMetadataItem: taskMetadataItem,
objectMetadataItems: generatedMockObjectMetadataItems,
recordGqlFields: operationSignature.fields,
objectPermissionsByObjectMetadataId: {},
});
const mockResult = jest.fn(() => ({
data: {
createTask: {
...mockedActivity,
__typename: 'Activity',
assigneeId: '',
authorId: '1',
reminderAt: null,
createdAt: mockedDate,
},
},
}));
const mocks: MockedResponse[] = [
{
request: {
query: gql`
mutation CreateOneTask($input: TaskCreateInput!) {
createTask(data: $input) {
__typename
assignee {
__typename
id
name {
firstName
lastName
}
}
assigneeId
attachments {
edges {
node {
__typename
authorId
companyId
createdAt
deletedAt
fullPath
id
name
noteId
opportunityId
personId
petId
rocketId
surveyResultId
taskId
type
updatedAt
}
}
}
bodyV2 {
blocknote
markdown
}
createdAt
dueAt
id
status
title
updatedAt
}
}
`,
query: createOneTaskMutation,
variables: {
input: mockedActivity,
},
},
result: jest.fn(() => ({
data: {
createTask: {
...mockedActivity,
__typename: 'Activity',
assigneeId: '',
authorId: '1',
reminderAt: null,
createdAt: mockedDate,
},
},
})),
result: mockResult,
},
];
@@ -114,6 +84,6 @@ describe('useCreateActivityInDB', () => {
});
});
expect(mocks[0].result).toHaveBeenCalled();
expect(mockResult).toHaveBeenCalled();
});
});
@@ -8,7 +8,7 @@ import { ToolStepRenderer } from '@/ai/components/ToolStepRenderer';
import { groupContiguousThinkingStepParts } from '@/ai/utils/groupContiguousThinkingStepParts';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { isStaticToolUIPart } from 'ai';
import { isToolUIPart, type ToolUIPart } from 'ai';
import { type ExtendedUIMessagePart } from 'twenty-shared/ai';
const StyledMessagePartsContainer = styled.div`
@@ -67,8 +67,13 @@ const MessagePartRenderer = ({
/>
);
default:
if (isStaticToolUIPart(part) === true) {
return <ToolStepRenderer toolPart={part} isStreaming={isStreaming} />;
if (isToolUIPart(part) === true && part.type !== 'dynamic-tool') {
return (
<ToolStepRenderer
toolPart={part as ToolUIPart}
isStreaming={isStreaming}
/>
);
}
return null;
}
@@ -1,4 +1,4 @@
import { isStaticToolUIPart } from 'ai';
import { isToolUIPart } from 'ai';
import { type ExtendedUIMessagePart } from 'twenty-shared/ai';
import { type ThinkingStepPart } from '@/ai/utils/thinkingStepPart';
@@ -10,5 +10,5 @@ export const isThinkingStepPart = (
return true;
}
return isStaticToolUIPart(part) && part.type !== 'tool-code_interpreter';
return isToolUIPart(part) && part.type !== 'tool-code_interpreter';
};
@@ -1,7 +1,10 @@
import { renderHook, waitFor } from '@testing-library/react';
import { fetchAllThreadMessagesOperationSignatureFactory } from '@/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory';
import { useEmailThreadInCommandMenu } from '@/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
import gql from 'graphql-tag';
import {
QUERY_DEFAULT_LIMIT_RECORDS,
@@ -10,7 +13,39 @@ import {
import { MessageParticipantRole } from 'twenty-shared/types';
import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { useEmailThreadInCommandMenu } from '@/command-menu/pages/message-thread/hooks/useEmailThreadInCommandMenu';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
const messageMetadataItem = getMockObjectMetadataItemOrThrow('message');
const messageParticipantMetadataItem =
getMockObjectMetadataItemOrThrow('messageParticipant');
const messageOperationSignature =
fetchAllThreadMessagesOperationSignatureFactory({
messageThreadId: '1',
});
const findManyMessagesQuery = generateFindManyRecordsQuery({
objectMetadataItem: messageMetadataItem,
objectMetadataItems: generatedMockObjectMetadataItems,
recordGqlFields: messageOperationSignature.fields,
objectPermissionsByObjectMetadataId: {},
});
const findManyMessageParticipantsQuery = generateFindManyRecordsQuery({
objectMetadataItem: messageParticipantMetadataItem,
objectMetadataItems: generatedMockObjectMetadataItems,
recordGqlFields: {
id: true,
role: true,
displayName: true,
messageId: true,
handle: true,
person: true,
workspaceMember: true,
},
objectPermissionsByObjectMetadataId: {},
});
const mocks = [
{
@@ -36,129 +71,7 @@ const mocks = [
},
{
request: {
query: gql`
query FindManyMessages(
$filter: MessageFilterInput
$orderBy: [MessageOrderByInput]
$lastCursor: String
$limit: Int
$offset: Int
) {
messages(
filter: $filter
orderBy: $orderBy
first: $limit
after: $lastCursor
offset: $offset
) {
edges {
node {
__typename
createdAt
headerMessageId
id
messageParticipants {
edges {
node {
__typename
displayName
handle
id
person {
__typename
avatarUrl
city
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
role
workspaceMember {
__typename
avatarUrl
colorScheme
createdAt
dateFormat
deletedAt
id
locale
name {
firstName
lastName
}
position
timeFormat
timeZone
updatedAt
userEmail
userId
}
}
}
}
messageThread {
__typename
id
}
receivedAt
subject
text
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
`,
query: findManyMessagesQuery,
variables: {
filter: { messageThreadId: { eq: '1' } },
orderBy: [{ receivedAt: 'AscNullsLast' }],
@@ -206,113 +119,7 @@ const mocks = [
},
{
request: {
query: gql`
query FindManyMessageParticipants(
$filter: MessageParticipantFilterInput
$orderBy: [MessageParticipantOrderByInput]
$lastCursor: String
$limit: Int
$offset: Int
) {
messageParticipants(
filter: $filter
orderBy: $orderBy
first: $limit
after: $lastCursor
offset: $offset
) {
edges {
node {
__typename
displayName
handle
id
messageId
person {
__typename
avatarUrl
city
companyId
createdAt
createdBy {
source
workspaceMemberId
name
context
}
deletedAt
emails {
primaryEmail
additionalEmails
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
name {
firstName
lastName
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
position
updatedAt
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
}
workPreference
xLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
}
role
workspaceMember {
__typename
avatarUrl
colorScheme
createdAt
dateFormat
deletedAt
id
locale
name {
firstName
lastName
}
position
timeFormat
timeZone
updatedAt
userEmail
userId
}
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
`,
query: findManyMessageParticipantsQuery,
variables: {
filter: {
messageId: { in: ['1', '2'] },
@@ -66,30 +66,30 @@ export const initialFavorites: Favorite[] = [
export const sortedFavorites = [
{
id: '1',
recordId: '1',
position: 0,
avatarType: 'rounded',
avatarUrl: '',
labelIdentifier: ' ',
link: '/object/person/1',
objectNameSingular: 'person',
forWorkspaceMemberId: '1',
favoriteFolderId: '1',
__typename: 'Favorite',
avatarType: 'squared',
avatarUrl: undefined,
favoriteFolderId: '1',
forWorkspaceMemberId: '1',
id: '1',
labelIdentifier: 'ABC Corp',
link: '/object/company/2',
objectNameSingular: 'company',
position: 0,
recordId: '2',
},
{
id: '2',
recordId: '3',
position: 1,
avatarType: 'rounded',
avatarUrl: '',
labelIdentifier: ' ',
link: '/object/person/3',
objectNameSingular: 'person',
forWorkspaceMemberId: '1',
favoriteFolderId: '1',
__typename: 'Favorite',
avatarType: 'squared',
avatarUrl: undefined,
favoriteFolderId: '1',
forWorkspaceMemberId: '1',
id: '2',
labelIdentifier: 'Company Test',
link: '/object/company/4',
objectNameSingular: 'company',
position: 1,
recordId: '4',
},
{
__typename: 'Favorite',
@@ -90,6 +90,6 @@ describe('useColumnDefinitionsFromObjectMetadata', () => {
const { columnDefinitions } = result.current;
expect(columnDefinitions.length).toBe(22);
expect(columnDefinitions.length).toBe(25);
});
});
@@ -36,12 +36,12 @@ describe('useGetObjectRecordIdentifierByNameSingular', () => {
wrapper: Wrapper,
initialProps: {
record: { id: 'recordId' } as any,
objectNameSingular: 'viewSort',
objectNameSingular: 'blocklist',
},
},
);
expect(result.current.linkToShowPage).toBe('/object/viewSort/recordId');
expect(result.current.linkToShowPage).toBe('/object/blocklist/recordId');
rerender({
record: { id: 'recordId', avatarUrl: 'https://fake-url.com' },
@@ -25,10 +25,8 @@ export const mapPaginatedObjectMetadataItemsToObjectMetadataItems = ({
object.node;
return {
universalIdentifier: object.node.id,
...objectWithoutFieldsList,
fields: fieldsList.map((field) => ({
universalIdentifier: field.id,
...field,
})),
labelIdentifierFieldMetadataId,
@@ -6,6 +6,33 @@ import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
import { camelCaseStringSchema } from '~/utils/validation-schemas/camelCaseStringSchema';
export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
const relationObjectSchema = z.object({
__typename: z.literal('Relation').optional(),
type: z.enum(RelationType),
sourceFieldMetadata: z.object({
__typename: z.literal('Field').optional(),
id: z.uuid(),
name: z.string().trim().min(1),
}),
sourceObjectMetadata: z.object({
__typename: z.literal('Object').optional(),
id: z.uuid(),
namePlural: z.string().trim().min(1),
nameSingular: z.string().trim().min(1),
}),
targetFieldMetadata: z.object({
__typename: z.literal('Field').optional(),
id: z.uuid(),
name: z.string().trim().min(1),
}),
targetObjectMetadata: z.object({
__typename: z.literal('Object').optional(),
id: z.uuid(),
namePlural: z.string().trim().min(1),
nameSingular: z.string().trim().min(1),
}),
});
return z.object({
__typename: z.literal('Field').optional(),
createdAt: z.iso.datetime(),
@@ -16,6 +43,7 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
.nullable()
.optional(),
id: z.uuid(),
universalIdentifier: z.string(),
applicationId: z.uuid(),
isActive: z.boolean(),
isCustom: z.boolean(),
@@ -25,6 +53,8 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
isUIReadOnly: z.boolean(),
label: metadataLabelSchema(existingLabels),
isLabelSyncedWithName: z.boolean(),
morphId: z.string().nullable().optional(),
morphRelations: z.array(relationObjectSchema).nullable().optional(),
name: camelCaseStringSchema,
options: z
.array(
@@ -39,35 +69,7 @@ export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
.nullable()
.optional(),
settings: z.any().optional(),
relation: z
.object({
__typename: z.literal('Relation').optional(),
type: z.enum(RelationType),
sourceFieldMetadata: z.object({
__typename: z.literal('Field').optional(),
id: z.uuid(),
name: z.string().trim().min(1),
}),
sourceObjectMetadata: z.object({
__typename: z.literal('Object').optional(),
id: z.uuid(),
namePlural: z.string().trim().min(1),
nameSingular: z.string().trim().min(1),
}),
targetFieldMetadata: z.object({
__typename: z.literal('Field').optional(),
id: z.uuid(),
name: z.string().trim().min(1),
}),
targetObjectMetadata: z.object({
__typename: z.literal('Object').optional(),
id: z.uuid(),
namePlural: z.string().trim().min(1),
nameSingular: z.string().trim().min(1),
}),
})
.nullable()
.optional(),
relation: relationObjectSchema.nullable().optional(),
type: z.enum(FieldMetadataType),
updatedAt: z.iso.datetime(),
});
@@ -14,5 +14,6 @@ export const indexMetadataItemSchema = z.object({
indexType: z.enum(IndexType),
indexWhereClause: z.string().nullable(),
isUnique: z.boolean(),
isCustom: z.boolean().nullable().optional(),
objectMetadata: z.any(),
}) satisfies z.ZodType<IndexMetadataItem>;
@@ -16,7 +16,8 @@ export const objectMetadataItemSchema = z.object({
icon: z.string().startsWith('Icon').trim(),
applicationId: z.uuid(),
id: z.uuid(),
duplicateCriteria: z.array(z.array(z.string())),
universalIdentifier: z.string(),
duplicateCriteria: z.array(z.array(z.string())).nullable(),
imageIdentifierFieldMetadataId: z.uuid().nullable(),
isActive: z.boolean(),
isCustom: z.boolean(),
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Note with loadRelations="activity" 1`] = `
{
@@ -12,13 +12,8 @@ exports[`generateActivityTargetGqlFields snapshot tests should match snapshot fo
exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Note with loadRelations="both" 1`] = `
{
"company": {
"domainName": true,
"id": true,
"name": true,
},
"companyId": true,
"createdAt": true,
"createdBy": true,
"deletedAt": true,
"id": true,
"note": {
@@ -26,54 +21,103 @@ exports[`generateActivityTargetGqlFields snapshot tests should match snapshot fo
"title": true,
},
"noteId": true,
"opportunity": {
"position": true,
"searchVector": true,
"targetCompany": {
"id": true,
"name": true,
},
"opportunityId": true,
"person": {
"avatarUrl": true,
"targetCompanyId": true,
"targetEmploymentHistory": {
"id": true,
"name": true,
},
"personId": true,
"pet": {
"targetEmploymentHistoryId": true,
"targetOpportunity": {
"id": true,
"name": true,
},
"petId": true,
"rocket": {
"targetOpportunityId": true,
"targetPerson": {
"id": true,
"name": true,
},
"rocketId": true,
"surveyResult": {
"targetPersonId": true,
"targetPet": {
"id": true,
"name": true,
},
"surveyResultId": true,
"targetPetCareAgreement": {
"id": true,
"name": true,
},
"targetPetCareAgreementId": true,
"targetPetId": true,
"targetRocket": {
"id": true,
"name": true,
},
"targetRocketId": true,
"targetSurveyResult": {
"id": true,
"name": true,
},
"targetSurveyResultId": true,
"updatedAt": true,
"updatedBy": true,
}
`;
exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Note with loadRelations="relations" 1`] = `
{
"company": true,
"companyId": true,
"createdAt": true,
"createdBy": true,
"deletedAt": true,
"id": true,
"opportunity": true,
"opportunityId": true,
"person": true,
"personId": true,
"pet": true,
"petId": true,
"rocket": true,
"rocketId": true,
"surveyResult": true,
"surveyResultId": true,
"position": true,
"searchVector": true,
"targetCompany": {
"id": true,
"name": true,
},
"targetCompanyId": true,
"targetEmploymentHistory": {
"id": true,
"name": true,
},
"targetEmploymentHistoryId": true,
"targetOpportunity": {
"id": true,
"name": true,
},
"targetOpportunityId": true,
"targetPerson": {
"id": true,
"name": true,
},
"targetPersonId": true,
"targetPet": {
"id": true,
"name": true,
},
"targetPetCareAgreement": {
"id": true,
"name": true,
},
"targetPetCareAgreementId": true,
"targetPetId": true,
"targetRocket": {
"id": true,
"name": true,
},
"targetRocketId": true,
"targetSurveyResult": {
"id": true,
"name": true,
},
"targetSurveyResultId": true,
"updatedAt": true,
"updatedBy": true,
}
`;
@@ -89,67 +133,111 @@ exports[`generateActivityTargetGqlFields snapshot tests should match snapshot fo
exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Task with loadRelations="both" 1`] = `
{
"company": {
"domainName": true,
"id": true,
"name": true,
},
"companyId": true,
"createdAt": true,
"createdBy": true,
"deletedAt": true,
"id": true,
"opportunity": {
"position": true,
"searchVector": true,
"targetCompany": {
"id": true,
"name": true,
},
"opportunityId": true,
"person": {
"avatarUrl": true,
"targetCompanyId": true,
"targetEmploymentHistory": {
"id": true,
"name": true,
},
"personId": true,
"pet": {
"targetEmploymentHistoryId": true,
"targetOpportunity": {
"id": true,
"name": true,
},
"petId": true,
"rocket": {
"targetOpportunityId": true,
"targetPerson": {
"id": true,
"name": true,
},
"rocketId": true,
"surveyResult": {
"targetPersonId": true,
"targetPet": {
"id": true,
"name": true,
},
"surveyResultId": true,
"targetPetCareAgreement": {
"id": true,
"name": true,
},
"targetPetCareAgreementId": true,
"targetPetId": true,
"targetRocket": {
"id": true,
"name": true,
},
"targetRocketId": true,
"targetSurveyResult": {
"id": true,
"name": true,
},
"targetSurveyResultId": true,
"task": {
"id": true,
"title": true,
},
"taskId": true,
"updatedAt": true,
"updatedBy": true,
}
`;
exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Task with loadRelations="relations" 1`] = `
{
"company": true,
"companyId": true,
"createdAt": true,
"createdBy": true,
"deletedAt": true,
"id": true,
"opportunity": true,
"opportunityId": true,
"person": true,
"personId": true,
"pet": true,
"petId": true,
"rocket": true,
"rocketId": true,
"surveyResult": true,
"surveyResultId": true,
"position": true,
"searchVector": true,
"targetCompany": {
"id": true,
"name": true,
},
"targetCompanyId": true,
"targetEmploymentHistory": {
"id": true,
"name": true,
},
"targetEmploymentHistoryId": true,
"targetOpportunity": {
"id": true,
"name": true,
},
"targetOpportunityId": true,
"targetPerson": {
"id": true,
"name": true,
},
"targetPersonId": true,
"targetPet": {
"id": true,
"name": true,
},
"targetPetCareAgreement": {
"id": true,
"name": true,
},
"targetPetCareAgreementId": true,
"targetPetId": true,
"targetRocket": {
"id": true,
"name": true,
},
"targetRocketId": true,
"targetSurveyResult": {
"id": true,
"name": true,
},
"targetSurveyResultId": true,
"updatedAt": true,
"updatedBy": true,
}
`;
@@ -1,9 +1,8 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`generateDepthRecordGqlFieldsFromObject should generate depth one record gql fields from object 1`] = `
{
"accountOwner": {
"avatarUrl": true,
"id": true,
"name": true,
},
@@ -14,6 +13,13 @@ exports[`generateDepthRecordGqlFieldsFromObject should generate depth one record
"id": true,
"name": true,
},
"caredForPets": {
"id": true,
"pet": {
"id": true,
"name": true,
},
},
"createdAt": true,
"createdBy": true,
"deletedAt": true,
@@ -44,6 +50,14 @@ exports[`generateDepthRecordGqlFieldsFromObject should generate depth one record
"name": true,
},
"position": true,
"previousEmployees": {
"id": true,
"person": {
"avatarUrl": true,
"id": true,
"name": true,
},
},
"searchVector": true,
"tagline": true,
"taskTargets": {
@@ -55,8 +69,10 @@ exports[`generateDepthRecordGqlFieldsFromObject should generate depth one record
},
"timelineActivities": {
"id": true,
"name": true,
},
"updatedAt": true,
"updatedBy": true,
"visaSponsorship": true,
"workPolicy": true,
"xLink": true,
@@ -82,6 +98,7 @@ exports[`generateDepthRecordGqlFieldsFromObject should generate depth zero recor
"searchVector": true,
"tagline": true,
"updatedAt": true,
"updatedBy": true,
"visaSponsorship": true,
"workPolicy": true,
"xLink": true,
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`generateDepthRecordGqlFieldsFromRecord should generate depth one record gql fields from empty record 1`] = `
{
@@ -7,6 +7,7 @@ exports[`generateDepthRecordGqlFieldsFromRecord should generate depth one record
"address": false,
"annualRecurringRevenue": false,
"attachments": false,
"caredForPets": false,
"createdAt": false,
"createdBy": false,
"deletedAt": false,
@@ -22,11 +23,13 @@ exports[`generateDepthRecordGqlFieldsFromRecord should generate depth one record
"opportunities": false,
"people": false,
"position": false,
"previousEmployees": false,
"searchVector": false,
"tagline": false,
"taskTargets": false,
"timelineActivities": false,
"updatedAt": false,
"updatedBy": false,
"visaSponsorship": false,
"workPolicy": false,
"xLink": false,
@@ -40,6 +43,7 @@ exports[`generateDepthRecordGqlFieldsFromRecord should generate depth one record
"address": false,
"annualRecurringRevenue": false,
"attachments": false,
"caredForPets": false,
"createdAt": false,
"createdBy": false,
"deletedAt": false,
@@ -55,11 +59,13 @@ exports[`generateDepthRecordGqlFieldsFromRecord should generate depth one record
"opportunities": false,
"people": false,
"position": false,
"previousEmployees": false,
"searchVector": false,
"tagline": false,
"taskTargets": false,
"timelineActivities": false,
"updatedAt": false,
"updatedBy": false,
"visaSponsorship": false,
"workPolicy": false,
"xLink": false,
@@ -85,6 +91,7 @@ exports[`generateDepthRecordGqlFieldsFromRecord should generate depth zero recor
"searchVector": false,
"tagline": false,
"updatedAt": false,
"updatedBy": false,
"visaSponsorship": false,
"workPolicy": false,
"xLink": false,
@@ -1,5 +1,11 @@
export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = `
__typename
avatarFile {
fileId
label
extension
url
}
avatarUrl
city
companyId
@@ -36,6 +42,12 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = `
}
position
updatedAt
updatedBy {
source
workspaceMemberId
name
context
}
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
@@ -61,6 +73,12 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
}
}
}
avatarFile {
fileId
label
extension
url
}
avatarUrl
calendarEventParticipants {
edges {
@@ -71,6 +89,19 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
}
}
}
caredForPets {
edges {
node {
__typename
id
pet {
__typename
id
name
}
}
}
}
city
company {
__typename
@@ -154,6 +185,24 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
}
}
position
previousCompanies {
edges {
node {
__typename
company {
__typename
domainName {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
}
id
name
}
id
}
}
}
taskTargets {
edges {
node {
@@ -172,10 +221,17 @@ export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = `
node {
__typename
id
name
}
}
}
updatedAt
updatedBy {
source
workspaceMemberId
name
context
}
whatsapp {
primaryPhoneNumber
primaryPhoneCountryCode
@@ -32,7 +32,7 @@ import { getFieldInputEventContextProviderWithJestMocks } from './utils/getField
const RelationWorkspaceSetterEffect = () => {
const setRecordFieldInputLayoutDirectionLoading = useSetAtomComponentState(
recordFieldInputLayoutDirectionLoadingComponentState,
'relation-to-one-field-input-123-Relation',
'relation-to-one-field-input-123-company',
);
useEffect(() => {
@@ -75,18 +75,18 @@ const RelationManyToOneFieldInputWithContext = ({
<FieldContext.Provider
value={{
fieldDefinition: {
fieldMetadataId: 'e82262eb-7f58-4167-a23c-fc51ec584d1b',
label: 'Relation',
fieldMetadataId: 'f6be42ac-ccb8-4df0-8c22-a9627d655c76',
label: 'Company',
type: FieldMetadataType.RELATION,
iconName: 'IconLink',
metadata: {
fieldName: 'Relation',
fieldName: 'company',
relationObjectMetadataNamePlural: 'companies',
relationObjectMetadataNameSingular:
CoreObjectNameSingular.Company,
relationObjectMetadataId: '4a45f524-b8cb-40e8-8450-28e402b442cf',
relationObjectMetadataId: '69e0aa5d-a9c5-486b-a99d-fe191195a19d',
objectMetadataNameSingular: 'person',
relationFieldMetadataId: '3c211c59-02a1-4904-ad0f-5bb30b736461',
relationFieldMetadataId: '94902a74-b8ca-4a56-8573-7469f0b664f6',
},
},
recordId: recordId,
@@ -96,7 +96,7 @@ const RelationManyToOneFieldInputWithContext = ({
>
<RecordFieldComponentInstanceContext.Provider
value={{
instanceId: 'relation-to-one-field-input-123-Relation',
instanceId: 'relation-to-one-field-input-123-company',
}}
>
<FieldInputEventContextProviderWithJestMocks>
@@ -39,17 +39,17 @@ const RelationOneToManyFieldInputWithContext = () => {
const fieldDefinition = useMemo(
() => ({
fieldMetadataId: 'e82262eb-7f58-4167-a23c-fc51ec584d1b',
fieldMetadataId: '94902a74-b8ca-4a56-8573-7469f0b664f6',
label: 'People',
type: FieldMetadataType.RELATION,
iconName: 'IconLink',
metadata: {
fieldName: 'people',
relationType: RelationType.ONE_TO_MANY,
relationObjectMetadataNamePlural: 'companies',
relationObjectMetadataNameSingular: CoreObjectNameSingular.Company,
objectMetadataNameSingular: 'person',
relationFieldMetadataId: '3c211c59-02a1-4904-ad0f-5bb30b736461',
relationObjectMetadataNamePlural: 'people',
relationObjectMetadataNameSingular: CoreObjectNameSingular.Person,
objectMetadataNameSingular: 'company',
relationFieldMetadataId: 'f6be42ac-ccb8-4df0-8c22-a9627d655c76',
},
}),
[],
@@ -21,18 +21,7 @@ const DEFAULT_ACTION = {
objectName: 'person',
objectRecordId: '',
objectRecord: {},
fieldsToUpdate: [
'updatedAt',
'averageEstimatedNumberOfAtomsInTheUniverse',
'comments',
'createdAt',
'deletedAt',
'name',
'participants',
'percentageOfCompletion',
'score',
'shortNotes',
],
fieldsToUpdate: ['city', 'emails', 'jobTitle', 'name', 'phones'],
},
outputSchema: {},
errorHandlingOptions: {
@@ -107,7 +96,7 @@ export const DisabledWithEmptyValues: Story = {
const firstSelectedUpdatableField = await within(
await canvas.findByTestId('workflow-fields-multi-select'),
).findByText('Creation date');
).findByText('City');
await userEvent.click(firstSelectedUpdatableField);
@@ -164,7 +153,7 @@ export const DisabledWithDefaultStaticValues: Story = {
const firstSelectedUpdatableField = await within(
await canvas.findByTestId('workflow-fields-multi-select'),
).findByText('Creation date');
).findByText('City');
await userEvent.click(firstSelectedUpdatableField);
@@ -215,7 +204,7 @@ export const DisabledWithDefaultVariableValues: Story = {
const firstSelectedUpdatableField = await within(
await canvas.findByTestId('workflow-fields-multi-select'),
).findByText('Creation date');
).findByText('City');
await userEvent.click(firstSelectedUpdatableField);
File diff suppressed because it is too large Load Diff
@@ -4,16 +4,6 @@ import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/o
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result';
// TODO: remove once we have a way to generate the mocks against a seeded workspace
const addUniversalIdentifierToField = (
field: Record<string, unknown>,
): FieldMetadataItem => ({
...(field as FieldMetadataItem),
universalIdentifier:
(field as { universalIdentifier?: string }).universalIdentifier ??
(field as { id: string }).id,
});
export const generatedMockObjectMetadataItems: ObjectMetadataItem[] =
mockedStandardObjectMetadataQueryResult.objects.edges.map((edge) => {
const labelIdentifierFieldMetadataId =
@@ -24,20 +14,21 @@ export const generatedMockObjectMetadataItems: ObjectMetadataItem[] =
const { fieldsList, indexMetadataList, ...objectWithoutFieldsList } =
edge.node;
const fields = fieldsList.map(addUniversalIdentifierToField);
const fields = fieldsList.map(
(field) => field as unknown as FieldMetadataItem,
);
return {
...objectWithoutFieldsList,
universalIdentifier:
(objectWithoutFieldsList as { universalIdentifier?: string })
.universalIdentifier ?? objectWithoutFieldsList.id,
fields,
readableFields: fields,
updatableFields: fields,
labelIdentifierFieldMetadataId,
indexMetadatas: indexMetadataList.map((index) => ({
...index,
indexFieldMetadatas: [],
})),
indexMetadatas: indexMetadataList.map(
({ indexFieldMetadataList, ...index }) => ({
...index,
indexFieldMetadatas: indexFieldMetadataList,
}),
),
};
});
+1
View File
@@ -40,6 +40,7 @@
"src/**/*.d.ts",
"src/**/*.ts",
"src/**/*.tsx",
"scripts/**/*.ts",
".storybook/*.ts",
".storybook/*.tsx",
"lingui.config.ts",