Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a800bbc20f | |||
| e53fdf3e30 | |||
| a74d9d45fb | |||
| 26e5433636 | |||
| d11b5af46f | |||
| 3509620c2a | |||
| a0c5e4385a | |||
| 5a1f01df1f | |||
| c4f50e8563 | |||
| fb1b19ce66 | |||
| 8fceadf6ef | |||
| 86b0b3c4ac | |||
| ef1be09a82 | |||
| ce3a7a55be | |||
| eab05aca4a | |||
| b9f33b9d49 | |||
| 505f7c9a38 | |||
| 5caecffda7 |
@@ -1,2 +1,3 @@
|
||||
src/generated
|
||||
src/generated-metadata
|
||||
src/generated-metadata
|
||||
src/testing/mock-data/generated
|
||||
@@ -1,6 +1,10 @@
|
||||
import { setProjectAnnotations } from '@storybook/react-vite';
|
||||
import * as projectAnnotations from './preview';
|
||||
|
||||
// Pre-warm the dynamic import used by WorkflowStepDecorator so the
|
||||
// module is cached before any test runs (avoids flaky timeouts in CI).
|
||||
import('~/testing/utils/generatedMockObjectMetadataItems');
|
||||
|
||||
// This is an important step to apply the right configuration when testing your stories.
|
||||
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
|
||||
setProjectAnnotations([projectAnnotations]);
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"command": "dotenv npx tsx scripts/generate-mock-data.ts"
|
||||
"command": "dotenv npx vite-node scripts/generate-mock-data.ts"
|
||||
}
|
||||
},
|
||||
"chromatic": {
|
||||
|
||||
@@ -1,281 +1,15 @@
|
||||
/* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
import { generateMetadata } from './mock-data/generate-metadata.js';
|
||||
import { generateRecordData } from './mock-data/generate-record-data.js';
|
||||
import { authenticate } from './mock-data/utils.js';
|
||||
|
||||
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);
|
||||
const metadata = await generateMetadata(token);
|
||||
await generateRecordData(token, metadata);
|
||||
|
||||
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!');
|
||||
console.log('All mock data generated!');
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/* eslint-disable no-console */
|
||||
import { graphqlRequest, writeGeneratedFile } from './utils.js';
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const generateMetadata = async (token: string) => {
|
||||
console.log('Fetching object metadata from /metadata ...');
|
||||
|
||||
const metadata = await graphqlRequest('/metadata', METADATA_QUERY, token);
|
||||
|
||||
writeGeneratedFile(
|
||||
'metadata/objects/mock-objects-metadata.ts',
|
||||
'mockedStandardObjectMetadataQueryResult',
|
||||
'ObjectMetadataItemsQuery',
|
||||
"import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';",
|
||||
metadata,
|
||||
);
|
||||
|
||||
return metadata as {
|
||||
objects: { edges: { node: Record<string, unknown> }[] };
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
/* eslint-disable no-console */
|
||||
import { print } from 'graphql';
|
||||
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
|
||||
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
||||
|
||||
import { graphqlRequest, writeGeneratedFile } from './utils.js';
|
||||
|
||||
const RECORDS_LIMIT = 10;
|
||||
|
||||
// Production query builders omit __typename on connection/edge wrappers
|
||||
// since Apollo Client injects them automatically. Raw fetch needs them explicit.
|
||||
const addTypenamesToSelections = (query: string): string =>
|
||||
query.replace(/\{(?!\s*__typename\b)/g, '{\n__typename');
|
||||
|
||||
const toObjectMetadataItems = (rawMetadata: {
|
||||
objects: { edges: { node: Record<string, unknown> }[] };
|
||||
}): ObjectMetadataItem[] =>
|
||||
rawMetadata.objects.edges.map((edge) => {
|
||||
const { fieldsList, indexMetadataList, ...rest } = edge.node;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
fields: fieldsList,
|
||||
readableFields: fieldsList,
|
||||
updatableFields: fieldsList,
|
||||
indexMetadatas: ((indexMetadataList as unknown[]) ?? []).map(
|
||||
(index: any) => ({
|
||||
...index,
|
||||
indexFieldMetadatas: index.indexFieldMetadataList ?? [],
|
||||
}),
|
||||
),
|
||||
} as unknown as ObjectMetadataItem;
|
||||
});
|
||||
|
||||
export const generateRecordData = async (
|
||||
token: string,
|
||||
rawMetadata: { objects: { edges: { node: Record<string, unknown> }[] } },
|
||||
) => {
|
||||
const objectMetadataItems = toObjectMetadataItems(rawMetadata);
|
||||
|
||||
const companyObject = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'company',
|
||||
);
|
||||
|
||||
if (!companyObject) {
|
||||
throw new Error('Company object metadata not found');
|
||||
}
|
||||
|
||||
const recordGqlFields = generateDepthRecordGqlFieldsFromObject({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem: companyObject,
|
||||
depth: 1,
|
||||
});
|
||||
|
||||
const queryDocument = generateFindManyRecordsQuery({
|
||||
objectMetadataItem: companyObject,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
const query = addTypenamesToSelections(print(queryDocument));
|
||||
|
||||
console.log(
|
||||
`Fetching ${companyObject.namePlural} (limit: ${RECORDS_LIMIT}) from /graphql ...`,
|
||||
);
|
||||
|
||||
const data = (await graphqlRequest('/graphql', query, token, {
|
||||
limit: RECORDS_LIMIT,
|
||||
})) as Record<string, { edges: { node: Record<string, unknown> }[] }>;
|
||||
|
||||
const records = data[companyObject.namePlural].edges.map((edge) => edge.node);
|
||||
|
||||
console.log(` Got ${records.length} ${companyObject.namePlural}.`);
|
||||
|
||||
writeGeneratedFile(
|
||||
'data/companies/mock-companies-data.ts',
|
||||
'mockedCompanyRecords',
|
||||
'Record<string, unknown>[]',
|
||||
'',
|
||||
records,
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
/* 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 = 'jane.austen@apple.dev';
|
||||
const AUTH_PASSWORD = 'tim@apple.dev';
|
||||
const WORKSPACE_SUBDOMAIN = 'apple';
|
||||
|
||||
const serverUrl = new URL(SERVER_BASE_URL);
|
||||
export const WORKSPACE_ORIGIN = `${serverUrl.protocol}//${WORKSPACE_SUBDOMAIN}.${serverUrl.host}`;
|
||||
|
||||
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
export const GENERATED_DIR = path.resolve(
|
||||
currentDir,
|
||||
'../../src/testing/mock-data/generated',
|
||||
);
|
||||
|
||||
export const graphqlRequest = async (
|
||||
endpoint: string,
|
||||
query: string,
|
||||
token?: string,
|
||||
variables?: Record<string, unknown>,
|
||||
): 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, variables }),
|
||||
});
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
export 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;
|
||||
};
|
||||
|
||||
export const writeGeneratedFile = (
|
||||
relativePath: string,
|
||||
exportName: string,
|
||||
typeName: string,
|
||||
typeImport: string,
|
||||
data: unknown,
|
||||
) => {
|
||||
const filePath = path.join(GENERATED_DIR, relativePath);
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
|
||||
const content = [
|
||||
'/* eslint-disable */',
|
||||
'// @ts-nocheck',
|
||||
typeImport,
|
||||
'',
|
||||
'// This file was automatically generated — do not edit manually.',
|
||||
'',
|
||||
'// prettier-ignore',
|
||||
`export const ${exportName}: ${typeName} =`,
|
||||
JSON.stringify(data, null, 2) + ';',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
console.log(`Written: ${filePath}`);
|
||||
};
|
||||
+5
-5
@@ -11,7 +11,7 @@ import {
|
||||
QUERY_MAX_RECORDS,
|
||||
} from 'twenty-shared/constants';
|
||||
import { MessageParticipantRole } from 'twenty-shared/types';
|
||||
import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode';
|
||||
import { generateMockRecordNode } from '~/testing/utils/generateMockRecordNode';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
@@ -84,7 +84,7 @@ const mocks = [
|
||||
messages: {
|
||||
edges: [
|
||||
{
|
||||
node: generateEmptyJestRecordNode({
|
||||
node: generateMockRecordNode({
|
||||
objectNameSingular: 'message',
|
||||
input: {
|
||||
id: '1',
|
||||
@@ -95,7 +95,7 @@ const mocks = [
|
||||
cursor: '1',
|
||||
},
|
||||
{
|
||||
node: generateEmptyJestRecordNode({
|
||||
node: generateMockRecordNode({
|
||||
objectNameSingular: 'message',
|
||||
input: {
|
||||
id: '2',
|
||||
@@ -135,7 +135,7 @@ const mocks = [
|
||||
messageParticipants: {
|
||||
edges: [
|
||||
{
|
||||
node: generateEmptyJestRecordNode({
|
||||
node: generateMockRecordNode({
|
||||
objectNameSingular: 'messageParticipant',
|
||||
input: {
|
||||
id: 'messageParticipant-1',
|
||||
@@ -146,7 +146,7 @@ const mocks = [
|
||||
cursor: '1',
|
||||
},
|
||||
{
|
||||
node: generateEmptyJestRecordNode({
|
||||
node: generateMockRecordNode({
|
||||
objectNameSingular: 'messageParticipant',
|
||||
input: {
|
||||
id: 'messageParticipant-2',
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result';
|
||||
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/metadata/objects/mock-objects-metadata';
|
||||
|
||||
export const query = FIND_MANY_OBJECT_METADATA_ITEMS;
|
||||
|
||||
|
||||
+4
-1
@@ -1,13 +1,16 @@
|
||||
import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useCallback } from 'react';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { useStore } from 'jotai';
|
||||
|
||||
export const useLoadMockedObjectMetadataItems = () => {
|
||||
const store = useStore();
|
||||
const loadMockedObjectMetadataItems = useCallback(async () => {
|
||||
const { generatedMockObjectMetadataItems } = await import(
|
||||
'~/testing/utils/generatedMockObjectMetadataItems'
|
||||
);
|
||||
|
||||
if (
|
||||
!isDeeplyEqual(
|
||||
store.get(objectMetadataItemsState.atom),
|
||||
|
||||
+5
-2
@@ -1,12 +1,15 @@
|
||||
import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
|
||||
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
|
||||
import { isNonCompositeField } from '@/object-record/object-filter-dropdown/utils/isNonCompositeField';
|
||||
import { type ObjectPermissions } from 'twenty-shared/types';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
type ObjectPermissions,
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type MapFieldMetadataToGraphQLQueryArgs = {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`generateActivityTargetGqlFields snapshot tests should match snapshot for Note with loadRelations="activity" 1`] = `
|
||||
{
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import {
|
||||
variables,
|
||||
} from '@/object-record/hooks/__mocks__/useFindOneRecord';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode';
|
||||
import { generateMockRecordNode } from '~/testing/utils/generateMockRecordNode';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
|
||||
const mocks = [
|
||||
@@ -16,7 +16,7 @@ const mocks = [
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
person: generateEmptyJestRecordNode({
|
||||
person: generateMockRecordNode({
|
||||
objectNameSingular: 'person',
|
||||
input: { id: '6205681e-7c11-40b4-9e32-f523dbe54590' },
|
||||
withDepthOneRelation: true,
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import {
|
||||
type RecordUpdateHookParams,
|
||||
} from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useToggleEditOnlyInput } from '@/object-record/record-field/ui/hooks/useToggleEditOnlyInput';
|
||||
import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode';
|
||||
import { generateMockRecordNode } from '~/testing/utils/generateMockRecordNode';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
@@ -39,7 +39,7 @@ const mocks: MockedResponse[] = [
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
updateCompany: {
|
||||
...generateEmptyJestRecordNode({
|
||||
...generateMockRecordNode({
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
input: { id: recordId },
|
||||
withDepthOneRelation: true,
|
||||
|
||||
+22
-5
@@ -27,8 +27,23 @@ import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSe
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { RelationManyToOneFieldInput } from '@/object-record/record-field/ui/meta-types/input/components/RelationManyToOneFieldInput';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
import { getMockFieldMetadataItemOrThrow } from '~/testing/utils/getMockFieldMetadataItemOrThrow';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
import { getFieldInputEventContextProviderWithJestMocks } from './utils/getFieldInputEventContextProviderWithJestMocks';
|
||||
|
||||
const personMetadata = getMockObjectMetadataItemOrThrow('person');
|
||||
const companyMetadata = getMockObjectMetadataItemOrThrow('company');
|
||||
const companyFieldOnPerson = getMockFieldMetadataItemOrThrow({
|
||||
objectMetadataItem: personMetadata,
|
||||
fieldName: 'company',
|
||||
});
|
||||
const peopleFieldOnCompany = getMockFieldMetadataItemOrThrow({
|
||||
objectMetadataItem: companyMetadata,
|
||||
fieldName: 'people',
|
||||
});
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
const RelationWorkspaceSetterEffect = () => {
|
||||
const setRecordFieldInputLayoutDirectionLoading = useSetAtomComponentState(
|
||||
recordFieldInputLayoutDirectionLoadingComponentState,
|
||||
@@ -75,7 +90,7 @@ const RelationManyToOneFieldInputWithContext = ({
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition: {
|
||||
fieldMetadataId: 'f6be42ac-ccb8-4df0-8c22-a9627d655c76',
|
||||
fieldMetadataId: companyFieldOnPerson.id,
|
||||
label: 'Company',
|
||||
type: FieldMetadataType.RELATION,
|
||||
iconName: 'IconLink',
|
||||
@@ -84,9 +99,9 @@ const RelationManyToOneFieldInputWithContext = ({
|
||||
relationObjectMetadataNamePlural: 'companies',
|
||||
relationObjectMetadataNameSingular:
|
||||
CoreObjectNameSingular.Company,
|
||||
relationObjectMetadataId: '69e0aa5d-a9c5-486b-a99d-fe191195a19d',
|
||||
relationObjectMetadataId: companyMetadata.id,
|
||||
objectMetadataNameSingular: 'person',
|
||||
relationFieldMetadataId: '94902a74-b8ca-4a56-8573-7469f0b664f6',
|
||||
relationFieldMetadataId: peopleFieldOnCompany.id,
|
||||
},
|
||||
},
|
||||
recordId: recordId,
|
||||
@@ -154,7 +169,7 @@ export const Submit: Story = {
|
||||
|
||||
expect(handleSubmitMocked).toHaveBeenCalledTimes(0);
|
||||
|
||||
const item = await canvas.findByText('Linkedin', undefined, {
|
||||
const item = await canvas.findByText(companiesMock[0].name, undefined, {
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
@@ -171,7 +186,9 @@ export const Cancel: Story = {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
expect(handleCancelMocked).toHaveBeenCalledTimes(0);
|
||||
await canvas.findByText('Linkedin', undefined, { timeout: 3000 });
|
||||
await canvas.findByText(companiesMock[0].name, undefined, {
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
const emptyDiv = canvas.getByTestId('data-field-input-click-outside-div');
|
||||
|
||||
|
||||
+15
-2
@@ -24,6 +24,19 @@ import { recordStoreFamilySelector } from '@/object-record/record-store/states/s
|
||||
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { RelationType } from '~/generated-metadata/graphql';
|
||||
import { getMockFieldMetadataItemOrThrow } from '~/testing/utils/getMockFieldMetadataItemOrThrow';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
|
||||
const personMetadata = getMockObjectMetadataItemOrThrow('person');
|
||||
const companyMetadata = getMockObjectMetadataItemOrThrow('company');
|
||||
const companyFieldOnPerson = getMockFieldMetadataItemOrThrow({
|
||||
objectMetadataItem: personMetadata,
|
||||
fieldName: 'company',
|
||||
});
|
||||
const peopleFieldOnCompany = getMockFieldMetadataItemOrThrow({
|
||||
objectMetadataItem: companyMetadata,
|
||||
fieldName: 'people',
|
||||
});
|
||||
|
||||
const RelationWorkspaceSetterEffect = () => {
|
||||
useEffect(() => {
|
||||
@@ -39,7 +52,7 @@ const RelationOneToManyFieldInputWithContext = () => {
|
||||
|
||||
const fieldDefinition = useMemo(
|
||||
() => ({
|
||||
fieldMetadataId: '94902a74-b8ca-4a56-8573-7469f0b664f6',
|
||||
fieldMetadataId: peopleFieldOnCompany.id,
|
||||
label: 'People',
|
||||
type: FieldMetadataType.RELATION,
|
||||
iconName: 'IconLink',
|
||||
@@ -49,7 +62,7 @@ const RelationOneToManyFieldInputWithContext = () => {
|
||||
relationObjectMetadataNamePlural: 'people',
|
||||
relationObjectMetadataNameSingular: CoreObjectNameSingular.Person,
|
||||
objectMetadataNameSingular: 'company',
|
||||
relationFieldMetadataId: 'f6be42ac-ccb8-4df0-8c22-a9627d655c76',
|
||||
relationFieldMetadataId: companyFieldOnPerson.id,
|
||||
},
|
||||
}),
|
||||
[],
|
||||
|
||||
+2
-2
@@ -61,7 +61,7 @@ describe('computeViewRecordGqlOperationFilter', () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
name: {
|
||||
ilike: '%Linkedin%',
|
||||
ilike: `%${companiesMock[0].name}%`,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -116,7 +116,7 @@ describe('computeViewRecordGqlOperationFilter', () => {
|
||||
and: [
|
||||
{
|
||||
name: {
|
||||
ilike: '%Linkedin%',
|
||||
ilike: `%${companiesMock[0].name}%`,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
+7
-10
@@ -16,8 +16,8 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte
|
||||
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
|
||||
import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector';
|
||||
import { unselectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/unselectedRowIdsComponentSelector';
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentSelectorCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorCallbackState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { atom, useStore } from 'jotai';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
@@ -42,17 +42,17 @@ export const RecordIndexFiltersToContextStoreEffect = () => {
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const hasUserSelectedAllRowsAtom = useAtomComponentStateCallbackState(
|
||||
const hasUserSelectedAllRows = useAtomComponentStateValue(
|
||||
hasUserSelectedAllRowsComponentState,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const selectedRowIdsAtom = useAtomComponentSelectorCallbackState(
|
||||
const selectedRowIds = useAtomComponentSelectorValue(
|
||||
selectedRowIdsComponentSelector,
|
||||
recordIndexId,
|
||||
);
|
||||
|
||||
const unselectedRowIdsAtom = useAtomComponentSelectorCallbackState(
|
||||
const unselectedRowIds = useAtomComponentSelectorValue(
|
||||
unselectedRowIdsComponentSelector,
|
||||
recordIndexId,
|
||||
);
|
||||
@@ -88,17 +88,14 @@ export const RecordIndexFiltersToContextStoreEffect = () => {
|
||||
anyFieldFilterValue: string;
|
||||
},
|
||||
) => {
|
||||
const hasUserSelectedAllRows = get(hasUserSelectedAllRowsAtom);
|
||||
let newRule: ContextStoreTargetedRecordsRule;
|
||||
|
||||
if (hasUserSelectedAllRows) {
|
||||
const unselectedRowIds = get(unselectedRowIdsAtom);
|
||||
newRule = {
|
||||
mode: 'exclusion',
|
||||
excludedRecordIds: unselectedRowIds,
|
||||
};
|
||||
} else {
|
||||
const selectedRowIds = get(selectedRowIdsAtom);
|
||||
newRule = {
|
||||
mode: 'selection',
|
||||
selectedRecordIds: selectedRowIds,
|
||||
@@ -132,9 +129,9 @@ export const RecordIndexFiltersToContextStoreEffect = () => {
|
||||
},
|
||||
),
|
||||
[
|
||||
hasUserSelectedAllRowsAtom,
|
||||
selectedRowIdsAtom,
|
||||
unselectedRowIdsAtom,
|
||||
hasUserSelectedAllRows,
|
||||
selectedRowIds,
|
||||
unselectedRowIds,
|
||||
contextStoreTargetedRecordsRuleAtom,
|
||||
contextStoreFiltersAtom,
|
||||
contextStoreFilterGroupsAtom,
|
||||
|
||||
+4
@@ -321,6 +321,10 @@ export const useMultipleRecordPickerPerformSearch = () => {
|
||||
},
|
||||
}));
|
||||
|
||||
if (operationSignatures.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
performCombinedFindManyRecords({ operationSignatures }).then(
|
||||
({ result }) => {
|
||||
Object.values(result)
|
||||
|
||||
+7
-4
@@ -10,10 +10,13 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedViewsData } from '~/testing/mock-data/views';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'Modules/ObjectRecord/RecordTable/RecordTable',
|
||||
component: RecordTableWithWrappers,
|
||||
@@ -70,7 +73,7 @@ export const ScrolledLeft: Story = {
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findAllByText('Linkedin', {}, { timeout: 3000 });
|
||||
await canvas.findAllByText(companiesMock[0].name, {}, { timeout: 3000 });
|
||||
|
||||
const scrollWrapper = canvasElement.ownerDocument.body.querySelector(
|
||||
'.scroll-wrapper-x-enabled',
|
||||
@@ -88,7 +91,7 @@ export const ScrolledLeft: Story = {
|
||||
},
|
||||
});
|
||||
|
||||
await canvas.findByText('Facebook');
|
||||
await canvas.findByText(companiesMock[1].name);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -100,7 +103,7 @@ export const ScrolledBottom: Story = {
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findAllByText('Linkedin', {}, { timeout: 3000 });
|
||||
await canvas.findAllByText(companiesMock[0].name, {}, { timeout: 3000 });
|
||||
|
||||
const scrollWrapper = canvasElement.ownerDocument.body.querySelector(
|
||||
'.scroll-wrapper-y-enabled',
|
||||
@@ -118,6 +121,6 @@ export const ScrolledBottom: Story = {
|
||||
},
|
||||
});
|
||||
|
||||
await canvas.findByText('Facebook');
|
||||
await canvas.findByText(companiesMock[1].name);
|
||||
},
|
||||
};
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { recordIndexHasRecordsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexHasRecordsComponentSelector';
|
||||
|
||||
+42
-27
@@ -9,7 +9,7 @@ import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScro
|
||||
import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
export const RecordTableNoRecordGroupScrollToPreviousRecordEffect = () => {
|
||||
const { getScrollWrapperElement } = useScrollWrapperHTMLElement();
|
||||
@@ -22,8 +22,6 @@ export const RecordTableNoRecordGroupScrollToPreviousRecordEffect = () => {
|
||||
lastShowPageRecordIdState,
|
||||
);
|
||||
|
||||
const [hasInitializedScroll, setHasInitializedScroll] = useState(false);
|
||||
|
||||
const { scrollTableToPosition } = useScrollTableToPosition();
|
||||
|
||||
const { triggerInitialRecordTableDataLoad } =
|
||||
@@ -33,19 +31,49 @@ export const RecordTableNoRecordGroupScrollToPreviousRecordEffect = () => {
|
||||
|
||||
const { triggerFetchPagesWithoutDebounce } = useTriggerFetchPages();
|
||||
|
||||
const allRecordIdsRef = useRef(allRecordIds);
|
||||
allRecordIdsRef.current = allRecordIds;
|
||||
|
||||
const scrollTableToPositionRef = useRef(scrollTableToPosition);
|
||||
scrollTableToPositionRef.current = scrollTableToPosition;
|
||||
|
||||
const triggerInitialRecordTableDataLoadRef = useRef(
|
||||
triggerInitialRecordTableDataLoad,
|
||||
);
|
||||
triggerInitialRecordTableDataLoadRef.current =
|
||||
triggerInitialRecordTableDataLoad;
|
||||
|
||||
const processTreadmillScrollTopRef = useRef(processTreadmillScrollTop);
|
||||
processTreadmillScrollTopRef.current = processTreadmillScrollTop;
|
||||
|
||||
const getScrollWrapperElementRef = useRef(getScrollWrapperElement);
|
||||
getScrollWrapperElementRef.current = getScrollWrapperElement;
|
||||
|
||||
const triggerFetchPagesWithoutDebounceRef = useRef(
|
||||
triggerFetchPagesWithoutDebounce,
|
||||
);
|
||||
triggerFetchPagesWithoutDebounceRef.current =
|
||||
triggerFetchPagesWithoutDebounce;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isNonEmptyString(lastShowPageRecordId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
setLastShowPageRecordId(null);
|
||||
|
||||
const recordPosition = allRecordIds.findIndex(
|
||||
const recordPosition = allRecordIdsRef.current.findIndex(
|
||||
(recordId) => recordId === lastShowPageRecordId,
|
||||
);
|
||||
|
||||
await triggerInitialRecordTableDataLoad();
|
||||
await triggerInitialRecordTableDataLoadRef.current();
|
||||
|
||||
const { scrollWrapperElement } = getScrollWrapperElement();
|
||||
const { scrollWrapperElement } =
|
||||
getScrollWrapperElementRef.current();
|
||||
|
||||
const tableScrollWrapperHeight = scrollWrapperElement?.clientHeight ?? 0;
|
||||
const tableScrollWrapperHeight =
|
||||
scrollWrapperElement?.clientHeight ?? 0;
|
||||
|
||||
const numberOfRowsDisplayedInTable = Math.min(
|
||||
Math.floor(tableScrollWrapperHeight / (RECORD_TABLE_ROW_HEIGHT + 1)),
|
||||
@@ -56,7 +84,8 @@ export const RecordTableNoRecordGroupScrollToPreviousRecordEffect = () => {
|
||||
numberOfRowsDisplayedInTable / 2,
|
||||
);
|
||||
|
||||
const recordPositionInPx = recordPosition * (RECORD_TABLE_ROW_HEIGHT + 1);
|
||||
const recordPositionInPx =
|
||||
recordPosition * (RECORD_TABLE_ROW_HEIGHT + 1);
|
||||
|
||||
const targetScrollPositionInPx = Math.max(
|
||||
0,
|
||||
@@ -64,32 +93,18 @@ export const RecordTableNoRecordGroupScrollToPreviousRecordEffect = () => {
|
||||
halfNumberOfRowsVisible * (RECORD_TABLE_ROW_HEIGHT + 1),
|
||||
);
|
||||
|
||||
scrollTableToPosition({
|
||||
scrollTableToPositionRef.current({
|
||||
horizontalScrollInPx: 0,
|
||||
verticalScrollInPx: targetScrollPositionInPx,
|
||||
});
|
||||
|
||||
processTreadmillScrollTop(targetScrollPositionInPx);
|
||||
processTreadmillScrollTopRef.current(targetScrollPositionInPx);
|
||||
|
||||
setHasInitializedScroll(true);
|
||||
|
||||
await triggerFetchPagesWithoutDebounce();
|
||||
await triggerFetchPagesWithoutDebounceRef.current();
|
||||
};
|
||||
|
||||
if (isNonEmptyString(lastShowPageRecordId)) {
|
||||
run();
|
||||
}
|
||||
}, [
|
||||
hasInitializedScroll,
|
||||
lastShowPageRecordId,
|
||||
scrollTableToPosition,
|
||||
allRecordIds,
|
||||
setLastShowPageRecordId,
|
||||
triggerInitialRecordTableDataLoad,
|
||||
processTreadmillScrollTop,
|
||||
getScrollWrapperElement,
|
||||
triggerFetchPagesWithoutDebounce,
|
||||
]);
|
||||
run();
|
||||
}, [lastShowPageRecordId, setLastShowPageRecordId]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
+52
-21
@@ -13,9 +13,8 @@ import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/
|
||||
import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentState';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
// TODO: see if we can merge the initial and load more processes, to have only one load at scroll index effect
|
||||
export const RecordTableVirtualizedInitialDataLoadEffect = () => {
|
||||
const { recordTableId, objectNameSingular } = useRecordTableContextOrThrow();
|
||||
|
||||
@@ -53,45 +52,81 @@ export const RecordTableVirtualizedInitialDataLoadEffect = () => {
|
||||
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
|
||||
const currentViewId = currentView?.id ?? null;
|
||||
|
||||
const triggerInitialRecordTableDataLoadRef = useRef(
|
||||
triggerInitialRecordTableDataLoad,
|
||||
);
|
||||
triggerInitialRecordTableDataLoadRef.current =
|
||||
triggerInitialRecordTableDataLoad;
|
||||
|
||||
const setLastRecordTableQueryIdentifierRef = useRef(
|
||||
setLastRecordTableQueryIdentifier,
|
||||
);
|
||||
setLastRecordTableQueryIdentifierRef.current =
|
||||
setLastRecordTableQueryIdentifier;
|
||||
|
||||
const setLastContextStoreVirtualizedViewIdRef = useRef(
|
||||
setLastContextStoreVirtualizedViewId,
|
||||
);
|
||||
setLastContextStoreVirtualizedViewIdRef.current =
|
||||
setLastContextStoreVirtualizedViewId;
|
||||
|
||||
const setLastContextStoreVirtualizedVisibleRecordFieldsRef = useRef(
|
||||
setLastContextStoreVirtualizedVisibleRecordFields,
|
||||
);
|
||||
setLastContextStoreVirtualizedVisibleRecordFieldsRef.current =
|
||||
setLastContextStoreVirtualizedVisibleRecordFields;
|
||||
|
||||
const visibleRecordFieldsRef = useRef(visibleRecordFields);
|
||||
visibleRecordFieldsRef.current = visibleRecordFields;
|
||||
|
||||
const queryIdentifierRef = useRef(queryIdentifier);
|
||||
queryIdentifierRef.current = queryIdentifier;
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitializingVirtualTableDataLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
if ((currentView?.id ?? null) !== lastContextStoreVirtualizedViewId) {
|
||||
// Wait for the atomic batch from loadRecordIndexStates to populate
|
||||
// visibleRecordFields before triggering a fetch. On the next render
|
||||
// after the batch, fields will be populated and we'll proceed.
|
||||
if (isEmpty(visibleRecordFields)) {
|
||||
if (currentViewId !== lastContextStoreVirtualizedViewId) {
|
||||
if (isEmpty(visibleRecordFieldsRef.current)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLastContextStoreVirtualizedViewId(currentView?.id ?? null);
|
||||
setLastRecordTableQueryIdentifier(queryIdentifier);
|
||||
setLastContextStoreVirtualizedVisibleRecordFields(visibleRecordFields);
|
||||
setLastContextStoreVirtualizedViewIdRef.current(currentViewId);
|
||||
setLastRecordTableQueryIdentifierRef.current(
|
||||
queryIdentifierRef.current,
|
||||
);
|
||||
setLastContextStoreVirtualizedVisibleRecordFieldsRef.current(
|
||||
visibleRecordFieldsRef.current,
|
||||
);
|
||||
|
||||
await triggerInitialRecordTableDataLoad();
|
||||
await triggerInitialRecordTableDataLoadRef.current();
|
||||
} else if (
|
||||
queryIdentifier !== lastRecordTableQueryIdentifier &&
|
||||
!isFetchingMoreRecords
|
||||
) {
|
||||
setLastRecordTableQueryIdentifier(queryIdentifier);
|
||||
setLastRecordTableQueryIdentifierRef.current(queryIdentifier);
|
||||
|
||||
await triggerInitialRecordTableDataLoad();
|
||||
await triggerInitialRecordTableDataLoadRef.current();
|
||||
} else if (
|
||||
JSON.stringify(lastContextStoreVirtualizedVisibleRecordFields) !==
|
||||
JSON.stringify(visibleRecordFields)
|
||||
) {
|
||||
const lastFields = lastContextStoreVirtualizedVisibleRecordFields || [];
|
||||
const lastFields =
|
||||
lastContextStoreVirtualizedVisibleRecordFields || [];
|
||||
const currentFields = visibleRecordFields || [];
|
||||
|
||||
setLastContextStoreVirtualizedVisibleRecordFields(visibleRecordFields);
|
||||
setLastContextStoreVirtualizedVisibleRecordFieldsRef.current(
|
||||
visibleRecordFields,
|
||||
);
|
||||
|
||||
const shouldRefetchData = currentFields.length > lastFields.length;
|
||||
|
||||
if (shouldRefetchData) {
|
||||
await triggerInitialRecordTableDataLoad({
|
||||
await triggerInitialRecordTableDataLoadRef.current({
|
||||
shouldScrollToStart: isEmpty(lastFields),
|
||||
});
|
||||
}
|
||||
@@ -100,15 +135,11 @@ export const RecordTableVirtualizedInitialDataLoadEffect = () => {
|
||||
}, [
|
||||
queryIdentifier,
|
||||
lastRecordTableQueryIdentifier,
|
||||
triggerInitialRecordTableDataLoad,
|
||||
setLastRecordTableQueryIdentifier,
|
||||
isFetchingMoreRecords,
|
||||
isInitializingVirtualTableDataLoading,
|
||||
currentView,
|
||||
currentViewId,
|
||||
lastContextStoreVirtualizedViewId,
|
||||
setLastContextStoreVirtualizedViewId,
|
||||
lastContextStoreVirtualizedVisibleRecordFields,
|
||||
setLastContextStoreVirtualizedVisibleRecordFields,
|
||||
visibleRecordFields,
|
||||
]);
|
||||
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@ import { type ColumnDefinition } from '@/object-record/record-table/types/Column
|
||||
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
||||
import { findByProperty } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
import { getMockCompanyObjectMetadataItem } from '~/testing/mock-data/companies';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
|
||||
const COMPANY_MOCK_OBJECT = getMockCompanyObjectMetadataItem();
|
||||
const COMPANY_MOCK_OBJECT = getMockObjectMetadataItemOrThrow('company');
|
||||
|
||||
export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
[
|
||||
|
||||
@@ -12,7 +12,13 @@ import { PageDragDropProvider } from '@/navigation/components/PageDragDropProvid
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
|
||||
import { SignInAppNavigationDrawerMock } from '@/sign-in-background-mock/components/SignInAppNavigationDrawerMock';
|
||||
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
|
||||
import { lazy, Suspense } from 'react';
|
||||
|
||||
const SignInBackgroundMockPage = lazy(() =>
|
||||
import('@/sign-in-background-mock/components/SignInBackgroundMockPage').then(
|
||||
(module) => ({ default: module.SignInBackgroundMockPage }),
|
||||
),
|
||||
);
|
||||
import { useShowFullscreen } from '@/ui/layout/fullscreen/hooks/useShowFullscreen';
|
||||
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
|
||||
import { NAVIGATION_DRAWER_CONSTRAINTS } from '@/ui/layout/resizable-panel/constants/NavigationDrawerConstraints';
|
||||
@@ -106,7 +112,9 @@ export const DefaultLayout = () => {
|
||||
{showAuthModal ? (
|
||||
<>
|
||||
<StyledMainContainer>
|
||||
<SignInBackgroundMockPage />
|
||||
<Suspense fallback={null}>
|
||||
<SignInBackgroundMockPage />
|
||||
</Suspense>
|
||||
</StyledMainContainer>
|
||||
<AnimatePresence mode="wait">
|
||||
<LayoutGroup>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { lazy, Suspense } from 'react';
|
||||
|
||||
const SignInBackgroundMockPage = lazy(() =>
|
||||
import('@/sign-in-background-mock/components/SignInBackgroundMockPage').then(
|
||||
(module) => ({ default: module.SignInBackgroundMockPage }),
|
||||
),
|
||||
);
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
|
||||
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
|
||||
@@ -61,7 +67,9 @@ export const NotFound = () => {
|
||||
</StyledButtonContainer>
|
||||
</AnimatedPlaceholderErrorContainer>
|
||||
</StyledBackDrop>
|
||||
<SignInBackgroundMockPage />
|
||||
<Suspense fallback={null}>
|
||||
<SignInBackgroundMockPage />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,69 +10,75 @@ import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-
|
||||
import { workflowSelectedNodeComponentState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeComponentState';
|
||||
import { useStepsOutputSchema } from '@/workflow/workflow-variables/hooks/useStepsOutputSchema';
|
||||
import { type Decorator } from '@storybook/react-vite';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useAtomValue, useStore } from 'jotai';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
getWorkflowMock,
|
||||
getWorkflowNodeIdMock,
|
||||
mockedWorkflow,
|
||||
mockedWorkflowNodeId,
|
||||
mockedWorkflowVersion,
|
||||
} from '~/testing/mock-data/workflow';
|
||||
|
||||
export const WorkflowStepDecorator: Decorator = (Story) => {
|
||||
const workflowVisualizerComponentInstanceId = 'workflow-visualizer-test-id';
|
||||
|
||||
const workflowVersion = getWorkflowMock().versions.edges[0]
|
||||
.node as WorkflowVersion;
|
||||
const workflowVersion = mockedWorkflowVersion as WorkflowVersion;
|
||||
const { populateStepsOutputSchema } = useStepsOutputSchema();
|
||||
const { loadMockedObjectMetadataItems } = useLoadMockedObjectMetadataItems();
|
||||
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const handleMount = useCallback(async () => {
|
||||
await loadMockedObjectMetadataItems();
|
||||
useEffect(() => {
|
||||
const setup = async () => {
|
||||
await loadMockedObjectMetadataItems();
|
||||
|
||||
store.set(
|
||||
workflowVisualizerWorkflowIdComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
getWorkflowMock().id,
|
||||
);
|
||||
store.set(
|
||||
workflowVisualizerWorkflowVersionIdComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
workflowVersion.id,
|
||||
);
|
||||
store.set(
|
||||
workflowVisualizerWorkflowRunIdComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
'123',
|
||||
);
|
||||
store.set(
|
||||
workflowSelectedNodeComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
getWorkflowNodeIdMock(),
|
||||
);
|
||||
store.set(
|
||||
flowComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
{
|
||||
workflowVersionId: workflowVersion.id,
|
||||
trigger: workflowVersion.trigger,
|
||||
steps: workflowVersion.steps,
|
||||
},
|
||||
);
|
||||
store.set(
|
||||
commandMenuWorkflowIdComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
getWorkflowMock().id,
|
||||
);
|
||||
populateStepsOutputSchema(workflowVersion);
|
||||
setReady(true);
|
||||
store.set(
|
||||
workflowVisualizerWorkflowIdComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
mockedWorkflow.id,
|
||||
);
|
||||
store.set(
|
||||
workflowVisualizerWorkflowVersionIdComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
workflowVersion.id,
|
||||
);
|
||||
store.set(
|
||||
workflowVisualizerWorkflowRunIdComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
'123',
|
||||
);
|
||||
store.set(
|
||||
workflowSelectedNodeComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
mockedWorkflowNodeId,
|
||||
);
|
||||
store.set(
|
||||
flowComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
{
|
||||
workflowVersionId: workflowVersion.id,
|
||||
trigger: workflowVersion.trigger,
|
||||
steps: workflowVersion.steps,
|
||||
},
|
||||
);
|
||||
store.set(
|
||||
commandMenuWorkflowIdComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
mockedWorkflow.id,
|
||||
);
|
||||
populateStepsOutputSchema(workflowVersion);
|
||||
setReady(true);
|
||||
};
|
||||
|
||||
setup();
|
||||
}, [
|
||||
loadMockedObjectMetadataItems,
|
||||
populateStepsOutputSchema,
|
||||
@@ -80,9 +86,11 @@ export const WorkflowStepDecorator: Decorator = (Story) => {
|
||||
store,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
handleMount();
|
||||
}, [handleMount]);
|
||||
const workflowVersionId = useAtomValue(
|
||||
workflowVisualizerWorkflowVersionIdComponentState.atomFamily({
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<CommandMenuPageComponentInstanceContext.Provider
|
||||
@@ -95,7 +103,7 @@ export const WorkflowStepDecorator: Decorator = (Story) => {
|
||||
instanceId: workflowVisualizerComponentInstanceId,
|
||||
}}
|
||||
>
|
||||
{ready && <Story />}
|
||||
{ready && isDefined(workflowVersionId) && <Story />}
|
||||
</WorkflowVisualizerComponentInstanceContext.Provider>
|
||||
</CommandMenuPageComponentInstanceContext.Provider>
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ import { LIST_PLANS } from '@/billing/graphql/queries/listPlans';
|
||||
import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { mockBillingPlans } from '~/testing/mock-data/billing-plans';
|
||||
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result';
|
||||
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/metadata/objects/mock-objects-metadata';
|
||||
import { getRolesMock } from '~/testing/mock-data/roles';
|
||||
import { mockedTasks } from '~/testing/mock-data/tasks';
|
||||
import {
|
||||
@@ -35,12 +35,31 @@ import {
|
||||
workflowQueryResult,
|
||||
} from '~/testing/mock-data/workflow';
|
||||
import { oneSucceededWorkflowRunQueryResult } from '~/testing/mock-data/workflow-run';
|
||||
import { getConnectionTypename } from '@/object-record/cache/utils/getConnectionTypename';
|
||||
import { getEdgeTypename } from '@/object-record/cache/utils/getEdgeTypename';
|
||||
import { getEmptyPageInfo } from '@/object-record/cache/utils/getEmptyPageInfo';
|
||||
import { mockedViewFieldsData } from './mock-data/view-fields';
|
||||
|
||||
const peopleMock = getPeopleRecordConnectionMock();
|
||||
const companiesMock = getCompaniesRecordConnectionMock();
|
||||
const duplicateCompanyMock = getCompanyDuplicateMock();
|
||||
|
||||
// Wraps raw server-fetched records (which already have correct field shapes)
|
||||
// into a GraphQL connection response structure.
|
||||
const wrapRecordsAsConnection = (
|
||||
objectNameSingular: string,
|
||||
records: Record<string, unknown>[],
|
||||
) => ({
|
||||
__typename: getConnectionTypename(objectNameSingular),
|
||||
edges: records.map((node) => ({
|
||||
__typename: getEdgeTypename(objectNameSingular),
|
||||
node,
|
||||
cursor: '',
|
||||
})),
|
||||
pageInfo: getEmptyPageInfo(),
|
||||
totalCount: records.length,
|
||||
});
|
||||
|
||||
const getRootFieldNamesFromQuery = (query: string) => {
|
||||
try {
|
||||
const document = parse(query);
|
||||
@@ -201,62 +220,48 @@ export const graphqlMocks = {
|
||||
});
|
||||
}),
|
||||
graphql.query('Search', () => {
|
||||
const personSearchEdges = peopleMock
|
||||
.slice(0, 2)
|
||||
.map((person: Record<string, unknown>, index: number) => ({
|
||||
node: {
|
||||
__typename: 'SearchRecordDTO',
|
||||
recordId: person.id,
|
||||
objectNameSingular: 'person',
|
||||
objectLabelSingular: 'Person',
|
||||
label:
|
||||
`${(person.name as Record<string, string>)?.firstName ?? ''} ${(person.name as Record<string, string>)?.lastName ?? ''}`.trim(),
|
||||
imageUrl: '',
|
||||
tsRankCD: 0.2,
|
||||
tsRank: 0.12158542,
|
||||
},
|
||||
cursor: `cursor-${index + 1}`,
|
||||
}));
|
||||
|
||||
const companySearchEdges = companiesMock
|
||||
.slice(0, 2)
|
||||
.map((company: Record<string, unknown>, index: number) => ({
|
||||
node: {
|
||||
__typename: 'SearchRecordDTO',
|
||||
recordId: company.id,
|
||||
objectNameSingular: 'company',
|
||||
objectLabelSingular: 'Company',
|
||||
label: company.name,
|
||||
imageUrl: '',
|
||||
tsRankCD: 0.2,
|
||||
tsRank: 0.12158542,
|
||||
},
|
||||
cursor: `cursor-${personSearchEdges.length + index + 1}`,
|
||||
}));
|
||||
|
||||
const allEdges = [...personSearchEdges, ...companySearchEdges];
|
||||
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
search: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
__typename: 'SearchRecordDTO',
|
||||
recordId: '20202020-2d40-4e49-8df4-9c6a049191de',
|
||||
objectNameSingular: 'person',
|
||||
label: 'Louis Duss',
|
||||
imageUrl: '',
|
||||
tsRankCD: 0.2,
|
||||
tsRank: 0.12158542,
|
||||
},
|
||||
cursor: 'cursor-1',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
__typename: 'SearchRecordDTO',
|
||||
recordId: '20202020-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
objectNameSingular: 'company',
|
||||
label: 'Linkedin',
|
||||
imageUrl: 'https://twenty-icons.com/linkedin.com',
|
||||
tsRankCD: 0.2,
|
||||
tsRank: 0.12158542,
|
||||
},
|
||||
cursor: 'cursor-2',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
__typename: 'SearchRecordDTO',
|
||||
recordId: '20202020-3f74-492d-a101-2a70f50a1645',
|
||||
objectNameSingular: 'company',
|
||||
label: 'Libeo',
|
||||
imageUrl: 'https://twenty-icons.com/libeo.io',
|
||||
tsRankCD: 0.2,
|
||||
tsRank: 0.12158542,
|
||||
},
|
||||
cursor: 'cursor-3',
|
||||
},
|
||||
{
|
||||
node: {
|
||||
__typename: 'SearchRecordDTO',
|
||||
recordId: '20202020-ac73-4797-824e-87a1f5aea9e0',
|
||||
objectNameSingular: 'person',
|
||||
label: 'Sylvie Palmer',
|
||||
imageUrl: '',
|
||||
tsRankCD: 0.1,
|
||||
tsRank: 0.06079271,
|
||||
},
|
||||
cursor: 'cursor-4',
|
||||
},
|
||||
],
|
||||
edges: allEdges,
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: 'cursor-4',
|
||||
endCursor: allEdges[allEdges.length - 1]?.cursor ?? null,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -390,45 +395,7 @@ export const graphqlMocks = {
|
||||
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
companies: {
|
||||
edges: mockedData.map((company) => ({
|
||||
node: {
|
||||
...company,
|
||||
favorites: {
|
||||
edges: [],
|
||||
__typename: 'FavoriteConnection',
|
||||
},
|
||||
attachments: {
|
||||
edges: [],
|
||||
__typename: 'AttachmentConnection',
|
||||
},
|
||||
people: {
|
||||
edges: [],
|
||||
__typename: 'PersonConnection',
|
||||
},
|
||||
opportunities: {
|
||||
edges: [],
|
||||
__typename: 'OpportunityConnection',
|
||||
},
|
||||
taskTargets: {
|
||||
edges: [],
|
||||
__typename: 'TaskTargetConnection',
|
||||
},
|
||||
noteTargets: {
|
||||
edges: [],
|
||||
__typename: 'NoteTargetConnection',
|
||||
},
|
||||
},
|
||||
cursor: null,
|
||||
})),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
},
|
||||
totalCount: mockedData.length,
|
||||
},
|
||||
companies: wrapRecordsAsConnection('company', mockedData),
|
||||
},
|
||||
});
|
||||
}),
|
||||
@@ -436,46 +403,7 @@ export const graphqlMocks = {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
companyDuplicates: [
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
...duplicateCompanyMock,
|
||||
favorites: {
|
||||
edges: [],
|
||||
__typename: 'FavoriteConnection',
|
||||
},
|
||||
attachments: {
|
||||
edges: [],
|
||||
__typename: 'AttachmentConnection',
|
||||
},
|
||||
people: {
|
||||
edges: [],
|
||||
__typename: 'PersonConnection',
|
||||
},
|
||||
opportunities: {
|
||||
edges: [],
|
||||
__typename: 'OpportunityConnection',
|
||||
},
|
||||
taskTargets: {
|
||||
edges: [],
|
||||
__typename: 'TaskTargetConnection',
|
||||
},
|
||||
noteTargets: {
|
||||
edges: [],
|
||||
__typename: 'NoteTargetConnection',
|
||||
},
|
||||
},
|
||||
cursor: null,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
},
|
||||
},
|
||||
wrapRecordsAsConnection('company', [duplicateCompanyMock]),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,784 +1,21 @@
|
||||
import { type Company } from '@/companies/types/Company';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { mockedCompanyRecords } from '~/testing/mock-data/generated/data/companies/mock-companies-data';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
|
||||
export const companiesQueryResult = {
|
||||
companies: {
|
||||
__typename: 'CompanyConnection',
|
||||
totalCount: 13,
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo',
|
||||
hasNextPage: false,
|
||||
startCursor:
|
||||
'WzEsICIyMDIwMjAyMC0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgiXQ==',
|
||||
endCursor: 'WzEzLCAiMjAyMDIwMjAtMTQ1NS00YzU3LWFmYWYtZGQ1ZGMwODYzNjFkIl0=',
|
||||
},
|
||||
edges: [
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzEsICIyMDIwMjAyMC0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
employees: 100,
|
||||
createdAt: '2025-01-05T09:00:20.412Z',
|
||||
updatedAt: '2025-01-05T10:15:30.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Linkedin',
|
||||
accountOwnerId: null,
|
||||
accountOwner: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: 'https://linkedin.com',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
position: 1,
|
||||
idealCustomerProfile: true,
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
previousEmployees: {
|
||||
__typename: 'Person',
|
||||
id: '20202020-2d40-4e49-8df4-9c6a049191de',
|
||||
email: 'louis.duss@google.com',
|
||||
position: 14,
|
||||
testJson: null,
|
||||
testRating: null,
|
||||
companyId: '20202020-c21e-4ec2-873b-de4264d89025',
|
||||
avatarUrl: '',
|
||||
updatedAt: '2025-01-05T11:36:42.400Z',
|
||||
testMultiSelect: null,
|
||||
testBoolean: true,
|
||||
testSelect: 'OPTION_1',
|
||||
testDateOnly: null,
|
||||
bestCompanyId: null,
|
||||
testUuid: null,
|
||||
phone: '+33788901234',
|
||||
createdAt: '2025-01-05T09:00:20.412Z',
|
||||
city: 'Seattle',
|
||||
testPhone: '',
|
||||
jobTitle: 'CTO',
|
||||
testCurrency: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: 'twitter.com',
|
||||
},
|
||||
testLinks: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: '',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Louis',
|
||||
lastName: 'Duss',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: 'linkedin.com',
|
||||
},
|
||||
testAddress: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
testLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzIsICIyMDIwMjAyMC01ZDgxLTQ2ZDYtYmY4My1mN2ZkMzNlYTYxMDIiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-5d81-46d6-bf83-f7fd33ea6102',
|
||||
employees: null,
|
||||
createdAt: '2025-01-06T08:30:15.412Z',
|
||||
updatedAt: '2025-01-06T14:45:22.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Facebook',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://facebook.com',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 2,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzMsICIyMDIwMjAyMC0wNzEzLTQwYTUtODIxNi04MjgwMjQwMWQzM2UiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-0713-40a5-8216-82802401d33e',
|
||||
employees: null,
|
||||
createdAt: '2025-01-07T10:15:30.412Z',
|
||||
updatedAt: '2025-01-07T16:20:45.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Anthropic',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://anthropic.com',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 3,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzQsICIyMDIwMjAyMC1lZDg5LTQxM2EtYjMxYS05NjI5ODZlNjdiYjQiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-ed89-413a-b31a-962986e67bb4',
|
||||
employees: null,
|
||||
createdAt: '2025-01-08T11:45:10.412Z',
|
||||
updatedAt: '2025-01-08T17:30:25.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Microsoft',
|
||||
idealCustomerProfile: true,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://microsoft.com',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 4,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzUsICIyMDIwMjAyMC0xNzFlLTRiY2MtOWNmNy00MzQ0OGQ2ZmIyNzgiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-171e-4bcc-9cf7-43448d6fb278',
|
||||
employees: null,
|
||||
createdAt: '2025-01-09T09:20:45.412Z',
|
||||
updatedAt: '2025-01-09T15:10:30.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Airbnb',
|
||||
idealCustomerProfile: true,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://airbnb.com',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 5,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzYsICIyMDIwMjAyMC1jMjFlLTRlYzItODczYi1kZTQyNjRkODkwMjUiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-c21e-4ec2-873b-de4264d89025',
|
||||
employees: null,
|
||||
createdAt: '2025-01-10T13:30:20.412Z',
|
||||
updatedAt: '2025-01-10T18:45:35.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Google',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://google.com',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 6,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzcsICIyMDIwMjAyMC03MDdlLTQ0ZGMtYTFkMi0zMDAzMGJmMWE5NDQiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-707e-44dc-a1d2-30030bf1a944',
|
||||
employees: null,
|
||||
createdAt: '2025-01-11T07:15:50.412Z',
|
||||
updatedAt: '2025-01-11T12:25:15.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Netflix',
|
||||
idealCustomerProfile: true,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://netflix.com',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 7,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzgsICIyMDIwMjAyMC0zZjc0LTQ5MmQtYTEwMS0yYTcwZjUwYTE2NDUiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-3f74-492d-a101-2a70f50a1645',
|
||||
employees: null,
|
||||
createdAt: '2025-01-12T14:40:25.412Z',
|
||||
updatedAt: '2025-01-12T19:55:40.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Libeo',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://libeo.io',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 8,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzksICIyMDIwMjAyMC1jZmJmLTQxNTYtYTc5MC1lMzk4NTRkY2Q0ZWIiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-cfbf-4156-a790-e39854dcd4eb',
|
||||
employees: null,
|
||||
createdAt: '2025-01-13T08:25:35.412Z',
|
||||
updatedAt: '2025-01-13T13:40:50.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Claap',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://claap.com',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 9,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzEwLCAiMjAyMDIwMjAtZjg2Yi00MTlmLWI3OTQtMDIzMTlhYmU4NjM3Il0=',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-f86b-419f-b794-02319abe8637',
|
||||
employees: null,
|
||||
createdAt: '2025-01-14T16:10:15.412Z',
|
||||
updatedAt: '2025-01-14T21:30:25.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Hasura',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://hasura.io',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 10,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzExLCAiMjAyMDIwMjAtNTUxOC00NTUzLTk0MzMtNDJkOGViODI4MzRiIl0=',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-5518-4553-9433-42d8eb82834b',
|
||||
employees: null,
|
||||
createdAt: '2025-01-15T12:50:40.412Z',
|
||||
updatedAt: '2025-01-15T17:15:55.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Wework',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://wework.com',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 11,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzEyLCAiMjAyMDIwMjAtZjc5ZS00MGRkLWJkMDYtYzM2ZTZhYmI0Njc4Il0=',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-f79e-40dd-bd06-c36e6abb4678',
|
||||
employees: null,
|
||||
createdAt: '2025-01-16T10:35:20.412Z',
|
||||
updatedAt: '2025-01-16T15:50:35.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Samsung',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://samsung.com',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 12,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzEzLCAiMjAyMDIwMjAtMTQ1NS00YzU3LWFmYWYtZGQ1ZGMwODYzNjFkIl0=',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-1455-4c57-afaf-dd5dc086361d',
|
||||
employees: null,
|
||||
createdAt: '2025-01-17T11:20:10.412Z',
|
||||
updatedAt: '2025-01-17T16:45:25.412Z',
|
||||
deletedAt: null,
|
||||
name: 'Algolia',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
accountOwnerId: null,
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: 'https://algolia.com',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
previousEmployees: null,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
position: 13,
|
||||
xLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Links',
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const allMockedCompanyRecords = getRecordsFromRecordConnection({
|
||||
recordConnection: companiesQueryResult.companies,
|
||||
}) as ObjectRecord[];
|
||||
const allMockedCompanyRecords = mockedCompanyRecords as ObjectRecord[];
|
||||
|
||||
export const getCompaniesMock = () => {
|
||||
return [...allMockedCompanyRecords] as Company[];
|
||||
};
|
||||
|
||||
export const getCompaniesRecordConnectionMock = () => {
|
||||
const companiesMock = companiesQueryResult.companies.edges.map(
|
||||
(edge) => edge.node,
|
||||
);
|
||||
|
||||
return companiesMock;
|
||||
return [...allMockedCompanyRecords];
|
||||
};
|
||||
|
||||
export const getMockCompanyObjectMetadataItem = () => {
|
||||
const companyObjectMetadataItem = getMockObjectMetadataItemOrThrow('company');
|
||||
|
||||
return companyObjectMetadataItem;
|
||||
return getMockObjectMetadataItemOrThrow('company');
|
||||
};
|
||||
|
||||
export const getCompanyDuplicateMock = () => {
|
||||
@@ -811,26 +48,3 @@ export const findMockCompanyRecord = ({
|
||||
|
||||
return company;
|
||||
};
|
||||
|
||||
export const getEmptyCompanyMock = () => {
|
||||
return {
|
||||
id: '9231e6ee-4cc2-4c7b-8c55-dff16f4d968a',
|
||||
name: '',
|
||||
domainName: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: '',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: [],
|
||||
},
|
||||
address: {},
|
||||
accountOwner: null,
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
employees: null,
|
||||
idealCustomerProfile: null,
|
||||
linkedinLink: null,
|
||||
xLink: null,
|
||||
_activityCount: null,
|
||||
__typename: 'Company',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { companiesQueryResult } from '~/testing/mock-data/companies';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
import { mockedWorkspaceMemberData } from '~/testing/mock-data/users';
|
||||
import { mockedViewsData } from '~/testing/mock-data/views';
|
||||
|
||||
@@ -18,8 +18,8 @@ export const mockedFavoritesData = [
|
||||
workflowVersion: null,
|
||||
forWorkspaceMemberId: mockedWorkspaceMemberData.id,
|
||||
forWorkspaceMember: mockedWorkspaceMemberData,
|
||||
companyId: companiesQueryResult.companies.edges[0].node.id,
|
||||
company: companiesQueryResult.companies.edges[0].node,
|
||||
companyId: getCompaniesMock()[0].id,
|
||||
company: getCompaniesMock()[0],
|
||||
viewId: null,
|
||||
view: null,
|
||||
taskId: null,
|
||||
|
||||
+2165
File diff suppressed because it is too large
Load Diff
+20204
-20205
File diff suppressed because it is too large
Load Diff
@@ -1,29 +1,10 @@
|
||||
import { type RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
|
||||
|
||||
export const getWorkflowMock = () => {
|
||||
return workflowQueryResult.workflows.edges[0].node;
|
||||
};
|
||||
|
||||
export const getWorkflowVersionsMock = () => {
|
||||
return {
|
||||
...getWorkflowMock().versions,
|
||||
__typename: 'WorkflowVersionConnection',
|
||||
totalCount: 1,
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor:
|
||||
'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9',
|
||||
endCursor:
|
||||
'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getWorkflowNodeIdMock = () => {
|
||||
return getWorkflowMock().versions.edges[0].node.steps[0].id;
|
||||
};
|
||||
// Keep function wrappers as aliases for backward compatibility — they just
|
||||
// return the pre-computed constants so callers can be migrated gradually.
|
||||
export const getWorkflowMock = () => mockedWorkflow;
|
||||
export const getWorkflowVersionsMock = () => mockedWorkflowVersions;
|
||||
export const getWorkflowNodeIdMock = () => mockedWorkflowNodeId;
|
||||
|
||||
export const MOCKED_STEP_ID = '04d5f3bf-9714-400d-ba27-644006a5fb1b';
|
||||
|
||||
@@ -1552,3 +1533,24 @@ export const workflowQueryResult = {
|
||||
],
|
||||
},
|
||||
} satisfies { workflows: RecordGqlConnection };
|
||||
|
||||
export const mockedWorkflow = workflowQueryResult.workflows.edges[0].node;
|
||||
|
||||
export const mockedWorkflowVersion = mockedWorkflow.versions.edges[0].node;
|
||||
|
||||
export const mockedWorkflowVersions = {
|
||||
...mockedWorkflow.versions,
|
||||
__typename: 'WorkflowVersionConnection' as const,
|
||||
totalCount: 1,
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo' as const,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor:
|
||||
'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9',
|
||||
endCursor:
|
||||
'eyJjcmVhdGVkQXQiOiIyMDI0LTA5LTE5VDEwOjEwOjA0LjcyNVoiLCJpZCI6ImY2MTg4NDNhLTI2YmUtNGE1NC1hNjBmLWY0Y2U4OGE1OTRmMCJ9',
|
||||
},
|
||||
};
|
||||
|
||||
export const mockedWorkflowNodeId = mockedWorkflowVersion.steps[0].id;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
import { generateMockRecord } from '~/testing/utils/generateMockRecordNode';
|
||||
|
||||
export const generateMockRecordConnection = ({
|
||||
objectNameSingular,
|
||||
records,
|
||||
computeReferences = false,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
records: Record<string, unknown>[];
|
||||
computeReferences?: boolean;
|
||||
}) => {
|
||||
const objectMetadataItem =
|
||||
getMockObjectMetadataItemOrThrow(objectNameSingular);
|
||||
|
||||
const prefilledRecords = records.map((recordInput) =>
|
||||
generateMockRecord({ objectNameSingular, input: recordInput }),
|
||||
);
|
||||
|
||||
return getRecordConnectionFromRecords({
|
||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||
objectMetadataItem,
|
||||
records: prefilledRecords,
|
||||
computeReferences,
|
||||
});
|
||||
};
|
||||
+25
-18
@@ -1,37 +1,44 @@
|
||||
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { prefillRecord } from '@/object-record/utils/prefillRecord';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
|
||||
type GenerateEmptyJestRecordNodeArgs = {
|
||||
objectNameSingular: string;
|
||||
input: Record<string, unknown>;
|
||||
withDepthOneRelation?: boolean;
|
||||
};
|
||||
export const generateEmptyJestRecordNode = ({
|
||||
export const generateMockRecord = ({
|
||||
objectNameSingular,
|
||||
input,
|
||||
withDepthOneRelation = false,
|
||||
}: GenerateEmptyJestRecordNodeArgs) => {
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
input: Record<string, unknown>;
|
||||
}): ObjectRecord => {
|
||||
const objectMetadataItem =
|
||||
getMockObjectMetadataItemOrThrow(objectNameSingular);
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
throw new Error(
|
||||
`ObjectMetadataItem not found for objectNameSingular: ${objectNameSingular} while generating empty Jest record node`,
|
||||
);
|
||||
}
|
||||
return prefillRecord({ objectMetadataItem, input });
|
||||
};
|
||||
|
||||
const prefilledRecord = prefillRecord({
|
||||
objectMetadataItem,
|
||||
input,
|
||||
});
|
||||
export const generateMockRecordNode = ({
|
||||
objectNameSingular,
|
||||
input,
|
||||
withDepthOneRelation = false,
|
||||
computeReferences,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
input: Record<string, unknown>;
|
||||
withDepthOneRelation?: boolean;
|
||||
computeReferences?: boolean;
|
||||
}) => {
|
||||
const objectMetadataItem =
|
||||
getMockObjectMetadataItemOrThrow(objectNameSingular);
|
||||
|
||||
const record = generateMockRecord({ objectNameSingular, input });
|
||||
|
||||
return getRecordNodeFromRecord({
|
||||
record: prefilledRecord,
|
||||
record,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||
computeReferences,
|
||||
recordGqlFields: withDepthOneRelation
|
||||
? generateDepthRecordGqlFieldsFromObject({
|
||||
objectMetadataItem,
|
||||
@@ -2,7 +2,7 @@ import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataIte
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
|
||||
|
||||
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result';
|
||||
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/metadata/objects/mock-objects-metadata';
|
||||
|
||||
export const generatedMockObjectMetadataItems: ObjectMetadataItem[] =
|
||||
mockedStandardObjectMetadataQueryResult.objects.edges.map((edge) => {
|
||||
|
||||
Reference in New Issue
Block a user