diff --git a/.github/workflows/ci-front.yaml b/.github/workflows/ci-front.yaml index aa6955723796..b61506709f93 100644 --- a/.github/workflows/ci-front.yaml +++ b/.github/workflows/ci-front.yaml @@ -31,6 +31,8 @@ jobs: uses: actions/checkout@v4 - name: Install dependencies uses: ./.github/workflows/actions/yarn-install + - name: Diagnostic disk space issue + run: df -h - name: Front / Restore Storybook Task Cache uses: ./.github/workflows/actions/task-cache with: diff --git a/.github/workflows/ci-server.yaml b/.github/workflows/ci-server.yaml index 857dec2fc863..074d63fdda40 100644 --- a/.github/workflows/ci-server.yaml +++ b/.github/workflows/ci-server.yaml @@ -54,7 +54,7 @@ jobs: - name: Server / Write .env run: npx nx reset:env twenty-server - name: Worker / Run - run: MESSAGE_QUEUE_TYPE=sync npx nx worker twenty-server + run: npx nx run twenty-server:worker:ci server-test: runs-on: ubuntu-latest diff --git a/oss-gg/twenty-content-challenges/1-create-youtube-video-about-20.md b/oss-gg/twenty-content-challenges/1-create-youtube-video-about-20.md new file mode 100644 index 000000000000..455b5e35bae3 --- /dev/null +++ b/oss-gg/twenty-content-challenges/1-create-youtube-video-about-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Create a YouTube Video about Twenty showcasing a specific way to use Twenty effectively. +**Points**: 750 Points +**Proof**: Add your oss handle and YouTube video link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » YouTube Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) YouTube Link: [YouTube](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-content-challenges/2-write-blog-post-about-20.md b/oss-gg/twenty-content-challenges/2-write-blog-post-about-20.md new file mode 100644 index 000000000000..a4c4e6bee944 --- /dev/null +++ b/oss-gg/twenty-content-challenges/2-write-blog-post-about-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Write a blog post about sharing your experience using Twenty in a detailed format on any platform. +**Points**: 750 Points +**Proof**: Add your oss handle and blog link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » blog Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) blog Link: [blog](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-content-challenges/3-write-selfthost-guide-blog-post-20.md b/oss-gg/twenty-content-challenges/3-write-selfthost-guide-blog-post-20.md new file mode 100644 index 000000000000..c7352ec430fc --- /dev/null +++ b/oss-gg/twenty-content-challenges/3-write-selfthost-guide-blog-post-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Write a blog post about self-hosting Twenty in a detailed format on any platform. +**Points**: 750 Points +**Proof**: Add your oss handle and blog link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » blog Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) blog Link: [blog](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-content-challenges/4-create-promotional-video-20-share.md b/oss-gg/twenty-content-challenges/4-create-promotional-video-20-share.md new file mode 100644 index 000000000000..e52cb43a4247 --- /dev/null +++ b/oss-gg/twenty-content-challenges/4-create-promotional-video-20-share.md @@ -0,0 +1,21 @@ +**Side Quest**: Create a promotional video for Twenty and share it on social media. +**Points**: 750 Points +**Proof**: Add your oss handle and video link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-design-challenges/1-design-promotional-poster-20-share.md b/oss-gg/twenty-design-challenges/1-design-promotional-poster-20-share.md new file mode 100644 index 000000000000..b995788bc3f1 --- /dev/null +++ b/oss-gg/twenty-design-challenges/1-design-promotional-poster-20-share.md @@ -0,0 +1,21 @@ +**Side Quest**: Design a promotional poster of Twenty and share it on social media. +**Points**: 300 Points +**Proof**: Add your oss handle and poster link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » poster Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) poster Link: [poster](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-design-challenges/2-design-new-logo-twenty.md b/oss-gg/twenty-design-challenges/2-design-new-logo-twenty.md new file mode 100644 index 000000000000..e8e70ca913cb --- /dev/null +++ b/oss-gg/twenty-design-challenges/2-design-new-logo-twenty.md @@ -0,0 +1,21 @@ +**Side Quest**: Design/Create new Twenty logo, tweet your design, and mention @twentycrm. +**Points**: 300 Points +**Proof**: Create a logo uploade it on any of the platform and add your oss handle and logo link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » Logo Link: https://link.to/content » tweet Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Logo Link: [logo](https://twenty.com/) tweet Link: [tweet](https://x.com) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-design-challenges/3-create-custom-interfact-theme-20.md b/oss-gg/twenty-design-challenges/3-create-custom-interfact-theme-20.md new file mode 100644 index 000000000000..e51945ea9988 --- /dev/null +++ b/oss-gg/twenty-design-challenges/3-create-custom-interfact-theme-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Duplicate the Figma file from the main repo and customize the variables to create a unique interface theme for Twenty. +**Points**: 750 Points +**Proof**: Add your oss handle and Figma link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » Figma Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) Figma Link: [Figma](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-dev-challenges/1-write-migration-script-other-crm-to-20.md b/oss-gg/twenty-dev-challenges/1-write-migration-script-other-crm-to-20.md new file mode 100644 index 000000000000..249d8e158cfa --- /dev/null +++ b/oss-gg/twenty-dev-challenges/1-write-migration-script-other-crm-to-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Develop a script to facilitate the migration of data from another CRM to Twenty. +**Points**: 750 Points +**Proof**: Add your oss handle and record video and share link to the list below. In video show the working proof of your created script. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-dev-challenges/2-create-raycast-integration-for-20.md b/oss-gg/twenty-dev-challenges/2-create-raycast-integration-for-20.md new file mode 100644 index 000000000000..e4793c40d66f --- /dev/null +++ b/oss-gg/twenty-dev-challenges/2-create-raycast-integration-for-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Develop an integration for Raycast that enables users to create records on any object within Twenty directly from Raycast. +**Points**: 1500 Points +**Proof**: Add your oss handle and record video and share link to the list below. In video show the workflow of the your integration created and perform some task. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » video Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) video Link: [video](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-no-code-challenges/1-create-n8n-template-integrate-20-API.md b/oss-gg/twenty-no-code-challenges/1-create-n8n-template-integrate-20-API.md new file mode 100644 index 000000000000..6786e5a94553 --- /dev/null +++ b/oss-gg/twenty-no-code-challenges/1-create-n8n-template-integrate-20-API.md @@ -0,0 +1,21 @@ +**Side Quest**: Create an n8n workflow that empowers Twenty by connecting it to another tool. +**Points**: 750 Points +**Proof**: Add your oss handle and template link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » template Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) template Link: [template](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-no-code-challenges/2-write-selfthost-guide-blog-post-20.md b/oss-gg/twenty-no-code-challenges/2-write-selfthost-guide-blog-post-20.md new file mode 100644 index 000000000000..58fa6de4d8d6 --- /dev/null +++ b/oss-gg/twenty-no-code-challenges/2-write-selfthost-guide-blog-post-20.md @@ -0,0 +1,21 @@ +**Side Quest**: Write a comprehensive guide on how to integrate Twenty with marketing automation tool (n8n, Zapier). Include a concrete use case and explain how to leverage AI to write API requests for non-developers and share it. +**Points**: 1500 Points +**Proof**: Add your oss handle and guide link to the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR oss.gg HANDLE » guide Link: https://link.to/content + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 02-October-2024 by [yourhandle](https://oss.gg/yourhandle) guide Link: [guide](https://twenty.com/) + +--- \ No newline at end of file diff --git a/oss-gg/twenty-side-quest/1-quote-tweet-20-oss-gg-launch.md b/oss-gg/twenty-side-quest/1-quote-tweet-20-oss-gg-launch.md new file mode 100644 index 000000000000..0a33e2287146 --- /dev/null +++ b/oss-gg/twenty-side-quest/1-quote-tweet-20-oss-gg-launch.md @@ -0,0 +1,23 @@ +**Side Quest**: Meme Magic - Craft a meme where a brick plays a role. Tweet it, and tag us @papermarkio to submit. +**Points**: 150 Points +**Proof**: Add a screenshot of meme to the PR description. Add a link to your tweet in the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 01-October-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- diff --git a/oss-gg/twenty-side-quest/2-tweet-about-fav-twenty-feature.md b/oss-gg/twenty-side-quest/2-tweet-about-fav-twenty-feature.md new file mode 100644 index 000000000000..0a33e2287146 --- /dev/null +++ b/oss-gg/twenty-side-quest/2-tweet-about-fav-twenty-feature.md @@ -0,0 +1,23 @@ +**Side Quest**: Meme Magic - Craft a meme where a brick plays a role. Tweet it, and tag us @papermarkio to submit. +**Points**: 150 Points +**Proof**: Add a screenshot of meme to the PR description. Add a link to your tweet in the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 01-October-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- diff --git a/oss-gg/twenty-side-quest/3-meme-magic.md b/oss-gg/twenty-side-quest/3-meme-magic.md new file mode 100644 index 000000000000..0a33e2287146 --- /dev/null +++ b/oss-gg/twenty-side-quest/3-meme-magic.md @@ -0,0 +1,23 @@ +**Side Quest**: Meme Magic - Craft a meme where a brick plays a role. Tweet it, and tag us @papermarkio to submit. +**Points**: 150 Points +**Proof**: Add a screenshot of meme to the PR description. Add a link to your tweet in the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 01-October-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- diff --git a/oss-gg/twenty-side-quest/4-gif-magic.md b/oss-gg/twenty-side-quest/4-gif-magic.md new file mode 100644 index 000000000000..0e38ace584d6 --- /dev/null +++ b/oss-gg/twenty-side-quest/4-gif-magic.md @@ -0,0 +1,23 @@ +**Side Quest**: GIF Magic - Craft a GIF where a brick plays a role. Upload it to GIPHY with tags 'open source', 'foss', 'papermarkio'. +**Points**: 150 Points +**Proof**: Add a screenshot of GIF on Giphy to the PR description. Add a link to your GIPHY in the list below. + +Please follow the following schema: + +--- + +» 05-April-2024 by YOUR NAME +» Link to Tweet: https://giphy.com/... + +--- + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 01-October-2024 by YOUR NAME +» Link to Tweet: https://x.com/... + +--- diff --git a/oss-gg/twenty-side-quest/5-quest-wizard.md b/oss-gg/twenty-side-quest/5-quest-wizard.md new file mode 100644 index 000000000000..2dfe4bd9c86b --- /dev/null +++ b/oss-gg/twenty-side-quest/5-quest-wizard.md @@ -0,0 +1,19 @@ +**Side Quest**: Complete all papermarkio side quests +**Points**: 300 Points +**Proof**: Add screenshots for each side quest to the PR description. Add your name to the list below. + +Please follow the following schema: + +--- + + » 05-April-2024 by YOUR NAME + +//////////////////////////// + +Your turn 👇 + +//////////////////////////// + +» 01-October-2024 by X + +--- diff --git a/packages/twenty-emails/package.json b/packages/twenty-emails/package.json index a792a42a373a..1f20fbe7eb64 100644 --- a/packages/twenty-emails/package.json +++ b/packages/twenty-emails/package.json @@ -1,6 +1,6 @@ { "name": "twenty-emails", - "version": "0.31.canary", + "version": "0.31.0-canary", "description": "", "author": "", "private": true, diff --git a/packages/twenty-front/.storybook/preview.tsx b/packages/twenty-front/.storybook/preview.tsx index f49ed56c58b9..1d67634e2a54 100644 --- a/packages/twenty-front/.storybook/preview.tsx +++ b/packages/twenty-front/.storybook/preview.tsx @@ -1,7 +1,7 @@ -import { useEffect } from 'react'; import { ThemeProvider } from '@emotion/react'; import { Preview } from '@storybook/react'; import { initialize, mswDecorator } from 'msw-storybook-addon'; +import { useEffect } from 'react'; import { useDarkMode } from 'storybook-dark-mode'; import { THEME_DARK, THEME_LIGHT, ThemeContextProvider } from 'twenty-ui'; @@ -13,12 +13,16 @@ import 'react-loading-skeleton/dist/skeleton.css'; initialize({ onUnhandledRequest: async (request: Request) => { const fileExtensionsToIgnore = - /\.(ts|tsx|js|jsx|svg|css|png)(\?v=[a-zA-Z0-9]+)?/; + /\.(ts|tsx|js|jsx|svg|css|png|woff2)(\?v=[a-zA-Z0-9]+)?/; if (fileExtensionsToIgnore.test(request.url)) { return; } + if (request.url.startsWith('http://localhost:3000/files/data:image')) { + return; + } + const requestBody = await request.json(); // eslint-disable-next-line no-console console.warn(`Unhandled ${request.method} request to ${request.url} diff --git a/packages/twenty-front/jest.config.ts b/packages/twenty-front/jest.config.ts index c71df7b77aff..8ed7f398db4e 100644 --- a/packages/twenty-front/jest.config.ts +++ b/packages/twenty-front/jest.config.ts @@ -2,6 +2,7 @@ import { JestConfigWithTsJest, pathsToModuleNameMapper } from 'ts-jest'; // eslint-disable-next-line @typescript-eslint/no-var-requires const tsConfig = require('./tsconfig.json'); +process.env.TZ = 'GMT'; const jestConfig: JestConfigWithTsJest = { // to enable logs, comment out the following line @@ -25,7 +26,7 @@ const jestConfig: JestConfigWithTsJest = { coverageThreshold: { global: { statements: 60, - lines: 60, + lines: 55, functions: 50, }, }, diff --git a/packages/twenty-front/nyc.config.cjs b/packages/twenty-front/nyc.config.cjs index 3fbf2dfd6204..8ae501c6910f 100644 --- a/packages/twenty-front/nyc.config.cjs +++ b/packages/twenty-front/nyc.config.cjs @@ -16,7 +16,7 @@ const modulesCoverage = { }; const pagesCoverage = { - branches: 40, + branches: 35, statements: 60, lines: 60, functions: 45, diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index 96e6442a3f56..9370f2460b92 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -1,6 +1,6 @@ { "name": "twenty-front", - "version": "0.31.canary", + "version": "0.31.0-canary", "private": true, "type": "module", "scripts": { diff --git a/packages/twenty-front/src/App.tsx b/packages/twenty-front/src/App.tsx index 1c6adcfdf60e..e8757d1b9913 100644 --- a/packages/twenty-front/src/App.tsx +++ b/packages/twenty-front/src/App.tsx @@ -58,43 +58,41 @@ const ProvidersThatNeedRouterContext = () => { const pageTitle = getPageTitleFromPath(pathname); return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 4f48fe60c424..64d5248023f6 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -992,6 +992,7 @@ export type RelationDefinition = { /** Relation definition type */ export enum RelationDefinitionType { + ManyToMany = 'MANY_TO_MANY', ManyToOne = 'MANY_TO_ONE', OneToMany = 'ONE_TO_MANY', OneToOne = 'ONE_TO_ONE' @@ -999,6 +1000,7 @@ export enum RelationDefinitionType { /** Type of the relation */ export enum RelationMetadataType { + ManyToMany = 'MANY_TO_MANY', ManyToOne = 'MANY_TO_ONE', OneToMany = 'ONE_TO_MANY', OneToOne = 'ONE_TO_ONE' diff --git a/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx b/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx index 38803c9efecf..c8de2f64c46a 100644 --- a/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx +++ b/packages/twenty-front/src/loading/components/LeftPanelSkeletonLoader.tsx @@ -3,6 +3,7 @@ import { motion } from 'framer-motion'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { ANIMATION, BACKGROUND_LIGHT, GRAY_SCALE } from 'twenty-ui'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { MainNavigationDrawerItemsSkeletonLoader } from '~/loading/components/MainNavigationDrawerItemsSkeletonLoader'; @@ -67,7 +68,10 @@ export const LeftPanelSkeletonLoader = () => { highlightColor={BACKGROUND_LIGHT.transparent.lighter} borderRadius={4} > - + diff --git a/packages/twenty-front/src/loading/components/MainNavigationDrawerItemsSkeletonLoader.tsx b/packages/twenty-front/src/loading/components/MainNavigationDrawerItemsSkeletonLoader.tsx index 2d660dfda236..bfa360a98070 100644 --- a/packages/twenty-front/src/loading/components/MainNavigationDrawerItemsSkeletonLoader.tsx +++ b/packages/twenty-front/src/loading/components/MainNavigationDrawerItemsSkeletonLoader.tsx @@ -1,3 +1,4 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import styled from '@emotion/styled'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { BACKGROUND_LIGHT, GRAY_SCALE } from 'twenty-ui'; @@ -26,9 +27,18 @@ export const MainNavigationDrawerItemsSkeletonLoader = ({ highlightColor={BACKGROUND_LIGHT.transparent.lighter} borderRadius={4} > - {title && } + {title && ( + + )} {Array.from({ length }).map((_, index) => ( - + ))} diff --git a/packages/twenty-front/src/loading/components/RightPanelSkeletonLoader.tsx b/packages/twenty-front/src/loading/components/RightPanelSkeletonLoader.tsx index 1c47a6cdcb6c..9e98594369ab 100644 --- a/packages/twenty-front/src/loading/components/RightPanelSkeletonLoader.tsx +++ b/packages/twenty-front/src/loading/components/RightPanelSkeletonLoader.tsx @@ -1,3 +1,4 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import styled from '@emotion/styled'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { @@ -60,7 +61,10 @@ const StyledSkeletonHeaderLoader = () => { highlightColor={BACKGROUND_LIGHT.transparent.lighter} borderRadius={4} > - + ); @@ -73,7 +77,7 @@ const StyledSkeletonAddLoader = () => { highlightColor={BACKGROUND_LIGHT.transparent.lighter} borderRadius={4} > - + ); }; diff --git a/packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx b/packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx index 21478ed73c32..0b65ab1143c3 100644 --- a/packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/activities/components/SkeletonLoader.tsx @@ -1,6 +1,6 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledSkeletonContainer = styled.div` align-items: center; @@ -25,6 +25,21 @@ const StyledSkeletonSubSectionContent = styled.div` justify-content: center; `; +export const SKELETON_LOADER_HEIGHT_SIZES = { + standard: { + xs: 13, + s: 16, + m: 24, + l: 32, + xl: 40, + }, + columns: { + s: 84, + m: 120, + xxl: 542, + }, +}; + const SkeletonColumnLoader = ({ height }: { height: number }) => { const theme = useTheme(); return ( @@ -55,15 +70,35 @@ export const SkeletonLoader = ({ borderRadius={4} > - + {withSubSections && skeletonItems.map(({ id }, index) => ( - + - - - {index === 1 && } + + + {index === 1 && ( + + )} ))} diff --git a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx index f88c7b79a1b5..0af5cec8224a 100644 --- a/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx +++ b/packages/twenty-front/src/modules/activities/emails/right-drawer/hooks/__tests__/useRightDrawerEmailThread.test.tsx @@ -1,56 +1,410 @@ -import { MockedProvider } from '@apollo/client/testing'; -import { renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; - -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; -import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; +import { renderHook, waitFor } from '@testing-library/react'; +import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; +import gql from 'graphql-tag'; +import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { useRightDrawerEmailThread } from '../useRightDrawerEmailThread'; -jest.mock('@/object-record/hooks/useFindOneRecord', () => ({ - __esModule: true, - useFindOneRecord: jest.fn(), -})); +const mocks = [ + { + request: { + query: gql` + query FindOneMessageThread($objectRecordId: ID!) { + messageThread(filter: { id: { eq: $objectRecordId } }) { + __typename + id + } + } + `, + variables: { objectRecordId: '1' }, + }, + result: jest.fn(() => ({ + data: { + messageThread: { + id: '1', + __typename: 'MessageThread', + }, + }, + })), + }, + { + request: { + query: gql` + query FindManyMessages( + $filter: MessageFilterInput + $orderBy: [MessageOrderByInput] + $lastCursor: String + $limit: Int + ) { + messages( + filter: $filter + orderBy: $orderBy + first: $limit + after: $lastCursor + ) { + edges { + node { + __typename + createdAt + headerMessageId + id + messageParticipants { + edges { + node { + __typename + displayName + handle + id + person { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + role + workspaceMember { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } + } + } + } + messageThread { + __typename + id + } + receivedAt + subject + text + } + cursor + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + } + } + `, + variables: { + filter: { messageThreadId: { eq: '1' } }, + orderBy: [{ receivedAt: 'AscNullsLast' }], + lastCursor: undefined, + limit: 10, + }, + }, + result: jest.fn(() => ({ + data: { + messages: { + edges: [ + { + node: generateEmptyJestRecordNode({ + objectNameSingular: 'message', + input: { + id: '1', + text: 'Message 1', + createdAt: '2024-10-03T10:20:10.145Z', + }, + }), + cursor: '1', + }, + { + node: generateEmptyJestRecordNode({ + objectNameSingular: 'message', + input: { + id: '2', + text: 'Message 2', + createdAt: '2024-10-03T10:20:10.145Z', + }, + }), + cursor: '2', + }, + ], + totalCount: 2, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '1', + endCursor: '2', + }, + }, + }, + })), + }, + { + request: { + query: gql` + query FindManyMessageParticipants( + $filter: MessageParticipantFilterInput + $orderBy: [MessageParticipantOrderByInput] + $lastCursor: String + $limit: Int + ) { + messageParticipants( + filter: $filter + orderBy: $orderBy + first: $limit + after: $lastCursor + ) { + edges { + node { + __typename + displayName + handle + id + messageId + person { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + role + workspaceMember { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } + } + cursor + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + } + } + `, + variables: { + filter: { messageId: { in: ['1', '2'] }, role: { eq: 'from' } }, + orderBy: undefined, + lastCursor: undefined, + limit: undefined, + }, + }, + result: jest.fn(() => ({ + data: { + messageParticipants: { + edges: [ + { + node: generateEmptyJestRecordNode({ + objectNameSingular: 'messageParticipant', + input: { + id: 'messageParticipant-1', + role: 'from', + messageId: '1', + }, + }), + cursor: '1', + }, + { + node: generateEmptyJestRecordNode({ + objectNameSingular: 'messageParticipant', + input: { + id: 'messageParticipant-2', + role: 'from', + messageId: '2', + }, + }), + cursor: '2', + }, + ], + totalCount: 2, + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '1', + endCursor: '2', + }, + }, + }, + })), + }, +]; -jest.mock('@/object-record/hooks/useFindManyRecords', () => ({ - __esModule: true, - useFindManyRecords: jest.fn(), -})); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, + onInitializeRecoilSnapshot: ({ set }) => { + set(viewableRecordIdState, '1'); + }, +}); describe('useRightDrawerEmailThread', () => { it('should return correct values', async () => { - const mockThread = { id: '1' }; - const mockMessages = [ - { id: '1', text: 'Message 1' }, - { id: '2', text: 'Message 2' }, + { + __typename: 'Message', + createdAt: '2024-10-03T10:20:10.145Z', + headerMessageId: '', + id: '1', + messageParticipants: [], + messageThread: null, + receivedAt: null, + sender: { + __typename: 'MessageParticipant', + displayName: '', + handle: '', + id: 'messageParticipant-1', + messageId: '1', + person: null, + role: 'from', + workspaceMember: null, + }, + subject: '', + text: 'Message 1', + }, + { + __typename: 'Message', + createdAt: '2024-10-03T10:20:10.145Z', + headerMessageId: '', + id: '2', + messageParticipants: [], + messageThread: null, + receivedAt: null, + sender: { + __typename: 'MessageParticipant', + displayName: '', + handle: '', + id: 'messageParticipant-2', + messageId: '2', + person: null, + role: 'from', + workspaceMember: null, + }, + subject: '', + text: 'Message 2', + }, ]; - const mockFetchMoreRecords = jest.fn(); - - (useFindOneRecord as jest.Mock).mockReturnValue({ - record: mockThread, - loading: false, - fetchMoreRecords: mockFetchMoreRecords, - }); - - (useFindManyRecords as jest.Mock).mockReturnValue({ - records: mockMessages, - loading: false, - fetchMoreRecords: mockFetchMoreRecords, - }); - const { result } = renderHook(() => useRightDrawerEmailThread(), { - wrapper: ({ children }) => ( - - {children} - - ), + wrapper: Wrapper, }); - expect(result.current.thread).toBeDefined(); - expect(result.current.messages).toEqual(mockMessages); - expect(result.current.threadLoading).toBeFalsy(); - expect(result.current.fetchMoreMessages).toBeInstanceOf(Function); + await waitFor(() => { + expect(result.current.thread).toBeDefined(); + expect(result.current.messages).toEqual(mockMessages); + expect(result.current.threadLoading).toBeFalsy(); + expect(result.current.fetchMoreMessages).toBeInstanceOf(Function); + }); }); }); diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx index 260e931a3ae2..7d1426f4cb75 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useActivityTargetObjectRecords.test.tsx @@ -9,7 +9,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members'; const cache = new InMemoryCache(); diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx index 1b56bc49d5da..baddb1029bda 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useCreateActivityInDB.test.tsx @@ -1,13 +1,11 @@ -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { MockedResponse } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; import gql from 'graphql-tag'; import pick from 'lodash.pick'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; import { useCreateActivityInDB } from '@/activities/hooks/useCreateActivityInDB'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { mockedTasks } from '~/testing/mock-data/tasks'; const mockedDate = '2024-03-15T12:00:00.000Z'; @@ -26,14 +24,44 @@ const mocks: MockedResponse[] = [ mutation CreateOneTask($input: TaskCreateInput!) { createTask(data: $input) { __typename - updatedAt + assignee { + __typename + id + name { + firstName + lastName + } + } + assigneeId + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } + } + body createdAt dueAt id status - body - assigneeId title + updatedAt } } `, @@ -56,15 +84,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useCreateActivityInDB', () => { it('Should create activity in DB', async () => { diff --git a/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx index 665702704d1b..855c0b55bd29 100644 --- a/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx +++ b/packages/twenty-front/src/modules/activities/hooks/__tests__/useOpenCreateActivityDrawer.test.tsx @@ -1,7 +1,6 @@ -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { MockedResponse } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot, useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; @@ -9,7 +8,8 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import gql from 'graphql-tag'; import pick from 'lodash.pick'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockedTasks } from '~/testing/mock-data/tasks'; const mockedDate = '2024-03-15T12:00:00.000Z'; @@ -61,13 +61,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const mockObjectMetadataItems = generatedMockObjectMetadataItems; diff --git a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts index 400c1f398a95..c35d27837121 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts @@ -15,6 +15,7 @@ import { Task } from '@/activities/types/Task'; import { TaskTarget } from '@/activities/types/TaskTarget'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; import { ActivityTargetableObject } from '../types/ActivityTargetableEntity'; @@ -52,7 +53,9 @@ export const useOpenCreateActivityDrawer = ({ const setViewableRecordNameSingular = useSetRecoilState( viewableRecordNameSingularState, ); - + const setIsNewViewableRecordLoading = useSetRecoilState( + isNewViewableRecordLoadingState, + ); const setIsUpsertingActivityInDB = useSetRecoilState( isUpsertingActivityInDBState, ); @@ -64,6 +67,11 @@ export const useOpenCreateActivityDrawer = ({ targetableObjects: ActivityTargetableObject[]; customAssignee?: WorkspaceMember; }) => { + openRightDrawer(RightDrawerPages.ViewRecord); + setIsNewViewableRecordLoading(true); + setViewableRecordId(null); + setViewableRecordNameSingular(activityObjectNameSingular); + const activity = await createOneActivity({ assigneeId: customAssignee?.id, }); @@ -101,10 +109,9 @@ export const useOpenCreateActivityDrawer = ({ setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false }); setViewableRecordId(activity.id); - setViewableRecordNameSingular(activityObjectNameSingular); - openRightDrawer(RightDrawerPages.ViewRecord); setIsUpsertingActivityInDB(false); + setIsNewViewableRecordLoading(false); }; return openCreateActivityDrawer; diff --git a/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskGroups.stories.tsx b/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskGroups.stories.tsx index 444049cc1844..aefa6f2ed59a 100644 --- a/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskGroups.stories.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskGroups.stories.tsx @@ -42,5 +42,8 @@ export const WithTasks: Story = { }, parameters: { msw: graphqlMocks, + container: { + width: '500px', + }, }, }; diff --git a/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskList.stories.tsx b/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskList.stories.tsx index 1113febb6223..c2a65b772be9 100644 --- a/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskList.stories.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/__stories__/TaskList.stories.tsx @@ -3,6 +3,7 @@ import { ComponentDecorator } from 'twenty-ui'; import { TaskList } from '@/activities/tasks/components/TaskList'; import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; import { mockedTasks } from '~/testing/mock-data/tasks'; @@ -10,13 +11,21 @@ import { mockedTasks } from '~/testing/mock-data/tasks'; const meta: Meta = { title: 'Modules/Activity/TaskList', component: TaskList, - decorators: [MemoryRouterDecorator, ComponentDecorator, SnackBarDecorator], + decorators: [ + ComponentDecorator, + MemoryRouterDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + ], args: { title: 'Tasks', tasks: mockedTasks, }, parameters: { msw: graphqlMocks, + container: { + width: '500px', + }, }, }; diff --git a/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx b/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx index ae484382ffcc..16ebbec0f38a 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskGroups.tsx @@ -27,6 +27,7 @@ import { TaskList } from './TaskList'; const StyledContainer = styled.div` display: flex; flex-direction: column; + width: 100%; `; type TaskGroupsProps = { diff --git a/packages/twenty-front/src/modules/activities/tasks/components/TaskList.tsx b/packages/twenty-front/src/modules/activities/tasks/components/TaskList.tsx index 04e56c8d65ed..10e7982ca04f 100644 --- a/packages/twenty-front/src/modules/activities/tasks/components/TaskList.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/components/TaskList.tsx @@ -12,6 +12,7 @@ type TaskListProps = { const StyledContainer = styled.div` align-items: flex-start; + width: 100%; align-self: stretch; display: flex; flex-direction: column; diff --git a/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx b/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx index ba3e2ac53482..814b72fb6ce1 100644 --- a/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx @@ -1,11 +1,10 @@ -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { MockedResponse } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; import gql from 'graphql-tag'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; import { useCompleteTask } from '@/activities/tasks/hooks/useCompleteTask'; import { Task } from '@/activities/types/Task'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const task: Task = { id: '123', @@ -28,21 +27,123 @@ const mocks: MockedResponse[] = [ mutation UpdateOneTask($idToUpdate: ID!, $input: TaskUpdateInput!) { updateTask(id: $idToUpdate, data: $input) { __typename - updatedAt - createdAt - deletedAt - dueAt - id - status + assignee { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } + assigneeId + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } + } body + createdAt createdBy { source workspaceMemberId name } - assigneeId + deletedAt + dueAt + favorites { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + position + rocketId + taskId + updatedAt + viewId + workflowId + workspaceMemberId + } + } + } + id position + status + taskTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + taskId + updatedAt + } + } + } + timelineActivities { + edges { + node { + __typename + companyId + createdAt + deletedAt + happensAt + id + linkedObjectMetadataId + linkedRecordCachedName + linkedRecordId + name + noteId + opportunityId + personId + properties + rocketId + taskId + updatedAt + workspaceMemberId + } + } + } title + updatedAt } } `, @@ -72,13 +173,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useCompleteTask', () => { it('should complete task', async () => { diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/hooks/__tests__/useTimelineActivities.test.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/hooks/__tests__/useTimelineActivities.test.tsx index 894d541c888b..2d1989cc68ea 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/hooks/__tests__/useTimelineActivities.test.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/hooks/__tests__/useTimelineActivities.test.tsx @@ -1,21 +1,16 @@ import { renderHook } from '@testing-library/react'; import { useTimelineActivities } from '@/activities/timelineActivities/hooks/useTimelineActivities'; -import { ReactNode } from 'react'; -import { getJestHookWrapper } from '~/testing/jest/getJestHookWrapper'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; jest.mock('@/object-record/hooks/useFindManyRecords', () => ({ useFindManyRecords: jest.fn(), })); -const Wrappers = getJestHookWrapper({ +const Wrapper = getJestMetadataAndApolloMocksWrapper({ apolloMocks: [], }); -const Wrapper = ({ children }: { children: ReactNode }) => ( - {children} -); - describe('useTimelineActivities', () => { afterEach(() => { jest.clearAllMocks(); diff --git a/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx index 0cb945bdc4b2..224438b853cf 100644 --- a/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx +++ b/packages/twenty-front/src/modules/activities/timelineActivities/rows/main-object/components/__stories__/EventRowMainObjectUpdated.stories.tsx @@ -5,7 +5,7 @@ import { EventRowMainObjectUpdated } from '@/activities/timelineActivities/rows/ import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; -import { mockedPersonObjectMetadataItem } from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const meta: Meta = { title: 'Modules/TimelineActivities/Rows/MainObject/EventRowMainObjectUpdated', @@ -35,7 +35,9 @@ const meta: Meta = { }, }, } as TimelineActivity, - mainObjectMetadataItem: mockedPersonObjectMetadataItem, + mainObjectMetadataItem: generatedMockObjectMetadataItems.find( + (item) => item.namePlural === 'person', + ), }, decorators: [ ComponentDecorator, diff --git a/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider.tsx b/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider.tsx index 3c4d8556a4bf..9409540ead23 100644 --- a/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider.tsx +++ b/packages/twenty-front/src/modules/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider.tsx @@ -1,56 +1,46 @@ -import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; - -import { isLoadingTokensFromExtensionState } from '@/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState'; -import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState'; -import { isDefined } from '~/utils/isDefined'; -import { isInFrame } from '~/utils/isInIframe'; - -const StyledContainer = styled.div` - align-items: center; - display: flex; - flex-direction: column; - height: 100vh; - justify-content: center; -`; - -const AppInaccessible = ({ message }: { message: string }) => { - return ( - - twenty-icon -

{message}

-
- ); -}; +// const StyledContainer = styled.div` +// align-items: center; +// display: flex; +// flex-direction: column; +// height: 100vh; +// justify-content: center; +// `; + +// const AppInaccessible = ({ message }: { message: string }) => { +// return ( +// +// twenty-icon +//

{message}

+//
+// ); +// }; export const ChromeExtensionSidecarProvider: React.FC< React.PropsWithChildren > = ({ children }) => { - const isLoadingTokensFromExtension = useRecoilValue( - isLoadingTokensFromExtensionState, - ); - const chromeExtensionId = useRecoilValue(chromeExtensionIdState); - - if (!isInFrame()) return <>{children}; - - if (!isDefined(chromeExtensionId)) - return ( - - ); - - if (isDefined(isLoadingTokensFromExtension) && !isLoadingTokensFromExtension) - return ( - - ); - - return isLoadingTokensFromExtension && <>{children}; + return <>{children}; + + // TODO: this is conflictting with storybook tests + // if (!isInFrame()) return <>{children}; + + // if (!isDefined(chromeExtensionId)) + // return ( + // + // ); + + // if (isDefined(isLoadingTokensFromExtension) && !isLoadingTokensFromExtension) + // return ( + // + // ); + + // return isLoadingTokensFromExtension && <>{children}; }; diff --git a/packages/twenty-front/src/modules/favorites/components/FavoritesSkeletonLoader.tsx b/packages/twenty-front/src/modules/favorites/components/FavoritesSkeletonLoader.tsx index 3822a12bc9b6..5e5d1ea7f8ec 100644 --- a/packages/twenty-front/src/modules/favorites/components/FavoritesSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/favorites/components/FavoritesSkeletonLoader.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledSkeletonContainer = styled.div` display: flex; @@ -25,10 +26,19 @@ export const FavoritesSkeletonLoader = () => { borderRadius={4} > - + - - + + diff --git a/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts b/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts index 98813ad384c7..cf0fe8e888b1 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts @@ -87,262 +87,264 @@ export const mocks = [ mutation CreateOneFavorite($input: FavoriteCreateInput!) { createFavorite(data: $input) { __typename - noteId - taskId - person { + company { __typename - name { - firstName - lastName + accountOwnerId + address { + addressStreet1 + addressStreet2 + addressCity + addressState + addressCountry + addressPostcode + addressLat + addressLng + } + annualRecurringRevenue { + amountMicros + currencyCode + } + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + id + idealCustomerProfile + introVideo { + primaryLinkUrl + primaryLinkLabel + secondaryLinks } linkedinLink { primaryLinkUrl primaryLinkLabel secondaryLinks } - deletedAt - createdAt + name + position + tagline updatedAt - jobTitle - intro - workPrefereance - performanceRating + visaSponsorship + workPolicy xLink { primaryLinkUrl primaryLinkLabel secondaryLinks } - city - companyId - phones { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones + } + companyId + createdAt + deletedAt + id + note { + __typename + body + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + position + title + updatedAt + } + noteId + opportunity { + __typename + amount { + amountMicros + currencyCode } + closeDate + companyId + createdAt createdBy { source workspaceMemberId name } + deletedAt id + name + pointOfContactId position + stage + updatedAt + } + opportunityId + person { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt emails { primaryEmail additionalEmails } - avatarUrl + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt whatsapp { primaryPhoneNumber primaryPhoneCountryCode additionalPhones } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } } - task { + personId + position + rocket { __typename - updatedAt createdAt - deletedAt - dueAt - id - status - body createdBy { source workspaceMemberId name } - assigneeId - position - title - } - rocketId - viewId - updatedAt - workflowId - personId - workspaceMemberId - note { - __typename deletedAt id + name position updatedAt + } + rocketId + task { + __typename + assigneeId + body + createdAt createdBy { source workspaceMemberId name } - body + deletedAt + dueAt + id + position + status title - createdAt + updatedAt } - createdAt + taskId + updatedAt view { __typename - id - type + createdAt + deletedAt icon - key + id isCompact kanbanFieldMetadataId + key + name objectMetadataId position - createdAt - deletedAt + type updatedAt - name } - opportunityId - position - deletedAt - id - companyId + viewId workflow { __typename - deletedAt - lastPublishedVersionId createdAt + deletedAt id - statuses + lastPublishedVersionId name position + statuses updatedAt } + workflowId workspaceMember { __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale name { firstName lastName } - avatarUrl - userId - createdAt - timeZone - id timeFormat + timeZone updatedAt - locale userEmail - deletedAt - colorScheme - dateFormat + userId } - company { - __typename - updatedAt - domainName { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - visaSponsorship - address { - addressStreet1 - addressStreet2 - addressCity - addressState - addressCountry - addressPostcode - addressLat - addressLng - } - position - employees - deletedAt - accountOwnerId - annualRecurringRevenue { - amountMicros - currencyCode - } - id - name - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - createdAt - createdBy { - source - workspaceMemberId - name - } - workPolicy - introVideo { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - tagline - idealCustomerProfile - } - rocket { - __typename - createdBy { - source - workspaceMemberId - name - } - updatedAt - name - position - createdAt - id - deletedAt - } - opportunity { - __typename - createdBy { - source - workspaceMemberId - name - } - amount { - amountMicros - currencyCode - } - stage - position - closeDate - id - name - pointOfContactId - companyId - updatedAt - deletedAt - createdAt - } - } - } - `, - variables: { - input: { - id: mockId, - personId: favoriteTargetObjectId, - position: 4, - workspaceMemberId: '1', - }, - }, - }, - result: jest.fn(() => ({ - data: { - createFavorite: { - id: favoriteId, - }, - }, - })), - }, - { - request: { - query: gql` - mutation DeleteOneFavorite($idToDelete: ID!) { - deleteFavorite(id: $idToDelete) { - id + workspaceMemberId + } + } + `, + variables: { + input: { + id: mockId, + personId: favoriteTargetObjectId, + position: 4, + workspaceMemberId: '1', + }, + }, + }, + result: jest.fn(() => ({ + data: { + createFavorite: { + id: favoriteId, + }, + }, + })), + }, + { + request: { + query: gql` + mutation DeleteOneFavorite($idToDelete: ID!) { + deleteFavorite(id: $idToDelete) { + __typename + deletedAt + id } } `, @@ -365,236 +367,236 @@ export const mocks = [ ) { updateFavorite(id: $idToUpdate, data: $input) { __typename - noteId - taskId - person { - __typename - name { - firstName - lastName - } - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - deletedAt - createdAt - updatedAt - jobTitle - intro - workPrefereance - performanceRating - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - city - companyId - phones { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } - createdBy { - source - workspaceMemberId - name - } - id - position - emails { - primaryEmail - additionalEmails - } - avatarUrl - whatsapp { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } - } - task { - __typename - updatedAt - createdAt - deletedAt - dueAt - id - status - body - createdBy { - source - workspaceMemberId - name - } - assigneeId - position - title - } - rocketId - viewId - updatedAt - workflowId - personId - workspaceMemberId - note { - __typename - deletedAt - id - position - updatedAt - createdBy { - source - workspaceMemberId - name - } - body - title - createdAt - } - createdAt - view { - __typename - id - type - icon - key - isCompact - kanbanFieldMetadataId - objectMetadataId - position - createdAt - deletedAt - updatedAt - name - } - opportunityId - position - deletedAt - id - companyId - workflow { - __typename - deletedAt - lastPublishedVersionId - createdAt - id - statuses - name - position - updatedAt - } - workspaceMember { - __typename - name { - firstName - lastName - } - avatarUrl - userId - createdAt - timeZone - id - timeFormat - updatedAt - locale - userEmail - deletedAt - colorScheme - dateFormat - } - company { - __typename - updatedAt - domainName { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - visaSponsorship - address { - addressStreet1 - addressStreet2 - addressCity - addressState - addressCountry - addressPostcode - addressLat - addressLng - } - position - employees - deletedAt - accountOwnerId - annualRecurringRevenue { - amountMicros - currencyCode - } - id - name - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - createdAt - createdBy { - source - workspaceMemberId - name - } - workPolicy - introVideo { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - tagline - idealCustomerProfile - } - rocket { - __typename - createdBy { - source - workspaceMemberId - name - } - updatedAt - name - position - createdAt - id - deletedAt - } - opportunity { - __typename - createdBy { - source + company { + __typename + accountOwnerId + address { + addressStreet1 + addressStreet2 + addressCity + addressState + addressCountry + addressPostcode + addressLat + addressLng + } + annualRecurringRevenue { + amountMicros + currencyCode + } + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + id + idealCustomerProfile + introVideo { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name + position + tagline + updatedAt + visaSponsorship + workPolicy + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + companyId + createdAt + deletedAt + id + note { + __typename + body + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + position + title + updatedAt + } + noteId + opportunity { + __typename + amount { + amountMicros + currencyCode + } + closeDate + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + name + pointOfContactId + position + stage + updatedAt + } + opportunityId + person { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + personId + position + rocket { + __typename + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + name + position + updatedAt + } + rocketId + task { + __typename + assigneeId + body + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + dueAt + id + position + status + title + updatedAt + } + taskId + updatedAt + view { + __typename + createdAt + deletedAt + icon + id + isCompact + kanbanFieldMetadataId + key + name + objectMetadataId + position + type + updatedAt + } + viewId + workflow { + __typename + createdAt + deletedAt + id + lastPublishedVersionId + name + position + statuses + updatedAt + } + workflowId + workspaceMember { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } workspaceMemberId - name - } - amount { - amountMicros - currencyCode - } - stage - position - closeDate - id - name - pointOfContactId - companyId - updatedAt - deletedAt - createdAt - } } } `, diff --git a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx index 3a30077ea285..9c984ececa94 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx +++ b/packages/twenty-front/src/modules/favorites/hooks/__tests__/useFavorites.test.tsx @@ -1,16 +1,14 @@ -import { MockedProvider } from '@apollo/client/testing'; import { DropResult, ResponderProvided } from '@hello-pangea/dnd'; import { act, renderHook, waitFor } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { favoriteId, favoriteTargetObjectRecord, @@ -29,15 +27,9 @@ jest.mock('@/object-record/hooks/useFindManyRecords', () => ({ useFindManyRecords: () => ({ records: initialFavorites }), })); -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useFavorites', () => { it('should fetch favorites successfully', async () => { diff --git a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts index 1ec02c2fd071..a7fb3b4e51ed 100644 --- a/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts +++ b/packages/twenty-front/src/modules/navigation/hooks/__tests__/useDefaultHomePagePath.test.ts @@ -6,7 +6,7 @@ import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePat import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData'; import { AppPath } from '@/types/AppPath'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockedUserData } from '~/testing/mock-data/users'; jest.mock('@/prefetch/hooks/usePrefetchedData'); diff --git a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader.tsx index 70e00d717330..70b965b1827f 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader.tsx @@ -1,3 +1,4 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; @@ -20,9 +21,18 @@ export const NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader: React. borderRadius={4} > - - - + + + ); diff --git a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx index dd1971790bb1..c8659e1689c8 100644 --- a/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx +++ b/packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsLoadEffect.tsx @@ -9,7 +9,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { WorkspaceActivationStatus } from '~/generated/graphql'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/ApolloMetadataClientProvider.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/ApolloMetadataClientMockedProvider.tsx similarity index 100% rename from packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/ApolloMetadataClientProvider.tsx rename to packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/ApolloMetadataClientMockedProvider.tsx diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts index f3c3e93f1b2c..0e7470a10359 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__mocks__/useFieldMetadataItem.ts @@ -48,7 +48,18 @@ export const queries = { createMetadataField: gql` mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) { createOneField(input: $input) { - ${baseFields} + id + type + name + label + description + icon + isCustom + isActive + isNullable + createdAt + updatedAt + settings defaultValue options } diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts index 558d2ff3034c..05c87497dcc6 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts @@ -3,7 +3,7 @@ import { Nullable } from 'twenty-ui'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('useColumnDefinitionsFromFieldMetadata', () => { it('should return empty definitions if no object is passed', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useCreateOneObjectMetadataItem.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useCreateOneObjectMetadataItem.test.tsx index 03443712d9de..cc92f241e31c 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useCreateOneObjectMetadataItem.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useCreateOneObjectMetadataItem.test.tsx @@ -1,10 +1,8 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { findManyViewsQuery, query, @@ -47,13 +45,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useCreateOneObjectMetadataItem', () => { it('should work as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx index 47b1a6c18c88..6795897e3cfd 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFieldMetadataItem.test.tsx @@ -1,6 +1,6 @@ -import { ReactNode } from 'react'; import { MockedProvider } from '@apollo/client/testing'; -import { act, renderHook } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; +import { act, ReactNode } from 'react'; import { RecoilRoot } from 'recoil'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx index 4d16bb6d8af9..02e39712fe57 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useFilteredObjectMetadataItems.test.tsx @@ -10,7 +10,7 @@ import { } from '@/object-metadata/hooks/__mocks__/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const mocks = [ { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectOrderByField.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectOrderByField.test.tsx index 21ec56ad60b4..41220e695b94 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectOrderByField.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectOrderByField.test.tsx @@ -1,15 +1,11 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { useGetObjectOrderByField } from '@/object-metadata/hooks/useGetObjectOrderByField'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - {children} - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); describe('useGetObjectOrderByField', () => { it('should work as expected', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx index a457bd27eff7..938796b78714 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetObjectRecordIdentifierByNameSingular.test.tsx @@ -3,7 +3,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('useGetObjectRecordIdentifierByNameSingular', () => { it('should work as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx index 12ef572b6adf..7455088f27bc 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useGetRelationMetadata.test.tsx @@ -5,7 +5,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const Wrapper = ({ children }: { children: ReactNode }) => ( diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapToObjectRecordIdentifier.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapToObjectRecordIdentifier.test.tsx index d0157678c04a..8792fb31299a 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapToObjectRecordIdentifier.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useMapToObjectRecordIdentifier.test.tsx @@ -1,7 +1,11 @@ import { renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { useMapToObjectRecordIdentifier } from '@/object-metadata/hooks/useMapToObjectRecordIdentifier'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; + +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); describe('useMapToObjectRecordIdentifier', () => { it('should work as expected', async () => { @@ -18,7 +22,7 @@ describe('useMapToObjectRecordIdentifier', () => { }); }, { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItem.test.tsx b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItem.test.tsx index ee757d7ff7b2..876846f2bd4b 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItem.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useObjectMetadataItem.test.tsx @@ -1,16 +1,12 @@ -import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - {children} - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); // Split into tests for each new hook describe('useObjectMetadataItem', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts index 25208307eabd..88ac0baab21d 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNamePluralFromSingular.ts @@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { WorkspaceActivationStatus } from '~/generated/graphql'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { isDefined } from '~/utils/isDefined'; export const useObjectNamePluralFromSingular = ({ diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts index 5a94a5191b9f..2e5127d8fcd9 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectNameSingularFromPlural.ts @@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { WorkspaceActivationStatus } from '~/generated/graphql'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { isDefined } from '~/utils/isDefined'; export const useObjectNameSingularFromPlural = ({ diff --git a/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts index 61ce60263dfc..8a403a55a379 100644 --- a/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts @@ -17,7 +17,7 @@ export type FieldMetadataItemOption = { export type FieldMetadataItem = Omit< Field, - '__typename' | 'defaultValue' | 'options' | 'settings' | 'relationDefinition' + '__typename' | 'defaultValue' | 'options' | 'relationDefinition' > & { __typename?: string; defaultValue?: any; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectMetadataItemBySingularName.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectMetadataItemBySingularName.test.ts index ea2f1d5eee19..92a7e414b822 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectMetadataItemBySingularName.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectMetadataItemBySingularName.test.ts @@ -1,5 +1,5 @@ import { getObjectMetadataItemByNameSingular } from '@/object-metadata/utils/getObjectMetadataItemBySingularName'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('getObjectMetadataItemBySingularName', () => { it('should work as expected', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectOrderByField.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectOrderByField.test.ts index 78c0e7047a4a..cc5691412fd3 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectOrderByField.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectOrderByField.test.ts @@ -1,5 +1,5 @@ import { getOrderByFieldForObjectMetadataItem } from '@/object-metadata/utils/getObjectOrderByField'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('getObjectOrderByField', () => { it('should work as expected', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectSlug.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectSlug.test.ts index c43c9d039e7c..526c6fe47635 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectSlug.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/getObjectSlug.test.ts @@ -1,5 +1,5 @@ import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('getObjectSlug', () => { it('should work as expected', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/isObjectMetadataAvailableForRelation.test.ts b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/isObjectMetadataAvailableForRelation.test.ts index caefb7feed45..ece3ae39de06 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/isObjectMetadataAvailableForRelation.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/isObjectMetadataAvailableForRelation.test.ts @@ -1,5 +1,5 @@ import { isObjectMetadataAvailableForRelation } from '@/object-metadata/utils/isObjectMetadataAvailableForRelation'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; describe('isObjectMetadataAvailableForRelation', () => { it('should work as expected', () => { diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx index 755ce29ac677..7208246e7e49 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapFieldMetadataToGraphQLQuery.test.tsx @@ -1,5 +1,5 @@ import { mapFieldMetadataToGraphQLQuery } from '@/object-metadata/utils/mapFieldMetadataToGraphQLQuery'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { normalizeGQLField } from '~/utils/normalizeGQLField'; const personObjectMetadataItem = generatedMockObjectMetadataItems.find( diff --git a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx index c47eae446715..d2650b69807f 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-metadata/utils/__tests__/mapObjectMetadataToGraphQLQuery.test.tsx @@ -1,5 +1,5 @@ import { mapObjectMetadataToGraphQLQuery } from '@/object-metadata/utils/mapObjectMetadataToGraphQLQuery'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { normalizeGQLQuery } from '~/utils/normalizeGQLQuery'; const personObjectMetadataItem = generatedMockObjectMetadataItems.find( diff --git a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts index 8ba9ebe23315..f372cd2eb3ac 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition.ts @@ -54,5 +54,6 @@ export const formatFieldMetadataItemAsFieldDefinition = ({ metadata: fieldDefintionMetadata, type: field.type, }), + settings: field.settings, }; }; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts index f9e248815d99..9e705d428ced 100644 --- a/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts +++ b/packages/twenty-front/src/modules/object-metadata/utils/mapObjectMetadataToGraphQLQuery.ts @@ -18,6 +18,7 @@ export const mapObjectMetadataToGraphQLQuery = ({ const fieldsThatShouldBeQueried = objectMetadataItem?.fields .filter((field) => field.isActive) + .sort((fieldA, fieldB) => fieldA.name.localeCompare(fieldB.name)) .filter((field) => shouldFieldBeQueried({ field, diff --git a/packages/twenty-front/src/modules/object-metadata/validation-schemas/__tests__/objectMetadataItemSchema.test.ts b/packages/twenty-front/src/modules/object-metadata/validation-schemas/__tests__/objectMetadataItemSchema.test.ts index 0e8d60c66fb9..cbb1b2c46b3d 100644 --- a/packages/twenty-front/src/modules/object-metadata/validation-schemas/__tests__/objectMetadataItemSchema.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/validation-schemas/__tests__/objectMetadataItemSchema.test.ts @@ -1,11 +1,12 @@ -import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata'; - +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { objectMetadataItemSchema } from '../objectMetadataItemSchema'; describe('objectMetadataItemSchema', () => { it('validates a valid object metadata item', () => { // Given - const validObjectMetadataItem = mockedCompanyObjectMetadataItem; + const validObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', + ); // When const result = objectMetadataItemSchema.parse(validObjectMetadataItem); diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts b/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts index 219d05f854e5..4641974a0632 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/__tests__/getRecordNodeFromRecord.test.ts @@ -1,10 +1,8 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { - mockedObjectMetadataItems, - mockedPersonObjectMetadataItem, -} from '~/testing/mock-data/metadata'; + import { getPeopleMock } from '~/testing/mock-data/people'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getRecordNodeFromRecord } from '../getRecordNodeFromRecord'; const peopleMock = getPeopleMock(); @@ -12,11 +10,18 @@ const peopleMock = getPeopleMock(); describe('getRecordNodeFromRecord', () => { it('computes relation records cache references by default', () => { // Given - const objectMetadataItems: ObjectMetadataItem[] = mockedObjectMetadataItems; - const objectMetadataItem: Pick< - ObjectMetadataItem, - 'fields' | 'namePlural' | 'nameSingular' - > = mockedPersonObjectMetadataItem; + const objectMetadataItems: ObjectMetadataItem[] = + generatedMockObjectMetadataItems; + const objectMetadataItem: + | Pick + | undefined = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', + ); + + if (!objectMetadataItem) { + throw new Error('Object metadata item not found'); + } + const recordGqlFields = { name: true, company: true, @@ -47,11 +52,18 @@ describe('getRecordNodeFromRecord', () => { it('does not compute relation records cache references when `computeReferences` is false', () => { // Given - const objectMetadataItems: ObjectMetadataItem[] = mockedObjectMetadataItems; - const objectMetadataItem: Pick< - ObjectMetadataItem, - 'fields' | 'namePlural' | 'nameSingular' - > = mockedPersonObjectMetadataItem; + const objectMetadataItems: ObjectMetadataItem[] = + generatedMockObjectMetadataItems; + const objectMetadataItem: + | Pick + | undefined = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', + ); + + if (!objectMetadataItem) { + throw new Error('Object metadata item not found'); + } + const recordGqlFields = { name: true, company: true, diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts index d7faf5a77f29..2ab3b25344fa 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/getRecordNodeFromRecord.ts @@ -65,7 +65,9 @@ export const getRecordNodeFromRecord = ({ RelationDefinitionType.OneToMany ) { const oneToManyObjectMetadataItem = objectMetadataItems.find( - (item) => item.namePlural === fieldName, + (item) => + item.namePlural === + field.relationDefinition?.targetObjectMetadata.namePlural, ); if (!oneToManyObjectMetadataItem) { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragment.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragment.ts deleted file mode 100644 index 8a2b3f08567d..000000000000 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragment.ts +++ /dev/null @@ -1,48 +0,0 @@ -export const PERSON_FRAGMENT = ` - __typename - name { - firstName - lastName - } - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - deletedAt - createdAt - updatedAt - jobTitle - intro - workPrefereance - performanceRating - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - city - companyId - phones { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } - createdBy { - source - workspaceMemberId - name - } - id - position - emails { - primaryEmail - additionalEmails - } - avatarUrl - whatsapp { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } -` diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragments.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragments.ts new file mode 100644 index 000000000000..21cf8b2848b2 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/personFragments.ts @@ -0,0 +1,327 @@ +export const PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS = ` + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink{ + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } +` + +export const PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS = ` + __typename + activityTargets { + edges { + node { + __typename + activityId + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + updatedAt + } + } + } + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } + } + avatarUrl + calendarEventParticipants { + edges { + node { + __typename + calendarEventId + createdAt + deletedAt + displayName + handle + id + isOrganizer + personId + responseStatus + updatedAt + workspaceMemberId + } + } + } + city + company { + __typename + accountOwnerId + address { + addressStreet1 + addressStreet2 + addressCity + addressState + addressCountry + addressPostcode + addressLat + addressLng + } + annualRecurringRevenue { + amountMicros + currencyCode + } + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + id + idealCustomerProfile + introVideo { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name + position + tagline + updatedAt + visaSponsorship + workPolicy + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + favorites { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + position + rocketId + taskId + updatedAt + viewId + workflowId + workspaceMemberId + } + } + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + messageParticipants { + edges { + node { + __typename + createdAt + deletedAt + displayName + handle + id + messageId + personId + role + updatedAt + workspaceMemberId + } + } + } + name { + firstName + lastName + } + noteTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + rocketId + updatedAt + } + } + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + pointOfContactForOpportunities { + edges { + node { + __typename + amount { + amountMicros + currencyCode + } + closeDate + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + name + pointOfContactId + position + stage + updatedAt + } + } + } + position + taskTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + taskId + updatedAt + } + } + } + timelineActivities { + edges { + node { + __typename + companyId + createdAt + deletedAt + happensAt + id + linkedObjectMetadataId + linkedRecordCachedName + linkedRecordId + name + noteId + opportunityId + personId + properties + rocketId + taskId + updatedAt + workspaceMemberId + } + } + } + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } +` diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts index fd28307168b5..b9d5b32b8b36 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateManyRecords.ts @@ -1,12 +1,12 @@ import { gql } from '@apollo/client'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { Person } from '@/people/types/Person'; export const query = gql` mutation CreatePeople($data: [PersonCreateInput!]!, $upsert: Boolean) { createPeople(data: $data, upsert: $upsert) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts index 6804a92c3525..6a0261794f3c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useCreateOneRecord.ts @@ -1,10 +1,10 @@ -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { gql } from '@apollo/client'; export const query = gql` mutation CreateOnePerson($input: PersonCreateInput!) { createPerson(data: $input) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts index f31b210e089b..2e7ce9bc5320 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts @@ -3,6 +3,8 @@ import { gql } from '@apollo/client'; export const query = gql` mutation DeleteOnePerson($idToDelete: ID!) { deletePerson(id: $idToDelete) { + __typename + deletedAt id } } diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts index 2e50e02aac84..784e178fc785 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts @@ -1,4 +1,4 @@ -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { gql } from '@apollo/client'; import { getPeopleMock } from '~/testing/mock-data/people'; @@ -9,7 +9,7 @@ export const query = gql` personDuplicates(ids: $ids) { edges { node { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } cursor } diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts index 26ac2981aa37..075dabb052f5 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts @@ -1,12 +1,12 @@ import { gql } from '@apollo/client'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { responseData as person } from './useUpdateOneRecord'; export const query = gql` query FindOnePerson($objectRecordId: ID!) { person(filter: { id: { eq: $objectRecordId } }) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts index 15bb27bc76b1..1c109b9e3ed4 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts @@ -1,10 +1,10 @@ -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { gql } from '@apollo/client'; export const query = gql` mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx index 9eb14eadd799..761680c43081 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecords.test.tsx @@ -1,8 +1,5 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { mocked } from '@storybook/test'; import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { v4 } from 'uuid'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; @@ -12,6 +9,7 @@ import { variables, } from '@/object-record/hooks/__mocks__/useCreateManyRecords'; import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; jest.mock('uuid', () => ({ v4: jest.fn(), @@ -37,13 +35,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useCreateManyRecords', () => { it('works as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecordsMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecordsMutation.test.tsx index 3b4d1ad42d4c..a5ab3efec117 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecordsMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateManyRecordsMutation.test.tsx @@ -1,18 +1,22 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useCreateManyRecordsMutation } from '@/object-record/hooks/useCreateManyRecordsMutation'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` mutation CreatePeople($data: [PersonCreateInput!]!, $upsert: Boolean) { createPeople(data: $data, upsert: $upsert) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useCreateManyRecordsMutation', () => { it('should return a valid createManyRecordsMutation', () => { const objectNameSingular = 'person'; @@ -23,7 +27,7 @@ describe('useCreateManyRecordsMutation', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx index 837af4dd2bf6..db26b860517a 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecord.test.tsx @@ -1,7 +1,4 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { @@ -9,6 +6,7 @@ import { responseData, } from '@/object-record/hooks/__mocks__/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const personId = 'a7286b9a-c039-4a89-9567-2dfa7953cda9'; const input = { name: { firstName: 'John', lastName: 'Doe' } }; @@ -31,13 +29,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useCreateOneRecord', () => { it('works as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecordMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecordMutation.test.tsx index 18db35f815b0..376a085cde7c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecordMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useCreateOneRecordMutation.test.tsx @@ -1,18 +1,22 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` mutation CreateOnePerson($input: PersonCreateInput!) { createPerson(data: $input) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useCreateOneRecordMutation', () => { it('should return a valid createOneRecordMutation', () => { const objectNameSingular = 'person'; @@ -23,7 +27,7 @@ describe('useCreateOneRecordMutation', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx index 1c7dd33d33e6..ada53864a7a6 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx @@ -1,7 +1,4 @@ -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; import { query, @@ -9,6 +6,7 @@ import { variables, } from '@/object-record/hooks/__mocks__/useDeleteManyRecords'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const people = [ 'a7286b9a-c039-4a89-9567-2dfa7953cda9', @@ -29,13 +27,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useDeleteManyRecords', () => { it('works as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecordsMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecordsMutation.test.tsx index 7f96b1be6114..cc5b85d6b664 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecordsMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecordsMutation.test.tsx @@ -1,8 +1,8 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` mutation DeleteManyPeople($filter: PersonFilterInput!) { @@ -12,6 +12,10 @@ const expectedQueryTemplate = ` } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useDeleteManyRecordsMutation', () => { it('should return a valid deleteManyRecordsMutation', () => { const objectNameSingular = 'person'; @@ -22,7 +26,7 @@ describe('useDeleteManyRecordsMutation', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx index 731a468a2835..0c347d309520 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecord.test.tsx @@ -1,7 +1,5 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; -import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; import { query, @@ -9,6 +7,7 @@ import { variables, } from '@/object-record/hooks/__mocks__/useDeleteOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const personId = 'a7286b9a-c039-4a89-9567-2dfa7953cda9'; @@ -26,13 +25,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useDeleteOneRecord', () => { it('works as expected', async () => { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx index 3bbc51f65a85..859355818ae8 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx @@ -1,17 +1,23 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` mutation DeleteOnePerson($idToDelete: ID!) { deletePerson(id: $idToDelete) { + __typename + deletedAt id } } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useDeleteOneRecordMutation', () => { it('should return a valid deleteOneRecordMutation', () => { const objectNameSingular = 'person'; @@ -22,7 +28,7 @@ describe('useDeleteOneRecordMutation', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx index 02095502e9ed..80b57d7dc5bd 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFetchAllRecordIds.test.tsx @@ -1,7 +1,6 @@ -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { ReactNode, useEffect } from 'react'; -import { RecoilRoot, useRecoilState } from 'recoil'; +import { useEffect } from 'react'; +import { useRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { @@ -16,8 +15,8 @@ import { variablesThirdRequest, } from '@/object-record/hooks/__mocks__/useFetchAllRecordIds'; import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds'; -import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const mocks = [ { @@ -52,22 +51,12 @@ const mocks = [ }, ]; +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); + describe('useFetchAllRecordIds', () => { it('fetches all record ids with fetch more synchronous loop', async () => { - const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - - ); - const { result } = renderHook( () => { const [, setObjectMetadataItems] = useRecoilState( diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecords.test.tsx index e8616d1da1a7..61cd950ff76f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecords.test.tsx @@ -1,11 +1,8 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { renderHook, waitFor } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { useFindDuplicateRecords } from '@/object-record/hooks/useFindDuplicateRecords'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { query, responseData, @@ -24,15 +21,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useFindDuplicateRecords', () => { it('should fetch duplicate records and return the correct data', async () => { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx index e7d0ad73348f..10cb78a400d3 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx @@ -1,16 +1,16 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useFindDuplicateRecordsQuery } from '@/object-record/hooks/useFindDuplicatesRecordsQuery'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` query FindDuplicatePerson($ids: [ID!]!) { personDuplicates(ids: $ids) { edges { node { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } cursor } @@ -23,6 +23,10 @@ const expectedQueryTemplate = ` } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useFindDuplicateRecordsQuery', () => { it('should return a valid findDuplicateRecordsQuery', () => { const objectNameSingular = 'person'; @@ -33,7 +37,7 @@ describe('useFindDuplicateRecordsQuery', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx index d86769f1d219..5d85b80c98da 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecords.test.tsx @@ -1,7 +1,5 @@ -import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { useSetRecoilState } from 'recoil'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; @@ -11,8 +9,8 @@ import { variables, } from '@/object-record/hooks/__mocks__/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const mocks = [ { @@ -28,29 +26,10 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); - +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); describe('useFindManyRecords', () => { - it('should skip fetch if currentWorkspaceMember is undefined', async () => { - const { result } = renderHook( - () => useFindManyRecords({ objectNameSingular: 'person' }), - { - wrapper: Wrapper, - }, - ); - - expect(result.current.loading).toBe(false); - expect(result.current.error).toBeUndefined(); - }); - it('should work as expected', async () => { const onCompleted = jest.fn(); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecordsQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecordsQuery.test.tsx index 3d2213a589f4..0c7fd0afbeb2 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecordsQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindManyRecordsQuery.test.tsx @@ -1,16 +1,16 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` query FindManyPeople($filter: PersonFilterInput, $orderBy: [PersonOrderByInput], $lastCursor: String, $limit: Int) { people(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor) { edges { node { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } cursor } @@ -25,6 +25,10 @@ const expectedQueryTemplate = ` } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useFindManyRecordsQuery', () => { it('should return a valid findManyRecordsQuery', () => { const objectNameSingular = 'person'; @@ -37,7 +41,7 @@ describe('useFindManyRecordsQuery', () => { computeReferences, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecord.test.tsx index 62e11efe51dd..e7249d9caf4b 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecord.test.tsx @@ -1,15 +1,12 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { renderHook, waitFor } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { query, - responseData, variables, } from '@/object-record/hooks/__mocks__/useFindOneRecord'; import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; +import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const mocks = [ { @@ -19,21 +16,19 @@ const mocks = [ }, result: jest.fn(() => ({ data: { - person: responseData, + person: generateEmptyJestRecordNode({ + objectNameSingular: 'person', + input: { id: '6205681e-7c11-40b4-9e32-f523dbe54590' }, + withDepthOneRelation: true, + }), }, })), }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const objectRecordId = '6205681e-7c11-40b4-9e32-f523dbe54590'; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx index 386e0d55f84f..32b2a169139d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx @@ -1,18 +1,22 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQuery'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` query FindOnePerson($objectRecordId: ID!) { person(filter: { id: { eq: $objectRecordId } }) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } } `.replace(/\s/g, ''); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useFindOneRecordQuery', () => { it('should return a valid findOneRecordQuery', () => { const objectNameSingular = 'person'; @@ -23,7 +27,7 @@ describe('useFindOneRecordQuery', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.test.tsx index feb33c81b30a..2cdf074ad1d5 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useGenerateFindManyRecordsForMultipleMetadataItemsQuery.test.tsx @@ -5,7 +5,7 @@ import { RecoilRoot } from 'recoil'; import { useGenerateCombinedFindManyRecordsQuery } from '@/object-record/multiple-objects/hooks/useGenerateCombinedFindManyRecordsQuery'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const Wrapper = ({ children }: { children: ReactNode }) => ( diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFindOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFindOneRecord.test.tsx index 013889b934cd..77833552d8db 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFindOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useLazyFindOneRecord.test.tsx @@ -1,7 +1,4 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook, waitFor } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; import { query, @@ -9,7 +6,7 @@ import { variables, } from '@/object-record/hooks/__mocks__/useFindOneRecord'; import { useLazyFindOneRecord } from '@/object-record/hooks/useLazyFindOneRecord'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const mocks = [ { @@ -25,15 +22,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const objectRecordId = '6205681e-7c11-40b4-9e32-f523dbe54590'; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx index 627991b7f41d..4cd4cdbffc11 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useObjectRecordTable.test.tsx @@ -1,13 +1,12 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { expect } from '@storybook/test'; import { renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; +import { ReactNode } from 'react'; +import { mocks } from '@/auth/hooks/__mocks__/useAuth'; import { useLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLoadRecordIndexTable'; import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const recordTableId = 'people'; const objectNameSingular = 'person'; @@ -17,20 +16,22 @@ const ObjectNamePluralSetter = ({ children }: { children: ReactNode }) => { return <>{children}; }; +const HookMockWrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); + const Wrapper = ({ children }: { children: ReactNode }) => { return ( - + - - {children} - + {children} - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecord.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecord.test.tsx index eb6e7048d316..d32ef37508b1 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecord.test.tsx @@ -1,7 +1,4 @@ -import { MockedProvider } from '@apollo/client/testing'; import { act, renderHook } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; import { query, @@ -9,6 +6,7 @@ import { variables, } from '@/object-record/hooks/__mocks__/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const person = { id: '36abbb63-34ed-4a16-89f5-f549ac55d0f9' }; const update = { @@ -37,13 +35,9 @@ const mocks = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - {children} - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const idToUpdate = '36abbb63-34ed-4a16-89f5-f549ac55d0f9'; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx index 7581e1612582..be862743e9b8 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx @@ -1,18 +1,22 @@ import { renderHook } from '@testing-library/react'; import { print } from 'graphql'; -import { RecoilRoot } from 'recoil'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { normalizeGQLQuery } from '~/utils/normalizeGQLQuery'; const expectedQueryTemplate = ` mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } }`; +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useUpdateOneRecordMutation', () => { it('should return a valid createManyRecordsMutation', () => { const objectNameSingular = 'person'; @@ -23,7 +27,7 @@ describe('useUpdateOneRecordMutation', () => { objectNameSingular, }), { - wrapper: RecoilRoot, + wrapper: Wrapper, }, ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicateRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicateRecords.ts index bd75db4a75a4..7b73f918fd74 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicateRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicateRecords.ts @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; import { useQuery } from '@apollo/client'; +import { useMemo } from 'react'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts index c5f22c9f640e..0dac0caa6d68 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindManyRecords.ts @@ -1,7 +1,5 @@ import { useQuery, WatchQueryFetchPolicy } from '@apollo/client'; -import { useRecoilValue } from 'recoil'; -import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult'; @@ -36,7 +34,6 @@ export const useFindManyRecords = ({ onCompleted, cursorFilter, }: UseFindManyRecordsParams) => { - const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, }); @@ -66,7 +63,7 @@ export const useFindManyRecords = ({ const { data, loading, error, fetchMore } = useQuery(findManyRecordsQuery, { - skip: skip || !objectMetadataItem || !currentWorkspaceMember, + skip: skip || !objectMetadataItem, variables: { filter, orderBy, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSourceEnumOptions.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSourceEnumOptions.ts index ee886da633e0..7006bb1ed8f1 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSourceEnumOptions.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getSourceEnumOptions.ts @@ -4,6 +4,7 @@ import { IconCsv, IconGmail, IconGoogleCalendar, + IconRobot, IconSettingsAutomation, IconUserCircle, } from 'twenty-ui'; @@ -52,5 +53,12 @@ export const getSourceEnumOptions = ( AvatarIcon: IconSettingsAutomation, isIconInverted: true, }, + { + id: 'SYSTEM', + name: 'System', + isSelected: selectedItemIds.includes('SYSTEM'), + AvatarIcon: IconRobot, + isIconInverted: true, + }, ]; }; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx index 346987c009c0..d336467e8db3 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx @@ -339,6 +339,7 @@ export const RecordBoardCard = ({ metadata: fieldDefinition.metadata, type: fieldDefinition.type, }), + settings: fieldDefinition.settings, }, useUpdateRecord: useUpdateOneRecordHook, hotkeyScope: InlineCellHotkeyScope.InlineCell, diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx index 4aeafd96f1fd..afaf8e9d78f4 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader.tsx @@ -1,7 +1,8 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { StyledBoardCardBody, StyledBoardCardHeader, @@ -43,7 +44,10 @@ export const RecordBoardColumnCardContainerSkeletonLoader = ({ > - + @@ -51,8 +55,14 @@ export const RecordBoardColumnCardContainerSkeletonLoader = ({ skeletonItems.map(({ id }) => ( - - + + ))} diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx index d0d7e93de02c..e0ae827280d9 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx @@ -93,7 +93,6 @@ export const RecordBoardColumnHeader = () => { newRecord, handleNewButtonClick, handleCreateSuccess, - handleEntitySelect, } = useColumnNewCardActions(columnDefinition.id); const { isOpportunitiesCompanyFieldDisabled } = diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts index f0921f82f380..97cb8c5d6052 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/hooks/useAddNewCard.ts @@ -1,6 +1,7 @@ import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector'; +import { useEntitySelectSearch } from '@/object-record/relation-picker/hooks/useEntitySelectSearch'; import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; @@ -17,6 +18,9 @@ export const useAddNewCard = () => { const columnContext = useContext(RecordBoardColumnContext); const { createOneRecord, selectFieldMetadataItem } = useContext(RecordBoardContext); + const { resetSearchFilter } = useEntitySelectSearch({ + relationPickerScopeId: 'relation-picker', + }); const { goBackToPreviousHotkeyScope, @@ -132,11 +136,12 @@ export const useAddNewCard = () => { company: null, }, ); + resetSearchFilter(); if (isOpportunity === true) { goBackToPreviousHotkeyScope(); } }, - [getColumnDefinitionId, goBackToPreviousHotkeyScope], + [getColumnDefinitionId, goBackToPreviousHotkeyScope, resetSearchFilter], ); const handleCreate = ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/__mocks__/fieldDefinitions.ts b/packages/twenty-front/src/modules/object-record/record-field/__mocks__/fieldDefinitions.ts index e688a2a64544..90cd7f176e26 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/__mocks__/fieldDefinitions.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/__mocks__/fieldDefinitions.ts @@ -9,10 +9,8 @@ import { FieldTextMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { - mockedCompanyObjectMetadataItem, - mockedPersonObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; + export const fieldMetadataId = 'fieldMetadataId'; export const textfieldDefinition: FieldDefinition = { @@ -24,7 +22,16 @@ export const textfieldDefinition: FieldDefinition = { metadata: { placeHolder: 'John Doe', fieldName: 'userName' }, }; -const relationFieldMetadataItem = mockedPersonObjectMetadataItem.fields?.find( +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + ({ nameSingular }) => nameSingular === 'person', +); + +if (!mockedPersonObjectMetadataItem) { + throw new Error('Person object metadata item not found'); +} + + +const relationFieldMetadataItem = mockedPersonObjectMetadataItem?.fields?.find( ({ name }) => name === 'company', ); @@ -91,7 +98,15 @@ export const ratingFieldDefinition: FieldDefinition = { }, }; -const booleanFieldMetadataItem = mockedCompanyObjectMetadataItem.fields?.find( +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +if (!mockedCompanyObjectMetadataItem) { + throw new Error('Company object metadata item not found'); +} + +const booleanFieldMetadataItem = mockedCompanyObjectMetadataItem?.fields?.find( ({ name }) => name === 'idealCustomerProfile', ); export const booleanFieldDefinition = formatFieldMetadataItemAsFieldDefinition({ diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx index 2e8756c2e8f0..4eea3aae834f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx @@ -1,11 +1,11 @@ import { gql } from '@apollo/client'; -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { MockedResponse } from '@apollo/client/testing'; import { act, renderHook, waitFor } from '@testing-library/react'; import { ReactNode } from 'react'; -import { RecoilRoot, useRecoilValue } from 'recoil'; +import { useRecoilValue } from 'recoil'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { PERSON_FRAGMENT } from '@/object-record/hooks/__mocks__/personFragment'; +import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { phonesFieldDefinition, @@ -20,11 +20,12 @@ import { usePersistField } from '@/object-record/record-field/hooks/usePersistFi import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const query = gql` mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { - ${PERSON_FRAGMENT} + ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } } `; @@ -72,6 +73,10 @@ const mocks: MockedResponse[] = [ const recordId = 'recordId'; +const JestMetadataAndApolloMocksWrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); + const getWrapper = (fieldDefinition: FieldDefinition) => ({ children }: { children: ReactNode }) => { @@ -91,7 +96,7 @@ const getWrapper = }; return ( - + - {children} + {children} - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx index 86a2037c5d2c..5ec442483de4 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx @@ -1,8 +1,7 @@ import { gql } from '@apollo/client'; -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; -import { act, renderHook, waitFor } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot } from 'recoil'; +import { MockedResponse } from '@apollo/client/testing'; +import { renderHook, waitFor } from '@testing-library/react'; +import { ReactNode, act } from 'react'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; @@ -13,6 +12,8 @@ import { RecordUpdateHookParams, } from '@/object-record/record-field/contexts/FieldContext'; import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput'; +import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const recordId = 'recordId'; @@ -26,13 +27,42 @@ const mocks: MockedResponse[] = [ ) { updateCompany(id: $idToUpdate, data: $input) { __typename - updatedAt - domainName { - primaryLinkUrl - primaryLinkLabel - secondaryLinks + accountOwner { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } + accountOwnerId + activityTargets { + edges { + node { + __typename + activityId + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + updatedAt + } + } } - visaSponsorship address { addressStreet1 addressStreet2 @@ -43,20 +73,31 @@ const mocks: MockedResponse[] = [ addressLat addressLng } - position - employees - deletedAt - accountOwnerId annualRecurringRevenue { amountMicros currencyCode } - id - name - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } } createdAt createdBy { @@ -64,7 +105,36 @@ const mocks: MockedResponse[] = [ workspaceMemberId name } - workPolicy + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + favorites { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + position + rocketId + taskId + updatedAt + viewId + workflowId + workspaceMemberId + } + } + } + id + idealCustomerProfile introVideo { primaryLinkUrl primaryLinkLabel @@ -75,8 +145,151 @@ const mocks: MockedResponse[] = [ primaryLinkLabel secondaryLinks } + name + noteTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + rocketId + updatedAt + } + } + } + opportunities { + edges { + node { + __typename + amount { + amountMicros + currencyCode + } + closeDate + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + name + pointOfContactId + position + stage + updatedAt + } + } + } + people { + edges { + node { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + } + } + position tagline - idealCustomerProfile + taskTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + taskId + updatedAt + } + } + } + timelineActivities { + edges { + node { + __typename + companyId + createdAt + deletedAt + happensAt + id + linkedObjectMetadataId + linkedRecordCachedName + linkedRecordId + name + noteId + opportunityId + personId + properties + rocketId + taskId + updatedAt + workspaceMemberId + } + } + } + updatedAt + visaSponsorship + workPolicy + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } } } `, @@ -87,8 +300,12 @@ const mocks: MockedResponse[] = [ }, result: jest.fn(() => ({ data: { - updateWorkspaceMember: { - id: 'recordId', + updateCompany: { + ...generateEmptyJestRecordNode({ + objectNameSingular: CoreObjectNameSingular.Company, + input: { id: recordId }, + withDepthOneRelation: true, + }), }, }, })), @@ -111,8 +328,13 @@ const Wrapper = ({ children }: { children: ReactNode }) => { return [updateEntity, { loading: false }]; }; + const JestMetadataAndApolloMocksWrapper = + getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, + }); + return ( - + { useUpdateRecord: useUpdateOneRecordMutation, }} > - {children} + {children} - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx index 087a4117c47b..cb30dbed3776 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/NumberFieldDisplay.tsx @@ -2,7 +2,11 @@ import { useNumberFieldDisplay } from '@/object-record/record-field/meta-types/h import { NumberDisplay } from '@/ui/field/display/components/NumberDisplay'; export const NumberFieldDisplay = () => { - const { fieldValue } = useNumberFieldDisplay(); - - return ; + const { fieldValue, fieldDefinition } = useNumberFieldDisplay(); + return ( + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx index 079b84520e91..9e2cb6b78622 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/DateTimeFieldDisplay.perf.stories.tsx @@ -51,6 +51,6 @@ export const Elipsis: Story = { export const Performance = getProfilingStory({ componentName: 'DateTimeFieldDisplay', averageThresholdInMs: 0.1, - numberOfRuns: 50, - numberOfTestsPerRun: 100, + numberOfRuns: 30, + numberOfTestsPerRun: 30, }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailsFieldDisplay.perf.stories.tsx similarity index 55% rename from packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailFieldDisplay.perf.stories.tsx rename to packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailsFieldDisplay.perf.stories.tsx index b901caa9c1cd..283224dd88a5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/EmailsFieldDisplay.perf.stories.tsx @@ -1,19 +1,22 @@ import { Meta, StoryObj } from '@storybook/react'; import { ComponentDecorator } from 'twenty-ui'; -import { EmailFieldDisplay } from '@/object-record/record-field/meta-types/display/components/EmailFieldDisplay'; +import { EmailsFieldDisplay } from '@/object-record/record-field/meta-types/display/components/EmailsFieldDisplay'; import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator'; import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; const meta: Meta = { - title: 'UI/Data/Field/Display/EmailFieldDisplay', + title: 'UI/Data/Field/Display/EmailsFieldDisplay', decorators: [ MemoryRouterDecorator, - getFieldDecorator('person', 'email'), + getFieldDecorator('person', 'emails', { + primaryEmail: 'test@test.com', + additionalEmails: ['toto@test.com'], + }), ComponentDecorator, ], - component: EmailFieldDisplay, + component: EmailsFieldDisplay, args: {}, parameters: { chromatic: { disableSnapshot: true }, @@ -22,25 +25,25 @@ const meta: Meta = { export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = {}; export const Elipsis: Story = { parameters: { - container: { width: 50 }, + container: { width: 100 }, }, decorators: [ - getFieldDecorator( - 'person', - 'email', - 'asdasdasdaksjdhkajshdkajhasmdkamskdsd@asdkjhaksjdhaksjd.com', - ), + getFieldDecorator('person', 'emails', { + primaryEmail: + 'asdasdasdaksjdhkajshdkajhasmdkamskdsd@asdkjhaksjdhaksjd.com', + additionalEmails: [], + }), ], }; export const Performance = getProfilingStory({ - componentName: 'EmailFieldDisplay', + componentName: 'EmailsFieldDisplay', averageThresholdInMs: 0.5, numberOfRuns: 50, numberOfTestsPerRun: 100, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/JsonFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/JsonFieldDisplay.perf.stories.tsx deleted file mode 100644 index be1567d863b0..000000000000 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/JsonFieldDisplay.perf.stories.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react'; -import { ComponentDecorator } from 'twenty-ui'; - -import { JsonFieldDisplay } from '@/object-record/record-field/meta-types/display/components/JsonFieldDisplay'; -import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator'; -import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; -import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; - -const meta: Meta = { - title: 'UI/Data/Field/Display/JsonFieldDisplay', - decorators: [ - MemoryRouterDecorator, - getFieldDecorator('company', 'testRawJson', { - key1: 'value1', - key2: 'value2', - }), - ComponentDecorator, - ], - component: JsonFieldDisplay, - args: {}, - parameters: { - chromatic: { disableSnapshot: true }, - }, -}; - -export default meta; - -type Story = StoryObj; - -export const Default: Story = {}; - -export const Elipsis: Story = { - parameters: { - container: { width: 50 }, - }, -}; - -export const Performance = getProfilingStory({ - componentName: 'JsonFieldDisplay', - averageThresholdInMs: 0.1, - numberOfRuns: 50, - numberOfTestsPerRun: 100, -}); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/MultiSelectFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/MultiSelectFieldDisplay.perf.stories.tsx index ec9a8291d2c5..0eae95f09db0 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/MultiSelectFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/MultiSelectFieldDisplay.perf.stories.tsx @@ -23,7 +23,7 @@ const meta: Meta = { title: 'UI/Data/Field/Display/MultiSelectFieldDisplay', decorators: [ MemoryRouterDecorator, - getFieldDecorator('company', 'testMultiSelect', [ + getFieldDecorator('company', 'workPolicy', [ 'Option 1', 'Option 2', 'Option 3', diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhoneFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhonesFieldDisplay.perf.stories.tsx similarity index 61% rename from packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhoneFieldDisplay.perf.stories.tsx rename to packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhonesFieldDisplay.perf.stories.tsx index fed4bbe247a3..94f37ee5cfb6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhoneFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/PhonesFieldDisplay.perf.stories.tsx @@ -1,19 +1,19 @@ import { Meta, StoryObj } from '@storybook/react'; import { ComponentDecorator } from 'twenty-ui'; -import { PhoneFieldDisplay } from '@/object-record/record-field/meta-types/display/components/PhoneFieldDisplay'; +import { PhonesFieldDisplay } from '@/object-record/record-field/meta-types/display/components/PhonesFieldDisplay'; import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator'; import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; const meta: Meta = { - title: 'UI/Data/Field/Display/PhoneFieldDisplay', + title: 'UI/Data/Field/Display/PhonesFieldDisplay', decorators: [ MemoryRouterDecorator, - getFieldDecorator('person', 'phone'), + getFieldDecorator('person', 'phones'), ComponentDecorator, ], - component: PhoneFieldDisplay, + component: PhonesFieldDisplay, args: {}, parameters: { chromatic: { disableSnapshot: true }, @@ -22,7 +22,7 @@ const meta: Meta = { export default meta; -type Story = StoryObj; +type Story = StoryObj; export const Default: Story = {}; @@ -33,11 +33,17 @@ export const Elipsis: Story = { }; export const WrongNumber: Story = { - decorators: [getFieldDecorator('person', 'phone', 'sdklaskdj')], + decorators: [ + getFieldDecorator('person', 'phones', { + primaryPhoneNumber: '123-456-7890', + primaryPhoneCountryCode: '+1', + additionalPhones: null, + }), + ], }; export const Performance = getProfilingStory({ - componentName: 'PhoneFieldDisplay', + componentName: 'PhonesFieldDisplay', averageThresholdInMs: 0.5, numberOfRuns: 20, numberOfTestsPerRun: 100, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RatingFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RatingFieldDisplay.perf.stories.tsx index 3c31dc6d5682..3d4b5950aea0 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RatingFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RatingFieldDisplay.perf.stories.tsx @@ -10,7 +10,7 @@ const meta: Meta = { title: 'UI/Data/Field/Display/RatingFieldDisplay', decorators: [ MemoryRouterDecorator, - getFieldDecorator('company', 'testRating'), + getFieldDecorator('person', 'performanceRating'), ComponentDecorator, ], component: RatingFieldDisplay, @@ -30,5 +30,5 @@ export const Performance = getProfilingStory({ componentName: 'RatingFieldDisplay', averageThresholdInMs: 0.5, numberOfRuns: 30, - numberOfTestsPerRun: 50, + numberOfTestsPerRun: 30, }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationToOneFieldDisplay.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationToOneFieldDisplay.perf.stories.tsx index 49a076d80a47..989de5e7439f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationToOneFieldDisplay.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/display/components/__stories__/perf/RelationToOneFieldDisplay.perf.stories.tsx @@ -30,7 +30,7 @@ export const Default: Story = {}; export const Performance = getProfilingStory({ componentName: 'RelationFieldDisplay', - averageThresholdInMs: 0.2, + averageThresholdInMs: 0.22, numberOfRuns: 20, numberOfTestsPerRun: 100, }); diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts index 5bdceda11e73..097bcb8beef5 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/hooks/useNumberField.ts @@ -5,10 +5,11 @@ import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecor import { FieldNumberValue } from '@/object-record/record-field/types/FieldMetadata'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { FieldMetadataType } from '~/generated-metadata/graphql'; + import { - canBeCastAsIntegerOrNull, - castAsIntegerOrNull, -} from '~/utils/cast-as-integer-or-null'; + canBeCastAsNumberOrNull, + castAsNumberOrNull, +} from '~/utils/cast-as-number-or-null'; import { FieldContext } from '../../contexts/FieldContext'; import { usePersistField } from '../../hooks/usePersistField'; @@ -32,11 +33,11 @@ export const useNumberField = () => { const persistField = usePersistField(); const persistNumberField = (newValue: string) => { - if (!canBeCastAsIntegerOrNull(newValue)) { + if (!canBeCastAsNumberOrNull(newValue)) { return; } - const castedValue = castAsIntegerOrNull(newValue); + const castedValue = castAsNumberOrNull(newValue); persistField(castedValue); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailsFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailsFieldInput.tsx index 32418cebc607..d933aeabcd06 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailsFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/EmailsFieldInput.tsx @@ -1,6 +1,7 @@ import { useEmailsField } from '@/object-record/record-field/meta-types/hooks/useEmailsField'; import { EmailsFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/EmailsFieldMenuItem'; -import { useMemo } from 'react'; +import { emailSchema } from '@/object-record/record-field/validation-schemas/emailSchema'; +import { useCallback, useMemo } from 'react'; import { isDefined } from 'twenty-ui'; import { FieldMetadataType } from '~/generated-metadata/graphql'; import { MultiItemFieldInput } from './MultiItemFieldInput'; @@ -29,6 +30,14 @@ export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => { }); }; + const validateInput = useCallback( + (input: string) => ({ + isValid: emailSchema.safeParse(input).success, + errorMessage: '', + }), + [], + ); + const isPrimaryEmail = (index: number) => index === 0 && emails?.length > 1; return ( @@ -38,6 +47,7 @@ export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => { onCancel={onCancel} placeholder="Email" fieldMetadataType={FieldMetadataType.Emails} + validateInput={validateInput} renderItem={({ value: email, index, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinksFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinksFieldInput.tsx index 97bc9578c97c..e52cc95c041f 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinksFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/LinksFieldInput.tsx @@ -51,7 +51,10 @@ export const LinksFieldInput = ({ onCancel }: LinksFieldInputProps) => { onCancel={onCancel} placeholder="URL" fieldMetadataType={FieldMetadataType.Links} - validateInput={(input) => absoluteUrlSchema.safeParse(input).success} + validateInput={(input) => ({ + isValid: absoluteUrlSchema.safeParse(input).success, + errorMessage: '', + })} formatInput={(input) => ({ url: input, label: '' })} renderItem={({ value: link, diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx index a73e49498805..7e3e93ec2c48 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemFieldInput.tsx @@ -30,7 +30,7 @@ type MultiItemFieldInputProps = { onPersist: (updatedItems: T[]) => void; onCancel?: () => void; placeholder: string; - validateInput?: (input: string) => boolean; + validateInput?: (input: string) => { isValid: boolean; errorMessage: string }; formatInput?: (input: string) => T; renderItem: (props: { value: T; @@ -74,8 +74,21 @@ export const MultiItemFieldInput = ({ const [isInputDisplayed, setIsInputDisplayed] = useState(false); const [inputValue, setInputValue] = useState(''); const [itemToEditIndex, setItemToEditIndex] = useState(-1); + const [errorData, setErrorData] = useState({ + isValid: true, + errorMessage: '', + }); const isAddingNewItem = itemToEditIndex === -1; + const handleOnChange = (value: string) => { + setInputValue(value); + if (!validateInput) return; + + if (errorData.isValid) { + setErrorData(errorData); + } + }; + const handleAddButtonClick = () => { setItemToEditIndex(-1); setIsInputDisplayed(true); @@ -105,7 +118,13 @@ export const MultiItemFieldInput = ({ }; const handleSubmitInput = () => { - if (validateInput !== undefined && !validateInput(inputValue)) return; + if (validateInput !== undefined) { + const validationData = validateInput(inputValue) ?? { isValid: true }; + if (!validationData.isValid) { + setErrorData(validationData); + return; + } + } const newItem = formatInput ? formatInput(inputValue) @@ -160,6 +179,7 @@ export const MultiItemFieldInput = ({ placeholder={placeholder} value={inputValue} hotkeyScope={hotkeyScope} + hasError={!errorData.isValid} renderInput={ renderInput ? (props) => @@ -170,7 +190,7 @@ export const MultiItemFieldInput = ({ }) : undefined } - onChange={(event) => setInputValue(event.target.value)} + onChange={(event) => handleOnChange(event.target.value)} onEnter={handleSubmitInput} rightComponent={ = { infoTooltipContent?: string; defaultValue?: any; editButtonIcon?: IconComponent; + settings?: { + decimals?: number; + }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/emailSchema.ts b/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/emailSchema.ts new file mode 100644 index 000000000000..fae5812b3ad7 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/emailSchema.ts @@ -0,0 +1,3 @@ +import { z } from 'zod'; + +export const emailSchema = z.string().email(); diff --git a/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts b/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts new file mode 100644 index 000000000000..48981e4a5a34 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +export const numberFieldDefaultValueSchema = z.object({ + decimals: z.number().nullable(), +}); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingBooleanFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingBooleanFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingBooleanFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingBooleanFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingCurrencyFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingCurrencyFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingCurrencyFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingCurrencyFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingDateFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingDateFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingDateFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingDateFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingFloatFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingFloatFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingFloatFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingFloatFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingStringFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingStringFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingStringFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingStringFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingUUIDFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingUUIDFilter.test.ts similarity index 100% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isMatchingUUIDFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isMatchingUUIDFilter.test.ts diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isRecordMatchingFilter.test.ts similarity index 98% rename from packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.spec.ts rename to packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isRecordMatchingFilter.test.ts index ed0b22070777..1c5e1d2b4f46 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.spec.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/isRecordMatchingFilter.test.ts @@ -1,10 +1,11 @@ import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter'; import { getCompaniesMock } from '~/testing/mock-data/companies'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { Company } from '@/companies/types/Company'; import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName'; -import { isRecordMatchingFilter } from './isRecordMatchingFilter'; +import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter'; +import { expect } from '@storybook/test'; const companiesMock = getCompaniesMock(); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/turnObjectDropdownFilterIntoQueryFilter.test.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/turnObjectDropdownFilterIntoQueryFilter.test.ts new file mode 100644 index 000000000000..6486ca29b92e --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/turnObjectDropdownFilterIntoQueryFilter.test.ts @@ -0,0 +1,1063 @@ +import { Filter } from '@/object-record/object-filter-dropdown/types/Filter'; +import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter'; +import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; +import { getCompaniesMock } from '~/testing/mock-data/companies'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; + +const companiesMock = getCompaniesMock(); + +const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +)!; + +const personMockObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +)!; + +jest.useFakeTimers().setSystemTime(new Date('2020-01-01')); + +describe('turnObjectDropdownFilterIntoQueryFilter', () => { + it('should work as expected for single filter', () => { + const companyMockNameFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'name', + ); + + const nameFilter: Filter = { + id: 'company-name-filter', + value: companiesMock[0].name, + fieldMetadataId: companyMockNameFieldMetadataId?.id, + displayValue: companiesMock[0].name, + operand: ViewFilterOperand.Contains, + definition: { + type: 'TEXT', + fieldMetadataId: companyMockNameFieldMetadataId?.id, + label: 'Name', + iconName: 'text', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [nameFilter], + companyMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + name: { + ilike: '%Linkedin%', + }, + }); + }); + + it('should work as expected for multiple filters', () => { + const companyMockNameFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'name', + ); + + const companyMockEmployeesFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'employees', + ); + + const nameFilter: Filter = { + id: 'company-name-filter', + value: companiesMock[0].name, + fieldMetadataId: companyMockNameFieldMetadataId?.id, + displayValue: companiesMock[0].name, + operand: ViewFilterOperand.Contains, + definition: { + type: 'TEXT', + fieldMetadataId: companyMockNameFieldMetadataId?.id, + label: 'Name', + iconName: 'text', + }, + }; + + const employeesFilter: Filter = { + id: 'company-employees-filter', + value: '1000', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + displayValue: '1000', + operand: ViewFilterOperand.GreaterThan, + definition: { + type: 'NUMBER', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + label: 'Employees', + iconName: 'number', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [nameFilter, employeesFilter], + companyMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + name: { + ilike: '%Linkedin%', + }, + }, + { + employees: { + gte: 1000, + }, + }, + ], + }); + }); +}); + +describe('should work as expected for the different field types', () => { + it('address field type', () => { + const companyMockAddressFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'address', + ); + + const addressFilterContains: Filter = { + id: 'company-address-filter-contains', + value: '123 Main St', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + displayValue: '123 Main St', + operand: ViewFilterOperand.Contains, + definition: { + type: 'ADDRESS', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + label: 'Address', + iconName: 'address', + }, + }; + + const addressFilterDoesNotContain: Filter = { + id: 'company-address-filter-does-not-contain', + value: '123 Main St', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + displayValue: '123 Main St', + operand: ViewFilterOperand.DoesNotContain, + definition: { + type: 'ADDRESS', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + label: 'Address', + iconName: 'address', + }, + }; + + const addressFilterIsEmpty: Filter = { + id: 'company-address-filter-is-empty', + value: '', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsEmpty, + definition: { + type: 'ADDRESS', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + label: 'Address', + iconName: 'address', + }, + }; + + const addressFilterIsNotEmpty: Filter = { + id: 'company-address-filter-is-not-empty', + value: '', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsNotEmpty, + definition: { + type: 'ADDRESS', + fieldMetadataId: companyMockAddressFieldMetadataId?.id, + label: 'Address', + iconName: 'address', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [ + addressFilterContains, + addressFilterDoesNotContain, + addressFilterIsEmpty, + addressFilterIsNotEmpty, + ], + companyMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + or: [ + { + address: { + addressStreet1: { + ilike: '%123 Main St%', + }, + }, + }, + { + address: { + addressStreet2: { + ilike: '%123 Main St%', + }, + }, + }, + { + address: { + addressCity: { + ilike: '%123 Main St%', + }, + }, + }, + { + address: { + addressState: { + ilike: '%123 Main St%', + }, + }, + }, + { + address: { + addressCountry: { + ilike: '%123 Main St%', + }, + }, + }, + { + address: { + addressPostcode: { + ilike: '%123 Main St%', + }, + }, + }, + ], + }, + { + and: [ + { + not: { + address: { + addressStreet1: { + ilike: '%123 Main St%', + }, + }, + }, + }, + { + not: { + address: { + addressStreet2: { + ilike: '%123 Main St%', + }, + }, + }, + }, + { + not: { + address: { + addressCity: { + ilike: '%123 Main St%', + }, + }, + }, + }, + ], + }, + { + and: [ + { + or: [ + { + address: { + addressStreet1: { + ilike: '', + }, + }, + }, + { + address: { + addressStreet1: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressStreet2: { + ilike: '', + }, + }, + }, + { + address: { + addressStreet2: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressCity: { + ilike: '', + }, + }, + }, + { + address: { + addressCity: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressState: { + ilike: '', + }, + }, + }, + { + address: { + addressState: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressCountry: { + ilike: '', + }, + }, + }, + { + address: { + addressCountry: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressPostcode: { + ilike: '', + }, + }, + }, + { + address: { + addressPostcode: { + is: 'NULL', + }, + }, + }, + ], + }, + ], + }, + { + not: { + and: [ + { + or: [ + { + address: { + addressStreet1: { + ilike: '', + }, + }, + }, + { + address: { + addressStreet1: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressStreet2: { + ilike: '', + }, + }, + }, + { + address: { + addressStreet2: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressCity: { + ilike: '', + }, + }, + }, + { + address: { + addressCity: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressState: { + ilike: '', + }, + }, + }, + { + address: { + addressState: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressCountry: { + ilike: '', + }, + }, + }, + { + address: { + addressCountry: { + is: 'NULL', + }, + }, + }, + ], + }, + { + or: [ + { + address: { + addressPostcode: { + ilike: '', + }, + }, + }, + { + address: { + addressPostcode: { + is: 'NULL', + }, + }, + }, + ], + }, + ], + }, + }, + ], + }); + }); + + it('phones field type', () => { + const personMockPhonesFieldMetadataId = + personMockObjectMetadataItem.fields.find( + (field) => field.name === 'phones', + ); + + const phonesFilterContains: Filter = { + id: 'person-phones-filter-contains', + value: '1234567890', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + displayValue: '1234567890', + operand: ViewFilterOperand.Contains, + definition: { + type: 'PHONES', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + label: 'Phones', + iconName: 'phone', + }, + }; + + const phonesFilterDoesNotContain: Filter = { + id: 'person-phones-filter-does-not-contain', + value: '1234567890', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + displayValue: '1234567890', + operand: ViewFilterOperand.DoesNotContain, + definition: { + type: 'PHONES', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + label: 'Phones', + iconName: 'phone', + }, + }; + + const phonesFilterIsEmpty: Filter = { + id: 'person-phones-filter-is-empty', + value: '', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsEmpty, + definition: { + type: 'PHONES', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + label: 'Phones', + iconName: 'phone', + }, + }; + + const phonesFilterIsNotEmpty: Filter = { + id: 'person-phones-filter-is-not-empty', + value: '', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsNotEmpty, + definition: { + type: 'PHONES', + fieldMetadataId: personMockPhonesFieldMetadataId?.id, + label: 'Phones', + iconName: 'phone', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [ + phonesFilterContains, + phonesFilterDoesNotContain, + phonesFilterIsEmpty, + phonesFilterIsNotEmpty, + ], + personMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + or: [ + { + phones: { + primaryPhoneNumber: { + ilike: '%1234567890%', + }, + }, + }, + { + phones: { + primaryPhoneCountryCode: { + ilike: '%1234567890%', + }, + }, + }, + ], + }, + { + and: [ + { + not: { + phones: { + primaryPhoneNumber: { + ilike: '%1234567890%', + }, + }, + }, + }, + { + not: { + phones: { + primaryPhoneCountryCode: { + ilike: '%1234567890%', + }, + }, + }, + }, + ], + }, + { + and: [ + { + or: [ + { + phones: { + primaryPhoneNumber: { + is: 'NULL', + }, + }, + }, + { + phones: { + primaryPhoneNumber: { + ilike: '', + }, + }, + }, + ], + }, + { + or: [ + { + phones: { + primaryPhoneCountryCode: { + is: 'NULL', + }, + }, + }, + { + phones: { + primaryPhoneCountryCode: { + ilike: '', + }, + }, + }, + ], + }, + ], + }, + { + not: { + and: [ + { + or: [ + { + phones: { + primaryPhoneNumber: { + is: 'NULL', + }, + }, + }, + { + phones: { + primaryPhoneNumber: { + ilike: '', + }, + }, + }, + ], + }, + { + or: [ + { + phones: { + primaryPhoneCountryCode: { + is: 'NULL', + }, + }, + }, + { + phones: { + primaryPhoneCountryCode: { + ilike: '', + }, + }, + }, + ], + }, + ], + }, + }, + ], + }); + }); + + it('emails field type', () => { + const personMockEmailFieldMetadataId = + personMockObjectMetadataItem.fields.find( + (field) => field.name === 'emails', + ); + + const emailsFilterContains: Filter = { + id: 'person-emails-filter-contains', + value: 'test@test.com', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + displayValue: 'test@test.com', + operand: ViewFilterOperand.Contains, + definition: { + type: 'EMAILS', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + iconName: 'email', + label: 'Emails', + }, + }; + + const emailsFilterDoesNotContain: Filter = { + id: 'person-emails-filter-does-not-contain', + value: 'test@test.com', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + displayValue: 'test@test.com', + operand: ViewFilterOperand.DoesNotContain, + definition: { + type: 'EMAILS', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + label: 'Emails', + iconName: 'email', + }, + }; + + const emailsFilterIsEmpty: Filter = { + id: 'person-emails-filter-is-empty', + value: '', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsEmpty, + definition: { + type: 'EMAILS', + label: 'Emails', + iconName: 'email', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + }, + }; + + const emailsFilterIsNotEmpty: Filter = { + id: 'person-emails-filter-is-not-empty', + value: '', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsNotEmpty, + definition: { + type: 'EMAILS', + label: 'Emails', + iconName: 'email', + fieldMetadataId: personMockEmailFieldMetadataId?.id, + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [ + emailsFilterContains, + emailsFilterDoesNotContain, + emailsFilterIsEmpty, + emailsFilterIsNotEmpty, + ], + personMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + or: [ + { + emails: { + primaryEmail: { + ilike: '%test@test.com%', + }, + }, + }, + ], + }, + { + and: [ + { + not: { + emails: { + primaryEmail: { + ilike: '%test@test.com%', + }, + }, + }, + }, + ], + }, + { + or: [ + { + emails: { + primaryEmail: { + ilike: '', + }, + }, + }, + { + emails: { + primaryEmail: { + is: 'NULL', + }, + }, + }, + ], + }, + { + not: { + or: [ + { + emails: { + primaryEmail: { + ilike: '', + }, + }, + }, + { + emails: { + primaryEmail: { + is: 'NULL', + }, + }, + }, + ], + }, + }, + ], + }); + }); + + it('date field type', () => { + const companyMockDateFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'createdAt', + ); + + const dateFilterIsAfter: Filter = { + id: 'company-date-filter-is-after', + value: '2024-09-17T20:46:58.922Z', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + displayValue: '2024-09-17T20:46:58.922Z', + operand: ViewFilterOperand.IsAfter, + definition: { + type: 'DATE_TIME', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + label: 'Created At', + iconName: 'date', + }, + }; + + const dateFilterIsBefore: Filter = { + id: 'company-date-filter-is-before', + value: '2024-09-17T20:46:58.922Z', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + displayValue: '2024-09-17T20:46:58.922Z', + operand: ViewFilterOperand.IsBefore, + definition: { + type: 'DATE_TIME', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + label: 'Created At', + iconName: 'date', + }, + }; + + const dateFilterIs: Filter = { + id: 'company-date-filter-is', + value: '2024-09-17T20:46:58.922Z', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + displayValue: '2024-09-17T20:46:58.922Z', + operand: ViewFilterOperand.Is, + definition: { + type: 'DATE_TIME', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + label: 'Created At', + iconName: 'date', + }, + }; + + const dateFilterIsEmpty: Filter = { + id: 'company-date-filter-is-empty', + value: '', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsEmpty, + definition: { + type: 'DATE_TIME', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + label: 'Created At', + iconName: 'date', + }, + }; + + const dateFilterIsNotEmpty: Filter = { + id: 'company-date-filter-is-not-empty', + value: '', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsNotEmpty, + definition: { + type: 'DATE_TIME', + fieldMetadataId: companyMockDateFieldMetadataId?.id, + label: 'Created At', + iconName: 'date', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [ + dateFilterIsAfter, + dateFilterIsBefore, + dateFilterIs, + dateFilterIsEmpty, + dateFilterIsNotEmpty, + ], + companyMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + createdAt: { + gt: '2024-09-17T20:46:58.922Z', + }, + }, + { + createdAt: { + lt: '2024-09-17T20:46:58.922Z', + }, + }, + { + and: [ + { + createdAt: { + lte: '2024-09-17T23:59:59.999Z', + }, + }, + { + createdAt: { + gte: '2024-09-17T00:00:00.000Z', + }, + }, + ], + }, + { + createdAt: { + is: 'NULL', + }, + }, + { + not: { + createdAt: { + is: 'NULL', + }, + }, + }, + ], + }); + }); + + it('number field type', () => { + const companyMockEmployeesFieldMetadataId = + companyMockObjectMetadataItem.fields.find( + (field) => field.name === 'employees', + ); + + const employeesFilterIsGreaterThan: Filter = { + id: 'company-employees-filter-is-greater-than', + value: '1000', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + displayValue: '1000', + operand: ViewFilterOperand.GreaterThan, + definition: { + type: 'NUMBER', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + label: 'Employees', + iconName: 'number', + }, + }; + + const employeesFilterIsLessThan: Filter = { + id: 'company-employees-filter-is-less-than', + value: '1000', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + displayValue: '1000', + operand: ViewFilterOperand.LessThan, + definition: { + type: 'NUMBER', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + label: 'Employees', + iconName: 'number', + }, + }; + + const employeesFilterIsEmpty: Filter = { + id: 'company-employees-filter-is-empty', + value: '', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsEmpty, + definition: { + type: 'NUMBER', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + label: 'Employees', + iconName: 'number', + }, + }; + + const employeesFilterIsNotEmpty: Filter = { + id: 'company-employees-filter-is-not-empty', + value: '', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + displayValue: '', + operand: ViewFilterOperand.IsNotEmpty, + definition: { + type: 'NUMBER', + fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, + label: 'Employees', + iconName: 'number', + }, + }; + + const result = turnObjectDropdownFilterIntoQueryFilter( + [ + employeesFilterIsGreaterThan, + employeesFilterIsLessThan, + employeesFilterIsEmpty, + employeesFilterIsNotEmpty, + ], + companyMockObjectMetadataItem.fields, + ); + + expect(result).toEqual({ + and: [ + { + employees: { + gte: 1000, + }, + }, + { + employees: { + lte: 1000, + }, + }, + { + employees: { + is: 'NULL', + }, + }, + { + not: { + employees: { + is: 'NULL', + }, + }, + }, + ], + }); + }); +}); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts index 14584485370e..323e36e2985a 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter.ts @@ -10,7 +10,6 @@ import { RecordGqlOperationFilter, RelationFilter, StringFilter, - URLFilter, UUIDFilter, } from '@/object-record/graphql/types/RecordGqlOperationFilter'; import { FilterType } from '@/object-record/object-filter-dropdown/types/FilterType'; @@ -40,8 +39,6 @@ const applyEmptyFilters = ( switch (filterType) { case 'TEXT': - case 'EMAIL': - case 'PHONE': emptyRecordFilter = { or: [ { [correspondingField.name]: { ilike: '' } as StringFilter }, @@ -86,16 +83,6 @@ const applyEmptyFilters = ( }; break; } - case 'LINK': - emptyRecordFilter = { - or: [ - { [correspondingField.name]: { url: { ilike: '' } } as URLFilter }, - { - [correspondingField.name]: { url: { is: 'NULL' } } as URLFilter, - }, - ], - }; - break; case 'LINKS': { const linksFilters = generateILikeFiltersForCompositeFields( '', @@ -305,8 +292,6 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( } switch (rawUIFilter.definition.type) { - case 'EMAIL': - case 'PHONE': case 'TEXT': switch (rawUIFilter.operand) { case ViewFilterOperand.Contains: @@ -627,43 +612,6 @@ export const turnObjectDropdownFilterIntoQueryFilter = ( ); } break; - case 'LINK': - switch (rawUIFilter.operand) { - case ViewFilterOperand.Contains: - objectRecordFilters.push({ - [correspondingField.name]: { - url: { - ilike: `%${rawUIFilter.value}%`, - }, - } as URLFilter, - }); - break; - case ViewFilterOperand.DoesNotContain: - objectRecordFilters.push({ - not: { - [correspondingField.name]: { - url: { - ilike: `%${rawUIFilter.value}%`, - }, - } as URLFilter, - }, - }); - break; - case ViewFilterOperand.IsEmpty: - case ViewFilterOperand.IsNotEmpty: - applyEmptyFilters( - rawUIFilter.operand, - correspondingField, - objectRecordFilters, - rawUIFilter.definition.type, - ); - break; - default: - throw new Error( - `Unknown operand ${rawUIFilter.operand} for ${rawUIFilter.definition.type} filter`, - ); - } - break; case 'LINKS': { const linksFilters = generateILikeFiltersForCompositeFields( rawUIFilter.value, diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts index 36947097459e..df178df4c4fd 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexTable.ts @@ -1,5 +1,6 @@ import { useRecoilValue } from 'recoil'; +import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; @@ -9,6 +10,7 @@ import { useRecordTableRecordGqlFields } from '@/object-record/record-index/hook import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { SIGN_IN_BACKGROUND_MOCK_COMPANIES } from '@/sign-in-background-mock/constants/SignInBackgroundMockCompanies'; +import { isNull } from '@sniptt/guards'; import { WorkspaceActivationStatus } from '~/generated/graphql'; export const useFindManyParams = ( @@ -43,6 +45,7 @@ export const useLoadRecordIndexTable = (objectNameSingular: string) => { const { setRecordTableData, setIsRecordTableInitialLoading } = useRecordTable(); const currentWorkspace = useRecoilValue(currentWorkspaceState); + const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const params = useFindManyParams(objectNameSingular); const recordGqlFields = useRecordTableRecordGqlFields({ objectMetadataItem }); @@ -63,6 +66,7 @@ export const useLoadRecordIndexTable = (objectNameSingular: string) => { onError: () => { setIsRecordTableInitialLoading(false); }, + skip: isNull(currentWorkspaceMember), }); return { diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useTableData.test.tsx b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useTableData.test.tsx index 752deafc8e7f..d670b908cb22 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useTableData.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/__tests__/useTableData.test.tsx @@ -1,18 +1,18 @@ -import { act, renderHook, waitFor } from '@testing-library/react'; -import { ReactNode } from 'react'; +import { renderHook, waitFor } from '@testing-library/react'; +import { act } from 'react'; import { percentage, sleep, useTableData } from '../useTableData'; +import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments'; import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard'; import { recordBoardKanbanFieldMetadataNameComponentState } from '@/object-record/record-board/states/recordBoardKanbanFieldMetadataNameComponentState'; import { useRecordIndexOptionsForBoard } from '@/object-record/record-index/options/hooks/useRecordIndexOptionsForBoard'; -import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext'; import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; import { ViewType } from '@/views/types/ViewType'; -import { MockedProvider, MockedResponse } from '@apollo/client/testing'; +import { MockedResponse } from '@apollo/client/testing'; import gql from 'graphql-tag'; -import { BrowserRouter as Router } from 'react-router-dom'; -import { RecoilRoot, useRecoilValue } from 'recoil'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { useRecoilValue } from 'recoil'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const defaultResponseData = { pageInfo: { @@ -23,10 +23,10 @@ const defaultResponseData = { }, totalCount: 1, }; + const mockPerson = { __typename: 'Person', updatedAt: '2021-08-03T19:20:06.000Z', - myCustomObjectId: '123', whatsapp: { primaryPhoneNumber: '+1', primaryPhoneCountryCode: '234-567-890', @@ -41,7 +41,10 @@ const mockPerson = { firstName: 'firstName', lastName: 'lastName', }, - email: 'email', + emails: { + primaryEmail: 'email', + additionalEmails: [], + }, position: 'position', createdBy: { source: 'source', @@ -57,7 +60,7 @@ const mockPerson = { }, performanceRating: 1, createdAt: '2021-08-03T19:20:06.000Z', - phone: { + phones: { primaryPhoneNumber: '+1', primaryPhoneCountryCode: '234-567-890', additionalPhones: [], @@ -66,8 +69,10 @@ const mockPerson = { city: 'city', companyId: '1', intro: 'intro', - workPreference: 'workPrefereance', + deletedAt: null, + workPreference: 'workPreference', }; + const mocks: MockedResponse[] = [ { request: { @@ -86,52 +91,7 @@ const mocks: MockedResponse[] = [ ) { edges { node { - __typename - name { - firstName - lastName - } - linkedinLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - deletedAt - createdAt - updatedAt - jobTitle - intro - workPrefereance - performanceRating - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks - } - city - companyId - phones { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } - createdBy { - source - workspaceMemberId - name - } - id - position - emails { - primaryEmail - additionalEmails - } - avatarUrl - whatsapp { - primaryPhoneNumber - primaryPhoneCountryCode - additionalPhones - } + ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } cursor } @@ -167,21 +127,9 @@ const mocks: MockedResponse[] = [ }, ]; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - - {children} - - - - -); +const WrapperWithResponse = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: mocks, +}); const graphqlEmptyResponse = [ { @@ -197,21 +145,9 @@ const graphqlEmptyResponse = [ }, ]; -const WrapperWithEmptyResponse = ({ children }: { children: ReactNode }) => ( - - - - - {children} - - - - -); +const WrapperWithEmptyResponse = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: graphqlEmptyResponse, +}); describe('useTableData', () => { const recordIndexId = 'people'; @@ -225,6 +161,7 @@ describe('useTableData', () => { useTableData({ recordIndexId, objectNameSingular, + pageSize: 30, callback, delayMs: 0, viewType: ViewType.Kanban, @@ -249,10 +186,11 @@ describe('useTableData', () => { recordIndexId, objectNameSingular, callback, + pageSize: 30, delayMs: 0, }), - { wrapper: Wrapper }, + { wrapper: WrapperWithResponse }, ); await act(async () => { @@ -292,7 +230,7 @@ describe('useTableData', () => { }; }, { - wrapper: Wrapper, + wrapper: WrapperWithResponse, }, ); @@ -340,8 +278,10 @@ describe('useTableData', () => { relationObjectMetadataNameSingular: '', relationType: undefined, targetFieldMetadataName: '', + settings: {}, }, position: 7, + settings: {}, showLabel: undefined, size: 100, type: 'DATE_TIME', @@ -379,7 +319,7 @@ describe('useTableData', () => { }; }, { - wrapper: Wrapper, + wrapper: WrapperWithResponse, }, ); diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader.tsx index 2e673478dbd5..bd912d7b55bd 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; import { useTheme } from '@emotion/react'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { StyledSkeletonDiv } from './RecordInlineCellContainer'; export const RecordInlineCellSkeletonLoader = () => { @@ -13,7 +14,10 @@ export const RecordInlineCellSkeletonLoader = () => { borderRadius={4} > - + ); diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader.tsx index 38ee35d74787..88a499952c1f 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledSkeletonDiv = styled.div` align-items: center; @@ -22,8 +23,14 @@ export const PropertyBoxSkeletonLoader = () => { > {skeletonItems.map(({ id }) => ( - - + + ))} diff --git a/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx b/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx index e68ce3883ef0..6a76206e6e75 100644 --- a/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx +++ b/packages/twenty-front/src/modules/object-record/record-right-drawer/components/RightDrawerRecord.tsx @@ -19,15 +19,6 @@ export const RightDrawerRecord = () => { viewableRecordNameSingularState, ); const viewableRecordId = useRecoilValue(viewableRecordIdState); - - if (!viewableRecordNameSingular) { - throw new Error(`Object name is not defined`); - } - - if (!viewableRecordId) { - throw new Error(`Record id is not defined`); - } - const { objectNameSingular, objectRecordId } = useRecordShowPage( viewableRecordNameSingular ?? '', viewableRecordId ?? '', diff --git a/packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts b/packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts new file mode 100644 index 000000000000..904677204cc6 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-right-drawer/states/isNewViewableRecordLoading.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui'; + +export const isNewViewableRecordLoadingState = createState({ + key: 'activities/is-new-viewable-record-loading', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx index 5f23472fe180..e72df975f22f 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/components/RecordShowContainer.tsx @@ -21,6 +21,7 @@ import { RecordInlineCell } from '@/object-record/record-inline-cell/components/ import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox'; import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader'; import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope'; +import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection'; import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection'; import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState'; @@ -33,6 +34,7 @@ import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer'; import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer'; import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer'; import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard'; +import { ShowPageSummaryCardSkeletonLoader } from '@/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { FieldMetadataType, @@ -80,7 +82,9 @@ export const RecordShowContainer = ({ recordId: objectRecordId, }), ); - + const isNewViewableRecordLoading = useRecoilValue( + isNewViewableRecordLoadingState, + ); const [uploadImage] = useUploadImageMutation(); const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular }); @@ -162,53 +166,56 @@ export const RecordShowContainer = ({ const isReadOnly = objectMetadataItem.isRemote; const isMobile = useIsMobile() || isInRightDrawer; const isPrefetchLoading = useIsPrefetchLoading(); + const isNewRightDrawerItemLoading = + isInRightDrawer && isNewViewableRecordLoading; - const summaryCard = isDefined(recordFromStore) ? ( - - - - } - avatarType={recordIdentifier?.avatarType ?? 'rounded'} - onUploadPicture={ - objectNameSingular === 'person' ? onUploadPicture : undefined - } - /> - ) : ( - <> - ); + useUpdateRecord: useUpdateOneObjectRecordMutation, + hotkeyScope: InlineCellHotkeyScope.InlineCell, + isCentered: true, + }} + > + + + } + avatarType={recordIdentifier?.avatarType ?? 'rounded'} + onUploadPicture={ + objectNameSingular === 'person' ? onUploadPicture : undefined + } + /> + ) : ( + + ); const fieldsBox = ( <> diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx index 06cb79025bc5..aca53588e1be 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/__stories__/RecordDetailRelationSection.stories.tsx @@ -9,15 +9,23 @@ import { RecordStoreDecorator } from '~/testing/decorators/RecordStoreDecorator' import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; import { getCompaniesMock } from '~/testing/mock-data/companies'; -import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata'; import { getPeopleMock } from '~/testing/mock-data/people'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { RecordDetailRelationSection } from '../RecordDetailRelationSection'; const companiesMock = getCompaniesMock(); const peopleMock = getPeopleMock(); +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +if (!mockedCompanyObjectMetadataItem) { + throw new Error('Company object metadata item not found'); +} + const meta: Meta = { title: 'Modules/ObjectRecord/RecordShow/RecordDetailSection/RecordDetailRelationSection', diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx index 9b68416e8192..b866e344842f 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx @@ -21,7 +21,7 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockPerformance } from './mock'; const RelationFieldValueSetterEffect = () => { diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSkeletonLoader.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSkeletonLoader.tsx index b010bdaecd6d..f62018d33388 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellSkeletonLoader.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledSkeletonContainer = styled.div` padding-left: ${({ theme }) => theme.spacing(2)}; @@ -15,7 +16,10 @@ const StyledRecordTableCellLoader = ({ width }: { width?: number }) => { highlightColor={theme.background.transparent.lighter} borderRadius={4} > - + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useUpsertRecord.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useUpsertRecord.test.tsx index e592107c5a1b..cf18193570e2 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useUpsertRecord.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__tests__/useUpsertRecord.test.tsx @@ -10,7 +10,7 @@ import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fiel import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { useUpsertRecord } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecord'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const draftValue = 'updated Name'; diff --git a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx index 9eb2e7b3642e..0aeca9fcb3aa 100644 --- a/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx +++ b/packages/twenty-front/src/modules/object-record/relation-picker/hooks/__tests__/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray.test.tsx @@ -4,7 +4,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray } from '@/object-record/relation-picker/hooks/useMultiObjectRecordsQueryResultFormattedAsObjectRecordForSelectArray'; import { RelationPickerScopeInternalContext } from '@/object-record/relation-picker/scopes/scope-internal-context/RelationPickerScopeInternalContext'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; const scopeId = 'scopeId'; const Wrapper = ({ children }: { children: React.ReactNode }) => ( diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useOpenObjectRecordsSpreasheetImportDialog.test.tsx b/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useOpenObjectRecordsSpreasheetImportDialog.test.tsx index da745581eb75..a7e47f8ba713 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useOpenObjectRecordsSpreasheetImportDialog.test.tsx +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/__tests__/useOpenObjectRecordsSpreasheetImportDialog.test.tsx @@ -1,13 +1,12 @@ import { gql } from '@apollo/client'; -import { MockedProvider } from '@apollo/client/testing'; -import { act, renderHook, waitFor } from '@testing-library/react'; -import { ReactNode } from 'react'; -import { RecoilRoot, useRecoilValue } from 'recoil'; +import { renderHook, waitFor } from '@testing-library/react'; +import { act } from 'react'; +import { useRecoilValue } from 'recoil'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { spreadsheetImportDialogState } from '@/spreadsheet-import/states/spreadsheetImportDialogState'; -import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; import { useOpenObjectRecordsSpreasheetImportDialog } from '../hooks/useOpenObjectRecordsSpreasheetImportDialog'; const companyId = 'cb2e9f4b-20c3-4759-9315-4ffeecfaf71a'; @@ -26,13 +25,42 @@ const companyMocks = [ ) { createCompanies(data: $data, upsert: $upsert) { __typename - updatedAt - domainName { - primaryLinkUrl - primaryLinkLabel - secondaryLinks + accountOwner { + __typename + avatarUrl + colorScheme + createdAt + dateFormat + deletedAt + id + locale + name { + firstName + lastName + } + timeFormat + timeZone + updatedAt + userEmail + userId + } + accountOwnerId + activityTargets { + edges { + node { + __typename + activityId + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + updatedAt + } + } } - visaSponsorship address { addressStreet1 addressStreet2 @@ -43,20 +71,31 @@ const companyMocks = [ addressLat addressLng } - position - employees - deletedAt - accountOwnerId annualRecurringRevenue { amountMicros currencyCode } - id - name - xLink { - primaryLinkUrl - primaryLinkLabel - secondaryLinks + attachments { + edges { + node { + __typename + activityId + authorId + companyId + createdAt + deletedAt + fullPath + id + name + noteId + opportunityId + personId + rocketId + taskId + type + updatedAt + } + } } createdAt createdBy { @@ -64,7 +103,36 @@ const companyMocks = [ workspaceMemberId name } - workPolicy + deletedAt + domainName { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + employees + favorites { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + position + rocketId + taskId + updatedAt + viewId + workflowId + workspaceMemberId + } + } + } + id + idealCustomerProfile introVideo { primaryLinkUrl primaryLinkLabel @@ -75,8 +143,151 @@ const companyMocks = [ primaryLinkLabel secondaryLinks } + name + noteTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + noteId + opportunityId + personId + rocketId + updatedAt + } + } + } + opportunities { + edges { + node { + __typename + amount { + amountMicros + currencyCode + } + closeDate + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + id + name + pointOfContactId + position + stage + updatedAt + } + } + } + people { + edges { + node { + __typename + avatarUrl + city + companyId + createdAt + createdBy { + source + workspaceMemberId + name + } + deletedAt + emails { + primaryEmail + additionalEmails + } + id + intro + jobTitle + linkedinLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + name { + firstName + lastName + } + performanceRating + phones { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + position + updatedAt + whatsapp { + primaryPhoneNumber + primaryPhoneCountryCode + additionalPhones + } + workPreference + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } + } + } + } + position tagline - idealCustomerProfile + taskTargets { + edges { + node { + __typename + companyId + createdAt + deletedAt + id + opportunityId + personId + rocketId + taskId + updatedAt + } + } + } + timelineActivities { + edges { + node { + __typename + companyId + createdAt + deletedAt + happensAt + id + linkedObjectMetadataId + linkedRecordCachedName + linkedRecordId + name + noteId + opportunityId + personId + properties + rocketId + taskId + updatedAt + workspaceMemberId + } + } + } + updatedAt + visaSponsorship + workPolicy + xLink { + primaryLinkUrl + primaryLinkLabel + secondaryLinks + } } } `, @@ -99,6 +310,9 @@ const companyMocks = [ createCompanies: [ { id: companyId, + favorites: { + edges: [], + }, }, ], }, @@ -112,17 +326,9 @@ const fakeCsv = () => { return new File([blob], 'fakeData.csv', { type: 'text/csv' }); }; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - -); +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: companyMocks, +}); // TODO: improve object metadata item seeds to have more field types to add tests on composite fields here describe('useSpreadsheetCompanyImport', () => { diff --git a/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx b/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx index 83fa74e7b864..263b70decf0f 100644 --- a/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx +++ b/packages/twenty-front/src/modules/search/hooks/__tests__/useFilteredSearchEntityQuery.test.tsx @@ -8,7 +8,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { EntitiesForMultipleEntitySelect } from '@/object-record/relation-picker/types/EntitiesForMultipleEntitySelect'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { query, responseData, diff --git a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelDetails.stories.tsx b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelDetails.stories.tsx index 5ef756baf4c6..a4e9f5294e83 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelDetails.stories.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelDetails.stories.tsx @@ -3,12 +3,18 @@ import { ComponentDecorator } from 'twenty-ui'; import { SettingsAccountsCalendarChannelDetails } from '@/settings/accounts/components/SettingsAccountsCalendarChannelDetails'; import { CalendarChannelVisibility } from '~/generated/graphql'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; const meta: Meta = { title: 'Modules/Settings/Accounts/CalendarChannels/SettingsAccountsCalendarChannelDetails', component: SettingsAccountsCalendarChannelDetails, - decorators: [ComponentDecorator], + decorators: [ + ComponentDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + ], args: { calendarChannel: { id: '20202020-ef5a-4822-9e08-ce6e6a4dcb6a', diff --git a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelsGeneral.stories.tsx b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelsGeneral.stories.tsx index 49f279316908..83c05df92b98 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelsGeneral.stories.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsCalendarChannelsGeneral.stories.tsx @@ -2,12 +2,18 @@ import { Meta, StoryObj } from '@storybook/react'; import { ComponentDecorator } from 'twenty-ui'; import { SettingsAccountsCalendarChannelsGeneral } from '@/settings/accounts/components/SettingsAccountsCalendarChannelsGeneral'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; const meta: Meta = { title: 'Modules/Settings/Accounts/CalendarChannels/SettingsAccountsCalendarChannelsGeneral', component: SettingsAccountsCalendarChannelsGeneral, - decorators: [ComponentDecorator], + decorators: [ + ComponentDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + ], }; export default meta; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsMessageChannelDetails.stories.tsx b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsMessageChannelDetails.stories.tsx index 02264d8e66fe..a951150c94f3 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsMessageChannelDetails.stories.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/__stories__/SettingsAccountsMessageChannelDetails.stories.tsx @@ -4,12 +4,18 @@ import { ComponentDecorator } from 'twenty-ui'; import { MessageChannelContactAutoCreationPolicy } from '@/accounts/types/MessageChannel'; import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/components/SettingsAccountsMessageChannelDetails'; import { MessageChannelVisibility } from '~/generated/graphql'; +import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; +import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; const meta: Meta = { title: 'Modules/Settings/Accounts/MessageChannels/SettingsAccountsMessageChannelDetails', component: SettingsAccountsMessageChannelDetails, - decorators: [ComponentDecorator], + decorators: [ + ComponentDecorator, + ObjectMetadataItemsDecorator, + SnackBarDecorator, + ], args: { messageChannel: { id: '20202020-ef5a-4822-9e08-ce6e6a4dcb6a', diff --git a/packages/twenty-front/src/modules/settings/data-model/constants/RelationTypes.ts b/packages/twenty-front/src/modules/settings/data-model/constants/RelationTypes.ts index da5e9b8b9617..179f60fe8979 100644 --- a/packages/twenty-front/src/modules/settings/data-model/constants/RelationTypes.ts +++ b/packages/twenty-front/src/modules/settings/data-model/constants/RelationTypes.ts @@ -1,5 +1,6 @@ import { IconComponent, + IllustrationIconManyToMany, IllustrationIconOneToMany, IllustrationIconOneToOne, } from 'twenty-ui'; @@ -34,4 +35,11 @@ export const RELATION_TYPES: Record< imageSrc: OneToManySvg, isImageFlipped: true, }, + // Not supported yet + [RelationDefinitionType.ManyToMany]: { + label: 'Belongs to many', + Icon: IllustrationIconManyToMany, + imageSrc: OneToManySvg, + isImageFlipped: true, + }, }; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx index a71cc1654bcf..882b57f7d1c1 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx @@ -11,6 +11,8 @@ import { settingsDataModelFieldCurrencyFormSchema } from '@/settings/data-model/ import { SettingsDataModelFieldCurrencySettingsFormCard } from '@/settings/data-model/fields/forms/currency/components/SettingsDataModelFieldCurrencySettingsFormCard'; import { settingsDataModelFieldDateFormSchema } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateForm'; import { SettingsDataModelFieldDateSettingsFormCard } from '@/settings/data-model/fields/forms/date/components/SettingsDataModelFieldDateSettingsFormCard'; +import { settingsDataModelFieldNumberFormSchema } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm'; +import { SettingsDataModelFieldNumberSettingsFormCard } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard'; import { settingsDataModelFieldRelationFormSchema } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm'; import { SettingsDataModelFieldRelationSettingsFormCard } from '@/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationSettingsFormCard'; import { @@ -52,6 +54,10 @@ const multiSelectFieldFormSchema = z .object({ type: z.literal(FieldMetadataType.MultiSelect) }) .merge(settingsDataModelFieldMultiSelectFormSchema); +const numberFieldFormSchema = z + .object({ type: z.literal(FieldMetadataType.Number) }) + .merge(settingsDataModelFieldNumberFormSchema); + const otherFieldsFormSchema = z.object({ type: z.enum( Object.keys( @@ -63,6 +69,7 @@ const otherFieldsFormSchema = z.object({ FieldMetadataType.MultiSelect, FieldMetadataType.Date, FieldMetadataType.DateTime, + FieldMetadataType.Number, ]), ) as [FieldMetadataType, ...FieldMetadataType[]], ), @@ -78,13 +85,17 @@ export const settingsDataModelFieldSettingsFormSchema = z.discriminatedUnion( relationFieldFormSchema, selectFieldFormSchema, multiSelectFieldFormSchema, + numberFieldFormSchema, otherFieldsFormSchema, ], ); type SettingsDataModelFieldSettingsFormCardProps = { isCreatingField?: boolean; - fieldMetadataItem: Pick & + fieldMetadataItem: Pick< + FieldMetadataItem, + 'icon' | 'label' | 'type' | 'isCustom' + > & Partial>; } & Pick; @@ -163,6 +174,16 @@ export const SettingsDataModelFieldSettingsFormCard = ({ ); } + if (fieldMetadataItem.type === FieldMetadataType.Number) { + return ( + + ); + } + if ( fieldMetadataItem.type === FieldMetadataType.Select || fieldMetadataItem.type === FieldMetadataType.MultiSelect diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldDescriptionForm.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldDescriptionForm.stories.tsx index 7499adea0338..58f33bf2210f 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldDescriptionForm.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldDescriptionForm.stories.tsx @@ -3,7 +3,7 @@ import { ComponentDecorator } from 'twenty-ui'; import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator'; -import { mockedPersonObjectMetadataItem } from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { SettingsDataModelFieldDescriptionForm } from '../SettingsDataModelFieldDescriptionForm'; const meta: Meta = { @@ -25,11 +25,15 @@ type Story = StoryObj; export const Default: Story = {}; +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.namePlural === 'person', +); + export const WithFieldMetadataItem: Story = { args: { - fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find( + fieldMetadataItem: mockedPersonObjectMetadataItem?.fields.find( ({ description }) => description === 'description', - )!, + ), }, }; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldIconLabelForm.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldIconLabelForm.stories.tsx index 150efdc46683..555db1bc92d1 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldIconLabelForm.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldIconLabelForm.stories.tsx @@ -5,7 +5,7 @@ import { ComponentDecorator } from 'twenty-ui'; import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; -import { mockedPersonObjectMetadataItem } from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { SettingsDataModelFieldIconLabelForm } from '../SettingsDataModelFieldIconLabelForm'; const StyledContainer = styled.div` @@ -32,11 +32,15 @@ type Story = StoryObj; export const Default: Story = {}; +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.namePlural === 'person', +); + export const WithFieldMetadataItem: Story = { args: { - fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find( + fieldMetadataItem: mockedPersonObjectMetadataItem?.fields.find( ({ name }) => name === 'name', - )!, + ), }, }; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldSettingsFormCard.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldSettingsFormCard.stories.tsx index aff46aa2da0e..09aaf60c282c 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldSettingsFormCard.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldSettingsFormCard.stories.tsx @@ -7,10 +7,18 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { graphqlMocks } from '~/testing/graphqlMocks'; -import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { SettingsDataModelFieldSettingsFormCard } from '../SettingsDataModelFieldSettingsFormCard'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +if (!mockedCompanyObjectMetadataItem) { + throw new Error('Company object metadata item not found'); +} + const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( ({ type }) => type === FieldMetadataType.Text, )!; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput.tsx new file mode 100644 index 000000000000..706bcea37154 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput.tsx @@ -0,0 +1,168 @@ +import styled from '@emotion/styled'; + +import { Button } from '@/ui/input/button/components/Button'; +import { TextInput } from '@/ui/input/components/TextInput'; +import { IconInfoCircle, IconMinus, IconPlus } from 'twenty-ui'; +import { castAsNumberOrNull } from '~/utils/cast-as-number-or-null'; + +type SettingsDataModelFieldNumberDecimalsInputProps = { + value: number; + onChange: (value: number) => void; + disabled?: boolean; +}; + +const StyledCounterContainer = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.noisy}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: 4px; + display: flex; + flex-direction: column; + flex: 1; + gap: ${({ theme }) => theme.spacing(1)}; + justify-content: center; +`; + +const StyledExampleText = styled.div` + color: ${({ theme }) => theme.font.color.primary}; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: ${({ theme }) => theme.font.weight.regular}; +`; + +const StyledCounterControlsIcons = styled.div` + align-items: center; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledCounterInnerContainer = styled.div` + align-items: center; + align-self: stretch; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; + padding: ${({ theme }) => theme.spacing(2)}; + height: 24px; +`; + +const StyledTextInput = styled(TextInput)` + width: ${({ theme }) => theme.spacing(16)}; + input { + width: ${({ theme }) => theme.spacing(16)}; + height: ${({ theme }) => theme.spacing(6)}; + text-align: center; + font-weight: ${({ theme }) => theme.font.weight.medium}; + background: ${({ theme }) => theme.background.noisy}; + } + input ~ div { + padding-right: ${({ theme }) => theme.spacing(0)}; + border-radius: ${({ theme }) => theme.spacing(1)}; + background: ${({ theme }) => theme.background.noisy}; + } +`; + +const StyledTitle = styled.div` + color: ${({ theme }) => theme.font.color.light}; + font-size: ${({ theme }) => theme.font.size.xs}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin-bottom: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledControlButton = styled(Button)` + height: ${({ theme }) => theme.spacing(6)}; + width: ${({ theme }) => theme.spacing(6)}; + padding: 0; + justify-content: center; + svg { + height: ${({ theme }) => theme.spacing(4)}; + width: ${({ theme }) => theme.spacing(4)}; + } +`; + +const StyledInfoButton = styled(Button)` + height: ${({ theme }) => theme.spacing(6)}; + width: ${({ theme }) => theme.spacing(6)}; + padding: 0; + justify-content: center; + svg { + color: ${({ theme }) => theme.font.color.extraLight}; + height: ${({ theme }) => theme.spacing(4)}; + width: ${({ theme }) => theme.spacing(4)}; + } +`; + +const MIN_VALUE = 0; +const MAX_VALUE = 100; +export const SettingsDataModelFieldNumberDecimalsInput = ({ + value, + onChange, + disabled, +}: SettingsDataModelFieldNumberDecimalsInputProps) => { + const exampleValue = (1000).toFixed(value); + + const handleIncrementCounter = () => { + if (value < MAX_VALUE) { + const newValue = value + 1; + onChange(newValue); + } + }; + + const handleDecrementCounter = () => { + if (value > MIN_VALUE) { + const newValue = value - 1; + onChange(newValue); + } + }; + + const handleTextInputChange = (value: string) => { + const castedNumber = castAsNumberOrNull(value); + if (castedNumber === null) { + onChange(MIN_VALUE); + return; + } + + if (castedNumber < MIN_VALUE) { + return; + } + + if (castedNumber > MAX_VALUE) { + onChange(MAX_VALUE); + return; + } + onChange(castedNumber); + }; + return ( + <> + Number of decimals + + + Example: {exampleValue} + + + + handleTextInputChange(value)} + disabled={disabled} + /> + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx new file mode 100644 index 000000000000..3a80bf0e6104 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm.tsx @@ -0,0 +1,55 @@ +import { Controller, useFormContext } from 'react-hook-form'; +import { z } from 'zod'; + +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { numberFieldDefaultValueSchema } from '@/object-record/record-field/validation-schemas/numberFieldDefaultValueSchema'; +import { SettingsDataModelFieldNumberDecimalsInput } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberDecimalInput'; +import { CardContent } from '@/ui/layout/card/components/CardContent'; +import { DEFAULT_DECIMAL_VALUE } from '~/utils/format/number'; + +export const settingsDataModelFieldNumberFormSchema = z.object({ + settings: numberFieldDefaultValueSchema, +}); + +export type SettingsDataModelFieldNumberFormValues = z.infer< + typeof settingsDataModelFieldNumberFormSchema +>; + +type SettingsDataModelFieldNumberFormProps = { + disabled?: boolean; + fieldMetadataItem: Pick< + FieldMetadataItem, + 'icon' | 'label' | 'type' | 'defaultValue' | 'settings' + >; +}; + +export const SettingsDataModelFieldNumberForm = ({ + disabled, + fieldMetadataItem, +}: SettingsDataModelFieldNumberFormProps) => { + const { control } = useFormContext(); + + return ( + + { + const count = value?.decimals ?? 0; + + return ( + onChange({ decimals: value })} + disabled={disabled} + > + ); + }} + /> + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx new file mode 100644 index 000000000000..edea86760fbf --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberSettingsFormCard.tsx @@ -0,0 +1,45 @@ +import styled from '@emotion/styled'; + +import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard'; +import { SettingsDataModelFieldNumberForm } from '@/settings/data-model/fields/forms/number/components/SettingsDataModelFieldNumberForm'; +import { + SettingsDataModelFieldPreviewCard, + SettingsDataModelFieldPreviewCardProps, +} from '@/settings/data-model/fields/preview/components/SettingsDataModelFieldPreviewCard'; + +type SettingsDataModelFieldNumberSettingsFormCardProps = { + disabled?: boolean; + fieldMetadataItem: Pick< + FieldMetadataItem, + 'icon' | 'label' | 'type' | 'defaultValue' + >; +} & Pick; + +const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)` + display: grid; + flex: 1 1 100%; +`; + +export const SettingsDataModelFieldNumberSettingsFormCard = ({ + disabled, + fieldMetadataItem, + objectMetadataItem, +}: SettingsDataModelFieldNumberSettingsFormCardProps) => { + return ( + + } + form={ + + } + /> + ); +}; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx index 63305b53d04d..d6f3fe48be10 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/relation/components/SettingsDataModelFieldRelationForm.tsx @@ -14,6 +14,7 @@ import { RelationType } from '@/settings/data-model/types/RelationType'; import { IconPicker } from '@/ui/input/components/IconPicker'; import { Select } from '@/ui/input/components/Select'; import { TextInput } from '@/ui/input/components/TextInput'; +import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { RelationDefinitionType } from '~/generated-metadata/graphql'; export const settingsDataModelFieldRelationFormSchema = z.object({ @@ -44,13 +45,12 @@ const StyledContainer = styled.div` padding: ${({ theme }) => theme.spacing(4)}; `; -const StyledSelectsContainer = styled.div` +const StyledSelectsContainer = styled.div<{ isMobile: boolean }>` display: grid; gap: ${({ theme }) => theme.spacing(4)}; - grid-template-columns: 1fr 1fr; + grid-template-columns: ${({ isMobile }) => (isMobile ? '1fr' : '1fr 1fr')}; margin-bottom: ${({ theme }) => theme.spacing(4)}; `; - const StyledInputsLabel = styled.span` color: ${({ theme }) => theme.font.color.light}; display: block; @@ -66,7 +66,11 @@ const StyledInputsContainer = styled.div` `; const RELATION_TYPE_OPTIONS = Object.entries(RELATION_TYPES) - .filter(([value]) => 'ONE_TO_ONE' !== value && 'MANY_TO_MANY' !== value) + .filter( + ([value]) => + RelationDefinitionType.OneToOne !== value && + RelationDefinitionType.ManyToMany !== value, + ) .map(([value, { label, Icon }]) => ({ label, value: value as RelationType, @@ -94,9 +98,11 @@ export const SettingsDataModelFieldRelationForm = ({ watchFormValue('relation.objectMetadataId'), ); + const isMobile = useIsMobile(); + return ( - + & Partial>; @@ -27,14 +27,23 @@ const StyledFieldPreviewCard = styled(SettingsDataModelFieldPreviewCard)` flex: 1 1 100%; `; -const StyledPreviewContent = styled.div` +const StyledPreviewContent = styled.div<{ isMobile: boolean }>` display: flex; - flex-direction: column; gap: 6px; + flex-direction: ${({ isMobile }) => (isMobile ? 'column' : 'row')}; `; -const StyledRelationImage = styled.img<{ flip?: boolean }>` - transform: ${({ flip }) => (flip ? 'scaleX(-1) rotate(270deg)' : 'none')}; +const StyledRelationImage = styled.img<{ flip?: boolean; isMobile: boolean }>` + transform: ${({ flip, isMobile }) => { + let transform = ''; + if (isMobile) { + transform += 'rotate(90deg) '; + } + if (flip === true) { + transform += 'scaleX(-1)'; + } + return transform.trim(); + }}; margin: auto; width: 54px; `; @@ -46,7 +55,7 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({ const { watch: watchFormValue } = useFormContext(); const { findObjectMetadataItemById } = useFilteredObjectMetadataItems(); - + const isMobile = useIsMobile(); const { initialRelationObjectMetadataItem, initialRelationType, @@ -69,7 +78,7 @@ export const SettingsDataModelFieldRelationSettingsFormCard = ({ return ( + item.nameSingular === 'company', +); + +const mockedOpportunityObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); + const meta: Meta = { title: 'Modules/Settings/DataModel/Fields/Preview/SettingsDataModelFieldPreviewCard', @@ -38,7 +47,7 @@ type Story = StoryObj; export const LabelIdentifier: Story = { args: { - fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find( + fieldMetadataItem: mockedPersonObjectMetadataItem?.fields.find( ({ name, type }) => name === 'name' && type === FieldMetadataType.FullName, ), @@ -47,7 +56,7 @@ export const LabelIdentifier: Story = { export const Text: Story = { args: { - fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find( + fieldMetadataItem: mockedPersonObjectMetadataItem?.fields.find( ({ name, type }) => name === 'city' && type === FieldMetadataType.Text, ), }, @@ -55,7 +64,7 @@ export const Text: Story = { export const Boolean: Story = { args: { - fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( + fieldMetadataItem: mockedCompanyObjectMetadataItem?.fields.find( ({ name, type }) => name === 'idealCustomerProfile' && type === FieldMetadataType.Boolean, ), @@ -65,7 +74,7 @@ export const Boolean: Story = { export const Currency: Story = { args: { - fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( + fieldMetadataItem: mockedCompanyObjectMetadataItem?.fields.find( ({ name, type }) => name === 'annualRecurringRevenue' && type === FieldMetadataType.Currency, @@ -76,7 +85,7 @@ export const Currency: Story = { export const Date: Story = { args: { - fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( + fieldMetadataItem: mockedCompanyObjectMetadataItem?.fields.find( ({ type }) => type === FieldMetadataType.DateTime, ), objectMetadataItem: mockedCompanyObjectMetadataItem, @@ -85,7 +94,7 @@ export const Date: Story = { export const Links: Story = { args: { - fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( + fieldMetadataItem: mockedCompanyObjectMetadataItem?.fields.find( ({ name, type }) => name === 'linkedinLink' && type === FieldMetadataType.Links, ), @@ -95,7 +104,7 @@ export const Links: Story = { export const Number: Story = { args: { - fieldMetadataItem: mockedCompanyObjectMetadataItem.fields.find( + fieldMetadataItem: mockedCompanyObjectMetadataItem?.fields.find( ({ type }) => type === FieldMetadataType.Number, ), objectMetadataItem: mockedCompanyObjectMetadataItem, @@ -114,7 +123,7 @@ export const Rating: Story = { export const Relation: Story = { args: { - fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find( + fieldMetadataItem: mockedPersonObjectMetadataItem?.fields.find( ({ name }) => name === 'company', ), relationObjectMetadataItem: mockedCompanyObjectMetadataItem, @@ -123,7 +132,7 @@ export const Relation: Story = { export const Select: Story = { args: { - fieldMetadataItem: mockedOpportunityObjectMetadataItem.fields.find( + fieldMetadataItem: mockedOpportunityObjectMetadataItem?.fields.find( ({ name, type }) => name === 'stage' && type === FieldMetadataType.Select, ), objectMetadataItem: mockedOpportunityObjectMetadataItem, diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx index a3fbadbf1a24..c9c6b0d4367b 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/hooks/__tests__/useFieldPreviewValue.test.tsx @@ -1,35 +1,34 @@ -import { ReactNode } from 'react'; -import { MockedProvider } from '@apollo/client/testing'; import { renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; -import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem'; -import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { FieldMetadataType } from '~/generated/graphql'; -import { - mockedCompanyObjectMetadataItem, - mockedOpportunityObjectMetadataItem, - mockedPersonObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { useFieldPreviewValue } from '../useFieldPreviewValue'; -const Wrapper = ({ children }: { children: ReactNode }) => ( - - - - {children} - - - +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', ); +const mockedOpportunityObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); + +const Wrapper = getJestMetadataAndApolloMocksWrapper({ + apolloMocks: [], +}); + describe('useFieldPreviewValue', () => { it('returns null if skip is true', () => { // Given const fieldName = 'amount'; - const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedOpportunityObjectMetadataItem?.fields.find( ({ name, type }) => name === fieldName && type === FieldMetadataType.Currency, ); @@ -52,7 +51,7 @@ describe('useFieldPreviewValue', () => { it("returns the field's preview value for a Currency field", () => { // Given const fieldName = 'amount'; - const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedOpportunityObjectMetadataItem?.fields.find( ({ name, type }) => name === fieldName && type === FieldMetadataType.Currency, ); @@ -106,7 +105,7 @@ describe('useFieldPreviewValue', () => { it("returns the field's preview value for a Select field", () => { // Given const fieldName = 'stage'; - const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedOpportunityObjectMetadataItem?.fields.find( ({ name, type }) => name === fieldName && type === FieldMetadataType.Select, ); @@ -169,7 +168,7 @@ describe('useFieldPreviewValue', () => { it("returns the field's preview value for other field types", () => { // Given const fieldName = 'employees'; - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ name }) => name === fieldName, ); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getCurrencyFieldPreviewValue.test.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getCurrencyFieldPreviewValue.test.ts index a8d9b9a4af7e..8eeda74fbb79 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getCurrencyFieldPreviewValue.test.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getCurrencyFieldPreviewValue.test.ts @@ -1,16 +1,22 @@ import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { - mockedCompanyObjectMetadataItem, - mockedOpportunityObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getCurrencyFieldPreviewValue } from '../getCurrencyFieldPreviewValue'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedOpportunityObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + describe('getCurrencyFieldPreviewValue', () => { it('returns null if the field is not a Currency field', () => { // Given - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ type }) => type !== FieldMetadataType.Currency, ); @@ -26,7 +32,7 @@ describe('getCurrencyFieldPreviewValue', () => { }); const fieldName = 'amount'; - const fieldMetadataItem = mockedOpportunityObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedOpportunityObjectMetadataItem?.fields.find( ({ name, type }) => name === fieldName && type === FieldMetadataType.Currency, ); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getFieldPreviewValue.test.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getFieldPreviewValue.test.ts index 7acf2b0cfe4c..c3a649dbad77 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getFieldPreviewValue.test.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getFieldPreviewValue.test.ts @@ -1,17 +1,21 @@ import { getFieldPreviewValue } from '@/settings/data-model/fields/preview/utils/getFieldPreviewValue'; import { getSettingsFieldTypeConfig } from '@/settings/data-model/utils/getSettingsFieldTypeConfig'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { - mockedCompanyObjectMetadataItem, - mockedCustomObjectMetadataItem, - mockedPersonObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; + +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); describe('getFieldPreviewValue', () => { it("returns the field's defaultValue from metadata if it exists", () => { // Given const fieldName = 'idealCustomerProfile'; - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ name }) => name === fieldName, ); @@ -29,7 +33,7 @@ describe('getFieldPreviewValue', () => { it('returns a placeholder defaultValue if the field metadata does not have a defaultValue', () => { // Given const fieldName = 'employees'; - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ name }) => name === fieldName, ); @@ -50,25 +54,7 @@ describe('getFieldPreviewValue', () => { it('returns null if the field is supported in Settings but has no pre-configured placeholder defaultValue', () => { // Given const fieldName = 'company'; - const fieldMetadataItem = mockedPersonObjectMetadataItem.fields.find( - ({ name }) => name === fieldName, - ); - - if (!fieldMetadataItem) { - throw new Error(`Field '${fieldName}' not found`); - } - - // When - const result = getFieldPreviewValue({ fieldMetadataItem }); - - // Then - expect(result).toBeNull(); - }); - - it('returns null if the field is not supported in Settings', () => { - // Given - const fieldName = 'position'; - const fieldMetadataItem = mockedCustomObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedPersonObjectMetadataItem?.fields.find( ({ name }) => name === fieldName, ); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getMultiSelectFieldPreviewValue.test.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getMultiSelectFieldPreviewValue.test.ts index 824d6e820167..2737a829ee91 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getMultiSelectFieldPreviewValue.test.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getMultiSelectFieldPreviewValue.test.ts @@ -1,15 +1,20 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { - mockedCompanyObjectMetadataItem, - mockedCustomObjectMetadataItem, -} from '~/testing/mock-data/metadata'; - +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getMultiSelectFieldPreviewValue } from '../getMultiSelectFieldPreviewValue'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedOpportunityObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + describe('getMultiSelectFieldPreviewValue', () => { it('returns null if the field is not a Multi-Select field', () => { // Given - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ type }) => type !== FieldMetadataType.MultiSelect, ); @@ -24,10 +29,11 @@ describe('getMultiSelectFieldPreviewValue', () => { expect(previewValue).toBeNull(); }); - const fieldName = 'priority'; - const selectFieldMetadataItem = mockedCustomObjectMetadataItem.fields.find( - ({ name, type }) => name === fieldName && type === FieldMetadataType.Select, - ); + const fieldName = 'stage'; + const selectFieldMetadataItem = + mockedOpportunityObjectMetadataItem?.fields.find( + ({ name }) => name === fieldName, + ); if (!selectFieldMetadataItem) { throw new Error(`Field '${fieldName}' not found`); @@ -52,7 +58,13 @@ describe('getMultiSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toEqual(['MEDIUM', 'LOW']); + expect(previewValue).toEqual([ + 'NEW', + 'SCREENING', + 'MEETING', + 'PROPOSAL', + 'CUSTOMER', + ]); }); it("returns all option values if no defaultValue was found in the field's metadata", () => { @@ -69,7 +81,13 @@ describe('getMultiSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toEqual(['LOW', 'MEDIUM', 'HIGH']); + expect(previewValue).toEqual([ + 'NEW', + 'SCREENING', + 'MEETING', + 'PROPOSAL', + 'CUSTOMER', + ]); expect(previewValue).toEqual( fieldMetadataItemWithDefaultValue.options?.map(({ value }) => value), ); @@ -89,7 +107,13 @@ describe('getMultiSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toEqual(['LOW', 'MEDIUM', 'HIGH']); + expect(previewValue).toEqual([ + 'NEW', + 'SCREENING', + 'MEETING', + 'PROPOSAL', + 'CUSTOMER', + ]); expect(previewValue).toEqual( fieldMetadataItemWithDefaultValue.options?.map(({ value }) => value), ); diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getSelectFieldPreviewValue.test.ts b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getSelectFieldPreviewValue.test.ts index 3572d58d9007..109feefb3ab1 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getSelectFieldPreviewValue.test.ts +++ b/packages/twenty-front/src/modules/settings/data-model/fields/preview/utils/__tests__/getSelectFieldPreviewValue.test.ts @@ -1,15 +1,21 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { - mockedCompanyObjectMetadataItem, - mockedCustomObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getSelectFieldPreviewValue } from '../getSelectFieldPreviewValue'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedOpportunityObjectMetadataItem = + generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', + ); + describe('getSelectFieldPreviewValue', () => { it('returns null if the field is not a Select field', () => { // Given - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ type }) => type !== FieldMetadataType.Select, ); @@ -24,9 +30,9 @@ describe('getSelectFieldPreviewValue', () => { expect(previewValue).toBeNull(); }); - const fieldName = 'priority'; - const fieldMetadataItem = mockedCustomObjectMetadataItem.fields.find( - ({ name, type }) => name === fieldName && type === FieldMetadataType.Select, + const fieldName = 'stage'; + const fieldMetadataItem = mockedOpportunityObjectMetadataItem?.fields.find( + ({ name }) => name === fieldName, ); if (!fieldMetadataItem) { @@ -35,7 +41,7 @@ describe('getSelectFieldPreviewValue', () => { it("returns the defaultValue as an option value if a valid defaultValue is found in the field's metadata", () => { // Given - const defaultValue = "'MEDIUM'"; + const defaultValue = "'NEW'"; const fieldMetadataItemWithDefaultValue = { ...fieldMetadataItem, defaultValue, @@ -47,7 +53,7 @@ describe('getSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toBe('MEDIUM'); + expect(previewValue).toBe('NEW'); }); it("returns the first option value if no defaultValue was found in the field's metadata", () => { @@ -64,7 +70,7 @@ describe('getSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toBe('LOW'); + expect(previewValue).toBe('NEW'); expect(previewValue).toBe( fieldMetadataItemWithDefaultValue.options?.[0]?.value, ); @@ -84,7 +90,7 @@ describe('getSelectFieldPreviewValue', () => { }); // Then - expect(previewValue).toBe('LOW'); + expect(previewValue).toBe('NEW'); expect(previewValue).toBe( fieldMetadataItemWithDefaultValue.options?.[0]?.value, ); diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/__stories__/SettingsDataModelObjectAboutForm.stories.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/__stories__/SettingsDataModelObjectAboutForm.stories.tsx index 4345f59229e9..173106174fdf 100644 --- a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/__stories__/SettingsDataModelObjectAboutForm.stories.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/__stories__/SettingsDataModelObjectAboutForm.stories.tsx @@ -4,9 +4,12 @@ import { ComponentDecorator } from 'twenty-ui'; import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; -import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { SettingsDataModelObjectAboutForm } from '../SettingsDataModelObjectAboutForm'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); const StyledContainer = styled.div` flex: 1; diff --git a/packages/twenty-front/src/modules/settings/data-model/utils/__tests__/getFieldPreviewValueFromRecord.test.ts b/packages/twenty-front/src/modules/settings/data-model/utils/__tests__/getFieldPreviewValueFromRecord.test.ts index 95b0aac275e1..750604ef1515 100644 --- a/packages/twenty-front/src/modules/settings/data-model/utils/__tests__/getFieldPreviewValueFromRecord.test.ts +++ b/packages/twenty-front/src/modules/settings/data-model/utils/__tests__/getFieldPreviewValueFromRecord.test.ts @@ -1,10 +1,14 @@ import { ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { - mockedCompanyObjectMetadataItem, - mockedPersonObjectMetadataItem, -} from '~/testing/mock-data/metadata'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getFieldPreviewValueFromRecord } from '../getFieldPreviewValueFromRecord'; +const mockedCompanyObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const mockedPersonObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); describe('getFieldPreviewValueFromRecord', () => { describe('RELATION field', () => { @@ -21,9 +25,13 @@ describe('getFieldPreviewValueFromRecord', () => { }, __typename: 'Opportunity', }; - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ name }) => name === 'people', - )!; + ); + + if (!fieldMetadataItem) { + throw new Error('Field not found'); + } // When const result = getFieldPreviewValueFromRecord({ @@ -43,9 +51,13 @@ describe('getFieldPreviewValueFromRecord', () => { company: relationRecord, __typename: 'Opportunity', }; - const fieldMetadataItem = mockedPersonObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedPersonObjectMetadataItem?.fields.find( ({ name }) => name === 'company', - )!; + ); + + if (!fieldMetadataItem) { + throw new Error('Field not found'); + } // When const result = getFieldPreviewValueFromRecord({ @@ -62,9 +74,13 @@ describe('getFieldPreviewValueFromRecord', () => { it('returns the record field value', () => { // Given const record = { id: '', name: 'Twenty', __typename: 'Opportunity' }; - const fieldMetadataItem = mockedCompanyObjectMetadataItem.fields.find( + const fieldMetadataItem = mockedCompanyObjectMetadataItem?.fields.find( ({ name }) => name === 'name', - )!; + ); + + if (!fieldMetadataItem) { + throw new Error('Field not found'); + } // When const result = getFieldPreviewValueFromRecord({ diff --git a/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx b/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx index dcfcfaf5e7e1..6d9bf03e00e2 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx @@ -71,7 +71,7 @@ export const ProfilePictureUploader = () => { return result; } catch (error) { - setErrorMessage('An error occured while uploading the picture.'); + setErrorMessage('An error occurred while uploading the picture.'); } }; @@ -97,7 +97,7 @@ export const ProfilePictureUploader = () => { setCurrentWorkspaceMember({ ...currentWorkspaceMember, avatarUrl: null }); } catch (error) { - setErrorMessage('An error occured while removing the picture.'); + setErrorMessage('An error occurred while removing the picture.'); } }; diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx index 93785116af7e..286a90faadca 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx +++ b/packages/twenty-front/src/modules/settings/serverless-functions/components/SettingsServerlessFunctionsTable.tsx @@ -1,3 +1,4 @@ +import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsServerlessFunctionsFieldItemTableRow } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsFieldItemTableRow'; import { SettingsServerlessFunctionsTableEmpty } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsTableEmpty'; import { useGetManyServerlessFunctions } from '@/settings/serverless-functions/hooks/useGetManyServerlessFunctions'; @@ -24,26 +25,28 @@ export const SettingsServerlessFunctionsTable = () => { return ( <> {serverlessFunctions.length ? ( - - - Name - Runtime - - - - {serverlessFunctions.map( - (serverlessFunction: ServerlessFunction) => ( - - ), - )} - -
+ + + + Name + Runtime + + + + {serverlessFunctions.map( + (serverlessFunction: ServerlessFunction) => ( + + ), + )} + +
+
) : ( )} diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/hooks/__tests__/useServerlessFunctionUpdateFormState.test.ts b/packages/twenty-front/src/modules/settings/serverless-functions/hooks/__tests__/useServerlessFunctionUpdateFormState.test.ts index e0799f13ac2e..36de554158df 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/hooks/__tests__/useServerlessFunctionUpdateFormState.test.ts +++ b/packages/twenty-front/src/modules/settings/serverless-functions/hooks/__tests__/useServerlessFunctionUpdateFormState.test.ts @@ -1,5 +1,5 @@ -import { renderHook } from '@testing-library/react'; import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState'; +import { renderHook } from '@testing-library/react'; import { RecoilRoot } from 'recoil'; jest.mock( @@ -44,6 +44,6 @@ describe('useServerlessFunctionUpdateFormState', () => { const { formValues } = result.current; - expect(formValues).toEqual({ name: '', description: '', code: '' }); + expect(formValues).toEqual({ name: '', description: '', code: undefined }); }); }); diff --git a/packages/twenty-front/src/modules/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState.ts b/packages/twenty-front/src/modules/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState.ts index 367710df7137..9e8a13483810 100644 --- a/packages/twenty-front/src/modules/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState.ts +++ b/packages/twenty-front/src/modules/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState.ts @@ -1,6 +1,6 @@ -import { Dispatch, SetStateAction, useState } from 'react'; import { useGetOneServerlessFunction } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunction'; import { useGetOneServerlessFunctionSourceCode } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode'; +import { Dispatch, SetStateAction, useState } from 'react'; import { FindOneServerlessFunctionSourceCodeQuery } from '~/generated-metadata/graphql'; export type ServerlessFunctionNewFormValues = { diff --git a/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx b/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx index 86d4d1b93231..78440fe021d2 100644 --- a/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/support/components/SupportButtonSkeletonLoader.tsx @@ -1,5 +1,6 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; export const SupportButtonSkeletonLoader = () => { const theme = useTheme(); @@ -9,7 +10,7 @@ export const SupportButtonSkeletonLoader = () => { highlightColor={theme.background.transparent.lighter} borderRadius={4} > - + ); }; diff --git a/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx b/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx index ba107aa612f0..b5111d245a0a 100644 --- a/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx +++ b/packages/twenty-front/src/modules/ui/field/display/components/ActorDisplay.tsx @@ -7,6 +7,7 @@ import { IconCalendar, IconCsv, IconGmail, + IconRobot, } from 'twenty-ui'; type ActorDisplayProps = Partial & { @@ -29,12 +30,15 @@ export const ActorDisplay = ({ return IconGmail; case 'CALENDAR': return IconCalendar; + case 'SYSTEM': + return IconRobot; default: return undefined; } }, [source]); - const isIconInverted = source === 'API' || source === 'IMPORT'; + const isIconInverted = + source === 'API' || source === 'IMPORT' || source === 'SYSTEM'; return ( ( - {value && formatNumber(Number(value))} +export const NumberDisplay = ({ value, decimals }: NumberDisplayProps) => ( + + {value && formatNumber(Number(value), decimals)} + ); diff --git a/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx b/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx index f4853bc48c69..820831f7c77b 100644 --- a/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx +++ b/packages/twenty-front/src/modules/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledDropdownMenuSkeletonContainer = styled.div` --horizontal-padding: ${({ theme }) => theme.spacing(1)}; @@ -21,7 +22,7 @@ export const DropdownMenuSkeletonItem = () => { baseColor={theme.background.quaternary} highlightColor={theme.background.secondary} > - + ); diff --git a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuInput.tsx b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuInput.tsx index 5a123105c153..145da879ad11 100644 --- a/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuInput.tsx +++ b/packages/twenty-front/src/modules/ui/layout/dropdown/components/DropdownMenuInput.tsx @@ -7,10 +7,14 @@ import { RGBA, TEXT_INPUT_STYLE } from 'twenty-ui'; import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents'; import { useCombinedRefs } from '~/hooks/useCombinedRefs'; -const StyledInput = styled.input<{ withRightComponent?: boolean }>` +const StyledInput = styled.input<{ + withRightComponent?: boolean; + hasError?: boolean; +}>` ${TEXT_INPUT_STYLE} - border: 1px solid ${({ theme }) => theme.border.color.medium}; + border: 1px solid ${({ theme, hasError }) => + hasError ? theme.border.color.danger : theme.border.color.medium}; border-radius: ${({ theme }) => theme.border.radius.sm}; box-sizing: border-box; font-weight: ${({ theme }) => theme.font.weight.medium}; @@ -19,8 +23,10 @@ const StyledInput = styled.input<{ withRightComponent?: boolean }>` width: 100%; &:focus { - border-color: ${({ theme }) => theme.color.blue}; - box-shadow: 0px 0px 0px 3px ${({ theme }) => RGBA(theme.color.blue, 0.1)}; + ${({ theme, hasError = false }) => { + if (hasError) return ''; + return `box-shadow: 0px 0px 0px 3px ${RGBA(theme.color.blue, 0.1)}`; + }}; } ${({ withRightComponent }) => @@ -44,6 +50,12 @@ const StyledRightContainer = styled.div` transform: translateY(-50%); `; +const StyledErrorDiv = styled.div` + color: ${({ theme }) => theme.color.red}; + padding: 0 ${({ theme }) => theme.spacing(2)} + ${({ theme }) => theme.spacing(1)}; +`; + type HTMLInputProps = InputHTMLAttributes; export type DropdownMenuInputProps = HTMLInputProps & { @@ -60,6 +72,8 @@ export type DropdownMenuInputProps = HTMLInputProps & { autoFocus: HTMLInputProps['autoFocus']; placeholder: HTMLInputProps['placeholder']; }) => React.ReactNode; + error?: string | null; + hasError?: boolean; }; export const DropdownMenuInput = forwardRef< @@ -81,6 +95,8 @@ export const DropdownMenuInput = forwardRef< onTab, rightComponent, renderInput, + error = '', + hasError = false, }, ref, ) => { @@ -99,28 +115,32 @@ export const DropdownMenuInput = forwardRef< }); return ( - - {renderInput ? ( - renderInput({ - value, - onChange, - autoFocus, - placeholder, - }) - ) : ( - - )} - {!!rightComponent && ( - {rightComponent} - )} - + <> + + {renderInput ? ( + renderInput({ + value, + onChange, + autoFocus, + placeholder, + }) + ) : ( + + )} + {!!rightComponent && ( + {rightComponent} + )} + + {error && {error}} + ); }, ); diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx index 9176af20340a..b80b943e1730 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx @@ -5,6 +5,7 @@ import { Chip, ChipAccent, ChipSize, useIcons } from 'twenty-ui'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; +import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState'; import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState'; import { RightDrawerTopBarCloseButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarCloseButton'; @@ -66,6 +67,10 @@ export const RightDrawerTopBar = () => { viewableRecordNameSingularState, ); + const isNewViewableRecordLoading = useRecoilValue( + isNewViewableRecordLoadingState, + ); + const viewableRecordId = useRecoilValue(viewableRecordIdState); const { objectMetadataItem } = useObjectMetadataItem({ @@ -95,6 +100,7 @@ export const RightDrawerTopBar = () => { > {!isRightDrawerMinimized && ( } size={ChipSize.Large} diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageActivityContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageActivityContainer.tsx index a69f54528cba..2e38456c4fb9 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageActivityContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageActivityContainer.tsx @@ -1,8 +1,10 @@ import { RichTextEditor } from '@/activities/components/RichTextEditor'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; const StyledShowPageActivityContainer = styled.div` margin-top: ${({ theme }) => theme.spacing(6)}; @@ -16,7 +18,11 @@ export const ShowPageActivityContainer = ({ 'targetObjectNameSingular' | 'id' >; }) => { - return ( + const isNewViewableRecordLoading = useRecoilValue( + isNewViewableRecordLoadingState, + ); + + return !isNewViewableRecordLoading ? ( + ) : ( + <> ); }; diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx index c06129296187..dab0a2104e12 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx @@ -7,6 +7,7 @@ import { TimelineActivities } from '@/activities/timelineActivities/components/T import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; +import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { Button } from '@/ui/input/button/components/Button'; @@ -135,6 +136,10 @@ export const ShowPageRightContainer = ({ const isMobile = useIsMobile(); + const isNewViewableRecordLoading = useRecoilValue( + isNewViewableRecordLoadingState, + ); + const tabs = [ { id: 'richText', @@ -272,7 +277,7 @@ export const ShowPageRightContainer = ({ diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx index 9627be5d7e61..dca1b2cea957 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx @@ -1,3 +1,4 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { ChangeEvent, ReactNode, useRef } from 'react'; @@ -88,9 +89,9 @@ const StyledShowPageSummaryCardSkeletonLoader = () => { highlightColor={theme.background.transparent.lighter} borderRadius={4} > - + - + ); diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader.tsx new file mode 100644 index 000000000000..51b15f506c67 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader.tsx @@ -0,0 +1,36 @@ +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; + +const StyledContainer = styled.div` + display: flex; + gap: ${({ theme }) => theme.spacing(4)}; + height: ${({ theme }) => theme.spacing(19)}; + margin: ${({ theme }) => theme.spacing(4)}; +`; + +const StyledRectangularSkeleton = styled(Skeleton)` + height: ${({ theme }) => theme.spacing(4)}; + width: ${({ theme }) => theme.spacing(24)}; + margin: ${({ theme }) => theme.spacing(1)}; + border-radius: ${({ theme }) => theme.border.radius.sm}; +`; + +export const ShowPageSummaryCardSkeletonLoader = () => { + const theme = useTheme(); + return ( + + + + + + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader.tsx index fcd3ffe20eaa..5391ea3e320d 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader.tsx @@ -1,6 +1,7 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; const StyledSkeletonTitle = styled.div` margin-bottom: ${(props) => props.theme.spacing(2)}; @@ -16,7 +17,10 @@ export const NavigationDrawerSectionTitleSkeletonLoader = () => { borderRadius={4} > - + ); diff --git a/packages/twenty-front/src/modules/views/components/ViewBarSkeletonLoader.tsx b/packages/twenty-front/src/modules/views/components/ViewBarSkeletonLoader.tsx index a7d0c1b247c1..a82565afa813 100644 --- a/packages/twenty-front/src/modules/views/components/ViewBarSkeletonLoader.tsx +++ b/packages/twenty-front/src/modules/views/components/ViewBarSkeletonLoader.tsx @@ -1,5 +1,6 @@ -import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { useTheme } from '@emotion/react'; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; export const ViewBarSkeletonLoader = () => { const theme = useTheme(); @@ -9,7 +10,7 @@ export const ViewBarSkeletonLoader = () => { highlightColor={theme.background.transparent.lighter} borderRadius={4} > - + ); }; diff --git a/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts b/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts index 139ac042751f..cbf5f19b35ae 100644 --- a/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts +++ b/packages/twenty-front/src/modules/views/utils/mapViewFieldsToColumnDefinitions.ts @@ -50,6 +50,7 @@ export const mapViewFieldsToColumnDefinitions = ({ isSortable: correspondingColumnDefinition.isSortable, isFilterable: correspondingColumnDefinition.isFilterable, defaultValue: correspondingColumnDefinition.defaultValue, + settings: correspondingColumnDefinition.settings, } as ColumnDefinition; }) .filter(isDefined); diff --git a/packages/twenty-front/src/pages/auth/PasswordReset.tsx b/packages/twenty-front/src/pages/auth/PasswordReset.tsx index 79ea7074021d..11cf5d016b42 100644 --- a/packages/twenty-front/src/pages/auth/PasswordReset.tsx +++ b/packages/twenty-front/src/pages/auth/PasswordReset.tsx @@ -10,6 +10,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import { useSetRecoilState } from 'recoil'; import { z } from 'zod'; +import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader'; import { Logo } from '@/auth/components/Logo'; import { Title } from '@/auth/components/Title'; import { useAuth } from '@/auth/hooks/useAuth'; @@ -174,7 +175,7 @@ export const PasswordReset = () => { highlightColor={theme.background.secondary} > = { }, }); }), - graphql.query('FindManyViews', () => { - return HttpResponse.json({ - data: { - views: viewQueryResultMock, - }, - }); - }), graphqlMocks.handlers, ], }, diff --git a/packages/twenty-front/src/pages/settings/__stories__/SettingsAppearance.stories.tsx b/packages/twenty-front/src/pages/settings/__stories__/SettingsAppearance.stories.tsx index 1aa66449abcc..2b7ab8f8e528 100644 --- a/packages/twenty-front/src/pages/settings/__stories__/SettingsAppearance.stories.tsx +++ b/packages/twenty-front/src/pages/settings/__stories__/SettingsAppearance.stories.tsx @@ -38,6 +38,24 @@ export const Default: Story = { }, }; +export const DateTimeSettingsTimeFormat: Story = { + play: async () => { + const canvas = within(document.body); + + await canvas.findByText('Date and time'); + + const timeFormatSelect = await canvas.findByText('24h (08:33)'); + + userEvent.click(timeFormatSelect); + + const timeFormatOptions = await canvas.findByText('12h (8:33 AM)'); + + userEvent.click(timeFormatOptions); + + await canvas.findByText('12h (8:33 AM)'); + }, +}; + export const DateTimeSettingsTimezone: Story = { play: async () => { const canvas = within(document.body); @@ -77,21 +95,3 @@ export const DateTimeSettingsDateFormat: Story = { await canvas.findByText('Jun 13, 2022'); }, }; - -export const DateTimeSettingsTimeFormat: Story = { - play: async () => { - const canvas = within(document.body); - - await canvas.findByText('Date and time'); - - const timeFormatSelect = await canvas.findByText('24h (08:33)'); - - userEvent.click(timeFormatSelect); - - const timeFormatOptions = await canvas.findByText('12h (8:33 AM)'); - - userEvent.click(timeFormatOptions); - - await canvas.findByText('12h (8:33 AM)'); - }, -}; diff --git a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx index e828bfa766e8..fc7cecd82dee 100644 --- a/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx +++ b/packages/twenty-front/src/pages/settings/integrations/__stories__/SettingsIntegrationDatabase.stories.tsx @@ -1,3 +1,4 @@ +import { expect } from '@storybook/jest'; import { Meta, StoryObj } from '@storybook/react'; import { within } from '@storybook/test'; @@ -33,6 +34,6 @@ export const Default: Story = { const canvas = within(canvasElement); sleep(1000); - await canvas.findByText('PostgreSQL database'); + expect(await canvas.findByText('PostgreSQL database')).toBeInTheDocument(); }, }; diff --git a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctions.tsx b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctions.tsx index 00dcabb77bdc..5f9e58ce7e2a 100644 --- a/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctions.tsx +++ b/packages/twenty-front/src/pages/settings/serverless-functions/SettingsServerlessFunctions.tsx @@ -1,4 +1,3 @@ -import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsServerlessFunctionsTable } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsTable'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { SettingsPath } from '@/types/SettingsPath'; @@ -35,11 +34,9 @@ export const SettingsServerlessFunctions = () => { }, ]} > - -
- -
-
+
+ +
); }; diff --git a/packages/twenty-front/src/testing/decorators/ChipGeneratorsDecorator.tsx b/packages/twenty-front/src/testing/decorators/ChipGeneratorsDecorator.tsx index 283e7046e0ab..c107d5466387 100644 --- a/packages/twenty-front/src/testing/decorators/ChipGeneratorsDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/ChipGeneratorsDecorator.tsx @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import { PreComputedChipGeneratorsContext } from '@/object-metadata/context/PreComputedChipGeneratorsContext'; import { getRecordChipGenerators } from '@/object-record/utils/getRecordChipGenerators'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; export const ChipGeneratorsDecorator: Decorator = (Story) => { const { chipGeneratorPerObjectPerField, identifierChipGeneratorPerObject } = diff --git a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx index 7125e4326dc6..244772c809e6 100644 --- a/packages/twenty-front/src/testing/decorators/PageDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/PageDecorator.tsx @@ -12,7 +12,7 @@ import { import { RecoilRoot } from 'recoil'; import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect'; -import { ApolloMetadataClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloMetadataClientProvider'; +import { ApolloMetadataClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloMetadataClientMockedProvider'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { UserProviderEffect } from '@/users/components/UserProviderEffect'; import { ClientConfigProvider } from '~/modules/client-config/components/ClientConfigProvider'; @@ -21,6 +21,7 @@ import { UserProvider } from '~/modules/users/components/UserProvider'; import { mockedApolloClient } from '~/testing/mockedApolloClient'; import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver'; +import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider'; import { PrefetchDataProvider } from '@/prefetch/components/PrefetchDataProvider'; import { IconsProvider } from 'twenty-ui'; import { FullHeightStorybookLayout } from '../FullHeightStorybookLayout'; @@ -64,29 +65,33 @@ const ApolloStorybookDevLogEffect = () => { const Providers = () => { return ( - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/packages/twenty-front/src/testing/decorators/RootDecorator.tsx b/packages/twenty-front/src/testing/decorators/RootDecorator.tsx index 9c2633037b5b..5808791d1ae2 100644 --- a/packages/twenty-front/src/testing/decorators/RootDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/RootDecorator.tsx @@ -2,7 +2,7 @@ import { ApolloProvider } from '@apollo/client'; import { Decorator } from '@storybook/react'; import { RecoilRoot } from 'recoil'; -import { ApolloMetadataClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloMetadataClientProvider'; +import { ApolloMetadataClientMockedProvider } from '@/object-metadata/hooks/__mocks__/ApolloMetadataClientMockedProvider'; import { InitializeHotkeyStorybookHookEffect } from '../InitializeHotkeyStorybookHook'; import { mockedApolloClient } from '../mockedApolloClient'; diff --git a/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx b/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx index 62cee6f5077b..e61885ed9a0d 100644 --- a/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx +++ b/packages/twenty-front/src/testing/decorators/getFieldDecorator.tsx @@ -12,7 +12,7 @@ import { import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getCompaniesMock } from '~/testing/mock-data/companies'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { getPeopleMock } from '~/testing/mock-data/people'; import { mockedTasks } from '~/testing/mock-data/tasks'; import { isDefined } from '~/utils/isDefined'; @@ -56,7 +56,7 @@ const RecordMockSetterEffect = ({ export const getFieldDecorator = ( - objectNameSingular: 'company' | 'person' | 'task', + objectNameSingular: 'company' | 'person' | 'task' | 'workflowVersions', fieldName: string, fieldValue?: any, ): Decorator => diff --git a/packages/twenty-front/src/testing/graphqlMocks.ts b/packages/twenty-front/src/testing/graphqlMocks.ts index c5d319b460b7..a69d6c17c660 100644 --- a/packages/twenty-front/src/testing/graphqlMocks.ts +++ b/packages/twenty-front/src/testing/graphqlMocks.ts @@ -11,7 +11,6 @@ import { getCompanyDuplicateMock, } from '~/testing/mock-data/companies'; import { mockedClientConfig } from '~/testing/mock-data/config'; -import { mockedObjectMetadataItemsQueryResult } from '~/testing/mock-data/metadata'; import { mockedNotes } from '~/testing/mock-data/notes'; import { getPeopleMock } from '~/testing/mock-data/people'; import { mockedRemoteTables } from '~/testing/mock-data/remote-tables'; @@ -19,6 +18,7 @@ import { mockedUserData } from '~/testing/mock-data/users'; import { mockedViewsData } from '~/testing/mock-data/views'; import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members'; +import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result'; import { mockedTasks } from '~/testing/mock-data/tasks'; import { mockedRemoteServers } from './mock-data/remote-servers'; import { mockedViewFieldsData } from './mock-data/view-fields'; @@ -58,7 +58,7 @@ export const graphqlMocks = { getOperationName(FIND_MANY_OBJECT_METADATA_ITEMS) ?? '', () => { return HttpResponse.json({ - data: mockedObjectMetadataItemsQueryResult, + data: mockedStandardObjectMetadataQueryResult, }); }, ), @@ -297,7 +297,7 @@ export const graphqlMocks = { graphql.query('FindManyTasks', () => { return HttpResponse.json({ data: { - activities: { + tasks: { edges: mockedTasks.map(({ taskTargets, ...rest }) => ({ node: { ...rest, @@ -320,6 +320,26 @@ export const graphqlMocks = { }, }); }), + graphql.query('FindManyTaskTargets', () => { + return HttpResponse.json({ + data: { + taskTargets: { + edges: mockedTasks.flatMap((task) => + task.taskTargets.map((target) => ({ + node: target, + cursor: null, + })), + ), + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: null, + endCursor: null, + }, + }, + }, + }); + }), graphql.query('FindManyFavorites', () => { return HttpResponse.json({ data: { diff --git a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx index abd11cab1832..98e83cb833a5 100644 --- a/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx +++ b/packages/twenty-front/src/testing/jest/JestObjectMetadataItemSetter.tsx @@ -2,7 +2,7 @@ import { ReactNode, useEffect, useState } from 'react'; import { useSetRecoilState } from 'recoil'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; export const JestObjectMetadataItemSetter = ({ children, diff --git a/packages/twenty-front/src/testing/jest/generateEmptyJestRecordNode.ts b/packages/twenty-front/src/testing/jest/generateEmptyJestRecordNode.ts new file mode 100644 index 000000000000..f27e4f3a3cd8 --- /dev/null +++ b/packages/twenty-front/src/testing/jest/generateEmptyJestRecordNode.ts @@ -0,0 +1,37 @@ +import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; +import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; +import { prefillRecord } from '@/object-record/utils/prefillRecord'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; + +export const generateEmptyJestRecordNode = ({ + objectNameSingular, + input, + withDepthOneRelation = false, +}: { + objectNameSingular: string; + input: Record; + withDepthOneRelation?: boolean; +}) => { + const objectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === objectNameSingular, + ); + + if (!objectMetadataItem) { + throw new Error( + `ObjectMetadataItem not found for objectNameSingular: ${objectNameSingular} while generating empty Jest record node`, + ); + } + + const prefilledRecord = prefillRecord({ objectMetadataItem, input }); + + return getRecordNodeFromRecord({ + record: prefilledRecord, + objectMetadataItem, + objectMetadataItems: generatedMockObjectMetadataItems, + recordGqlFields: withDepthOneRelation + ? generateDepthOneRecordGqlFields({ + objectMetadataItem, + }) + : undefined, + }); +}; diff --git a/packages/twenty-front/src/testing/jest/getJestHookWrapper.tsx b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx similarity index 94% rename from packages/twenty-front/src/testing/jest/getJestHookWrapper.tsx rename to packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx index 3c8f21553323..ea0e6528f0bc 100644 --- a/packages/twenty-front/src/testing/jest/getJestHookWrapper.tsx +++ b/packages/twenty-front/src/testing/jest/getJestMetadataAndApolloMocksWrapper.tsx @@ -5,7 +5,7 @@ import { MutableSnapshot, RecoilRoot } from 'recoil'; import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope'; import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter'; -export const getJestHookWrapper = ({ +export const getJestMetadataAndApolloMocksWrapper = ({ apolloMocks, onInitializeRecoilSnapshot, }: { diff --git a/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts b/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts index c30b74ec2344..6832293415c8 100644 --- a/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts +++ b/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts @@ -51,6 +51,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ed0dfa31-8e2f-4b23-87e4-8fa55eb16729', type: 'TEXT', name: 'operation', @@ -72,6 +73,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '814795ef-6f2d-4798-a6f9-4e1c87c68d43', type: 'DATE_TIME', name: 'deletedAt', @@ -93,6 +95,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1c802ccf-c0ae-4b04-8c1e-f77417e6c3f8', type: 'DATE_TIME', name: 'updatedAt', @@ -114,6 +117,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6f210155-9cdc-48c6-9803-e20f63512024', type: 'DATE_TIME', name: 'createdAt', @@ -135,6 +139,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5fa54f64-3363-4a21-89ca-30d4816d8c77', type: 'UUID', name: 'id', @@ -156,6 +161,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a8876650-a7b6-4a9f-95b4-9ec1d6c232cc', type: 'TEXT', name: 'targetUrl', @@ -177,6 +183,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'afae3f60-bfeb-4faf-a899-b0eb0fefac51', type: 'TEXT', name: 'description', @@ -233,6 +240,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ad82920a-857a-4357-8e4a-ed70961ba5d8', type: 'DATE_TIME', name: 'updatedAt', @@ -254,6 +262,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '89662f00-b57c-49f6-aa48-d1c84f5fd7c7', type: 'UUID', name: 'personId', @@ -275,6 +284,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '19270b1d-73b4-4aa9-8106-c1c81351ec53', type: 'UUID', name: 'rocketId', @@ -296,6 +306,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0c796ac5-0592-455f-9a0a-66ad53c6e4cf', type: 'UUID', name: 'companyId', @@ -317,6 +328,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dadac630-f64e-4d3d-9923-78ca579373f3', type: 'RELATION', name: 'rocket', @@ -364,6 +376,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0fcdebbb-a332-45ec-ab46-c30d5d7f9ef0', type: 'DATE_TIME', name: 'createdAt', @@ -385,6 +398,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '37882bc2-c8d8-4cc5-bc13-4b820cc05b83', type: 'UUID', name: 'taskId', @@ -406,6 +420,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'da63ddb4-7c19-49f0-bf90-ac2cc9486ae7', type: 'DATE_TIME', name: 'deletedAt', @@ -427,6 +442,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26cc6ba3-cff7-4b84-bf78-71823187a824', type: 'RELATION', name: 'person', @@ -474,6 +490,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e4414119-6f9c-465a-9ee2-95d1fc5eec01', type: 'UUID', name: 'opportunityId', @@ -495,6 +512,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '598688a1-9766-439d-abd3-c0a47c8f36a3', type: 'RELATION', name: 'opportunity', @@ -542,6 +560,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'cd7f57e8-a67f-4be9-a971-b5609cb0fb83', type: 'RELATION', name: 'company', @@ -589,6 +608,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '400c9e53-283b-42d8-a69f-5010fb75d977', type: 'UUID', name: 'id', @@ -610,6 +630,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ec532542-a4dc-4722-99c3-fca6366db597', type: 'RELATION', name: 'task', @@ -692,6 +713,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26c57af1-5c70-4a9d-974f-e54c6a77a2b4', type: 'RELATION', name: 'rocket', @@ -739,6 +761,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '434d1fd2-e6e0-4de7-9b15-706398e34d2d', type: 'DATE_TIME', name: 'deletedAt', @@ -760,6 +783,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '06fcb5e2-b2f7-4118-a9f0-34558429b72c', type: 'DATE_TIME', name: 'updatedAt', @@ -781,6 +805,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4b6aaf36-4247-4bbb-b26d-64987b02f805', type: 'RELATION', name: 'company', @@ -828,6 +853,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c9bbd140-d9ab-4557-bd77-b446cd80774b', type: 'UUID', name: 'personId', @@ -849,6 +875,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b86fd021-b648-4a1e-b02e-080f0b280303', type: 'UUID', name: 'rocketId', @@ -870,6 +897,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'cd3a9c3d-a29e-4b27-9fdb-0e8959f21f10', type: 'UUID', name: 'opportunityId', @@ -891,6 +919,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e6a5a8b1-ebef-4e01-ba22-a5f86d894eb5', type: 'UUID', name: 'id', @@ -912,6 +941,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9d568132-6cbc-4e87-95e8-7c2509549391', type: 'RELATION', name: 'note', @@ -959,6 +989,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6f80afcd-8ed7-4bf9-a987-9de3d1cddc81', type: 'RELATION', name: 'opportunity', @@ -1006,6 +1037,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dc63ee78-06ac-4312-b223-1d41a7ea2af4', type: 'UUID', name: 'noteId', @@ -1027,6 +1059,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '646f3b4b-0fad-495a-a90d-136593464c7f', type: 'UUID', name: 'companyId', @@ -1048,6 +1081,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2b9481a9-c605-45d0-8aad-801a19c4b92c', type: 'DATE_TIME', name: 'createdAt', @@ -1069,6 +1103,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '77508442-f0de-4809-b690-3c998edfc0b5', type: 'RELATION', name: 'person', @@ -1151,6 +1186,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '27e79204-ba19-4791-8110-ec2bdc523e07', type: 'TEXT', name: 'messageThreadExternalId', @@ -1172,6 +1208,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1d9f0c85-c16f-41e9-9241-2acd90781cdd', type: 'RELATION', name: 'message', @@ -1219,6 +1256,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '72a85b74-9803-4279-9f74-dafb833847fb', type: 'DATE_TIME', name: 'createdAt', @@ -1240,6 +1278,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6cb121b9-32d1-44fe-af26-324d73ffe0ac', type: 'DATE_TIME', name: 'deletedAt', @@ -1261,6 +1300,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9491ddd2-aea5-47fe-bd93-09fb6969b20c', type: 'TEXT', name: 'messageExternalId', @@ -1282,6 +1322,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f3eafb28-947a-4c7a-9464-24fa5549fb03', type: 'UUID', name: 'id', @@ -1303,6 +1344,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dbfff493-11a0-4866-9ef6-e4e8418a661a', type: 'UUID', name: 'messageChannelId', @@ -1324,6 +1366,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '95488a9d-6473-47e8-aa62-a04d49238a2f', type: 'UUID', name: 'messageId', @@ -1345,6 +1388,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f4582d66-df12-4499-8ede-ab347427241b', type: 'RELATION', name: 'messageChannel', @@ -1392,6 +1436,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7bf79a5a-f5b7-495e-a336-4ddf85a5b2f7', type: 'SELECT', name: 'direction', @@ -1428,6 +1473,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a18edc4a-1b58-4a78-8de4-9564479b09cc', type: 'DATE_TIME', name: 'updatedAt', @@ -1484,6 +1530,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ea62fcb2-2161-47db-9151-19011419ac66', type: 'DATE_TIME', name: 'createdAt', @@ -1505,6 +1552,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e2f2b624-93bf-4d16-8517-bf66e43cabc4', type: 'RELATION', name: 'message', @@ -1552,6 +1600,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b92e3ff7-e5e0-4fec-b36e-cb496e2b57ae', type: 'UUID', name: 'id', @@ -1573,6 +1622,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ef5a67e6-88dd-4c92-a9be-7ab0605804e7', type: 'DATE_TIME', name: 'deletedAt', @@ -1594,6 +1644,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f3db188f-7329-40bc-9978-e30e5c07d962', type: 'RELATION', name: 'person', @@ -1641,6 +1692,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26c81156-529d-4bec-b5fb-f92e991907b5', type: 'UUID', name: 'messageId', @@ -1662,6 +1714,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aa502069-91c1-4eeb-bc8e-b22240564fe1', type: 'TEXT', name: 'displayName', @@ -1683,6 +1736,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd205df4f-f9de-4f61-9a85-40f340a4de23', type: 'RELATION', name: 'workspaceMember', @@ -1730,6 +1784,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0cf35f0a-eb64-47ef-88a4-55b92ca57c64', type: 'UUID', name: 'personId', @@ -1751,6 +1806,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e8550477-6034-45f4-8370-5a1cd75f7a55', type: 'UUID', name: 'workspaceMemberId', @@ -1772,6 +1828,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8dcde658-c38a-4659-8546-89c60465d36e', type: 'TEXT', name: 'handle', @@ -1793,6 +1850,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '56ff0ed7-7916-4610-834c-a7c0657fa9e7', type: 'DATE_TIME', name: 'updatedAt', @@ -1814,6 +1872,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7a8597e3-bbce-4ff5-9f6e-9bf3c7fd43fa', type: 'SELECT', name: 'role', @@ -1899,6 +1958,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9061081a-eed9-49b8-99ab-9a7b8ce7a355', type: 'BOOLEAN', name: 'isContactAutoCreationEnabled', @@ -1920,6 +1980,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'df104a7c-39e5-491d-8bde-6e3f75a3156b', type: 'DATE_TIME', name: 'syncedAt', @@ -1941,6 +2002,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '751ef307-be5e-4451-9434-e2bae8861873', type: 'BOOLEAN', name: 'excludeNonProfessionalEmails', @@ -1962,6 +2024,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9a8520ed-6577-46b0-a732-1a08aafb0160', type: 'SELECT', name: 'visibility', @@ -2005,6 +2068,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f7f7ea2-c5d0-47bc-bb0e-e3afc6d82b91', type: 'DATE_TIME', name: 'updatedAt', @@ -2026,6 +2090,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dafc931d-0466-4c83-91b3-72be3fdee12f', type: 'DATE_TIME', name: 'createdAt', @@ -2047,6 +2112,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '97ba10a8-da33-4d1f-a9c6-964f814f5fd7', type: 'DATE_TIME', name: 'deletedAt', @@ -2068,6 +2134,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a3c22a32-9c6c-4294-abc4-7b3f9b6d8816', type: 'RELATION', name: 'messageChannelMessageAssociations', @@ -2115,6 +2182,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c1686e5a-1af6-45d9-a9f8-d2ecaef71526', type: 'RELATION', name: 'connectedAccount', @@ -2162,6 +2230,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7eaa0427-04b4-4cf8-9676-4e28c88a0fa8', type: 'DATE_TIME', name: 'syncStageStartedAt', @@ -2183,6 +2252,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '290318a3-e994-4dee-8dd1-6bebe92043af', type: 'SELECT', name: 'syncStatus', @@ -2240,6 +2310,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7238d6c1-c54c-4787-aa4f-79294220acdc', type: 'BOOLEAN', name: 'isSyncEnabled', @@ -2261,6 +2332,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9416f68a-84cb-4b1f-805b-e58fb009cc44', type: 'TEXT', name: 'syncCursor', @@ -2282,6 +2354,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '09ec080b-c8fe-432f-aff1-c151861c3ec7', type: 'SELECT', name: 'contactAutoCreationPolicy', @@ -2326,6 +2399,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd30ddf0c-769a-4480-a9cf-fea5867f9eea', type: 'BOOLEAN', name: 'excludeGroupEmails', @@ -2347,6 +2421,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '839a7a90-3b1a-4f76-8207-5a0779ca909d', type: 'SELECT', name: 'type', @@ -2383,6 +2458,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b0026844-d54e-4fe2-af36-ed7ac7be1833', type: 'UUID', name: 'connectedAccountId', @@ -2404,6 +2480,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7ca7d194-3cdc-4ff9-a90f-6bfaedba8280', type: 'TEXT', name: 'handle', @@ -2425,6 +2502,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5f87d969-7dae-4a25-8b11-9577aa11285e', type: 'UUID', name: 'id', @@ -2446,6 +2524,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aafcc52e-d761-4b6a-97a6-1bc097e2ccd2', type: 'SELECT', name: 'syncStage', @@ -2510,6 +2589,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '33180dbd-4124-43eb-84c3-17e32b4848e4', type: 'NUMBER', name: 'throttleFailureCount', @@ -2566,6 +2646,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8ca32bd7-4999-4562-9e41-698a458944ea', type: 'DATE_TIME', name: 'deletedAt', @@ -2587,6 +2668,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '236195db-49d1-4386-99b9-4518ab7586f2', type: 'RELATION', name: 'accountOwner', @@ -2634,6 +2716,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '919dadd4-8dda-479f-a8a7-b4ed89eafae5', type: 'RELATION', name: 'calendarChannels', @@ -2681,6 +2764,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '75c78041-39ca-4b46-bcff-6ed0af05248e', type: 'TEXT', name: 'handleAliases', @@ -2702,6 +2786,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd3f821eb-c8b1-48a5-aebb-1bfe23c4128e', type: 'DATE_TIME', name: 'updatedAt', @@ -2723,6 +2808,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6f74d250-76f0-4eff-8e27-d1a12782517d', type: 'TEXT', name: 'lastSyncHistoryId', @@ -2744,6 +2830,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fd6c04f4-8dba-47ac-a597-90300eb1a079', type: 'UUID', name: 'id', @@ -2765,6 +2852,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8f7d7528-83a8-442e-b0b6-958f52a1de5e', type: 'DATE_TIME', name: 'authFailedAt', @@ -2786,6 +2874,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '762d59c0-f7e9-410f-b5dc-df66d764de0d', type: 'TEXT', name: 'handle', @@ -2808,6 +2897,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f45f5e44-f88a-490c-b112-df2465b612a3', type: 'TEXT', name: 'refreshToken', @@ -2829,6 +2919,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2aa282f3-5542-47e8-adf3-4384e9ce5d10', type: 'UUID', name: 'accountOwnerId', @@ -2850,6 +2941,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5f411e82-b626-4456-896c-d5a326e5e02a', type: 'TEXT', name: 'accessToken', @@ -2871,6 +2963,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c21734fd-6539-410f-bc1a-e91a3177b9c9', type: 'DATE_TIME', name: 'createdAt', @@ -2892,6 +2985,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5b0a8a38-9d02-4e06-8f07-2f76f0ab70eb', type: 'TEXT', name: 'provider', @@ -2913,6 +3007,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '178f1e8a-cbbf-448e-95f3-d1262d9ff33d', type: 'RELATION', name: 'messageChannels', @@ -2995,6 +3090,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9e7b7d2f-02fb-426b-8e3e-392225f5b6b3', type: 'RELATION', name: 'taskTargets', @@ -3042,6 +3138,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '78f1d502-397e-4cce-b096-a525b2d373e2', type: 'RELATION', name: 'noteTargets', @@ -3089,6 +3186,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c692555c-020e-46a4-b537-c9c4c7d3cd32', type: 'ACTOR', name: 'createdBy', @@ -3113,6 +3211,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6d32aa8b-28c0-4e35-b45e-9643fd8e1c33', type: 'CURRENCY', name: 'amount', @@ -3137,6 +3236,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c859b99c-2554-483d-8ffe-4d29cb9c8459', type: 'RELATION', name: 'company', @@ -3184,6 +3284,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7ad6853d-d1e9-46a8-a77a-38eeae27e1d6', type: 'RELATION', name: 'pointOfContact', @@ -3231,6 +3332,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f6c45230-619b-4432-82fd-e5bed0c4e8f4', type: 'SELECT', name: 'stage', @@ -3288,6 +3390,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5d3ca5b4-9468-4061-8c6b-ef03a9b123df', type: 'RELATION', name: 'favorites', @@ -3335,6 +3438,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3bc37e42-10f2-4501-94e4-760a7c3fa38e', type: 'POSITION', name: 'position', @@ -3356,6 +3460,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9019bf90-efbc-4499-a87b-0624bda5a559', type: 'RELATION', name: 'timelineActivities', @@ -3404,6 +3509,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c7472787-44e9-4641-9901-cc4909ca031d', type: 'DATE_TIME', name: 'closeDate', @@ -3425,6 +3531,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '01e75534-627f-4a49-bc28-c08170a71085', type: 'UUID', name: 'id', @@ -3446,6 +3553,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6d5401c0-d456-42c4-85d4-9666900615ef', type: 'TEXT', name: 'name', @@ -3467,6 +3575,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aa651efa-1576-4edc-9599-070666a76dda', type: 'RELATION', name: 'attachments', @@ -3514,6 +3623,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b8eb99bd-3485-44bb-8483-f0c600af4e92', type: 'UUID', name: 'pointOfContactId', @@ -3535,6 +3645,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '55fd22aa-80df-4c0b-b5ee-f19163d10a82', type: 'UUID', name: 'companyId', @@ -3556,6 +3667,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd487fe61-e181-4077-ba4e-9c7b466085ad', type: 'DATE_TIME', name: 'updatedAt', @@ -3577,6 +3689,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bf11e668-6598-42ee-8c81-563641403ba9', type: 'DATE_TIME', name: 'deletedAt', @@ -3598,6 +3711,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2e77023d-fdeb-454d-9368-ab638a68a0bb', type: 'DATE_TIME', name: 'createdAt', @@ -3619,6 +3733,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ac708d8f-abd4-436f-aec7-9a8c4ec2cd28', type: 'RELATION', name: 'activityTargets', @@ -3701,6 +3816,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4a2da98e-6880-47be-8673-165e1d77a910', type: 'RELATION', name: 'rocket', @@ -3748,6 +3864,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '03aab98d-e16c-48fb-91e7-bffc2300402d', type: 'UUID', name: 'opportunityId', @@ -3769,6 +3886,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e1d06322-0658-4c45-9c9b-8a42750c8751', type: 'RELATION', name: 'company', @@ -3816,6 +3934,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ca812263-7b62-4e7f-8f31-bed3de2d4a94', type: 'DATE_TIME', name: 'updatedAt', @@ -3837,6 +3956,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '984d38ae-335d-41fb-ac29-9029ff43c4c6', type: 'UUID', name: 'companyId', @@ -3858,6 +3978,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b2531e1e-39ad-46c8-af56-853fe1cc7dd6', type: 'RELATION', name: 'activity', @@ -3905,6 +4026,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26d681d7-4ef2-40eb-bd0c-7e0b7d6d6cb0', type: 'UUID', name: 'personId', @@ -3926,6 +4048,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dc9c8fec-55d2-40b6-9fcd-d441024be60f', type: 'DATE_TIME', name: 'createdAt', @@ -3947,6 +4070,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '08806161-5d03-4777-8825-b5cff93de042', type: 'UUID', name: 'activityId', @@ -3968,6 +4092,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9ff04b9e-f87c-44df-82ed-518748ca0d81', type: 'UUID', name: 'rocketId', @@ -3989,6 +4114,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '86438a20-0beb-4e73-93d9-cf91bfe9ac3f', type: 'UUID', name: 'id', @@ -4010,6 +4136,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f2ed506-e96e-48d3-8225-45a9c0b55e76', type: 'DATE_TIME', name: 'deletedAt', @@ -4031,6 +4158,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '867aff40-ddf5-4af8-a3f5-6359ab91eb2c', type: 'RELATION', name: 'person', @@ -4078,6 +4206,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c2708db4-5c1e-482b-bdd4-bd620612c15f', type: 'RELATION', name: 'opportunity', @@ -4160,6 +4289,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'eb926345-9d3c-4815-a044-bf1085b31cdc', type: 'UUID', name: 'assigneeId', @@ -4181,6 +4311,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7314a7d0-f318-4aa0-b8f3-db1953139d3f', type: 'TEXT', name: 'title', @@ -4202,6 +4333,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3e63dfad-08ba-422f-88e5-0c1ebffd6496', type: 'RELATION', name: 'author', @@ -4249,6 +4381,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5ceb1884-c3ff-4a31-9629-3ef599b1a461', type: 'DATE_TIME', name: 'updatedAt', @@ -4270,6 +4403,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ffdb396b-200a-4dc5-a3aa-e2c23b180ac0', type: 'TEXT', name: 'body', @@ -4291,6 +4425,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0f1cfe74-f960-44f9-91f3-fc9d25a4b96b', type: 'RELATION', name: 'comments', @@ -4338,6 +4473,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '248b356d-5a70-458d-a10b-92d70512497b', type: 'DATE_TIME', name: 'createdAt', @@ -4359,6 +4495,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f1f7b1a-2c98-42ca-8f6c-784406d7bad8', type: 'UUID', name: 'authorId', @@ -4380,6 +4517,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '799c1e27-3fa5-4411-bfa2-a1f494d2434a', type: 'DATE_TIME', name: 'reminderAt', @@ -4401,6 +4539,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dd5354bb-dd0a-4426-ac75-87e9ab171dc4', type: 'RELATION', name: 'assignee', @@ -4448,6 +4587,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd7b4584a-d0ed-49f8-b14c-198036b60fe8', type: 'DATE_TIME', name: 'dueAt', @@ -4469,6 +4609,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9b8808d3-be01-4595-9b20-ebb9553cd7db', type: 'DATE_TIME', name: 'completedAt', @@ -4490,6 +4631,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd0341ced-bdb0-4629-a816-d402df827bd7', type: 'TEXT', name: 'type', @@ -4511,6 +4653,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1dc0b9af-181a-4f2c-bd01-9c4bf355b9de', type: 'RELATION', name: 'activityTargets', @@ -4558,6 +4701,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b31fdd11-115b-4405-8265-c03329338f0c', type: 'DATE_TIME', name: 'deletedAt', @@ -4579,6 +4723,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bfa2ab3d-a55b-41ca-906b-9d497aee7ba8', type: 'RELATION', name: 'attachments', @@ -4626,6 +4771,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '33b0ccb9-19fc-42d6-b7af-87bc4411692e', type: 'UUID', name: 'id', @@ -4682,6 +4828,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e166c79b-99c1-4fb4-9575-5cf7e7f4811f', type: 'BOOLEAN', name: 'isOrganizer', @@ -4703,6 +4850,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '34f2ac5d-bb7e-446c-ab52-18e722345a24', type: 'UUID', name: 'workspaceMemberId', @@ -4724,6 +4872,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0c215138-fc5b-4be2-8c37-0acc7cb4e5a1', type: 'SELECT', name: 'responseStatus', @@ -4774,6 +4923,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '16b867dd-9fb1-43ab-8254-2478004b30b3', type: 'UUID', name: 'id', @@ -4795,6 +4945,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fdfea59a-624c-4af3-9901-1f86d1972b23', type: 'TEXT', name: 'displayName', @@ -4816,6 +4967,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f77bc8e-26dc-40b1-964a-f0748feb193a', type: 'RELATION', name: 'person', @@ -4863,6 +5015,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '597d153f-8724-4d61-8863-8bfae905721f', type: 'RELATION', name: 'calendarEvent', @@ -4910,6 +5063,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '83d94e19-f9f3-47bb-a277-6935af6ae69d', type: 'UUID', name: 'personId', @@ -4931,6 +5085,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'cc27c5b5-44cc-4f19-a56e-8ce172e2ab37', type: 'DATE_TIME', name: 'updatedAt', @@ -4952,6 +5107,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '22c34bd1-550b-4b5c-a2a4-fbb475a4420b', type: 'DATE_TIME', name: 'createdAt', @@ -4973,6 +5129,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7b7792da-246d-4015-855a-ea029f0b8a02', type: 'DATE_TIME', name: 'deletedAt', @@ -4994,6 +5151,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7794c913-c52f-4c06-921e-2b391b63e51e', type: 'UUID', name: 'calendarEventId', @@ -5015,6 +5173,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '30fd4e8a-2089-4443-ac06-5cd53d9a3fcf', type: 'RELATION', name: 'workspaceMember', @@ -5062,6 +5221,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c97d9e28-d807-4fff-ba2c-c72f99087f89', type: 'TEXT', name: 'handle', @@ -5118,6 +5278,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e8a5a86c-b1ce-4db1-8d51-d5b01d2b361b', type: 'DATE_TIME', name: 'updatedAt', @@ -5139,6 +5300,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '09a753b7-a5f9-4810-ae2f-389982f593c3', type: 'DATE_TIME', name: 'createdAt', @@ -5160,6 +5322,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7881aa82-a7ed-4090-923d-f1c0cf16a486', type: 'DATE_TIME', name: 'deletedAt', @@ -5181,6 +5344,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7f1d2847-9f16-457f-a57c-de36c401286a', type: 'UUID', name: 'recordId', @@ -5202,6 +5366,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1db6f800-bb46-45a2-a324-cfe52362ed9a', type: 'UUID', name: 'id', @@ -5223,6 +5388,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '785dc6cb-77dc-4bdd-be44-60ad8f1f45da', type: 'UUID', name: 'workspaceMemberId', @@ -5244,6 +5410,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3d6dc314-877d-4aaa-88bd-364dc50f780b', type: 'RELATION', name: 'workspaceMember', @@ -5291,6 +5458,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '80fe2b13-5cb0-48a0-9341-aec08481628f', type: 'RAW_JSON', name: 'context', @@ -5313,6 +5481,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0383ac8e-6138-4e06-a828-bfc391e53d01', type: 'TEXT', name: 'name', @@ -5334,6 +5503,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f4f0343a-8241-492a-8156-18d40c75f46a', type: 'RAW_JSON', name: 'properties', @@ -5355,6 +5525,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a8282a28-724f-487b-a9fa-f0d10c919237', type: 'TEXT', name: 'objectName', @@ -5376,6 +5547,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd85abbad-9616-456f-978c-4cced740490c', type: 'TEXT', name: 'objectMetadataId', @@ -5432,6 +5604,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '818ec7cd-9181-4c72-ad99-c19b00fde065', type: 'TEXT', name: 'syncCursor', @@ -5454,6 +5627,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '93658b38-cb56-4d2b-93f8-3a4c7714f7c4', type: 'TEXT', name: 'handle', @@ -5475,6 +5649,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'af61423d-0891-4e91-b6d3-2f7ed6363916', type: 'UUID', name: 'connectedAccountId', @@ -5496,6 +5671,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e921674b-2e9f-4f19-a067-920685cf9164', type: 'SELECT', name: 'visibility', @@ -5532,6 +5708,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0abc3baf-2797-4f88-8565-a3ffa4468b55', type: 'DATE_TIME', name: 'deletedAt', @@ -5553,6 +5730,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e567e960-f3c8-4ef0-b810-d8a4bc6a4f7d', type: 'BOOLEAN', name: 'isSyncEnabled', @@ -5574,6 +5752,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bb3531fe-a008-4505-820b-0cb2c365b05d', type: 'DATE_TIME', name: 'createdAt', @@ -5595,6 +5774,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd9a353c7-25ba-403f-8157-0d8bee911cec', type: 'UUID', name: 'id', @@ -5616,6 +5796,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd1596020-b595-4bf9-b3c1-782f9b49f41b', type: 'RELATION', name: 'calendarChannelEventAssociations', @@ -5663,6 +5844,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd757fd20-3568-411f-a2ba-ec2f7b30dc55', type: 'SELECT', name: 'syncStatus', @@ -5720,6 +5902,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '529b36bb-42d1-4361-ac0a-e683557cc879', type: 'DATE_TIME', name: 'updatedAt', @@ -5741,6 +5924,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8d7cb56f-29f1-40ad-90cf-4497bee669a1', type: 'SELECT', name: 'contactAutoCreationPolicy', @@ -5792,6 +5976,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c933e11e-7820-42e4-a589-389c2f314add', type: 'RELATION', name: 'connectedAccount', @@ -5839,6 +6024,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '01e23a9d-d8b7-47c6-a1f8-7e012ae9f54a', type: 'DATE_TIME', name: 'syncStageStartedAt', @@ -5860,6 +6046,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9c5f7d31-5b5e-4af1-9918-859ce66b6c08', type: 'SELECT', name: 'syncStage', @@ -5924,6 +6111,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0c81da27-4ff4-4bf4-8937-9a8ee627e46c', type: 'NUMBER', name: 'throttleFailureCount', @@ -5945,6 +6133,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5c73b1cc-ddda-46a8-a517-f0bb5f0c5f60', type: 'BOOLEAN', name: 'isContactAutoCreationEnabled', @@ -6001,6 +6190,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0cc37cbd-c7ce-4898-b34d-5da7736e7b54', type: 'DATE_TIME', name: 'updatedAt', @@ -6022,6 +6212,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '537e956c-58bb-4ed4-8127-beb0f2d04dd2', type: 'RELATION', name: 'messages', @@ -6069,6 +6260,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '728f4580-9220-4130-9a25-c56669ad0e43', type: 'DATE_TIME', name: 'deletedAt', @@ -6090,6 +6282,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fe0508ef-6e8e-4422-b822-67899af4aa58', type: 'DATE_TIME', name: 'createdAt', @@ -6111,6 +6304,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '823a20db-9431-422c-b0a1-4f559b992651', type: 'UUID', name: 'id', @@ -6167,6 +6361,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7011922c-2271-4960-81eb-b9f9ae3ae00c', type: 'DATE_TIME', name: 'updatedAt', @@ -6188,6 +6383,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '37abc2ae-9f44-4bc0-8277-e3ddfd54738c', type: 'RELATION', name: 'people', @@ -6235,6 +6431,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c264b8c8-1260-410b-aa34-d69b14bba19b', type: 'LINKS', name: 'domainName', @@ -6261,6 +6458,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '73c7c0bc-a328-488f-b357-fd9dc332ab75', type: 'BOOLEAN', name: 'visaSponsorship', @@ -6282,6 +6480,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b08a8600-2c4f-4e8a-8f32-6ffe7041e569', type: 'ADDRESS', name: 'address', @@ -6312,6 +6511,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '107fd869-fa4a-4ca5-b6b1-a918ec78851e', type: 'POSITION', name: 'position', @@ -6333,6 +6533,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f0ab2d8-3cdf-49d1-a8be-ef204e871968', type: 'NUMBER', name: 'employees', @@ -6354,6 +6555,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9677b321-e8fc-4d1b-9d7f-145a5dea0001', type: 'RELATION', name: 'favorites', @@ -6401,6 +6603,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8a21fcb9-5ee7-498c-a09d-1d3137be0540', type: 'DATE_TIME', name: 'deletedAt', @@ -6422,6 +6625,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '300545a5-6ca7-487a-8374-a662bab5d717', type: 'UUID', name: 'accountOwnerId', @@ -6444,6 +6648,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e41f22fd-ecd5-4b28-a2ed-a04d2a017c19', type: 'CURRENCY', name: 'annualRecurringRevenue', @@ -6469,6 +6674,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '24cc7f7b-e8d3-4c12-a3c5-caa5ccf61523', type: 'UUID', name: 'id', @@ -6490,6 +6696,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8421d3d3-d5ac-4065-a431-95780fda2ce7', type: 'RELATION', name: 'opportunities', @@ -6537,6 +6744,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2395970c-d0a2-43df-9f55-58299e930b34', type: 'RELATION', name: 'attachments', @@ -6584,6 +6792,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3b52848e-e419-4361-8fbc-3d4ed19f1956', type: 'TEXT', name: 'name', @@ -6605,6 +6814,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5f23c37a-9971-4060-a861-19f030848b90', type: 'LINKS', name: 'xLink', @@ -6630,6 +6840,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c5103301-686a-4c1f-86d1-69e32a4a34ae', type: 'DATE_TIME', name: 'createdAt', @@ -6651,6 +6862,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '81907e01-90e3-412a-9aa5-9ae8352b679d', type: 'ACTOR', name: 'createdBy', @@ -6675,6 +6887,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f8393bb6-05c2-44d7-bff8-4a7671b43f15', type: 'RELATION', name: 'timelineActivities', @@ -6722,6 +6935,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4882e808-7cd2-487f-911f-ab2d9353e60d', type: 'RELATION', name: 'accountOwner', @@ -6770,6 +6984,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a8cf3568-4d04-43cf-b8c2-2070a7eb0e4e', type: 'MULTI_SELECT', name: 'workPolicy', @@ -6813,6 +7028,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd69eb854-c043-43d9-a40e-65a0649fd1a9', type: 'RELATION', name: 'taskTargets', @@ -6860,6 +7076,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '531c4b2e-94a0-46f4-9395-277c3239413d', type: 'RELATION', name: 'noteTargets', @@ -6907,6 +7124,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'de10d71e-4cfa-4322-b967-761871e69bc0', type: 'LINKS', name: 'introVideo', @@ -6932,6 +7150,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e2e632fe-1c08-4c12-9aba-88f8595bf5be', type: 'RELATION', name: 'activityTargets', @@ -6979,6 +7198,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '16838104-8adf-45a3-93bf-e97551240b66', type: 'LINKS', name: 'linkedinLink', @@ -7004,6 +7224,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '195db745-305e-40f0-b740-d071c5c19214', type: 'TEXT', name: 'tagline', @@ -7025,6 +7246,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2d8e886a-3ebf-474c-8c7a-909e9d7fcc6f', type: 'BOOLEAN', name: 'idealCustomerProfile', @@ -7082,6 +7304,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c496110c-239a-4dd4-bdbd-022ca4fdc62c', type: 'RELATION', name: 'calendarEvent', @@ -7129,6 +7352,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8505bc8b-e978-401a-8983-f1f1e64aa26d', type: 'UUID', name: 'id', @@ -7150,6 +7374,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '18e21e78-56c2-492a-8082-f1e8ceb72e14', type: 'UUID', name: 'calendarChannelId', @@ -7171,6 +7396,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b61d8723-b739-42b6-b23d-b82e09b34669', type: 'DATE_TIME', name: 'deletedAt', @@ -7192,6 +7418,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '97a0be23-ba2e-4af3-8503-d4c27c293a37', type: 'DATE_TIME', name: 'createdAt', @@ -7213,6 +7440,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '906e35f2-e2d1-45d5-8326-cb7712a19e60', type: 'RELATION', name: 'calendarChannel', @@ -7260,6 +7488,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aa298945-e331-40de-a6db-855b58b99d05', type: 'TEXT', name: 'eventExternalId', @@ -7281,6 +7510,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0ec16ef5-2331-4a1e-b429-0e4fc3d9576f', type: 'DATE_TIME', name: 'updatedAt', @@ -7302,6 +7532,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2c765e90-ce86-4f6b-a528-94e38b5aaf54', type: 'UUID', name: 'calendarEventId', @@ -7358,6 +7589,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '78eec6b7-ab9c-4dee-ac64-2b9e40b467f6', type: 'TEXT', name: 'name', @@ -7379,6 +7611,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd2653ce4-585b-4a38-9e09-4ae8e2afae51', type: 'DATE_TIME', name: 'expiresAt', @@ -7400,6 +7633,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9e5cd8cb-e800-4b25-a9e6-cfb6d51623c6', type: 'DATE_TIME', name: 'updatedAt', @@ -7421,6 +7655,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '68704d45-b248-4e41-a07b-21d5874663bb', type: 'DATE_TIME', name: 'revokedAt', @@ -7442,6 +7677,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '77ef9f04-627b-4708-b400-076629ed9f20', type: 'DATE_TIME', name: 'createdAt', @@ -7463,6 +7699,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dc4892f0-a890-4a49-90ae-5a7999665785', type: 'UUID', name: 'id', @@ -7484,6 +7721,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bbe022a1-426f-40c2-ad1d-8294ef127c0b', type: 'DATE_TIME', name: 'deletedAt', @@ -7540,6 +7778,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9d2499b3-403c-473f-b61f-61bbea97afeb', type: 'UUID', name: 'noteId', @@ -7561,6 +7800,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0d232853-5ae9-43f3-ac4a-248c50e2a64e', type: 'UUID', name: 'taskId', @@ -7582,6 +7822,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8e506212-1b66-4981-95d6-f0ac83f5d869', type: 'RELATION', name: 'person', @@ -7629,6 +7870,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dc005581-351b-4fa0-9b39-da5bbe2554b7', type: 'RELATION', name: 'task', @@ -7676,6 +7918,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '86e97af0-ec16-4937-b5b6-e4531027be82', type: 'UUID', name: 'rocketId', @@ -7697,6 +7940,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c33a0768-3f55-4c7b-aa1a-07aeacf3fb85', type: 'UUID', name: 'viewId', @@ -7718,6 +7962,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'afc385d4-22d1-4f83-a67a-46450df368e9', type: 'DATE_TIME', name: 'updatedAt', @@ -7739,6 +7984,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '90a28b04-0214-424f-afad-172b7cd28073', type: 'UUID', name: 'workflowId', @@ -7760,6 +8006,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6a245087-243d-450b-9f00-c951d417d4ef', type: 'UUID', name: 'personId', @@ -7781,6 +8028,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '14e0bf40-d4eb-4893-a5f2-3df3ce749996', type: 'UUID', name: 'workspaceMemberId', @@ -7802,6 +8050,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0ecb3679-5ff7-4b55-9ed3-9927bc9e184b', type: 'RELATION', name: 'note', @@ -7849,6 +8098,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3a561744-45bd-45a6-af94-bfb4d4f508fe', type: 'DATE_TIME', name: 'createdAt', @@ -7870,6 +8120,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '46ff8942-360f-4837-9a83-007739c8ba05', type: 'RELATION', name: 'view', @@ -7917,6 +8168,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6e104959-9dfb-42a7-80da-0ddb0dab12f9', type: 'UUID', name: 'opportunityId', @@ -7938,6 +8190,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f112f2a8-4f21-454d-b3a7-fcd85f0eab72', type: 'NUMBER', name: 'position', @@ -7959,6 +8212,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e4102fb0-c6e3-47e8-a810-71e0b6453705', type: 'DATE_TIME', name: 'deletedAt', @@ -7980,6 +8234,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a1555234-ff28-4a04-871c-31008b39e442', type: 'UUID', name: 'id', @@ -8001,6 +8256,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '10509b5e-71c0-49e8-9cb8-d0ff7ee8691b', type: 'UUID', name: 'companyId', @@ -8022,6 +8278,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'eed525bd-edf3-4030-9f09-8ff68226a6a0', type: 'RELATION', name: 'workflow', @@ -8069,6 +8326,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5b559e4c-eb75-4a1e-b904-a486d2328b24', type: 'RELATION', name: 'workspaceMember', @@ -8116,6 +8374,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7155582f-4bbc-4643-be0c-38165b8a282f', type: 'RELATION', name: 'company', @@ -8163,6 +8422,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '11e6c0a0-e1fa-4931-a705-8725a79afe24', type: 'RELATION', name: 'rocket', @@ -8210,6 +8470,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1d3858e1-e4aa-484f-b422-8bbefa9409c8', type: 'RELATION', name: 'opportunity', @@ -8292,6 +8553,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '974ab999-a946-409f-820c-aa2e2c21f3ce', type: 'DATE_TIME', name: 'updatedAt', @@ -8313,6 +8575,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '70a9b04f-0d11-41a1-bce8-e7ac8bb0ed5d', type: 'DATE_TIME', name: 'createdAt', @@ -8334,6 +8597,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'da1dda8a-b7b6-4166-b11b-740a8414706b', type: 'UUID', name: 'authorId', @@ -8355,6 +8619,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3b07941b-15b6-4468-8e2f-52abf7ff36b3', type: 'TEXT', name: 'body', @@ -8376,6 +8641,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8f985123-68d4-4e8a-b75b-85a75f0f071e', type: 'UUID', name: 'id', @@ -8397,6 +8663,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e48bc6a2-211c-46aa-9f22-1859aedac28e', type: 'RELATION', name: 'activity', @@ -8444,6 +8711,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c39eb95c-2e9f-4fd3-abc3-103986dd21bb', type: 'UUID', name: 'activityId', @@ -8465,6 +8733,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '571e3b8c-0c80-4292-93b1-c73b4d976b05', type: 'DATE_TIME', name: 'deletedAt', @@ -8486,6 +8755,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b1ead0df-5b1c-4c00-b0c5-05a67ec37327', type: 'RELATION', name: 'author', @@ -8568,6 +8838,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0c1f5af5-c2d5-4f38-9fd4-6ce854e693d3', type: 'DATE_TIME', name: 'updatedAt', @@ -8589,6 +8860,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f06f5946-e54f-458d-9c77-47d6d8fbd995', type: 'DATE_TIME', name: 'endsAt', @@ -8610,6 +8882,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bc8be6a6-4707-498a-9e79-8ffb86e92a43', type: 'RELATION', name: 'calendarEventParticipants', @@ -8657,6 +8930,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6bfac067-9c6c-4b4d-bdaf-ecf7737b2599', type: 'TEXT', name: 'iCalUID', @@ -8678,6 +8952,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '45bd7c28-f450-4487-a556-1fd85be68beb', type: 'TEXT', name: 'title', @@ -8699,6 +8974,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '52d1ed74-b3d5-4339-b408-6f4c8dbc3969', type: 'DATE_TIME', name: 'createdAt', @@ -8720,6 +8996,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4078641f-c8b4-418c-9e7a-0bfdc411c4d9', type: 'DATE_TIME', name: 'externalUpdatedAt', @@ -8741,6 +9018,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ae4f8ff1-47f1-4b84-b61b-c519c939a409', type: 'TEXT', name: 'conferenceSolution', @@ -8762,6 +9040,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a77794e2-adea-40cb-a0f9-a91a6d4494ed', type: 'DATE_TIME', name: 'startsAt', @@ -8783,6 +9062,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '62d6a06b-0083-4702-be85-edd6ca882816', type: 'TEXT', name: 'location', @@ -8804,6 +9084,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '567c7852-6dc5-4c6e-826d-e4b253614e60', type: 'TEXT', name: 'recurringEventExternalId', @@ -8825,6 +9106,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c0ed400f-b0cf-4fe1-a929-89698dc020a5', type: 'DATE_TIME', name: 'externalCreatedAt', @@ -8846,6 +9128,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'da0d1cc9-be28-4b41-9ddf-48041702024b', type: 'TEXT', name: 'description', @@ -8867,6 +9150,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '52db308a-e428-4efd-95ed-2b0e19cbdc92', type: 'RELATION', name: 'calendarChannelEventAssociations', @@ -8914,6 +9198,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '63904769-6145-4230-b294-c4554c36a273', type: 'BOOLEAN', name: 'isCanceled', @@ -8935,6 +9220,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '48fac512-a780-4c44-b1dc-f178bd8ab3f8', type: 'DATE_TIME', name: 'deletedAt', @@ -8956,6 +9242,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1ba833a8-68ca-4396-b3ed-9a7411e1dc4f', type: 'UUID', name: 'id', @@ -8977,6 +9264,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3050381d-7191-4d18-be27-9a92cbefb57a', type: 'LINKS', name: 'conferenceLink', @@ -9002,6 +9290,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b6cf9c5d-e923-4026-9316-c3a513ce7c12', type: 'BOOLEAN', name: 'isFullDay', @@ -9058,6 +9347,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ac76a0ff-f5c1-4127-b354-0b3ce2b3696b', type: 'RELATION', name: 'favorites', @@ -9105,6 +9395,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e32f7a81-d208-4c14-afb4-a4befc938670', type: 'RELATION', name: 'assignedActivities', @@ -9152,6 +9443,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '85df4525-aed1-4e46-b718-b5bc963da41d', type: 'RELATION', name: 'auditLogs', @@ -9199,6 +9491,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a1ffe54a-6b76-47fa-bd25-df479a31eee2', type: 'FULL_NAME', name: 'name', @@ -9223,6 +9516,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7370da8c-294f-4671-91e5-7f87f4dccc1e', type: 'RELATION', name: 'calendarEventParticipants', @@ -9270,6 +9564,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '13531c23-42ca-4b1e-a6e3-3fcfad74a3e9', type: 'RELATION', name: 'connectedAccounts', @@ -9317,6 +9612,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a5fc2711-0e3d-40ac-938a-8beafeac1f57', type: 'RELATION', name: 'timelineActivities', @@ -9364,6 +9660,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bb548a08-0706-4021-833d-e527c23e2a48', type: 'RELATION', name: 'accountOwnerForCompanies', @@ -9411,6 +9708,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fc4f41c0-01ea-428d-9e0c-e2496acc765b', type: 'TEXT', name: 'avatarUrl', @@ -9432,6 +9730,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f74a56bf-9230-4452-b6a0-099ba9f7de0d', type: 'RELATION', name: 'authoredComments', @@ -9479,6 +9778,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e2895a0d-0e4f-4673-a778-2b99cac1ab20', type: 'UUID', name: 'userId', @@ -9500,6 +9800,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4cceec11-98cc-41c6-b08c-b04887a0ac22', type: 'DATE_TIME', name: 'createdAt', @@ -9521,6 +9822,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1dc4eef9-d576-4106-b19e-2fc91777470d', type: 'TEXT', name: 'timeZone', @@ -9542,6 +9844,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2f548d63-6cf6-4116-ae5a-03fcc35ffd1d', type: 'UUID', name: 'id', @@ -9563,6 +9866,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6cc143e8-f88a-4d75-a52c-d5c7f7419d97', type: 'SELECT', name: 'timeFormat', @@ -9606,6 +9910,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b9f4e812-721f-4c83-9f1e-42b79042d905', type: 'DATE_TIME', name: 'updatedAt', @@ -9627,6 +9932,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '12e6ccfa-23ef-4b68-a043-6012bc7b9c67', type: 'TEXT', name: 'locale', @@ -9648,6 +9954,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b75d64ca-eb86-439d-ad61-3f23efec07e4', type: 'TEXT', name: 'userEmail', @@ -9669,6 +9976,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dd63010d-24f1-4e62-9b49-b4728bb3bd81', type: 'DATE_TIME', name: 'deletedAt', @@ -9690,6 +9998,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f173a8e9-af79-4377-9b21-5cb1ca27bc87', type: 'TEXT', name: 'colorScheme', @@ -9711,6 +10020,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f76b3faa-0bc6-45ed-9654-0421171a1f1a', type: 'RELATION', name: 'authoredActivities', @@ -9758,6 +10068,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c5df2d21-5db8-4e72-82bf-aeb3ec984ca9', type: 'RELATION', name: 'authoredAttachments', @@ -9805,6 +10116,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd82db237-ca6d-4bee-8d69-dfa0f753707b', type: 'RELATION', name: 'messageParticipants', @@ -9852,6 +10164,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e64fef03-fda1-4b5c-8894-d1bee725e7e2', type: 'RELATION', name: 'blocklist', @@ -9899,6 +10212,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3653df1b-5687-45c1-a2bf-afc261fe85a8', type: 'RELATION', name: 'assignedTasks', @@ -9946,6 +10260,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '18a0276f-ce5e-4fc7-81fc-c32f6dd844f3', type: 'SELECT', name: 'dateFormat', @@ -10031,6 +10346,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f58f100f-8ee9-4a0c-8f35-8bdcb561d586', type: 'DATE_TIME', name: 'createdAt', @@ -10052,6 +10368,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a3740f53-215a-477d-82be-b57265731d83', type: 'DATE_TIME', name: 'deletedAt', @@ -10073,6 +10390,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '67dbcf4d-f15b-4703-ba84-4bc2b9903579', type: 'UUID', name: 'id', @@ -10094,6 +10412,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd26efc67-251a-4bdd-a585-177717e298a6', type: 'TEXT', name: 'eventName', @@ -10115,6 +10434,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c8d7ed48-3d0e-47fa-a4af-5a62e578c128', type: 'UUID', name: 'workflowId', @@ -10137,6 +10457,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a424fba4-d3f4-41b1-bf9a-4b809ad628a9', type: 'RELATION', name: 'workflow', @@ -10184,6 +10505,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aabd6970-7bf9-488b-8411-5bc654574d58', type: 'DATE_TIME', name: 'updatedAt', @@ -10240,6 +10562,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ee4d98d0-37a4-42cc-8794-653099d4df54', type: 'BOOLEAN', name: 'isVisible', @@ -10261,6 +10584,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '643509ce-5666-4264-8b09-f095e23a1624', type: 'NUMBER', name: 'position', @@ -10282,6 +10606,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c672e744-21f3-4d23-abd8-fcc03fad503a', type: 'UUID', name: 'viewId', @@ -10303,6 +10628,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '49e8ada7-3a80-49c7-869a-3ebfdae35387', type: 'NUMBER', name: 'size', @@ -10324,6 +10650,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dd7510e1-4f22-4376-8436-19d7d631ea77', type: 'DATE_TIME', name: 'createdAt', @@ -10345,6 +10672,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f74ba2c1-22ab-4827-85ad-d2dbbe2a9b51', type: 'RELATION', name: 'view', @@ -10392,6 +10720,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '959d36dc-493b-4e08-ae3d-38680bab1d0d', type: 'UUID', name: 'id', @@ -10413,6 +10742,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '300b7bf6-e1fa-4cd2-a601-7a125b7bf1b8', type: 'DATE_TIME', name: 'deletedAt', @@ -10434,6 +10764,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0f7acd2a-ccd1-437e-b80a-bf4555a7c034', type: 'UUID', name: 'fieldMetadataId', @@ -10455,6 +10786,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '217be100-e82e-4aae-9566-b091823a5466', type: 'DATE_TIME', name: 'updatedAt', @@ -10511,6 +10843,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '330a6b96-c7eb-41ae-962e-eb528dc16aaf', type: 'DATE_TIME', name: 'updatedAt', @@ -10532,6 +10865,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '45fe30a3-141f-4908-be8b-b826f84edb75', type: 'UUID', name: 'viewId', @@ -10553,6 +10887,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7924f4d6-c92b-436f-a86d-26b2dcc521aa', type: 'TEXT', name: 'direction', @@ -10574,6 +10909,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fc34ddd1-7f8a-4f23-a2b6-9a6d3165cc0a', type: 'DATE_TIME', name: 'deletedAt', @@ -10595,6 +10931,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e4f8f882-dec2-4d6b-a141-936e87d3fd27', type: 'UUID', name: 'id', @@ -10616,6 +10953,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '25ae0938-795a-491a-b029-e4672412e85f', type: 'RELATION', name: 'view', @@ -10663,6 +11001,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '540715e7-6531-4746-b567-b0a7fcda60ef', type: 'UUID', name: 'fieldMetadataId', @@ -10684,6 +11023,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26889121-d2d9-49dc-86ac-51c64d123197', type: 'DATE_TIME', name: 'createdAt', @@ -10739,6 +11079,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9aed4be2-3434-489f-a8a3-384311ee585e', type: 'RELATION', name: 'activityTargets', @@ -10786,6 +11127,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '13794481-6a3a-48cb-80c2-109b7558f7b3', type: 'RELATION', name: 'attachments', @@ -10833,6 +11175,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '79da64bc-eea3-476e-801b-f08c86a8c337', type: 'RELATION', name: 'favorites', @@ -10880,6 +11223,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0a688ec2-f55c-4485-a7a9-9438c19bcbe3', type: 'ACTOR', name: 'createdBy', @@ -10904,6 +11248,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '17cbb6ed-1acc-43e9-a802-96a2b373a067', type: 'DATE_TIME', name: 'updatedAt', @@ -10925,6 +11270,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ba68ee30-5dc6-47db-bf6f-db25d829feb5', type: 'TEXT', name: 'name', @@ -10946,6 +11292,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ff73fd8a-9b60-4480-82d0-7b96c3c3aab6', type: 'POSITION', name: 'position', @@ -10967,6 +11314,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '83515891-eb29-472e-9cde-4a1d42b6855d', type: 'RELATION', name: 'taskTargets', @@ -11014,6 +11362,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '73428c65-a426-4c59-b50b-0dea5ffe9bf0', type: 'DATE_TIME', name: 'createdAt', @@ -11035,6 +11384,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6c65c781-9def-4c6c-98d4-25d3c0c085b8', type: 'UUID', name: 'id', @@ -11056,6 +11406,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b2643f4a-319e-49d4-a7b7-cbfff4712bf7', type: 'DATE_TIME', name: 'deletedAt', @@ -11077,6 +11428,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '75f64c23-e9a5-4ada-8dc6-3c2c2ea27280', type: 'RELATION', name: 'noteTargets', @@ -11124,6 +11476,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aeefa434-6843-4b47-92f6-3ce6d8e93860', type: 'RELATION', name: 'timelineActivities', @@ -11206,6 +11559,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fa957d80-c61a-4298-aab9-54e8fba2110d', type: 'UUID', name: 'id', @@ -11227,6 +11581,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f966b379-0438-4b9c-8696-3edf18c197f7', type: 'RELATION', name: 'workspaceMember', @@ -11274,6 +11629,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '37f7671b-aa7c-4fed-b50c-9b7cc7f59aa8', type: 'TEXT', name: 'handle', @@ -11295,6 +11651,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '862261a3-5473-4e16-9220-16444ee99243', type: 'DATE_TIME', name: 'updatedAt', @@ -11316,6 +11673,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1233f40e-cc97-4281-a8a8-c23de8961693', type: 'DATE_TIME', name: 'createdAt', @@ -11337,6 +11695,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a75f6045-cf57-4b05-960b-5719ce2037c9', type: 'DATE_TIME', name: 'deletedAt', @@ -11358,6 +11717,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c30e3bff-ce4e-4e64-80e3-bf417ceefa25', type: 'UUID', name: 'workspaceMemberId', @@ -11414,6 +11774,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2d00a390-5ae4-40d9-8d6c-6abbcdc0bc75', type: 'DATE_TIME', name: 'updatedAt', @@ -11435,6 +11796,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2aab6f08-2cab-4beb-8775-1ad8b89c3313', type: 'ACTOR', name: 'createdBy', @@ -11459,6 +11821,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f40e2edc-c0dd-46d8-9a01-a0afd33a00db', type: 'UUID', name: 'id', @@ -11480,6 +11843,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '421de2e5-36a1-4fb2-ae00-4e2f62dd67e6', type: 'UUID', name: 'workflowVersionId', @@ -11502,6 +11866,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c9e837bb-4516-43ad-8756-7a9c2ad33ca9', type: 'DATE_TIME', name: 'createdAt', @@ -11523,6 +11888,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aec86cc0-1fb6-4ff0-b1df-85b74ee6a974', type: 'UUID', name: 'workflowId', @@ -11544,6 +11910,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b38c3f7d-d455-4f1c-b674-66b86c0d56cc', type: 'RELATION', name: 'workflowVersion', @@ -11591,6 +11958,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '446d4c37-71ac-4a61-8b65-16d70250cbc5', type: 'SELECT', name: 'status', @@ -11641,6 +12009,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0ed4a200-4d2e-4d4d-81b2-45240ae9e1b8', type: 'DATE_TIME', name: 'endedAt', @@ -11662,6 +12031,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a5444b51-1ad5-46ae-b9d9-ef5f1def9232', type: 'RELATION', name: 'workflow', @@ -11709,6 +12079,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b388b202-371a-4663-9299-afc6be461a8c', type: 'DATE_TIME', name: 'deletedAt', @@ -11730,6 +12101,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2e84a165-18af-492f-8662-14fbd5852b0c', type: 'DATE_TIME', name: 'startedAt', @@ -11786,6 +12158,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6f41b722-fbc4-44d9-9315-51498900e157', type: 'DATE_TIME', name: 'updatedAt', @@ -11807,6 +12180,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1d53ad9c-bdf4-4660-b4b6-e79162d13c39', type: 'RELATION', name: 'favorites', @@ -11854,6 +12228,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e7df66e0-50cc-4047-9b91-8f5ee1ff7246', type: 'DATE_TIME', name: 'createdAt', @@ -11875,6 +12250,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '34ec585e-b42e-4e63-8e4b-7f9bf6a4e79b', type: 'DATE_TIME', name: 'deletedAt', @@ -11896,6 +12272,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '22b735aa-95d7-44fe-a4a6-965171e3f7b7', type: 'DATE_TIME', name: 'dueAt', @@ -11917,6 +12294,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fea3a2f3-73ca-4142-8d15-3b8877b68cee', type: 'UUID', name: 'id', @@ -11938,6 +12316,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4f82dbad-cf61-4dd1-ad90-4cec71b288be', type: 'RELATION', name: 'attachments', @@ -11985,6 +12364,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b52cbdab-0c97-4601-b5ad-de766ec9f940', type: 'SELECT', name: 'status', @@ -12028,6 +12408,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1eea9af9-53a3-4085-9391-4a2fad697eb7', type: 'RELATION', name: 'timelineActivities', @@ -12075,6 +12456,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '78360243-9830-4f40-a515-59cd8faf88b1', type: 'RICH_TEXT', name: 'body', @@ -12096,6 +12478,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '33d7eb92-8231-417c-8efe-eb837c6ccadf', type: 'ACTOR', name: 'createdBy', @@ -12120,6 +12503,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5f9a8761-e5e6-492c-87ab-401c7b0e25cd', type: 'UUID', name: 'assigneeId', @@ -12141,6 +12525,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7a062a59-07da-4d2d-a0e8-f79f87e2e5e3', type: 'RELATION', name: 'assignee', @@ -12188,6 +12573,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '80932806-6350-4941-a291-4d1430275d65', type: 'RELATION', name: 'taskTargets', @@ -12235,6 +12621,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'daf389ba-49e6-4753-8a5a-a0330c5ab154', type: 'POSITION', name: 'position', @@ -12256,6 +12643,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0413dccd-00a6-4abd-967a-45233e8cf666', type: 'TEXT', name: 'title', @@ -12312,6 +12700,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '38f5127c-ab53-45bc-90b9-a3883d697eb9', type: 'DATE_TIME', name: 'deletedAt', @@ -12333,6 +12722,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b2a7002e-19f2-4f16-8b5b-ea452aeb7104', type: 'TEXT', name: 'lastPublishedVersionId', @@ -12354,6 +12744,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd6fb9120-9aa5-4fdf-a84a-2805bb359855', type: 'RELATION', name: 'versions', @@ -12401,6 +12792,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '99d7e7e6-ac6a-4b74-9129-d4a9759bc928', type: 'DATE_TIME', name: 'createdAt', @@ -12422,6 +12814,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '06a5a424-930c-4465-9943-2a9c486f2038', type: 'UUID', name: 'id', @@ -12443,6 +12836,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '650b02ef-32b5-4b10-bdab-a49f4e3b7a9b', type: 'MULTI_SELECT', name: 'statuses', @@ -12484,6 +12878,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b14865e9-26a6-4ae8-930d-d22c21c7696c', type: 'RELATION', name: 'eventListeners', @@ -12532,6 +12927,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4f32f14b-5009-4624-8b19-e65084368349', type: 'RELATION', name: 'runs', @@ -12579,6 +12975,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bab376b1-a7d9-48b8-88b7-f302bc6d483d', type: 'TEXT', name: 'name', @@ -12600,6 +12997,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '141281c7-5b28-41d4-99bd-31c1e4c88e9b', type: 'RELATION', name: 'favorites', @@ -12647,6 +13045,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f21077b2-283f-4362-98fd-e5ea5f87d621', type: 'POSITION', name: 'position', @@ -12668,6 +13067,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b063853c-a442-4b52-8f89-f750c44a2e04', type: 'DATE_TIME', name: 'updatedAt', @@ -12725,6 +13125,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6615546f-8744-4b36-83d5-59c1ef72e845', type: 'UUID', name: 'id', @@ -12746,6 +13147,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '299c9bdc-5137-4d2b-8225-ddb81a720bfe', type: 'UUID', name: 'rocketId', @@ -12767,6 +13169,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8626f50f-1d39-40e7-a05b-528c42fe4313', type: 'RELATION', name: 'task', @@ -12814,6 +13217,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f2f27d9e-c959-4dee-aed4-f87c64229c3f', type: 'RELATION', name: 'rocket', @@ -12861,6 +13265,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '62e278a5-d719-4b49-a820-fc5ed358311e', type: 'DATE_TIME', name: 'happensAt', @@ -12882,6 +13287,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '145c8790-abd1-49fb-9857-650da78c6717', type: 'RELATION', name: 'company', @@ -12929,6 +13335,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '08dd3999-ca07-43cf-b872-a2bed317aa6a', type: 'TEXT', name: 'name', @@ -12950,6 +13357,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bbdf103a-60cb-4c39-a982-1704e26f6735', type: 'UUID', name: 'noteId', @@ -12971,6 +13379,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9e9d4989-fdf2-4e71-90af-aae2ef0b4923', type: 'RELATION', name: 'person', @@ -13018,6 +13427,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1b496b65-948d-4614-889f-2a9e0a6292f3', type: 'UUID', name: 'linkedObjectMetadataId', @@ -13039,6 +13449,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'cd22914c-60d6-4e45-9936-9ff345d3a5bb', type: 'DATE_TIME', name: 'updatedAt', @@ -13060,6 +13471,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '77f9059d-33f5-4fa1-8641-4976d38eaeb7', type: 'TEXT', name: 'linkedRecordCachedName', @@ -13081,6 +13493,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '137a88ef-9c08-414c-adcc-2d450624acf8', type: 'RELATION', name: 'workspaceMember', @@ -13128,6 +13541,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b2df9370-1560-47f6-89b6-88293de46572', type: 'UUID', name: 'personId', @@ -13149,6 +13563,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '9cb24716-07e8-4c54-a0c8-7005a619314e', type: 'RAW_JSON', name: 'properties', @@ -13170,6 +13585,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bc5277df-a1ec-4ae2-affe-e42aed88f0b9', type: 'UUID', name: 'taskId', @@ -13191,6 +13607,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'cda06c7c-9c62-4579-b3ba-2e8275c604b1', type: 'DATE_TIME', name: 'createdAt', @@ -13212,6 +13629,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '95fa7d72-fd85-4a32-a489-a5d03199debb', type: 'UUID', name: 'workspaceMemberId', @@ -13233,6 +13651,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1d16560a-4a8f-448c-8289-70e3e98ad3b4', type: 'UUID', name: 'linkedRecordId', @@ -13254,6 +13673,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c646eaf5-2754-4924-a212-c5747b0d1d41', type: 'UUID', name: 'companyId', @@ -13275,6 +13695,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '88a829b3-6d66-4e53-b1ad-ed02e544e4d2', type: 'RELATION', name: 'opportunity', @@ -13322,6 +13743,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'db7ab4e8-9d93-4f45-bbbf-f68eb31776cb', type: 'RELATION', name: 'note', @@ -13369,6 +13791,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1ee1c4bc-7281-4eff-bc32-6afaff324477', type: 'DATE_TIME', name: 'deletedAt', @@ -13390,6 +13813,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '225d1acc-2ccc-458d-acd5-06f7d8647a38', type: 'UUID', name: 'opportunityId', @@ -13447,6 +13871,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f926708e-cc19-4e5f-8684-d14a1a1bc7df', type: 'RELATION', name: 'pointOfContactForOpportunities', @@ -13495,6 +13920,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5a063961-bffe-4d68-a0c8-e86b6b26f85e', type: 'FULL_NAME', name: 'name', @@ -13519,6 +13945,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd02d52c8-379b-476d-84d6-1222c2179db7', type: 'LINKS', name: 'linkedinLink', @@ -13544,6 +13971,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '504f7e23-1476-422b-ac1d-5d86d3d33022', type: 'RELATION', name: 'company', @@ -13591,6 +14019,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'daab9749-7d39-44cb-9557-23e0e257aaad', type: 'RELATION', name: 'attachments', @@ -13638,6 +14067,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6e46c199-d4f9-4c60-b9bb-19da859991b4', type: 'RELATION', name: 'timelineActivities', @@ -13685,6 +14115,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'aa5e7931-e042-45d5-af4a-e4c22979a3b7', type: 'RELATION', name: 'calendarEventParticipants', @@ -13732,6 +14163,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a8a3d68a-a1b1-4912-aa13-51f088c6a754', type: 'DATE_TIME', name: 'deletedAt', @@ -13753,6 +14185,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e442f814-f8a6-44e9-b60c-d736afec87dd', type: 'DATE_TIME', name: 'createdAt', @@ -13774,6 +14207,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a91e4125-7885-4b3c-9672-0f6d2fc49c07', type: 'DATE_TIME', name: 'updatedAt', @@ -13795,6 +14229,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4a9a6c3e-633d-47d6-99bc-b8e6e9b6aad9', type: 'TEXT', name: 'jobTitle', @@ -13816,6 +14251,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b801b0d2-2a24-42be-b878-ef439ef7ea78', type: 'TEXT', name: 'intro', @@ -13837,6 +14273,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c188f5b7-2259-4207-87d2-5232ec775029', type: 'RELATION', name: 'favorites', @@ -13884,9 +14321,10 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5cdf5de5-6c7f-4787-b47d-de5e3787e670', type: 'MULTI_SELECT', - name: 'workPrefereance', + name: 'workPreference', label: 'Work Preference', description: "Person's Work Preference", icon: 'IconHome', @@ -13927,6 +14365,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1315ca79-3abd-4b7f-917e-8949db3e01f3', type: 'RATING', name: 'performanceRating', @@ -13979,6 +14418,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '749e13b2-5430-4993-83db-635c6ff11d1a', type: 'LINKS', name: 'xLink', @@ -14004,6 +14444,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '0f7b1621-5da6-439a-927f-948fd2dd6f29', type: 'RELATION', name: 'noteTargets', @@ -14051,6 +14492,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '759da4e9-e9c6-4827-9802-5b7b4021448f', type: 'TEXT', name: 'city', @@ -14072,6 +14514,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '48f76fc3-3c82-463d-afa7-977011eed7c8', type: 'UUID', name: 'companyId', @@ -14093,6 +14536,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '23c64ee1-4935-4a25-b401-afc08a0967fd', type: 'RELATION', name: 'activityTargets', @@ -14140,6 +14584,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd4a5fecc-285b-42d7-8eb4-96fc2a6838c4', type: 'PHONES', name: 'phones', @@ -14165,6 +14610,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bb5900a5-29ff-46bb-8bbb-7dbedd844e29', type: 'ACTOR', name: 'createdBy', @@ -14189,6 +14635,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '54fbea0f-ce3f-4d28-8fa4-abcfe1ae3d54', type: 'UUID', name: 'id', @@ -14210,6 +14657,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '718ee8a6-a294-4609-91d4-7ab0e83d996f', type: 'POSITION', name: 'position', @@ -14231,6 +14679,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '779bdf5a-a28d-48be-8d02-b6ca93851829', type: 'RELATION', name: 'messageParticipants', @@ -14278,6 +14727,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7f8249b8-5919-4fee-81cf-b4dfdb89cf4d', type: 'EMAILS', name: 'emails', @@ -14302,6 +14752,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'b328a712-5dc3-457a-aa56-8631f1b57248', type: 'RELATION', name: 'taskTargets', @@ -14349,6 +14800,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '377efd0d-9798-4522-b9db-73c19bf55e26', type: 'TEXT', name: 'avatarUrl', @@ -14370,6 +14822,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3a34e81d-d5b1-417d-9831-a355040a6f44', type: 'PHONES', name: 'whatsapp', @@ -14430,6 +14883,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f115229b-f5b3-4623-ac80-f8bf3df5e077', type: 'RELATION', name: 'favorites', @@ -14477,6 +14931,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ed46299b-4006-45b4-aaa4-2dc7c0c25613', type: 'DATE_TIME', name: 'deletedAt', @@ -14498,6 +14953,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '54952f24-63cc-475e-ab4c-b0f3ad6400bc', type: 'UUID', name: 'id', @@ -14519,6 +14975,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'bef43ac8-834a-4a86-8bfb-5bed6cd94a57', type: 'RELATION', name: 'noteTargets', @@ -14566,6 +15023,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '210b544b-46f7-422d-84e7-328ef270f081', type: 'RELATION', name: 'timelineActivities', @@ -14613,6 +15071,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd80706c2-3c5d-491f-9759-b05b25004799', type: 'POSITION', name: 'position', @@ -14634,6 +15093,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2238e043-a33f-4e8d-99d6-386cd0d2ea3b', type: 'DATE_TIME', name: 'updatedAt', @@ -14655,6 +15115,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c996b13e-a787-44f7-ad7a-2ba22afd46bc', type: 'ACTOR', name: 'createdBy', @@ -14679,6 +15140,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '61375724-caaa-46b1-8e50-4b2f23afac71', type: 'RICH_TEXT', name: 'body', @@ -14700,6 +15162,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7ffc4d21-aa5d-4f0f-bc91-61c72660f2ba', type: 'TEXT', name: 'title', @@ -14721,6 +15184,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '27b7400b-e754-4db9-b895-0a9252a015bf', type: 'DATE_TIME', name: 'createdAt', @@ -14742,6 +15206,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '80479763-1c19-4465-a62f-cd5e37c9165a', type: 'RELATION', name: 'attachments', @@ -14824,6 +15289,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4e4f0f26-89bd-41eb-bc00-a591e041ca7d', type: 'DATE_TIME', name: 'updatedAt', @@ -14845,6 +15311,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '362cb4b7-dda1-4136-9385-4f5402b4f700', type: 'UUID', name: 'id', @@ -14866,6 +15333,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd13848d4-b41e-43b0-87ca-ab40c00fd739', type: 'DATE_TIME', name: 'createdAt', @@ -14887,6 +15355,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '81685712-ecaa-4d04-a8d9-e6c93a6bfe7a', type: 'TEXT', name: 'displayValue', @@ -14908,6 +15377,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f72701d1-8337-4640-84b7-4570cfbe96aa', type: 'UUID', name: 'viewId', @@ -14929,6 +15399,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e323bb1b-8018-4b6f-b065-37c979caf7fc', type: 'DATE_TIME', name: 'deletedAt', @@ -14950,6 +15421,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '646c0bfc-071d-44f3-b8d4-428061106500', type: 'UUID', name: 'fieldMetadataId', @@ -14971,6 +15443,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '016a2312-ac04-4d46-b7c7-2d6e24e363c8', type: 'TEXT', name: 'value', @@ -14992,6 +15465,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'e09cc7ab-2750-4cd9-86e1-c257dac8b390', type: 'TEXT', name: 'operand', @@ -15013,6 +15487,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd1829b60-e886-4dac-8cf3-0d3b84da093d', type: 'RELATION', name: 'view', @@ -15095,6 +15570,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dd208a4e-3e5e-4192-83a1-adbc5b9123a1', type: 'RAW_JSON', name: 'steps', @@ -15116,6 +15592,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fe28de2e-8979-4cfd-9b66-03e67e93406b', type: 'UUID', name: 'id', @@ -15137,6 +15614,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dcdf9413-04c8-44f7-ace7-a11950ce3019', type: 'SELECT', name: 'status', @@ -15187,6 +15665,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ef3d8797-2f17-4f31-9970-697fc230df7f', type: 'RELATION', name: 'runs', @@ -15234,6 +15713,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ebe58c33-17d2-418b-b33f-f5c3907e97d7', type: 'DATE_TIME', name: 'deletedAt', @@ -15255,6 +15735,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1b223e47-9228-41c4-a420-ff6ed516393e', type: 'DATE_TIME', name: 'createdAt', @@ -15276,6 +15757,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '54cde78b-4bd4-436b-b10b-e6de37494161', type: 'RELATION', name: 'workflow', @@ -15323,6 +15805,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4ebb0eb9-9ad6-4e5b-b01b-837b0e2c0718', type: 'RAW_JSON', name: 'trigger', @@ -15344,6 +15827,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ef2dd597-faaa-4b1d-96b7-5953cd8c8539', type: 'TEXT', name: 'name', @@ -15365,6 +15849,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd9f1d5c8-ce95-48c3-a4f3-0909aea7e322', type: 'DATE_TIME', name: 'updatedAt', @@ -15386,6 +15871,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '182c0466-9e03-4500-8cf7-22673e05b299', type: 'UUID', name: 'workflowId', @@ -15442,6 +15928,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '426142c4-5a52-4105-a880-387b6dba6362', type: 'DATE_TIME', name: 'deletedAt', @@ -15463,6 +15950,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4d459669-2ffd-4fd0-b28b-57d8a1eb9434', type: 'UUID', name: 'personId', @@ -15484,6 +15972,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1a6dd2bf-3da7-492d-80de-a8891c5307b7', type: 'UUID', name: 'opportunityId', @@ -15505,6 +15994,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3669c8b2-ba73-4e9b-be61-10a2023955fd', type: 'RELATION', name: 'activity', @@ -15552,6 +16042,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '023f376c-023a-4cae-89e8-961add0b3743', type: 'UUID', name: 'id', @@ -15573,6 +16064,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '6a5833a9-0741-44f0-948d-424a60d3264e', type: 'RELATION', name: 'note', @@ -15620,6 +16112,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1d3d2f23-c3e6-4ee0-b62e-7668e4f8147d', type: 'TEXT', name: 'fullPath', @@ -15641,6 +16134,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd9a80298-6f72-4b2f-a859-2bd355d36735', type: 'RELATION', name: 'author', @@ -15688,6 +16182,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '42dbb8d7-320b-460d-a25e-943222ae2a9b', type: 'TEXT', name: 'type', @@ -15709,6 +16204,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a7971798-8b85-477e-8a1e-9b2f2fb5da6d', type: 'UUID', name: 'rocketId', @@ -15730,6 +16226,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '610633e7-0221-4838-adaf-71943d18a5ca', type: 'RELATION', name: 'person', @@ -15777,6 +16274,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '26668e62-39e7-4842-a198-a657f44206f8', type: 'RELATION', name: 'task', @@ -15824,6 +16322,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1433d739-29ed-450c-8216-6afea26d21fb', type: 'RELATION', name: 'company', @@ -15871,6 +16370,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '1a8466d6-8be7-458a-a7ea-cdf11b4fe31d', type: 'DATE_TIME', name: 'updatedAt', @@ -15892,6 +16392,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7dc968df-7127-4e05-b63e-f2e9809324ee', type: 'UUID', name: 'noteId', @@ -15913,6 +16414,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7ffec116-99d2-402d-adea-68b731be4c74', type: 'UUID', name: 'taskId', @@ -15934,6 +16436,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd76c9b48-5a3e-445f-be15-948de8ba2fc2', type: 'TEXT', name: 'name', @@ -15955,6 +16458,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'fa9d964c-3d30-4d8b-bc57-9b382053e9e3', type: 'RELATION', name: 'opportunity', @@ -16002,6 +16506,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '133dcd8e-aa07-4c9e-a337-92dabd5f7d03', type: 'UUID', name: 'activityId', @@ -16023,6 +16528,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'd10c5485-159b-4700-a709-37d3049a8778', type: 'DATE_TIME', name: 'createdAt', @@ -16044,6 +16550,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '60f8c4fb-1ab9-44a7-a5bd-e89a0349feb7', type: 'RELATION', name: 'rocket', @@ -16091,6 +16598,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'adaa359c-c834-44bb-8639-44ef1affce2f', type: 'UUID', name: 'authorId', @@ -16112,6 +16620,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '3486e0e2-053c-4e27-9d5d-c27b5dd739ea', type: 'UUID', name: 'companyId', @@ -16168,6 +16677,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '827beccf-c3ca-4f24-a349-5d7c8690ac95', type: 'TEXT', name: 'headerMessageId', @@ -16189,6 +16699,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f51b5646-06b0-45e4-960f-5f8eaeb18c83', type: 'DATE_TIME', name: 'deletedAt', @@ -16210,6 +16721,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f32d4ef7-cadd-4ce5-84ce-a95fd76fde05', type: 'DATE_TIME', name: 'updatedAt', @@ -16231,6 +16743,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'c36f257c-2318-4b61-9c63-666e1fc0810c', type: 'DATE_TIME', name: 'receivedAt', @@ -16252,6 +16765,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '86a6dca6-5ad5-4576-b8f4-4be343e573de', type: 'RELATION', name: 'messageChannelMessageAssociations', @@ -16299,6 +16813,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '606cef74-d9c3-4abc-b6ae-bb778f518e49', type: 'RELATION', name: 'messageParticipants', @@ -16346,6 +16861,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '21c34ce8-7505-4d6c-9fc8-218dd8532a25', type: 'UUID', name: 'id', @@ -16367,6 +16883,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '48f20f05-700d-4609-965b-8a954bf07e8d', type: 'UUID', name: 'messageThreadId', @@ -16388,6 +16905,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '11f38fa4-e7b8-4275-b0b2-59688cb2eed8', type: 'RELATION', name: 'messageThread', @@ -16435,6 +16953,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2697ab29-9991-4188-9569-8bc6fc079ec6', type: 'TEXT', name: 'subject', @@ -16456,6 +16975,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f6a75fcf-f8a3-41dd-b1d0-efce8358e2d2', type: 'TEXT', name: 'text', @@ -16477,6 +16997,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '29f11f81-aab0-4b96-971f-24d84c81f1bf', type: 'DATE_TIME', name: 'createdAt', @@ -16533,6 +17054,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '61fe3c78-ab04-4a83-9a40-8560f7285abe', type: 'RELATION', name: 'favorites', @@ -16580,6 +17102,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '027418ee-6028-456f-a570-0b032d35b07f', type: 'RELATION', name: 'viewFields', @@ -16627,6 +17150,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '4385a72b-bb9d-4ac6-9c08-d9853c468726', type: 'UUID', name: 'id', @@ -16648,6 +17172,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '421bf244-013a-4962-970a-37150cf38057', type: 'TEXT', name: 'type', @@ -16669,6 +17194,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '8ee5c1d6-718a-4e43-b468-91563270ae35', type: 'TEXT', name: 'icon', @@ -16690,6 +17216,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '073a7e52-0c68-4853-a500-5470b026c914', type: 'SELECT', name: 'key', @@ -16719,6 +17246,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'ed944e8e-e84d-40ef-aa44-b423453c23f9', type: 'BOOLEAN', name: 'isCompact', @@ -16740,6 +17268,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '5454b125-73c2-438b-991b-eb361bcd6295', type: 'TEXT', name: 'kanbanFieldMetadataId', @@ -16761,6 +17290,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '7974ab3b-7d05-4738-a05b-b76840f98328', type: 'RELATION', name: 'viewFilters', @@ -16808,6 +17338,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '23b99490-be92-4edc-a939-07e0f64f13eb', type: 'RELATION', name: 'viewSorts', @@ -16855,6 +17386,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '36f1f251-121a-461c-aef5-aba2c9fa39a6', type: 'UUID', name: 'objectMetadataId', @@ -16876,6 +17408,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: '2fcf2221-da1f-4c19-acdc-adfb089ea219', type: 'POSITION', name: 'position', @@ -16897,6 +17430,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a0e02b0d-6681-41da-a8a8-48c0bc5ba690', type: 'DATE_TIME', name: 'createdAt', @@ -16918,6 +17452,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'dbc64e7b-2b59-4e9e-9e1e-bcd9e9f5a57a', type: 'DATE_TIME', name: 'deletedAt', @@ -16939,6 +17474,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'f5c9b9fc-87fd-4a74-af09-28a330a53bec', type: 'DATE_TIME', name: 'updatedAt', @@ -16960,6 +17496,7 @@ export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = __typename: 'fieldEdge', node: { __typename: 'field', + settings: {}, id: 'a24df79d-a05a-45e7-8791-b71957dff236', type: 'TEXT', name: 'name', diff --git a/packages/twenty-front/src/testing/mock-data/objectMetadataItems.ts b/packages/twenty-front/src/testing/mock-data/generatedMockObjectMetadataItems.ts similarity index 100% rename from packages/twenty-front/src/testing/mock-data/objectMetadataItems.ts rename to packages/twenty-front/src/testing/mock-data/generatedMockObjectMetadataItems.ts diff --git a/packages/twenty-front/src/testing/mock-data/metadata.ts b/packages/twenty-front/src/testing/mock-data/metadata.ts deleted file mode 100644 index f739afd46647..000000000000 --- a/packages/twenty-front/src/testing/mock-data/metadata.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { mapPaginatedObjectMetadataItemsToObjectMetadataItems } from '@/object-metadata/utils/mapPaginatedObjectMetadataItemsToObjectMetadataItems'; -import { - FieldMetadataType, - ObjectEdge, - ObjectMetadataItemsQuery, -} from '~/generated-metadata/graphql'; -import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/mock-metadata-query-result'; - -// TODO: replace with new mock -const customObjectMetadataItemEdge: ObjectEdge = { - __typename: 'objectEdge', - node: { - __typename: 'object', - id: 'efa1addc-a9cb-4789-b99e-a060fa84f982', - dataSourceId: 'd36e6a2d-28bc-459d-afd5-fe18e4405729', - nameSingular: 'myCustom', - namePlural: 'myCustoms', - labelSingular: 'My Custom', - labelPlural: 'My Customs', - description: 'A custom object example', - icon: 'IconLayoutCollage', - isCustom: true, - isRemote: false, - isActive: true, - isSystem: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - labelIdentifierFieldMetadataId: null, - imageIdentifierFieldMetadataId: null, - fields: { - __typename: 'ObjectFieldsConnection', - pageInfo: { - __typename: 'PageInfo', - hasNextPage: false, - hasPreviousPage: false, - startCursor: 'YXJyYXljb25uZWN0aW9uOjA=', - endCursor: 'YXJyYXljb25uZWN0aW9uOjEz', - }, - edges: [ - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'ea83af89-be10-49af-a605-10c3392ae007', - type: 'RELATION', - name: 'companies', - label: 'Companies', - description: 'A custom Relation example', - icon: 'IconTag', - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: true, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: null, - relationDefinition: { - relationId: '1ec22b36-9e3c-4f24-8cf6-6c387ec3f243', - __typename: 'RelationDefinition', - direction: 'ONE_TO_MANY', - sourceObjectMetadata: { - __typename: 'object', - id: 'efa1addc-a9cb-4789-b99e-a060fa84f982', - nameSingular: 'myCustom', - namePlural: 'myCustoms', - }, - sourceFieldMetadata: { - __typename: 'field', - id: 'ea83af89-be10-49af-a605-10c3392ae007', - name: 'companies', - }, - targetObjectMetadata: { - __typename: 'object', - id: 'dba899da-7d88-41ac-b70e-5ea612ab4b2e', - nameSingular: 'company', - namePlural: 'companies', - }, - targetFieldMetadata: { - __typename: 'field', - id: 'c9607ed7-168d-4743-a56a-689ffcfffe98', - name: 'myCustom', - }, - }, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'c5384d2a-9ec3-4e1b-b93f-86f53f122169', - type: 'UUID', - name: 'objectMetadataId', - label: 'Object Metadata Id', - description: 'View target object', - icon: null, - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: null, - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'bb4d96be-e4d9-47a9-812d-fcdfb063ebf3', - type: 'POSITION', - name: 'position', - label: 'Position', - description: 'View position', - icon: null, - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: true, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: null, - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'f20c68aa-3930-41c4-9f79-45dceda506df', - type: 'TEXT', - name: 'name', - label: 'Name', - description: 'Custom name', - icon: null, - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: "''", - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'a3ef848d-660a-4aef-9cd4-5baf25ce36ed', - type: 'DATE_TIME', - name: 'createdAt', - label: 'Creation date', - description: 'Creation date', - icon: 'IconCalendar', - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: 'now', - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: '92f3e27c-041d-45b2-b2bd-46db2b1aec3f', - type: 'DATE_TIME', - name: 'updatedAt', - label: 'Update date', - description: 'Update date', - icon: 'IconCalendar', - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: 'now', - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: '8d7987eb-99e8-4e54-a86c-86b3bd07d2be', - type: 'UUID', - name: 'id', - label: 'Id', - description: 'Id', - icon: 'Icon123', - isCustom: false, - isActive: true, - isSystem: true, - options: null, - isNullable: false, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: 'uuid', - relationDefinition: null, - }, - }, - { - __typename: 'fieldEdge', - node: { - __typename: 'field', - id: 'e07fcc3f-beec-4d91-8488-9d1d2cfa5f99', - type: FieldMetadataType.Select, - name: 'priority', - label: 'Priority', - description: 'A custom Select example', - icon: 'IconWarning', - isCustom: true, - isActive: true, - isSystem: false, - options: [ - { - id: '2b98dc02-0d99-4f3e-890e-e2e6b8f3196c', - value: 'LOW', - label: 'Low', - color: 'turquoise', - }, - { - id: 'd925a8de-d8ec-4b59-a079-64f4012e3311', - value: 'MEDIUM', - label: 'Medium', - color: 'yellow', - }, - { - id: '6f6e1421-8a42-4d4a-bf76-465b5f84b6d2', - value: 'HIGH', - label: 'High', - color: 'red', - }, - ], - isNullable: true, - createdAt: '2024-04-08T12:48:49.538Z', - updatedAt: '2024-04-08T12:48:49.538Z', - defaultValue: null, - relationDefinition: null, - }, - }, - ], - }, - }, -} as ObjectEdge; - -export const mockedObjectMetadataItemsQueryResult = { - ...mockedStandardObjectMetadataQueryResult, - objects: { - ...mockedStandardObjectMetadataQueryResult.objects, - edges: [ - ...mockedStandardObjectMetadataQueryResult.objects.edges, - customObjectMetadataItemEdge, - ], - }, -} as ObjectMetadataItemsQuery; - -export const mockedObjectMetadataItems = - mapPaginatedObjectMetadataItemsToObjectMetadataItems({ - pagedObjectMetadataItems: mockedObjectMetadataItemsQueryResult, - }); - -export const mockedCompanyObjectMetadataItem = mockedObjectMetadataItems?.find( - (object) => object.nameSingular === 'company', -) as ObjectMetadataItem; - -export const mockedPersonObjectMetadataItem = mockedObjectMetadataItems?.find( - (object) => object.nameSingular === 'person', -) as ObjectMetadataItem; - -export const mockedCustomObjectMetadataItem = mockedObjectMetadataItems?.find( - (object) => object.nameSingular === 'myCustom', -) as ObjectMetadataItem; - -export const mockedOpportunityObjectMetadataItem = - mockedObjectMetadataItems?.find( - (object) => object.nameSingular === 'opportunity', - ) as ObjectMetadataItem; diff --git a/packages/twenty-front/src/testing/mock-data/users.ts b/packages/twenty-front/src/testing/mock-data/users.ts index f7a4c2727e22..cc483bd1e570 100644 --- a/packages/twenty-front/src/testing/mock-data/users.ts +++ b/packages/twenty-front/src/testing/mock-data/users.ts @@ -26,6 +26,7 @@ type MockedUser = Pick< locale: string; defaultWorkspace: Workspace; workspaces: Array<{ workspace: Workspace }>; + workspaceMembers: WorkspaceMember[]; }; export const avatarUrl = @@ -107,6 +108,7 @@ export const mockedUserData: MockedUser = { defaultWorkspace: mockDefaultWorkspace, locale: 'en', workspaces: [{ workspace: mockDefaultWorkspace }], + workspaceMembers: [mockedWorkspaceMemberData], onboardingStatus: OnboardingStatus.Completed, userVars: {}, }; diff --git a/packages/twenty-front/src/testing/mock-data/view-fields.ts b/packages/twenty-front/src/testing/mock-data/view-fields.ts index b68cfb706a27..325e3610ccd7 100644 --- a/packages/twenty-front/src/testing/mock-data/view-fields.ts +++ b/packages/twenty-front/src/testing/mock-data/view-fields.ts @@ -1,240 +1,416 @@ +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { mockedViewsData } from './views'; +const companyObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const personObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); + +const opportunityObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', +); + export const mockedViewFieldsData = [ // Companies { id: '79035310-e955-4986-a4a4-73f9d9949c6a', - fieldMetadataId: '9e123592-cd2b-471c-8143-3cc0b46089ef', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'name', + )?.id, viewId: mockedViewsData[0].id, position: 0, isVisible: true, size: 180, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '2a96bbc8-d86d-439a-8e50-4b07ebd27750', - fieldMetadataId: 'domainName', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'domainName', + )?.id, viewId: mockedViewsData[0].id, position: 1, isVisible: true, size: 100, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '0c1b4c7b-6a3d-4fb0-bf2b-5d7c8fb844ed', - fieldMetadataId: 'accountOwner', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'accountOwner', + )?.id, viewId: mockedViewsData[0].id, position: 2, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'cc7f9560-32b5-4b82-8fd9-b05fe77c8cf7', - fieldMetadataId: 'createdAt', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'createdAt', + )?.id, viewId: mockedViewsData[0].id, position: 3, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '3de4d078-3396-4480-be2d-6f3b1a228b0d', - fieldMetadataId: 'employees', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'employees', + )?.id, viewId: mockedViewsData[0].id, position: 4, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '4650c8fb-0f1e-4342-88dc-adedae1445f9', - fieldMetadataId: 'linkedin', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'linkedinLink', + )?.id, viewId: mockedViewsData[0].id, position: 5, isVisible: true, size: 170, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '727430bf-6ff8-4c85-9828-cbe72ac0fc27', - fieldMetadataId: 'address', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'address', + )?.id, viewId: mockedViewsData[0].id, position: 6, isVisible: true, size: 170, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + + // Companies v2 + { + id: '79035310-e955-4986-a4a4-73f9d9949c6a', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'name', + )?.id, + viewId: mockedViewsData[3].id, + position: 0, + isVisible: true, + size: 180, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: '2a96bbc8-d86d-439a-8e50-4b07ebd27750', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'domainName', + )?.id, + viewId: mockedViewsData[3].id, + position: 1, + isVisible: true, + size: 100, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: '0c1b4c7b-6a3d-4fb0-bf2b-5d7c8fb844ed', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'accountOwner', + )?.id, + viewId: mockedViewsData[3].id, + position: 2, + isVisible: true, + size: 150, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: 'cc7f9560-32b5-4b82-8fd9-b05fe77c8cf7', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'createdAt', + )?.id, + viewId: mockedViewsData[3].id, + position: 3, + isVisible: true, + size: 150, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: '3de4d078-3396-4480-be2d-6f3b1a228b0d', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'employees', + )?.id, + viewId: mockedViewsData[3].id, + position: 4, + isVisible: true, + size: 150, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: '4650c8fb-0f1e-4342-88dc-adedae1445f9', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'linkedinLink', + )?.id, + viewId: mockedViewsData[3].id, + position: 5, + isVisible: true, + size: 170, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, + __typename: 'ViewField', + }, + { + id: '727430bf-6ff8-4c85-9828-cbe72ac0fc27', + fieldMetadataId: companyObjectMetadata?.fields.find( + (field) => field.name === 'address', + )?.id, + viewId: mockedViewsData[3].id, + position: 6, + isVisible: true, + size: 170, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, // People { id: '28894146-4fde-4a16-a9ca-1a31b5b788b4', - fieldMetadataId: 'displayName', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'name', + )?.id, viewId: mockedViewsData[1].id, position: 0, isVisible: true, size: 210, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'e1e24864-8601-4cd8-8a63-09c1285f2e39', - fieldMetadataId: 'email', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'emails', + )?.id, viewId: mockedViewsData[1].id, position: 1, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '5a1df716-7211-445a-9f16-9783a00998a7', - fieldMetadataId: 'company', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'company', + )?.id, viewId: mockedViewsData[1].id, position: 2, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'a6e1197a-7e84-4d92-ace2-367c0bc46c49', - fieldMetadataId: 'phone', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'phones', + )?.id, viewId: mockedViewsData[1].id, position: 3, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'c9343097-d14b-4559-a5fa-626c1527d39f', - fieldMetadataId: 'createdAt', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'createdAt', + )?.id, viewId: mockedViewsData[1].id, position: 4, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'a873e5f0-fed6-47e9-a712-6854eab3ec77', - fieldMetadataId: 'city', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'city', + )?.id, viewId: mockedViewsData[1].id, position: 5, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '66f134b8-5329-422f-b88e-83e6bb707eb5', - fieldMetadataId: 'jobTitle', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'jobTitle', + )?.id, viewId: mockedViewsData[1].id, position: 6, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '648faa24-cabb-482a-8578-ba3f09906017', - fieldMetadataId: 'linkedin', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'linkedinLink', + )?.id, viewId: mockedViewsData[1].id, position: 7, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '3a9e7f0d-a4ce-4ad5-aac7-3a24eb1a412d', - fieldMetadataId: 'x', + fieldMetadataId: personObjectMetadata?.fields.find( + (field) => field.name === 'xLink', + )?.id, viewId: mockedViewsData[1].id, position: 8, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, // Opportunities { id: '35a42e2d-83dd-4b57-ada6-f90616da706d', - fieldMetadataId: 'amount', + fieldMetadataId: opportunityObjectMetadata?.fields.find( + (field) => field.name === 'name', + )?.id, viewId: mockedViewsData[2].id, position: 0, isVisible: true, size: 180, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '3159acd8-463f-458d-bf9a-af8ac6f57dc0', - fieldMetadataId: 'closeDate', + fieldMetadataId: opportunityObjectMetadata?.fields.find( + (field) => field.name === 'closeDate', + )?.id, viewId: mockedViewsData[2].id, position: 2, isVisible: true, size: 100, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'afc0819d-b694-4e3c-a2e6-25261aa3ed2c', - fieldMetadataId: 'company', + fieldMetadataId: opportunityObjectMetadata?.fields.find( + (field) => field.name === 'company', + )?.id, viewId: mockedViewsData[2].id, position: 3, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: 'ec0507bb-aedc-4695-ba96-d81bdeb9db83', - fieldMetadataId: 'createdAt', + fieldMetadataId: opportunityObjectMetadata?.fields.find( + (field) => field.name === 'createdAt', + )?.id, viewId: mockedViewsData[2].id, position: 4, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, { id: '3f1585f6-44f6-45c5-b840-bc05af5d0008', - fieldMetadataId: 'pointOfContact', + fieldMetadataId: opportunityObjectMetadata?.fields.find( + (field) => field.name === 'pointOfContact', + )?.id, viewId: mockedViewsData[2].id, position: 5, isVisible: true, size: 150, createdAt: '2021-09-01T00:00:00.000Z', updatedAt: '2021-09-01T00:00:00.000Z', + deletedAt: null, __typename: 'ViewField', }, ]; diff --git a/packages/twenty-front/src/testing/mock-data/views.ts b/packages/twenty-front/src/testing/mock-data/views.ts index d13c0dc3fdfb..3318e158b4f6 100644 --- a/packages/twenty-front/src/testing/mock-data/views.ts +++ b/packages/twenty-front/src/testing/mock-data/views.ts @@ -1,813 +1,22 @@ -import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection'; -import { ViewType } from '@/views/types/ViewType'; +import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; -export const viewQueryResultMock: RecordGqlConnection = { - __typename: 'ViewConnection', - totalCount: 6, - pageInfo: { - __typename: 'PageInfo', - hasNextPage: false, - hasPreviousPage: false, - startCursor: 'WyIyY2M5MGJjZC0wNzkzLTRkMzctYWZlOS1kZTVlY2NmYmFlNzEiXQ==', - endCursor: 'WyJmZjhlZGQyMi02NjVhLTQ5NWYtODljYy03MGFiOGZkNWMxYTYiXQ==', - }, - edges: [ - { - __typename: 'ViewEdge', - cursor: 'WyIyY2M5MGJjZC0wNzkzLTRkMzctYWZlOS1kZTVlY2NmYmFlNzEiXQ==', - node: { - __typename: 'View', - position: 1, - updatedAt: '2024-07-11T10:21:33.304Z', - key: null, - id: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - objectMetadataId: '9c293c05-f461-456a-b5a2-2710b5b30447', - createdAt: '2024-07-11T10:21:33.304Z', - icon: 'IconLayoutKanban', - isCompact: false, - name: 'By Stage', - type: 'kanban' as ViewType, - kanbanFieldMetadataId: 'f74de381-4392-4662-a890-5ed3b5bd847d', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 2, - id: '05ffd5e0-69b0-4774-843a-fbae12231e7d', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'c4e1b90f-bf9a-4a04-b67a-0f88263d8706', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 4, - id: '573ae123-1eed-4671-8fff-d9ac9455b1b4', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '6e073ac2-034c-43ab-b0c6-206b1dd1174b', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 0, - id: 'ae4f318f-5059-41ba-b365-22daa0b3cb0e', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '4ee7183a-f1f6-42a6-94e5-79f741357760', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 1, - id: 'b5ac37dc-9f64-412f-a598-611bdb5d27f8', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '37593700-f3ac-43a2-9ce2-1b811fa3fbfc', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 5, - id: 'bda12277-2962-4b35-a549-665cbbe53483', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '031bc747-1787-4e46-9320-562a8b75f3ff', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 3, - id: 'f43e660f-bbf8-4a2f-aeb1-54890ac40f4b', - viewId: '2cc90bcd-0793-4d37-afe9-de5eccfbae71', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '2cc0fa2b-dbea-4fd0-b7f5-11fa54cd0242', - }, - }, - ], - }, - }, - }, - { - __typename: 'ViewEdge', - cursor: 'WyI1N2FkYTUyMy0zZDgzLTQzOTEtYThiOS0wZTkxOGUyNGE1MTkiXQ==', - node: { - __typename: 'View', - position: null, - updatedAt: '2024-07-12T09:52:15.595Z', - key: 'INDEX', - id: '57ada523-3d83-4391-a8b9-0e918e24a519', - objectMetadataId: '3561dbe5-39a2-40fa-a111-4af924e39908', - createdAt: '2024-07-12T09:52:15.595Z', - icon: 'IconListNumbers', - isCompact: false, - name: 'All Tests', - type: 'table', - kanbanFieldMetadataId: '', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-16T12:58:56.823Z', - position: 2, - id: '39a026d9-8362-4a4c-9b35-3d23218122a7', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-16T12:58:56.823Z', - isVisible: true, - size: 100, - fieldMetadataId: '9918f304-99d9-4d5b-8351-c6b6f7cc38bb', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-12T09:52:15.598Z', - position: 0, - id: '3ab70930-e60a-4bfd-830a-57355121d889', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-12T09:52:15.598Z', - isVisible: true, - size: 180, - fieldMetadataId: 'f7f485fc-0c14-4b70-a180-0508699a5c14', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-12T09:52:15.604Z', - position: 1, - id: '43ec0b2c-d94f-4eaf-a4bc-f00d409661b5', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-12T09:52:15.604Z', - isVisible: true, - size: 180, - fieldMetadataId: '66645848-4100-4649-bc0e-d50281df2fd6', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-12T09:52:15.604Z', - position: 2, - id: '53f01c19-7042-4551-97d8-d36b6ae28602', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-12T09:52:15.604Z', - isVisible: true, - size: 180, - fieldMetadataId: '1b3caa7a-343a-4b4b-8c2e-3371cd1dd237', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-16T12:59:52.966Z', - position: 3, - id: 'ecbc275e-f937-4d00-b035-6225e6f87c90', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-16T12:59:46.864Z', - isVisible: true, - size: 209, - fieldMetadataId: '9e3e6ed9-7889-4979-bc15-c7803bf437f1', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-12T09:52:15.605Z', - position: 3, - id: 'ed9f32e7-cd08-4bcf-b78f-838371cd282a', - viewId: '57ada523-3d83-4391-a8b9-0e918e24a519', - createdAt: '2024-07-12T09:52:15.605Z', - isVisible: true, - size: 180, - fieldMetadataId: 'ffa953c4-d8e0-49af-b2ef-f16e238f4687', - }, - }, - ], - }, - }, - }, - { - __typename: 'ViewEdge', - cursor: 'WyI1ODJmMjI0Yy0zYzNmLTQxMjctYjFlZC0yOTcxZDI3ZTU0YTQiXQ==', - node: { - __typename: 'View', - position: 0, - updatedAt: '2024-07-11T10:21:33.304Z', - key: 'INDEX', - id: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - objectMetadataId: '9c293c05-f461-456a-b5a2-2710b5b30447', - createdAt: '2024-07-11T10:21:33.304Z', - icon: 'IconTargetArrow', - isCompact: false, - name: 'All Opportunities', - type: 'table', - kanbanFieldMetadataId: '', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 2, - id: '0f0cacad-7f1d-4667-a0e8-466cddad3e65', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'c4e1b90f-bf9a-4a04-b67a-0f88263d8706', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 1, - id: '19d0d674-9825-492d-bbd0-c1de494201dc', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '37593700-f3ac-43a2-9ce2-1b811fa3fbfc', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 5, - id: '77d6a102-7b8e-40c0-9d53-33e9a8d0df0f', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '031bc747-1787-4e46-9320-562a8b75f3ff', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 4, - id: '8d1da76d-4056-4675-b2e5-907021c1b482', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '6e073ac2-034c-43ab-b0c6-206b1dd1174b', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 3, - id: 'a5556adc-e4a0-4f71-aee3-2ff2a4e53b31', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '2cc0fa2b-dbea-4fd0-b7f5-11fa54cd0242', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 0, - id: 'fa8cdb32-24f0-483d-a9f6-bc92f2704452', - viewId: '582f224c-3c3f-4127-b1ed-2971d27e54a4', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '4ee7183a-f1f6-42a6-94e5-79f741357760', - }, - }, - ], - }, - }, - }, - { - __typename: 'ViewEdge', - cursor: 'WyI2NTM3M2UxZS0xNmU1LTRlNWYtOWJjMS1jMDlkOTAxNTZmMjciXQ==', - node: { - __typename: 'View', - position: null, - updatedAt: '2024-07-11T15:41:08.076Z', - key: null, - id: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - objectMetadataId: 'b8115dc1-5304-4d22-b300-0b4efda42ebc', - createdAt: '2024-07-11T15:41:08.076Z', - icon: 'IconBuildingSkyscraper', - isCompact: false, - name: 'All Companies L', - type: 'kanban', - kanbanFieldMetadataId: '4ba829d2-c34a-40d0-9ae6-a65d11d2ff5a', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.141Z', - position: 5, - id: '00bf00d5-f257-4ed0-9a80-ce6d7fa2eace', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.141Z', - isVisible: true, - size: 170, - fieldMetadataId: 'f50611a0-d4b2-49a3-8110-1ca1282ad9c2', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.082Z', - position: 4, - id: '08ccb08d-d279-4738-bdc0-32a0f9b01390', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.082Z', - isVisible: true, - size: 150, - fieldMetadataId: '2334adb8-a0c5-408e-a449-6730f010aff1', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.115Z', - position: 2, - id: '478c93ae-1dcc-4d79-b821-b53431348abe', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.115Z', - isVisible: true, - size: 150, - fieldMetadataId: 'be572271-de80-4d55-ae25-6141ec48e1a7', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.083Z', - position: 7, - id: '4b28e7c9-f97b-4e86-80bf-ca7a1cc49f64', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.083Z', - isVisible: true, - size: 180, - fieldMetadataId: '4ba829d2-c34a-40d0-9ae6-a65d11d2ff5a', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.076Z', - position: 3, - id: '6402a5db-dc6f-433c-9de3-af19a6d71a28', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.076Z', - isVisible: true, - size: 150, - fieldMetadataId: '04f98129-3433-43f6-a5fa-5ede5314fafd', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.141Z', - position: 1, - id: '97938fb7-d3a2-42a1-8c04-7ff59d18e41c', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.141Z', - isVisible: true, - size: 100, - fieldMetadataId: '7b76bf52-04ff-4624-9dd5-26ef59be0d88', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.129Z', - position: 0, - id: 'c7429772-5214-49ee-9d96-c4c9ea929888', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.129Z', - isVisible: true, - size: 180, - fieldMetadataId: '716b202a-7f2f-4d7a-a78a-666db003d94f', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:09.083Z', - position: 6, - id: 'ec211171-6676-40d2-acc2-5fa13f11ed00', - viewId: '65373e1e-16e5-4e5f-9bc1-c09d90156f27', - createdAt: '2024-07-11T15:41:09.083Z', - isVisible: true, - size: 170, - fieldMetadataId: '479e7d9f-cd8a-4064-b009-65cb89a16c36', - }, - }, - ], - }, - }, - }, - { - __typename: 'ViewEdge', - cursor: 'WyJiZWU2NWJjNC05YmNiLTQ5YTgtOGVhNS0xYmQ5MjQxYjA5YzMiXQ==', - node: { - __typename: 'View', - position: 0, - updatedAt: '2024-07-11T10:21:33.304Z', - key: 'INDEX', - id: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - objectMetadataId: '48824ee2-367d-481f-b80b-ca1eeb85c4ab', - createdAt: '2024-07-11T10:21:33.304Z', - icon: 'IconUser', - isCompact: false, - name: 'All People', - type: 'table', - kanbanFieldMetadataId: '', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 3, - id: '2dc48490-3ee8-4ade-a979-d5326da33d43', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '34ed07ad-067a-4f5f-bdee-21a37616f96b', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 0, - id: '67af4225-56e0-4ef9-bcfc-4a551d676c2b', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 210, - fieldMetadataId: 'c485ed46-3f8a-4ee6-af70-628b9f18ad47', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 5, - id: '796bdd63-cc83-4f8c-b538-9f8e9dfb1937', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '4aadffed-1df4-4732-bb99-559f31a464af', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 7, - id: '80cb1229-5e05-45d4-89da-b2ec850ffb2f', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'a8298361-b7c8-4b6c-be6c-d33885e00237', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 8, - id: '835b104c-b9fc-4c9f-b659-3dc4bb54d9ef', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '125cbc00-7efb-473d-b0a6-581d3cf868dd', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 6, - id: '8f494c43-6b63-4033-b303-0110698cf19c', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '22566388-8ece-43dc-8205-371e662716d4', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 4, - id: 'b2d72e77-a323-4e2e-acef-598b6da04712', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'b328512c-ff13-431b-9c94-1018ef0bd53c', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 1, - id: 'c7e1a253-9af8-498a-b579-adab742acf2d', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '2eb50615-376c-45e8-b99b-440a92a912d3', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 2, - id: 'cceae812-2687-49b5-a0c8-eb59956865e8', - viewId: 'bee65bc4-9bcb-49a8-8ea5-1bd9241b09c3', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'f34c04f8-ce2f-4c92-8dbc-9166c6e0d49f', - }, - }, - ], - }, - }, - }, - { - __typename: 'ViewEdge', - cursor: 'WyJmZjhlZGQyMi02NjVhLTQ5NWYtODljYy03MGFiOGZkNWMxYTYiXQ==', - node: { - __typename: 'View', - position: 0, - updatedAt: '2024-07-11T10:21:33.304Z', - key: 'INDEX', - id: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - objectMetadataId: 'b8115dc1-5304-4d22-b300-0b4efda42ebc', - createdAt: '2024-07-11T10:21:33.304Z', - icon: 'IconBuildingSkyscraper', - isCompact: false, - name: 'All Companies', - type: 'table', - kanbanFieldMetadataId: '', - viewSorts: { - __typename: 'ViewSortConnection', - edges: [], - }, - viewFilters: { - __typename: 'ViewFilterConnection', - edges: [], - }, - viewFields: { - __typename: 'ViewFieldConnection', - edges: [ - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 3, - id: '1cb6aeed-8011-495f-9371-20bace45814a', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '04f98129-3433-43f6-a5fa-5ede5314fafd', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 6, - id: '3beb2130-bdc5-48d1-8cd0-22c5d0010ad2', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 170, - fieldMetadataId: '479e7d9f-cd8a-4064-b009-65cb89a16c36', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 4, - id: '5f73729b-9592-473a-8742-8e52b693c780', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: '2334adb8-a0c5-408e-a449-6730f010aff1', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T15:41:03.814Z', - position: 7, - id: '788627cb-0d3a-4659-ab4b-69deabf02f27', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T15:41:03.814Z', - isVisible: true, - size: 180, - fieldMetadataId: '4ba829d2-c34a-40d0-9ae6-a65d11d2ff5a', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 2, - id: 'ac5797a1-2d29-42d2-b9fb-d679a945eec5', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 150, - fieldMetadataId: 'be572271-de80-4d55-ae25-6141ec48e1a7', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 0, - id: 'ae98037e-38f7-4fbf-8ae1-c0b6754c6311', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 180, - fieldMetadataId: '716b202a-7f2f-4d7a-a78a-666db003d94f', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 5, - id: 'b4d9f94e-0c4b-4422-839a-f2ceb293fde1', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 170, - fieldMetadataId: 'f50611a0-d4b2-49a3-8110-1ca1282ad9c2', - }, - }, - { - __typename: 'ViewFieldEdge', - node: { - __typename: 'ViewField', - updatedAt: '2024-07-11T10:21:33.304Z', - position: 1, - id: 'd45b1412-ff6b-41e5-86df-0fb778033bb3', - viewId: 'ff8edd22-665a-495f-89cc-70ab8fd5c1a6', - createdAt: '2024-07-11T10:21:33.304Z', - isVisible: true, - size: 100, - fieldMetadataId: '7b76bf52-04ff-4624-9dd5-26ef59be0d88', - }, - }, - ], - }, - }, - }, - ], -}; +const companyObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'company', +); + +const personObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', +); + +const opportunityObjectMetadata = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'opportunity', +); export const mockedViewsData = [ { id: '37a8a866-eb17-4e76-9382-03143a2f6a80', name: 'All companies', - objectMetadataId: '701aecf9-eb1c-4d84-9d94-b954b231b64b', + objectMetadataId: companyObjectMetadata?.id, type: 'table', icon: 'IconSkyline', key: 'INDEX', @@ -822,7 +31,7 @@ export const mockedViewsData = [ { id: '6095799e-b48f-4e00-b071-10818083593a', name: 'All people', - objectMetadataId: 'person', + objectMetadataId: personObjectMetadata?.id, type: 'table', icon: 'IconPerson', key: 'INDEX', @@ -836,7 +45,7 @@ export const mockedViewsData = [ { id: 'e26f66b7-f890-4a5c-b4d2-ec09987b5308', name: 'All opportunities', - objectMetadataId: 'company', + objectMetadataId: opportunityObjectMetadata?.id, type: 'kanban', icon: 'IconOpportunity', key: 'INDEX', @@ -850,7 +59,7 @@ export const mockedViewsData = [ { id: '5c307222-1dd5-4ff3-ab06-8d990e9b3c74', name: 'All companies (v2)', - objectMetadataId: '701aecf9-eb1c-4d84-9d94-b954b231b64b', + objectMetadataId: companyObjectMetadata?.id, type: 'table', icon: 'IconSkyline', key: 'INDEX', diff --git a/packages/twenty-front/src/utils/__tests__/cast-as-integer-or-null.test.ts b/packages/twenty-front/src/utils/__tests__/cast-as-integer-or-null.test.ts deleted file mode 100644 index cc077afdb27c..000000000000 --- a/packages/twenty-front/src/utils/__tests__/cast-as-integer-or-null.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - canBeCastAsIntegerOrNull, - castAsIntegerOrNull, -} from '../cast-as-integer-or-null'; - -describe('canBeCastAsIntegerOrNull', () => { - it(`should return true if null`, () => { - expect(canBeCastAsIntegerOrNull(null)).toBeTruthy(); - }); - - it(`should return true if number`, () => { - expect(canBeCastAsIntegerOrNull(9)).toBeTruthy(); - }); - - it(`should return true if empty string`, () => { - expect(canBeCastAsIntegerOrNull('')).toBeTruthy(); - }); - - it(`should return true if integer string`, () => { - expect(canBeCastAsIntegerOrNull('9')).toBeTruthy(); - }); - - it(`should return false if undefined`, () => { - expect(canBeCastAsIntegerOrNull(undefined)).toBeFalsy(); - }); - - it(`should return false if non numeric string`, () => { - expect(canBeCastAsIntegerOrNull('9a')).toBeFalsy(); - }); - - it(`should return false if non numeric string #2`, () => { - expect(canBeCastAsIntegerOrNull('a9a')).toBeFalsy(); - }); - - it(`should return false if float`, () => { - expect(canBeCastAsIntegerOrNull(0.9)).toBeFalsy(); - }); - - it(`should return false if float string`, () => { - expect(canBeCastAsIntegerOrNull('0.9')).toBeFalsy(); - }); -}); - -describe('castAsIntegerOrNull', () => { - it(`should cast null to null`, () => { - expect(castAsIntegerOrNull(null)).toBe(null); - }); - - it(`should cast empty string to null`, () => { - expect(castAsIntegerOrNull('')).toBe(null); - }); - - it(`should cast an integer to an integer`, () => { - expect(castAsIntegerOrNull(9)).toBe(9); - }); - - it(`should cast an integer string to an integer`, () => { - expect(castAsIntegerOrNull('9')).toBe(9); - }); - - it(`should throw if trying to cast a float string to an integer`, () => { - expect(() => castAsIntegerOrNull('9.9')).toThrow(Error); - }); - - it(`should throw if trying to cast a non numeric string to an integer`, () => { - expect(() => castAsIntegerOrNull('9.9a')).toThrow(Error); - }); - - it(`should throw if trying to cast an undefined to an integer`, () => { - expect(() => castAsIntegerOrNull(undefined)).toThrow(Error); - }); -}); diff --git a/packages/twenty-front/src/utils/__tests__/cast-as-number-or-null.test.ts b/packages/twenty-front/src/utils/__tests__/cast-as-number-or-null.test.ts new file mode 100644 index 000000000000..082527de7ec7 --- /dev/null +++ b/packages/twenty-front/src/utils/__tests__/cast-as-number-or-null.test.ts @@ -0,0 +1,72 @@ +import { + canBeCastAsNumberOrNull, + castAsNumberOrNull, +} from '../cast-as-number-or-null'; + +describe('canBeCastAsNumberOrNull', () => { + it(`should return true if null`, () => { + expect(canBeCastAsNumberOrNull(null)).toBeTruthy(); + }); + + it(`should return true if number`, () => { + expect(canBeCastAsNumberOrNull(9)).toBeTruthy(); + }); + + it(`should return true if empty string`, () => { + expect(canBeCastAsNumberOrNull('')).toBeTruthy(); + }); + + it(`should return true if integer string`, () => { + expect(canBeCastAsNumberOrNull('9')).toBeTruthy(); + }); + + it(`should return false if undefined`, () => { + expect(canBeCastAsNumberOrNull(undefined)).toBeFalsy(); + }); + + it(`should return false if non numeric string`, () => { + expect(canBeCastAsNumberOrNull('9a')).toBeFalsy(); + }); + + it(`should return false if non numeric string #2`, () => { + expect(canBeCastAsNumberOrNull('a9a')).toBeFalsy(); + }); + + it(`should return true if float`, () => { + expect(canBeCastAsNumberOrNull(0.9)).toBeTruthy(); + }); + + it(`should return true if float string`, () => { + expect(canBeCastAsNumberOrNull('0.9')).toBeTruthy(); + }); +}); + +describe('castAsNumberOrNull', () => { + it(`should cast null to null`, () => { + expect(castAsNumberOrNull(null)).toBe(null); + }); + + it(`should cast empty string to null`, () => { + expect(castAsNumberOrNull('')).toBe(null); + }); + + it(`should cast an integer to an integer`, () => { + expect(castAsNumberOrNull(9)).toBe(9); + }); + + it(`should cast an integer string to an integer`, () => { + expect(castAsNumberOrNull('9')).toBe(9); + }); + + it(`should throw if trying to cast a float string to an integer`, () => { + expect(castAsNumberOrNull('9.9')).toBe(9.9); + }); + + it(`should throw if trying to cast a non numeric string to an integer`, () => { + expect(() => castAsNumberOrNull('9.9a')).toThrow(Error); + }); + + it(`should throw if trying to cast an undefined to an integer`, () => { + expect(() => castAsNumberOrNull(undefined)).toThrow(Error); + }); +}); diff --git a/packages/twenty-front/src/utils/cast-as-integer-or-null.ts b/packages/twenty-front/src/utils/cast-as-number-or-null.ts similarity index 82% rename from packages/twenty-front/src/utils/cast-as-integer-or-null.ts rename to packages/twenty-front/src/utils/cast-as-number-or-null.ts index 5cca0021dead..ef06e5b5a33e 100644 --- a/packages/twenty-front/src/utils/cast-as-integer-or-null.ts +++ b/packages/twenty-front/src/utils/cast-as-number-or-null.ts @@ -4,7 +4,7 @@ import { logError } from './logError'; const DEBUG_MODE = false; -export const canBeCastAsIntegerOrNull = ( +export const canBeCastAsNumberOrNull = ( probableNumberOrNull: string | undefined | number | null, ): probableNumberOrNull is number | null => { if (probableNumberOrNull === undefined) { @@ -16,7 +16,7 @@ export const canBeCastAsIntegerOrNull = ( if (isNumber(probableNumberOrNull)) { if (DEBUG_MODE) logError('typeof probableNumberOrNull === "number"'); - return Number.isInteger(probableNumberOrNull); + return true; } if (isNull(probableNumberOrNull)) { @@ -39,8 +39,8 @@ export const canBeCastAsIntegerOrNull = ( return false; } - if (Number.isInteger(stringAsNumber)) { - if (DEBUG_MODE) logError('Number.isInteger(stringAsNumber)'); + if (isNumber(stringAsNumber)) { + if (DEBUG_MODE) logError('isNumber(stringAsNumber)'); return true; } @@ -49,10 +49,10 @@ export const canBeCastAsIntegerOrNull = ( return false; }; -export const castAsIntegerOrNull = ( +export const castAsNumberOrNull = ( probableNumberOrNull: string | undefined | number | null, ): number | null => { - if (canBeCastAsIntegerOrNull(probableNumberOrNull) === false) { + if (canBeCastAsNumberOrNull(probableNumberOrNull) === false) { throw new Error('Cannot cast to number or null'); } diff --git a/packages/twenty-front/src/utils/format/__tests__/number.test.ts b/packages/twenty-front/src/utils/format/__tests__/number.test.ts index 37237e03dd53..8b2f6687f8f1 100644 --- a/packages/twenty-front/src/utils/format/__tests__/number.test.ts +++ b/packages/twenty-front/src/utils/format/__tests__/number.test.ts @@ -6,12 +6,15 @@ describe('formatNumber', () => { expect(formatNumber(123)).toEqual('123'); }); it(`Should format decimal numbers correctly`, () => { - expect(formatNumber(123.92)).toEqual('123.92'); + expect(formatNumber(123.92, 2)).toEqual('123.92'); }); it(`Should format large numbers correctly`, () => { expect(formatNumber(1234567)).toEqual('1,234,567'); }); it(`Should format large numbers with a decimal point correctly`, () => { - expect(formatNumber(7654321.89)).toEqual('7,654,321.89'); + expect(formatNumber(7654321.89, 2)).toEqual('7,654,321.89'); + }); + it('should format apply decimals correctly', () => { + expect(formatNumber(123.456, 2)).toEqual('123.46'); }); }); diff --git a/packages/twenty-front/src/utils/format/number.ts b/packages/twenty-front/src/utils/format/number.ts index 4937372d0cbd..a36cb6fffad8 100644 --- a/packages/twenty-front/src/utils/format/number.ts +++ b/packages/twenty-front/src/utils/format/number.ts @@ -1,2 +1,8 @@ -export const formatNumber = (value: number): string => - value.toLocaleString('en-US'); +export const DEFAULT_DECIMAL_VALUE = 0; + +export const formatNumber = (value: number, decimals?: number): string => { + return value.toLocaleString('en-US', { + minimumFractionDigits: decimals ?? DEFAULT_DECIMAL_VALUE, + maximumFractionDigits: decimals ?? DEFAULT_DECIMAL_VALUE, + }); +}; diff --git a/packages/twenty-server/jest.config.ts b/packages/twenty-server/jest.config.ts index 00c1b6f06fdb..f25c9e66dcca 100644 --- a/packages/twenty-server/jest.config.ts +++ b/packages/twenty-server/jest.config.ts @@ -7,7 +7,7 @@ const jestConfig: JestConfigWithTsJest = { displayName: 'twenty-server', rootDir: './', testEnvironment: 'node', - transformIgnorePatterns: ['../../node_modules/'], + transformIgnorePatterns: ['/node_modules/'], testRegex: '.*\\.spec\\.ts$', transform: { '^.+\\.(t|j)s$': 'ts-jest', diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index dde92f42a8da..d5c8f91b9c69 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -1,6 +1,6 @@ { "name": "twenty-server", - "version": "0.31.canary", + "version": "0.31.0-canary", "description": "", "author": "", "private": true, diff --git a/packages/twenty-server/project.json b/packages/twenty-server/project.json index a31ff4fb101f..ed8ad716b6e0 100644 --- a/packages/twenty-server/project.json +++ b/packages/twenty-server/project.json @@ -77,6 +77,14 @@ "options": { "cwd": "packages/twenty-server", "command": "node dist/src/queue-worker/queue-worker.js" + }, + "configurations": { + "ci": { + "env": { + "MESSAGE_QUEUE_TYPE": "sync", + "CACHE_STORAGE_TYPE": "memory" + } + } } }, "typeorm": { diff --git a/packages/twenty-server/src/constants/assets-path.ts b/packages/twenty-server/src/constants/assets-path.ts index 44c7724db0b8..04766287433a 100644 --- a/packages/twenty-server/src/constants/assets-path.ts +++ b/packages/twenty-server/src/constants/assets-path.ts @@ -1,3 +1,8 @@ import path from 'path'; -export const ASSET_PATH = path.resolve(__dirname, `../../assets`); +// If the code is built through the testing module, assets are not output to the dist/assets directory. +const IS_BUILT_THROUGH_TESTING_MODULE = !__dirname.includes('/dist/'); + +export const ASSET_PATH = IS_BUILT_THROUGH_TESTING_MODULE + ? path.resolve(__dirname, `../`) + : path.resolve(__dirname, `../../assets`); diff --git a/packages/twenty-server/src/database/typeorm-seeds/metadata/fieldsMetadata.ts b/packages/twenty-server/src/database/typeorm-seeds/metadata/fieldsMetadata.ts index 330975f00d02..cf605a51244e 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/metadata/fieldsMetadata.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/metadata/fieldsMetadata.ts @@ -104,7 +104,7 @@ export const getDevSeedPeopleCustomFields = ( { workspaceId, type: FieldMetadataType.MULTI_SELECT, - name: 'workPrefereance', + name: 'workPreference', label: 'Work Preference', description: "Person's Work Preference", icon: 'IconHome', diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1726848397026-addTypeOrmMetadata.ts b/packages/twenty-server/src/database/typeorm/core/migrations/1726848397026-addTypeOrmMetadata.ts similarity index 100% rename from packages/twenty-server/src/database/typeorm/metadata/migrations/1726848397026-addTypeOrmMetadata.ts rename to packages/twenty-server/src/database/typeorm/core/migrations/1726848397026-addTypeOrmMetadata.ts diff --git a/packages/twenty-server/src/database/typeorm/metadata/migrations/1725893697807-addIndexType.ts b/packages/twenty-server/src/database/typeorm/metadata/migrations/1725893697807-addIndexType.ts index 41edac1a5990..59a1828627ea 100644 --- a/packages/twenty-server/src/database/typeorm/metadata/migrations/1725893697807-addIndexType.ts +++ b/packages/twenty-server/src/database/typeorm/metadata/migrations/1725893697807-addIndexType.ts @@ -5,12 +5,12 @@ export class AddIndexType1725893697807 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( - `CREATE TYPE metadata."indextype_enum" AS ENUM ('BTREE', 'GIN')`, + `CREATE TYPE "metadata"."indexMetadata_indextype_enum" AS ENUM('BTREE', 'GIN')`, ); await queryRunner.query(` ALTER TABLE metadata."indexMetadata" - ADD COLUMN "indexType" metadata."indextype_enum" NOT NULL DEFAULT 'BTREE'; + ADD COLUMN "indexType" metadata."indexMetadata_indextype_enum" NOT NULL DEFAULT 'BTREE'; `); } @@ -19,6 +19,8 @@ export class AddIndexType1725893697807 implements MigrationInterface { ALTER TABLE metadata."indexMetadata" DROP COLUMN "indexType" `); - await queryRunner.query(`DROP TYPE metadata."indextype_enum"`); + await queryRunner.query( + `DROP TYPE metadata."indexMetadata_indextype_enum"`, + ); } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts new file mode 100644 index 000000000000..ca2506d18aa9 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory.ts @@ -0,0 +1,51 @@ +import { Injectable } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { + ResolverArgs, + WorkspaceResolverBuilderMethodNames, +} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; +import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; +import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service'; +import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; +import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; +import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service'; +import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service'; +import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service'; + +@Injectable() +export class GraphqlQueryResolverFactory { + constructor(private moduleRef: ModuleRef) {} + + public getResolver( + operationName: WorkspaceResolverBuilderMethodNames, + ): ResolverService { + switch (operationName) { + case 'findOne': + return this.moduleRef.get(GraphqlQueryFindOneResolverService); + case 'findMany': + return this.moduleRef.get(GraphqlQueryFindManyResolverService); + case 'findDuplicates': + return this.moduleRef.get(GraphqlQueryFindDuplicatesResolverService); + case 'search': + return this.moduleRef.get(GraphqlQuerySearchResolverService); + case 'createOne': + case 'createMany': + return this.moduleRef.get(GraphqlQueryCreateManyResolverService); + case 'destroyOne': + return this.moduleRef.get(GraphqlQueryDestroyOneResolverService); + case 'updateOne': + case 'deleteOne': + return this.moduleRef.get(GraphqlQueryUpdateOneResolverService); + case 'updateMany': + case 'deleteMany': + case 'restoreMany': + return this.moduleRef.get(GraphqlQueryUpdateManyResolverService); + default: + throw new Error(`Unsupported operation: ${operationName}`); + } + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts index 093364db8479..21f9bdbdccb0 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser.ts @@ -25,7 +25,7 @@ export class GraphqlQueryFilterConditionParser { public parse( queryBuilder: SelectQueryBuilder, objectNameSingular: string, - filter: RecordFilter, + filter: Partial, ): SelectQueryBuilder { if (!filter || Object.keys(filter).length === 0) { return queryBuilder; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts index fe37c4d445da..920fa01c56d6 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts @@ -58,7 +58,6 @@ export class GraphqlQueryFilterFieldParser { } const { sql, params } = this.computeWhereConditionParts( - fieldMetadata, operator, objectNameSingular, key, @@ -73,7 +72,6 @@ export class GraphqlQueryFilterFieldParser { } private computeWhereConditionParts( - fieldMetadata: FieldMetadataInterface, operator: string, objectNameSingular: string, key: string, @@ -185,7 +183,6 @@ export class GraphqlQueryFilterFieldParser { ); const { sql, params } = this.computeWhereConditionParts( - fieldMetadata, operator, objectNameSingular, fullFieldName, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts index d83667211bd0..0aa047fc31f7 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser.ts @@ -9,7 +9,6 @@ import { RecordFilter, RecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { GraphqlQueryFilterConditionParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser'; import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser'; @@ -17,6 +16,7 @@ import { GraphqlQuerySelectedFieldsParser } from 'src/engine/api/graphql/graphql import { FieldMetadataMap, ObjectMetadataMap, + ObjectMetadataMapItem, } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; export class GraphqlQueryParser { @@ -39,10 +39,10 @@ export class GraphqlQueryParser { ); } - applyFilterToBuilder( + public applyFilterToBuilder( queryBuilder: SelectQueryBuilder, objectNameSingular: string, - recordFilter: RecordFilter, + recordFilter: Partial, ): SelectQueryBuilder { return this.filterConditionParser.parse( queryBuilder, @@ -51,7 +51,7 @@ export class GraphqlQueryParser { ); } - applyDeletedAtToBuilder( + public applyDeletedAtToBuilder( queryBuilder: SelectQueryBuilder, recordFilter: RecordFilter, ): SelectQueryBuilder { @@ -88,7 +88,7 @@ export class GraphqlQueryParser { return false; }; - applyOrderToBuilder( + public applyOrderToBuilder( queryBuilder: SelectQueryBuilder, orderBy: RecordOrderBy, objectNameSingular: string, @@ -103,8 +103,8 @@ export class GraphqlQueryParser { return queryBuilder.orderBy(parsedOrderBys as OrderByCondition); } - parseSelectedFields( - parentObjectMetadata: ObjectMetadataInterface, + public parseSelectedFields( + parentObjectMetadata: ObjectMetadataMapItem, graphqlSelectedFields: Partial>, ): { select: Record; relations: Record } { const parentFields = diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts index 28cb362ca3f0..8c689ff8a979 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.module.ts @@ -1,16 +1,43 @@ import { Module } from '@nestjs/common'; +import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory'; import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; +import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; +import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; +import { GraphqlQueryFindDuplicatesResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service'; +import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; +import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; +import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service'; +import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service'; +import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service'; +import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module'; import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; + +const graphqlQueryResolvers = [ + GraphqlQueryFindOneResolverService, + GraphqlQueryFindManyResolverService, + GraphqlQueryFindDuplicatesResolverService, + GraphqlQueryCreateManyResolverService, + GraphqlQueryDestroyOneResolverService, + GraphqlQueryUpdateOneResolverService, + GraphqlQueryUpdateManyResolverService, + GraphqlQuerySearchResolverService, +]; + @Module({ imports: [ WorkspaceQueryHookModule, WorkspaceQueryRunnerModule, FeatureFlagModule, ], - providers: [GraphqlQueryRunnerService], + providers: [ + GraphqlQueryRunnerService, + GraphqlQueryResolverFactory, + ApiEventEmitterService, + ...graphqlQueryResolvers, + ], exports: [GraphqlQueryRunnerService], }) export class GraphqlQueryRunnerModule {} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts index 8c866695b6ad..4e3475927ecd 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service.ts @@ -6,285 +6,377 @@ import { RecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; +import { IEdge } from 'src/engine/api/graphql/workspace-query-runner/interfaces/edge.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { CreateManyResolverArgs, CreateOneResolverArgs, + DeleteManyResolverArgs, + DeleteOneResolverArgs, DestroyOneResolverArgs, + FindDuplicatesResolverArgs, FindManyResolverArgs, FindOneResolverArgs, + ResolverArgs, ResolverArgsType, + RestoreManyResolverArgs, SearchResolverArgs, + UpdateManyResolverArgs, + UpdateOneResolverArgs, + WorkspaceResolverBuilderMethodNames, } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; -import { GraphqlQueryCreateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service'; -import { GraphqlQueryDestroyOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service'; -import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service'; -import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; -import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service'; +import { GraphqlQueryResolverFactory } from 'src/engine/api/graphql/graphql-query-runner/factories/graphql-query-resolver.factory'; +import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory'; import { CallWebhookJobsJob, CallWebhookJobsJobData, CallWebhookJobsJobOperation, } from 'src/engine/api/graphql/workspace-query-runner/jobs/call-webhook-jobs.job'; -import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; -import { - WorkspaceQueryRunnerException, - WorkspaceQueryRunnerExceptionCode, -} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; -import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; -import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event'; -import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { LogExecutionTime } from 'src/engine/decorators/observability/log-execution-time.decorator'; -import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; +import { capitalize } from 'src/utils/capitalize'; @Injectable() export class GraphqlQueryRunnerService { constructor( - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly featureFlagService: FeatureFlagService, private readonly workspaceQueryHookService: WorkspaceQueryHookService, private readonly queryRunnerArgsFactory: QueryRunnerArgsFactory, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, @InjectMessageQueue(MessageQueue.webhookQueue) private readonly messageQueueService: MessageQueueService, + private readonly graphqlQueryResolverFactory: GraphqlQueryResolverFactory, + private readonly apiEventEmitterService: ApiEventEmitterService, ) {} + /** QUERIES */ + @LogExecutionTime() - async findOne< - ObjectRecord extends IRecord = IRecord, - Filter extends RecordFilter = RecordFilter, - >( + async findOne( args: FindOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const graphqlQueryFindOneResolverService = - new GraphqlQueryFindOneResolverService(this.twentyORMGlobalManager); - - const { authContext, objectMetadataItem } = options; - - if (!args.filter || Object.keys(args.filter).length === 0) { - throw new WorkspaceQueryRunnerException( - 'Missing filter argument', - WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT, - ); - } - - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'findOne', - args, - ); - - const computedArgs = (await this.queryRunnerArgsFactory.create( - hookedArgs, + ): Promise { + return this.executeQuery, ObjectRecord>( + 'findOne', + args, options, - ResolverArgsType.FindOne, - )) as FindOneResolverArgs; - - return graphqlQueryFindOneResolverService.findOne(computedArgs, options); + ); } @LogExecutionTime() async findMany< - ObjectRecord extends IRecord = IRecord, - Filter extends RecordFilter = RecordFilter, - OrderBy extends RecordOrderBy = RecordOrderBy, + ObjectRecord extends IRecord, + Filter extends RecordFilter, + OrderBy extends RecordOrderBy, >( args: FindManyResolverArgs, options: WorkspaceQueryRunnerOptions, + ): Promise>> { + return this.executeQuery< + FindManyResolverArgs, + IConnection> + >('findMany', args, options); + } + + @LogExecutionTime() + async findDuplicates( + args: FindDuplicatesResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise[]> { + return this.executeQuery< + FindDuplicatesResolverArgs>, + IConnection[] + >('findDuplicates', args, options); + } + + @LogExecutionTime() + async search( + args: SearchResolverArgs, + options: WorkspaceQueryRunnerOptions, ): Promise> { - const graphqlQueryFindManyResolverService = - new GraphqlQueryFindManyResolverService(this.twentyORMGlobalManager); + return this.executeQuery>( + 'search', + args, + options, + ); + } - const { authContext, objectMetadataItem } = options; + /** MUTATIONS */ - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'findMany', - args, + @LogExecutionTime() + async createOne( + args: CreateOneResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const results = await this.executeQuery< + CreateManyResolverArgs>, + ObjectRecord[] + >('createMany', { data: [args.data], upsert: args.upsert }, options); + + // TODO: emitCreateEvents should be moved to the ORM layer + if (results) { + this.apiEventEmitterService.emitCreateEvents( + results, + options.authContext, + options.objectMetadataItem, ); + } - const computedArgs = (await this.queryRunnerArgsFactory.create( - hookedArgs, - options, - ResolverArgsType.FindMany, - )) as FindManyResolverArgs; - - return graphqlQueryFindManyResolverService.findMany(computedArgs, options); + return results[0]; } @LogExecutionTime() - async createOne( - args: CreateOneResolverArgs>, + async createMany( + args: CreateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { - const graphqlQueryCreateManyResolverService = - new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager); + ): Promise { + const results = await this.executeQuery< + CreateManyResolverArgs>, + ObjectRecord[] + >('createMany', args, options); + + if (results) { + this.apiEventEmitterService.emitCreateEvents( + results, + options.authContext, + options.objectMetadataItem, + ); + } - const { authContext, objectMetadataItem } = options; + return results; + } - assertMutationNotOnRemoteObject(objectMetadataItem); + @LogExecutionTime() + public async updateOne( + args: UpdateOneResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const existingRecord = await this.executeQuery< + FindOneResolverArgs, + ObjectRecord + >( + 'findOne', + { + filter: { id: { eq: args.id } }, + }, + options, + ); - if (args.data.id) { - assertIsValidUuid(args.data.id); - } + const result = await this.executeQuery< + UpdateOneResolverArgs>, + ObjectRecord + >('updateOne', args, options); - const createManyArgs = { - data: [args.data], - upsert: args.upsert, - } as CreateManyResolverArgs; + this.apiEventEmitterService.emitUpdateEvents( + [existingRecord], + [result], + Object.keys(args.data), + options.authContext, + options.objectMetadataItem, + ); - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'createMany', - createManyArgs, - ); + return result; + } - const computedArgs = (await this.queryRunnerArgsFactory.create( - hookedArgs, + @LogExecutionTime() + public async updateMany( + args: UpdateManyResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const existingRecords = await this.executeQuery< + FindManyResolverArgs, + IConnection> + >( + 'findMany', + { + filter: args.filter, + }, options, - ResolverArgsType.CreateMany, - )) as CreateManyResolverArgs; + ); + + const result = await this.executeQuery< + UpdateManyResolverArgs>, + ObjectRecord[] + >('updateMany', args, options); + + this.apiEventEmitterService.emitUpdateEvents( + existingRecords.edges.map((edge) => edge.node), + result, + Object.keys(args.data), + options.authContext, + options.objectMetadataItem, + ); - const results = (await graphqlQueryCreateManyResolverService.createMany( - computedArgs, + return result; + } + + @LogExecutionTime() + public async deleteOne( + args: DeleteOneResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const result = await this.executeQuery< + UpdateOneResolverArgs>, + ObjectRecord + >( + 'deleteOne', + { + id: args.id, + data: { deletedAt: new Date() } as Partial, + }, options, - )) as ObjectRecord[]; + ); + + this.apiEventEmitterService.emitDeletedEvents( + [result], + options.authContext, + options.objectMetadataItem, + ); - await this.triggerWebhooks( - results, - CallWebhookJobsJobOperation.create, + return result; + } + + @LogExecutionTime() + public async deleteMany( + args: DeleteManyResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const result = await this.executeQuery< + UpdateManyResolverArgs>, + ObjectRecord[] + >( + 'deleteMany', + { + filter: args.filter, + + data: { deletedAt: new Date() } as Partial, + }, options, ); - this.emitCreateEvents( - results, - authContext, - objectMetadataItem, + this.apiEventEmitterService.emitDeletedEvents( + result, + options.authContext, + options.objectMetadataItem, ); - return results?.[0] as ObjectRecord; + return result; } @LogExecutionTime() - async search( - args: SearchResolverArgs, + async destroyOne( + args: DestroyOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise> { - const graphqlQuerySearchResolverService = - new GraphqlQuerySearchResolverService( - this.twentyORMGlobalManager, - this.featureFlagService, - ); + ): Promise { + const result = await this.executeQuery< + DestroyOneResolverArgs, + ObjectRecord + >('destroyOne', args, options); + + this.apiEventEmitterService.emitDestroyEvents( + [result], + options.authContext, + options.objectMetadataItem, + ); - return graphqlQuerySearchResolverService.search(args, options); + return result; } @LogExecutionTime() - async createMany( - args: CreateManyResolverArgs>, + public async restoreMany( + args: RestoreManyResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const graphqlQueryCreateManyResolverService = - new GraphqlQueryCreateManyResolverService(this.twentyORMGlobalManager); + ): Promise { + const result = await this.executeQuery< + UpdateManyResolverArgs>, + ObjectRecord + >( + 'restoreMany', + { + filter: args.filter, + data: { deletedAt: null } as Partial, + }, + options, + ); + + return result; + } + private async executeQuery( + operationName: WorkspaceResolverBuilderMethodNames, + args: Input, + options: WorkspaceQueryRunnerOptions, + ): Promise { const { authContext, objectMetadataItem } = options; - assertMutationNotOnRemoteObject(objectMetadataItem); + const resolver = + this.graphqlQueryResolverFactory.getResolver(operationName); - args.data.forEach((record) => { - if (record?.id) { - assertIsValidUuid(record.id); - } - }); + await resolver.validate(args, options); const hookedArgs = await this.workspaceQueryHookService.executePreQueryHooks( authContext, objectMetadataItem.nameSingular, - 'createMany', + operationName, args, ); - const computedArgs = (await this.queryRunnerArgsFactory.create( + const computedArgs = await this.queryRunnerArgsFactory.create( hookedArgs, options, - ResolverArgsType.CreateMany, - )) as CreateManyResolverArgs; + ResolverArgsType[capitalize(operationName)], + ); - const results = (await graphqlQueryCreateManyResolverService.createMany( - computedArgs, - options, - )) as ObjectRecord[]; + const results = await resolver.resolve(computedArgs as Input, options); await this.workspaceQueryHookService.executePostQueryHooks( authContext, objectMetadataItem.nameSingular, - 'createMany', - results, + operationName, + Array.isArray(results) ? results : [results], ); - await this.triggerWebhooks( - results, - CallWebhookJobsJobOperation.create, - options, - ); + const jobOperation = this.operationNameToJobOperation(operationName); - this.emitCreateEvents( - results, - authContext, - objectMetadataItem, - ); + if (jobOperation) { + await this.triggerWebhooks(results, jobOperation, options); + } return results; } - private emitCreateEvents( - records: BaseRecord[], - authContext: AuthContext, - objectMetadataItem: ObjectMetadataInterface, - ) { - this.workspaceEventEmitter.emit( - `${objectMetadataItem.nameSingular}.created`, - records.map( - (record) => - ({ - userId: authContext.user?.id, - recordId: record.id, - objectMetadata: objectMetadataItem, - properties: { - after: record, - }, - }) satisfies ObjectRecordCreateEvent, - ), - authContext.workspace.id, - ); + private operationNameToJobOperation( + operationName: WorkspaceResolverBuilderMethodNames, + ): CallWebhookJobsJobOperation | undefined { + switch (operationName) { + case 'createOne': + case 'createMany': + return CallWebhookJobsJobOperation.create; + case 'updateOne': + case 'updateMany': + case 'restoreMany': + return CallWebhookJobsJobOperation.update; + case 'deleteOne': + case 'deleteMany': + return CallWebhookJobsJobOperation.delete; + case 'destroyOne': + return CallWebhookJobsJobOperation.destroy; + default: + return undefined; + } } - private async triggerWebhooks( - jobsData: Record[] | undefined, + private async triggerWebhooks( + jobsData: T[] | undefined, operation: CallWebhookJobsJobOperation, options: WorkspaceQueryRunnerOptions, - ) { - if (!Array.isArray(jobsData)) { - return; - } + ): Promise { + if (!jobsData || !Array.isArray(jobsData)) return; + jobsData.forEach((jobData) => { this.messageQueueService.add( CallWebhookJobsJob.name, @@ -298,99 +390,4 @@ export class GraphqlQueryRunnerService { ); }); } - - @LogExecutionTime() - async destroyOne( - args: DestroyOneResolverArgs, - options: WorkspaceQueryRunnerOptions, - ): Promise { - const graphqlQueryDestroyOneResolverService = - new GraphqlQueryDestroyOneResolverService(this.twentyORMGlobalManager); - - const { authContext, objectMetadataItem } = options; - - assertMutationNotOnRemoteObject(objectMetadataItem); - assertIsValidUuid(args.id); - - const hookedArgs = - await this.workspaceQueryHookService.executePreQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'destroyOne', - args, - ); - - const computedArgs = (await this.queryRunnerArgsFactory.create( - hookedArgs, - options, - ResolverArgsType.DestroyOne, - )) as DestroyOneResolverArgs; - - const result = (await graphqlQueryDestroyOneResolverService.destroyOne( - computedArgs, - options, - )) as ObjectRecord; - - await this.workspaceQueryHookService.executePostQueryHooks( - authContext, - objectMetadataItem.nameSingular, - 'destroyOne', - [result], - ); - - await this.triggerWebhooks( - [result], - CallWebhookJobsJobOperation.destroy, - options, - ); - - this.emitDestroyEvents([result], authContext, objectMetadataItem); - - return result; - } - - private emitDestroyEvents( - records: BaseRecord[], - authContext: AuthContext, - objectMetadataItem: ObjectMetadataInterface, - ) { - this.workspaceEventEmitter.emit( - `${objectMetadataItem.nameSingular}.destroyed`, - records.map((record) => { - return { - userId: authContext.user?.id, - recordId: record.id, - objectMetadata: objectMetadataItem, - properties: { - before: this.removeNestedProperties(record), - }, - } satisfies ObjectRecordDeleteEvent; - }), - authContext.workspace.id, - ); - } - - private removeNestedProperties( - record: Record, - ) { - if (!record) { - return; - } - - const sanitizedRecord = {}; - - for (const [key, value] of Object.entries(record)) { - if (value && typeof value === 'object' && value['edges']) { - continue; - } - - if (key === '__typename') { - continue; - } - - sanitizedRecord[key] = value; - } - - return sanitizedRecord; - } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts similarity index 99% rename from packages/twenty-server/src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper.ts rename to packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts index b9a81ef245dc..5ccff3af114d 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper.ts @@ -20,7 +20,7 @@ import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspac import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; import { isPlainObject } from 'src/utils/is-plain-object'; -export class ObjectRecordsToGraphqlConnectionMapper { +export class ObjectRecordsToGraphqlConnectionHelper { private objectMetadataMap: ObjectMetadataMap; constructor(objectMetadataMap: ObjectMetadataMap) { diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts index f19c7cf06eb3..dd3e5abd4020 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts @@ -4,6 +4,7 @@ import { FindOptionsRelations, In, ObjectLiteral, + Repository, } from 'typeorm'; import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; @@ -16,17 +17,38 @@ import { ObjectMetadataMap, ObjectMetadataMapItem, } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util'; export class ProcessNestedRelationsHelper { - private readonly twentyORMGlobalManager: TwentyORMGlobalManager; + constructor() {} - constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { - this.twentyORMGlobalManager = twentyORMGlobalManager; + public async processNestedRelations( + objectMetadataMap: ObjectMetadataMap, + parentObjectMetadataItem: ObjectMetadataMapItem, + parentObjectRecords: ObjectRecord[], + relations: Record>, + limit: number, + authContext: any, + dataSource: DataSource, + ): Promise { + const processRelationTasks = Object.entries(relations).map( + ([relationName, nestedRelations]) => + this.processRelation( + objectMetadataMap, + parentObjectMetadataItem, + parentObjectRecords, + relationName, + nestedRelations, + limit, + authContext, + dataSource, + ), + ); + + await Promise.all(processRelationTasks); } - private async processFromRelation( + private async processRelation( objectMetadataMap: ObjectMetadataMap, parentObjectMetadataItem: ObjectMetadataMapItem, parentObjectRecords: ObjectRecord[], @@ -35,49 +57,71 @@ export class ProcessNestedRelationsHelper { limit: number, authContext: any, dataSource: DataSource, - ) { + ): Promise { const relationFieldMetadata = parentObjectMetadataItem.fields[relationName]; const relationMetadata = getRelationMetadata(relationFieldMetadata); - - const inverseRelationName = - objectMetadataMap[relationMetadata.toObjectMetadataId]?.fields[ - relationMetadata.toFieldMetadataId - ]?.name; - - const referenceObjectMetadata = getRelationObjectMetadata( + const relationDirection = deduceRelationDirection( relationFieldMetadata, - objectMetadataMap, + relationMetadata, ); - const referenceObjectMetadataName = referenceObjectMetadata.nameSingular; + const processor = + relationDirection === 'to' + ? this.processToRelation + : this.processFromRelation; - const relationRepository = await dataSource.getRepository( - referenceObjectMetadataName, + await processor.call( + this, + objectMetadataMap, + parentObjectMetadataItem, + parentObjectRecords, + relationName, + nestedRelations, + limit, + authContext, + dataSource, ); + } - const relationIds = parentObjectRecords.map((item) => item.id); - - const uniqueRelationIds = [...new Set(relationIds)]; - - const relationFindOptions: FindManyOptions = { - where: { - [`${inverseRelationName}Id`]: In(uniqueRelationIds), - }, - take: limit * parentObjectRecords.length, - }; + private async processFromRelation( + objectMetadataMap: ObjectMetadataMap, + parentObjectMetadataItem: ObjectMetadataMapItem, + parentObjectRecords: ObjectRecord[], + relationName: string, + nestedRelations: any, + limit: number, + authContext: any, + dataSource: DataSource, + ): Promise { + const { inverseRelationName, referenceObjectMetadata } = + this.getRelationMetadata( + objectMetadataMap, + parentObjectMetadataItem, + relationName, + ); + const relationRepository = dataSource.getRepository( + referenceObjectMetadata.nameSingular, + ); - const relationResults = await relationRepository.find(relationFindOptions); + const relationIds = this.getUniqueIds(parentObjectRecords, 'id'); + const relationResults = await this.findRelations( + relationRepository, + inverseRelationName, + relationIds, + limit * parentObjectRecords.length, + ); - parentObjectRecords.forEach((item) => { - (item as any)[relationName] = relationResults.filter( - (rel) => rel[`${inverseRelationName}Id`] === item.id, - ); - }); + this.assignRelationResults( + parentObjectRecords, + relationResults, + relationName, + `${inverseRelationName}Id`, + ); if (Object.keys(nestedRelations).length > 0) { await this.processNestedRelations( objectMetadataMap, - objectMetadataMap[referenceObjectMetadataName], + objectMetadataMap[referenceObjectMetadata.nameSingular], relationResults as ObjectRecord[], nestedRelations as Record>, limit, @@ -96,48 +140,37 @@ export class ProcessNestedRelationsHelper { limit: number, authContext: any, dataSource: DataSource, - ) { - const relationFieldMetadata = parentObjectMetadataItem.fields[relationName]; - - const referenceObjectMetadata = getRelationObjectMetadata( - relationFieldMetadata, + ): Promise { + const { referenceObjectMetadata } = this.getRelationMetadata( objectMetadataMap, + parentObjectMetadataItem, + relationName, ); - - const referenceObjectMetadataName = referenceObjectMetadata.nameSingular; - const relationRepository = dataSource.getRepository( - referenceObjectMetadataName, + referenceObjectMetadata.nameSingular, ); - const relationIds = parentObjectRecords.map( - (item) => item[`${relationName}Id`], + const relationIds = this.getUniqueIds( + parentObjectRecords, + `${relationName}Id`, + ); + const relationResults = await this.findRelations( + relationRepository, + 'id', + relationIds, + limit, ); - const uniqueRelationIds = [...new Set(relationIds)]; - - const relationFindOptions: FindManyOptions = { - where: { - id: In(uniqueRelationIds), - }, - take: limit, - }; - - const relationResults = await relationRepository.find(relationFindOptions); - - parentObjectRecords.forEach((item) => { - if (relationResults.length === 0) { - (item as any)[`${relationName}Id`] = null; - } - (item as any)[relationName] = relationResults.filter( - (rel) => rel.id === item[`${relationName}Id`], - )[0]; - }); + this.assignToRelationResults( + parentObjectRecords, + relationResults, + relationName, + ); if (Object.keys(nestedRelations).length > 0) { await this.processNestedRelations( objectMetadataMap, - objectMetadataMap[referenceObjectMetadataName], + objectMetadataMap[referenceObjectMetadata.nameSingular], relationResults as ObjectRecord[], nestedRelations as Record>, limit, @@ -147,48 +180,71 @@ export class ProcessNestedRelationsHelper { } } - public async processNestedRelations( + private getRelationMetadata( objectMetadataMap: ObjectMetadataMap, parentObjectMetadataItem: ObjectMetadataMapItem, - parentObjectRecords: ObjectRecord[], - relations: Record>, - limit: number, - authContext: any, - dataSource: DataSource, + relationName: string, ) { - for (const [relationName, nestedRelations] of Object.entries(relations)) { - const relationFieldMetadata = - parentObjectMetadataItem.fields[relationName]; - const relationMetadata = getRelationMetadata(relationFieldMetadata); - - const relationDirection = deduceRelationDirection( - relationFieldMetadata, - relationMetadata, + const relationFieldMetadata = parentObjectMetadataItem.fields[relationName]; + const relationMetadata = getRelationMetadata(relationFieldMetadata); + const referenceObjectMetadata = getRelationObjectMetadata( + relationFieldMetadata, + objectMetadataMap, + ); + const inverseRelationName = + objectMetadataMap[relationMetadata.toObjectMetadataId]?.fields[ + relationMetadata.toFieldMetadataId + ]?.name; + + return { inverseRelationName, referenceObjectMetadata }; + } + + private getUniqueIds(records: IRecord[], idField: string): any[] { + return [...new Set(records.map((item) => item[idField]))]; + } + + private async findRelations( + repository: Repository, + field: string, + ids: any[], + limit: number, + ): Promise { + if (ids.length === 0) { + return []; + } + const findOptions: FindManyOptions = { + where: { [field]: In(ids) }, + take: limit, + }; + + return repository.find(findOptions); + } + + private assignRelationResults( + parentRecords: IRecord[], + relationResults: any[], + relationName: string, + joinField: string, + ): void { + parentRecords.forEach((item) => { + (item as any)[relationName] = relationResults.filter( + (rel) => rel[joinField] === item.id, ); + }); + } - if (relationDirection === 'to') { - await this.processToRelation( - objectMetadataMap, - parentObjectMetadataItem, - parentObjectRecords, - relationName, - nestedRelations, - limit, - authContext, - dataSource, - ); - } else { - await this.processFromRelation( - objectMetadataMap, - parentObjectMetadataItem, - parentObjectRecords, - relationName, - nestedRelations, - limit, - authContext, - dataSource, - ); + private assignToRelationResults( + parentRecords: IRecord[], + relationResults: any[], + relationName: string, + ): void { + parentRecords.forEach((item) => { + if (relationResults.length === 0) { + (item as any)[`${relationName}Id`] = null; } - } + (item as any)[relationName] = + relationResults.find((rel) => rel.id === item[`${relationName}Id`]) ?? + null; + }); } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface.ts new file mode 100644 index 000000000000..f88691647425 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface.ts @@ -0,0 +1,12 @@ +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; + +export interface ResolverService { + resolve: ( + args: ResolverArgs, + options: WorkspaceQueryRunnerOptions, + ) => Promise; + validate: ( + args: ResolverArgs, + options: WorkspaceQueryRunnerOptions, + ) => Promise; +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts index f66497a6812a..aa8a81ce85a6 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts @@ -1,51 +1,53 @@ +import { Injectable } from '@nestjs/common'; + import graphqlFields from 'graphql-fields'; import { In, InsertResult } from 'typeorm'; +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; -import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; -import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util'; -import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; +import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; +import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -export class GraphqlQueryCreateManyResolverService { - private twentyORMGlobalManager: TwentyORMGlobalManager; - - constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { - this.twentyORMGlobalManager = twentyORMGlobalManager; - } +@Injectable() +export class GraphqlQueryCreateManyResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async createMany( + async resolve( args: CreateManyResolverArgs>, options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, objectMetadataItem, objectMetadataCollection, info } = + ): Promise { + const { authContext, info, objectMetadataMap, objectMetadataMapItem } = options; - const repository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( authContext.workspace.id, - objectMetadataItem.nameSingular, ); - - const objectMetadataMap = generateObjectMetadataMap( - objectMetadataCollection, - ); - const objectMetadata = getObjectMetadataOrThrow( - objectMetadataMap, - objectMetadataItem.nameSingular, + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, ); + const graphqlQueryParser = new GraphqlQueryParser( - objectMetadata.fields, + objectMetadataMapItem.fields, objectMetadataMap, ); const selectedFields = graphqlFields(info); - const { select, relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItem, + const { relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataMapItem, selectedFields, ); @@ -56,24 +58,59 @@ export class GraphqlQueryCreateManyResolverService { skipUpdateIfNoValuesChanged: true, }); - const upsertedRecords = await repository.find({ - where: { + const queryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const nonFormattedUpsertedRecords = (await queryBuilder + .where({ id: In(objectRecords.generatedMaps.map((record) => record.id)), - }, - select, - relations, - }); + }) + .take(QUERY_MAX_RECORDS) + .getMany()) as ObjectRecord[]; + + const upsertedRecords = formatResult( + nonFormattedUpsertedRecords, + objectMetadataMapItem, + objectMetadataMap, + ); + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (relations) { + await processNestedRelationsHelper.processNestedRelations( + objectMetadataMap, + objectMetadataMapItem, + upsertedRecords, + relations, + QUERY_MAX_RECORDS, + authContext, + dataSource, + ); + } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); return upsertedRecords.map((record: ObjectRecord) => typeORMObjectRecordsParser.processRecord( record, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, 1, 1, ), ); } + + async validate( + args: CreateManyResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataItem); + args.data.forEach((record) => { + if (record?.id) { + assertIsValidUuid(record.id); + } + }); + } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts index 53dad3eddd0b..3540dcbf9559 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts @@ -1,33 +1,68 @@ +import { Injectable } from '@nestjs/common'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { DestroyOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -export class GraphqlQueryDestroyOneResolverService { - private twentyORMGlobalManager: TwentyORMGlobalManager; - - constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { - this.twentyORMGlobalManager = twentyORMGlobalManager; - } +@Injectable() +export class GraphqlQueryDestroyOneResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async destroyOne( + async resolve( args: DestroyOneResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise { - const { authContext, objectMetadataItem } = options; + const { authContext, objectMetadataMapItem, objectMetadataMap } = options; const repository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( authContext.workspace.id, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, ); - const record = await repository.findOne({ + const nonFormattedRecordBeforeDeletion = await repository.findOne({ where: { id: args.id }, + withDeleted: true, }); + if (!nonFormattedRecordBeforeDeletion) { + throw new GraphqlQueryRunnerException( + 'Record not found', + GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, + ); + } + + const recordBeforeDeletion = formatResult( + [nonFormattedRecordBeforeDeletion], + objectMetadataMapItem, + objectMetadataMap, + )[0]; + await repository.delete(args.id); - return record as ObjectRecord; + return recordBeforeDeletion as ObjectRecord; + } + + async validate( + args: DestroyOneResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise { + if (!args.id) { + throw new GraphqlQueryRunnerException( + 'Missing id', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts new file mode 100644 index 000000000000..00561933043d --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-duplicates-resolver.service.ts @@ -0,0 +1,214 @@ +import { Injectable } from '@nestjs/common'; + +import isEmpty from 'lodash.isempty'; +import { In } from 'typeorm'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { + Record as IRecord, + OrderByDirection, + RecordFilter, +} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { FindDuplicatesResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { settings } from 'src/engine/constants/settings'; +import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants'; +import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryFindDuplicatesResolverService + implements + ResolverService[]> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve( + args: FindDuplicatesResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise[]> { + const { authContext, objectMetadataMapItem, objectMetadataMap } = options; + + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( + authContext.workspace.id, + ); + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, + ); + const existingRecordsQueryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + const duplicateRecordsQueryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataMap[objectMetadataMapItem.nameSingular].fields, + objectMetadataMap, + ); + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + let objectRecords: Partial[] = []; + + if (args.ids) { + const nonFormattedObjectRecords = (await existingRecordsQueryBuilder + .where({ id: In(args.ids) }) + .getMany()) as ObjectRecord[]; + + objectRecords = formatResult( + nonFormattedObjectRecords, + objectMetadataMapItem, + objectMetadataMap, + ); + } else if (args.data && !isEmpty(args.data)) { + objectRecords = formatData(args.data, objectMetadataMapItem); + } + + const duplicateConnections: IConnection[] = await Promise.all( + objectRecords.map(async (record) => { + const duplicateConditions = this.buildDuplicateConditions( + objectMetadataMapItem, + [record], + record.id, + ); + + if (isEmpty(duplicateConditions)) { + return typeORMObjectRecordsParser.createConnection( + [], + objectMetadataMapItem.nameSingular, + 0, + 0, + [{ id: OrderByDirection.AscNullsFirst }], + false, + false, + ); + } + + const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( + duplicateRecordsQueryBuilder, + objectMetadataMapItem.nameSingular, + duplicateConditions, + ); + + const nonFormattedDuplicates = + (await withFilterQueryBuilder.getMany()) as ObjectRecord[]; + + const duplicates = formatResult( + nonFormattedDuplicates, + objectMetadataMapItem, + objectMetadataMap, + ); + + return typeORMObjectRecordsParser.createConnection( + duplicates, + objectMetadataMapItem.nameSingular, + duplicates.length, + duplicates.length, + [{ id: OrderByDirection.AscNullsFirst }], + false, + false, + ); + }), + ); + + return duplicateConnections; + } + + private buildDuplicateConditions( + objectMetadataMapItem: ObjectMetadataMapItem, + records?: Partial[] | undefined, + filteringByExistingRecordId?: string, + ): Partial { + if (!records || records.length === 0) { + return {}; + } + + const criteriaCollection = this.getApplicableDuplicateCriteriaCollection( + objectMetadataMapItem, + ); + + const conditions = records.flatMap((record) => { + const criteriaWithMatchingArgs = criteriaCollection.filter((criteria) => + criteria.columnNames.every((columnName) => { + const value = record[columnName] as string | undefined; + + return ( + value && value.length >= settings.minLengthOfStringForDuplicateCheck + ); + }), + ); + + return criteriaWithMatchingArgs.map((criteria) => { + const condition = {}; + + criteria.columnNames.forEach((columnName) => { + condition[columnName] = { eq: record[columnName] }; + }); + + return condition; + }); + }); + + const filter: Partial = {}; + + if (conditions && !isEmpty(conditions)) { + filter.or = conditions; + + if (filteringByExistingRecordId) { + filter.id = { neq: filteringByExistingRecordId }; + } + } + + return filter; + } + + private getApplicableDuplicateCriteriaCollection( + objectMetadataMapItem: ObjectMetadataMapItem, + ) { + return DUPLICATE_CRITERIA_COLLECTION.filter( + (duplicateCriteria) => + duplicateCriteria.objectName === objectMetadataMapItem.nameSingular, + ); + } + + async validate( + args: FindDuplicatesResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise { + if (!args.data && !args.ids) { + throw new GraphqlQueryRunnerException( + 'You have to provide either "data" or "ids" argument', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + + if (args.data && args.ids) { + throw new GraphqlQueryRunnerException( + 'You cannot provide both "data" and "ids" arguments', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + + if (!args.ids && isEmpty(args.data)) { + throw new GraphqlQueryRunnerException( + 'The "data" condition can not be empty when "ids" input not provided', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts index 5caa30e4e51e..85fdd3948274 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts @@ -1,6 +1,9 @@ +import { Injectable } from '@nestjs/common'; + import { isDefined } from 'class-validator'; import graphqlFields from 'graphql-fields'; +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { Record as IRecord, OrderByDirection, @@ -17,26 +20,25 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; -import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; import { computeCursorArgFilter } from 'src/engine/api/graphql/graphql-query-runner/utils/compute-cursor-arg-filter'; -import { decodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; -import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util'; import { - ObjectMetadataMapItem, - generateObjectMetadataMap, -} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; + getCursor, + getPaginationInfo, +} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -export class GraphqlQueryFindManyResolverService { - private twentyORMGlobalManager: TwentyORMGlobalManager; - - constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { - this.twentyORMGlobalManager = twentyORMGlobalManager; - } +@Injectable() +export class GraphqlQueryFindManyResolverService + implements ResolverService> +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async findMany< + async resolve< ObjectRecord extends IRecord = IRecord, Filter extends RecordFilter = RecordFilter, OrderBy extends RecordOrderBy = RecordOrderBy, @@ -44,51 +46,41 @@ export class GraphqlQueryFindManyResolverService { args: FindManyResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise> { - const { authContext, objectMetadataItem, info, objectMetadataCollection } = + const { authContext, objectMetadataMapItem, info, objectMetadataMap } = options; - this.validateArgsOrThrow(args); - const dataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace( authContext.workspace.id, ); const repository = dataSource.getRepository( - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, ); const queryBuilder = repository.createQueryBuilder( - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, ); const countQueryBuilder = repository.createQueryBuilder( - objectMetadataItem.nameSingular, - ); - - const objectMetadataMap = generateObjectMetadataMap( - objectMetadataCollection, + objectMetadataMapItem.nameSingular, ); - const objectMetadata = getObjectMetadataOrThrow( - objectMetadataMap, - objectMetadataItem.nameSingular, - ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadata.fields, + objectMetadataMapItem.fields, objectMetadataMap, ); const withFilterCountQueryBuilder = graphqlQueryParser.applyFilterToBuilder( countQueryBuilder, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, args.filter ?? ({} as Filter), ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItem, + objectMetadataMapItem, selectedFields, ); const isForwardPagination = !isDefined(args.before); @@ -105,7 +97,7 @@ export class GraphqlQueryFindManyResolverService { ? await withDeletedCountQueryBuilder.getCount() : 0; - const cursor = this.getCursor(args); + const cursor = getCursor(args); let appliedFilters = args.filter ?? ({} as Filter); @@ -118,7 +110,7 @@ export class GraphqlQueryFindManyResolverService { const cursorArgFilter = computeCursorArgFilter( cursor, orderByWithIdCondition, - objectMetadata.fields, + objectMetadataMapItem.fields, isForwardPagination, ); @@ -131,14 +123,14 @@ export class GraphqlQueryFindManyResolverService { const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, appliedFilters, ); const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder( withFilterQueryBuilder, orderByWithIdCondition, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, isForwardPagination, ); @@ -153,11 +145,11 @@ export class GraphqlQueryFindManyResolverService { const objectRecords = formatResult( nonFormattedObjectRecords, - objectMetadata, + objectMetadataMapItem, objectMetadataMap, ); - const { hasNextPage, hasPreviousPage } = this.getPaginationInfo( + const { hasNextPage, hasPreviousPage } = getPaginationInfo( objectRecords, limit, isForwardPagination, @@ -167,14 +159,12 @@ export class GraphqlQueryFindManyResolverService { objectRecords.pop(); } - const processNestedRelationsHelper = new ProcessNestedRelationsHelper( - this.twentyORMGlobalManager, - ); + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); if (relations) { await processNestedRelationsHelper.processNestedRelations( objectMetadataMap, - objectMetadata, + objectMetadataMapItem, objectRecords, relations, limit, @@ -184,20 +174,25 @@ export class GraphqlQueryFindManyResolverService { } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); - return typeORMObjectRecordsParser.createConnection( + const result = typeORMObjectRecordsParser.createConnection( objectRecords, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, limit, totalCount, orderByWithIdCondition, hasNextPage, hasPreviousPage, ); + + return result; } - private validateArgsOrThrow(args: FindManyResolverArgs) { + async validate( + args: FindManyResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise { if (args.first && args.last) { throw new GraphqlQueryRunnerException( 'Cannot provide both first and last', @@ -235,49 +230,4 @@ export class GraphqlQueryFindManyResolverService { ); } } - - private getCursor( - args: FindManyResolverArgs, - ): Record | undefined { - if (args.after) return decodeCursor(args.after); - if (args.before) return decodeCursor(args.before); - - return undefined; - } - - private addOrderByColumnsToSelect( - order: Record, - select: Record, - ) { - for (const column of Object.keys(order || {})) { - if (!select[column]) { - select[column] = true; - } - } - } - - private addForeingKeyColumnsToSelect( - relations: Record, - select: Record, - objectMetadata: ObjectMetadataMapItem, - ) { - for (const column of Object.keys(relations || {})) { - if (!select[`${column}Id`] && objectMetadata.fields[`${column}Id`]) { - select[`${column}Id`] = true; - } - } - } - - private getPaginationInfo( - objectRecords: any[], - limit: number, - isForwardPagination: boolean, - ) { - const hasMoreRecords = objectRecords.length > limit; - - return { - hasNextPage: isForwardPagination && hasMoreRecords, - hasPreviousPage: !isForwardPagination && hasMoreRecords, - }; - } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts index b9e86e420cc9..164f30b68664 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts @@ -1,5 +1,8 @@ +import { Injectable } from '@nestjs/common'; + import graphqlFields from 'graphql-fields'; +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { Record as IRecord, RecordFilter, @@ -13,28 +16,31 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; -import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; -import { getObjectMetadataOrThrow } from 'src/engine/api/graphql/graphql-query-runner/utils/get-object-metadata-or-throw.util'; -import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; +import { + WorkspaceQueryRunnerException, + WorkspaceQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -export class GraphqlQueryFindOneResolverService { - private twentyORMGlobalManager: TwentyORMGlobalManager; - - constructor(twentyORMGlobalManager: TwentyORMGlobalManager) { - this.twentyORMGlobalManager = twentyORMGlobalManager; - } +@Injectable() +export class GraphqlQueryFindOneResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} - async findOne< + async resolve< ObjectRecord extends IRecord = IRecord, Filter extends RecordFilter = RecordFilter, >( args: FindOneResolverArgs, options: WorkspaceQueryRunnerOptions, - ): Promise { - const { authContext, objectMetadataItem, info, objectMetadataCollection } = + ): Promise { + const { authContext, objectMetadataMapItem, info, objectMetadataMap } = options; const dataSource = @@ -43,37 +49,28 @@ export class GraphqlQueryFindOneResolverService { ); const repository = dataSource.getRepository( - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, ); const queryBuilder = repository.createQueryBuilder( - objectMetadataItem.nameSingular, - ); - - const objectMetadataMap = generateObjectMetadataMap( - objectMetadataCollection, - ); - - const objectMetadata = getObjectMetadataOrThrow( - objectMetadataMap, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, ); const graphqlQueryParser = new GraphqlQueryParser( - objectMetadata.fields, + objectMetadataMapItem.fields, objectMetadataMap, ); const selectedFields = graphqlFields(info); const { relations } = graphqlQueryParser.parseSelectedFields( - objectMetadataItem, + objectMetadataMapItem, selectedFields, ); const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, args.filter ?? ({} as Filter), ); @@ -86,12 +83,10 @@ export class GraphqlQueryFindOneResolverService { const objectRecord = formatResult( nonFormattedObjectRecord, - objectMetadata, + objectMetadataMapItem, objectMetadataMap, ); - const limit = QUERY_MAX_RECORDS; - if (!objectRecord) { throw new GraphqlQueryRunnerException( 'Record not found', @@ -99,32 +94,42 @@ export class GraphqlQueryFindOneResolverService { ); } - const processNestedRelationsHelper = new ProcessNestedRelationsHelper( - this.twentyORMGlobalManager, - ); + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); const objectRecords = [objectRecord]; if (relations) { await processNestedRelationsHelper.processNestedRelations( objectMetadataMap, - objectMetadata, + objectMetadataMapItem, objectRecords, relations, - limit, + QUERY_MAX_RECORDS, authContext, dataSource, ); } const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); return typeORMObjectRecordsParser.processRecord( objectRecords[0], - objectMetadataItem.nameSingular, + objectMetadataMapItem.nameSingular, 1, 1, ) as ObjectRecord; } + + async validate( + args: FindOneResolverArgs, + _options: WorkspaceQueryRunnerOptions, + ): Promise { + if (!args.filter || Object.keys(args.filter).length === 0) { + throw new WorkspaceQueryRunnerException( + 'Missing filter argument', + WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts index c8d1892bbd2b..fe9d86f6cd84 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service.ts @@ -1,3 +1,6 @@ +import { Injectable } from '@nestjs/common'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; import { Record as IRecord, OrderByDirection, @@ -11,47 +14,25 @@ import { GraphqlQueryRunnerException, GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; -import { ObjectRecordsToGraphqlConnectionMapper } from 'src/engine/api/graphql/graphql-query-runner/orm-mappers/object-records-to-graphql-connection.mapper'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants'; -import { generateObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -export class GraphqlQuerySearchResolverService { - private twentyORMGlobalManager: TwentyORMGlobalManager; - private featureFlagService: FeatureFlagService; - +@Injectable() +export class GraphqlQuerySearchResolverService + implements ResolverService> +{ constructor( - twentyORMGlobalManager: TwentyORMGlobalManager, - featureFlagService: FeatureFlagService, - ) { - this.twentyORMGlobalManager = twentyORMGlobalManager; - this.featureFlagService = featureFlagService; - } + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly featureFlagService: FeatureFlagService, + ) {} - async search( + async resolve( args: SearchResolverArgs, options: WorkspaceQueryRunnerOptions, ): Promise> { - const { authContext, objectMetadataItem, objectMetadataCollection } = - options; - - const featureFlagsForWorkspace = - await this.featureFlagService.getWorkspaceFeatureFlags( - authContext.workspace.id, - ); - - const isQueryRunnerTwentyORMEnabled = - featureFlagsForWorkspace.IS_QUERY_RUNNER_TWENTY_ORM_ENABLED; - - const isSearchEnabled = featureFlagsForWorkspace.IS_SEARCH_ENABLED; - - if (!isQueryRunnerTwentyORMEnabled || !isSearchEnabled) { - throw new GraphqlQueryRunnerException( - 'This endpoint is not available yet, please use findMany instead.', - GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, - ); - } + const { authContext, objectMetadataItem, objectMetadataMap } = options; const repository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( @@ -59,21 +40,8 @@ export class GraphqlQuerySearchResolverService { objectMetadataItem.nameSingular, ); - const objectMetadataMap = generateObjectMetadataMap( - objectMetadataCollection, - ); - - const objectMetadata = objectMetadataMap[objectMetadataItem.nameSingular]; - - if (!objectMetadata) { - throw new GraphqlQueryRunnerException( - `Object metadata not found for ${objectMetadataItem.nameSingular}`, - GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND, - ); - } - const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionMapper(objectMetadataMap); + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); if (!args.searchInput) { return typeORMObjectRecordsParser.createConnection( @@ -100,7 +68,7 @@ export class GraphqlQuerySearchResolverService { 'DESC', ) .setParameter('searchTerms', searchTerms) - .limit(limit) + .take(limit) .getMany()) as ObjectRecord[]; const objectRecords = await repository.formatResult(resultsWithTsVector); @@ -129,4 +97,26 @@ export class GraphqlQuerySearchResolverService { return formattedWords.join(' | '); } + + async validate( + _args: SearchResolverArgs, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const featureFlagsForWorkspace = + await this.featureFlagService.getWorkspaceFeatureFlags( + options.authContext.workspace.id, + ); + + const isQueryRunnerTwentyORMEnabled = + featureFlagsForWorkspace.IS_QUERY_RUNNER_TWENTY_ORM_ENABLED; + + const isSearchEnabled = featureFlagsForWorkspace.IS_SEARCH_ENABLED; + + if (!isQueryRunnerTwentyORMEnabled || !isSearchEnabled) { + throw new GraphqlQueryRunnerException( + 'This endpoint is not available yet, please use findMany instead.', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + } } diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts new file mode 100644 index 000000000000..d8854223794b --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -0,0 +1,115 @@ +import { Injectable } from '@nestjs/common'; + +import graphqlFields from 'graphql-fields'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; +import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; +import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; +import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryUpdateManyResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve( + args: UpdateManyResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const { authContext, objectMetadataMapItem, objectMetadataMap, info } = + options; + + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( + authContext.workspace.id, + ); + + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, + ); + + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataMapItem.fields, + objectMetadataMap, + ); + + const selectedFields = graphqlFields(info); + + const { relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataMapItem, + selectedFields, + ); + + const queryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( + queryBuilder, + objectMetadataMapItem.nameSingular, + args.filter, + ); + + const data = formatData(args.data, objectMetadataMapItem); + + const result = await withFilterQueryBuilder + .update(data) + .returning('*') + .execute(); + + const nonFormattedUpdatedObjectRecords = result.raw; + + const updatedRecords = formatResult( + nonFormattedUpdatedObjectRecords, + objectMetadataMapItem, + objectMetadataMap, + ); + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (relations) { + await processNestedRelationsHelper.processNestedRelations( + objectMetadataMap, + objectMetadataMapItem, + updatedRecords, + relations, + QUERY_MAX_RECORDS, + authContext, + dataSource, + ); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + return updatedRecords.map((record: ObjectRecord) => + typeORMObjectRecordsParser.processRecord( + record, + objectMetadataMapItem.nameSingular, + 1, + 1, + ), + ); + } + + async validate( + args: UpdateManyResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataMapItem); + args.filter?.id?.in?.forEach((id: string) => assertIsValidUuid(id)); + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts new file mode 100644 index 000000000000..6fc4e1a72c58 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts @@ -0,0 +1,121 @@ +import { Injectable } from '@nestjs/common'; + +import graphqlFields from 'graphql-fields'; + +import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/interfaces/resolver-service.interface'; +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; +import { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant'; +import { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; +import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; +import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; +import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util'; +import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; +import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; + +@Injectable() +export class GraphqlQueryUpdateOneResolverService + implements ResolverService +{ + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async resolve( + args: UpdateOneResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + const { authContext, objectMetadataMapItem, objectMetadataMap, info } = + options; + + const dataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace( + authContext.workspace.id, + ); + + const repository = dataSource.getRepository( + objectMetadataMapItem.nameSingular, + ); + + const graphqlQueryParser = new GraphqlQueryParser( + objectMetadataMapItem.fields, + objectMetadataMap, + ); + + const selectedFields = graphqlFields(info); + + const { relations } = graphqlQueryParser.parseSelectedFields( + objectMetadataMapItem, + selectedFields, + ); + + const queryBuilder = repository.createQueryBuilder( + objectMetadataMapItem.nameSingular, + ); + + const data = formatData(args.data, objectMetadataMapItem); + + const result = await queryBuilder + .update(data) + .where({ id: args.id }) + .returning('*') + .execute(); + + const nonFormattedUpdatedObjectRecords = result.raw; + + const updatedRecords = formatResult( + nonFormattedUpdatedObjectRecords, + objectMetadataMapItem, + objectMetadataMap, + ); + + if (updatedRecords.length === 0) { + throw new GraphqlQueryRunnerException( + 'Record not found', + GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND, + ); + } + + const updatedRecord = updatedRecords[0] as ObjectRecord; + + const processNestedRelationsHelper = new ProcessNestedRelationsHelper(); + + if (relations) { + await processNestedRelationsHelper.processNestedRelations( + objectMetadataMap, + objectMetadataMapItem, + [updatedRecord], + relations, + QUERY_MAX_RECORDS, + authContext, + dataSource, + ); + } + + const typeORMObjectRecordsParser = + new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap); + + return typeORMObjectRecordsParser.processRecord( + updatedRecord, + objectMetadataMapItem.nameSingular, + 1, + 1, + ); + } + + async validate( + args: UpdateOneResolverArgs>, + options: WorkspaceQueryRunnerOptions, + ): Promise { + assertMutationNotOnRemoteObject(options.objectMetadataMapItem); + assertIsValidUuid(args.id); + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts new file mode 100644 index 000000000000..8cb2c9cc7a04 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service.ts @@ -0,0 +1,137 @@ +import { Injectable } from '@nestjs/common'; + +import { Record as IRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + +import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; + +@Injectable() +export class ApiEventEmitterService { + constructor(private readonly workspaceEventEmitter: WorkspaceEventEmitter) {} + + public emitCreateEvents( + records: T[], + authContext: AuthContext, + objectMetadataItem: ObjectMetadataInterface, + ): void { + this.workspaceEventEmitter.emit( + `${objectMetadataItem.nameSingular}.created`, + records.map((record) => ({ + userId: authContext.user?.id, + recordId: record.id, + objectMetadata: objectMetadataItem, + properties: { + before: null, + after: this.removeGraphQLAndNestedProperties(record), + }, + })), + authContext.workspace.id, + ); + } + + public emitUpdateEvents( + existingRecords: T[], + records: T[], + updatedFields: string[], + authContext: AuthContext, + objectMetadataItem: ObjectMetadataInterface, + ): void { + const mappedExistingRecords = existingRecords.reduce( + (acc, { id, ...record }) => ({ + ...acc, + [id]: record, + }), + {}, + ); + + this.workspaceEventEmitter.emit( + `${objectMetadataItem.nameSingular}.updated`, + records.map((record) => { + return { + userId: authContext.user?.id, + recordId: record.id, + objectMetadata: objectMetadataItem, + properties: { + before: mappedExistingRecords[record.id] + ? this.removeGraphQLAndNestedProperties( + mappedExistingRecords[record.id], + ) + : undefined, + after: this.removeGraphQLAndNestedProperties(record), + updatedFields, + }, + }; + }), + authContext.workspace.id, + ); + } + + public emitDeletedEvents( + records: T[], + authContext: AuthContext, + objectMetadataItem: ObjectMetadataInterface, + ): void { + this.workspaceEventEmitter.emit( + `${objectMetadataItem.nameSingular}.deleted`, + records.map((record) => { + return { + userId: authContext.user?.id, + recordId: record.id, + objectMetadata: objectMetadataItem, + properties: { + before: this.removeGraphQLAndNestedProperties(record), + after: null, + }, + }; + }), + authContext.workspace.id, + ); + } + + public emitDestroyEvents( + records: T[], + authContext: AuthContext, + objectMetadataItem: ObjectMetadataInterface, + ): void { + this.workspaceEventEmitter.emit( + `${objectMetadataItem.nameSingular}.destroyed`, + records.map((record) => { + return { + userId: authContext.user?.id, + recordId: record.id, + objectMetadata: objectMetadataItem, + properties: { + before: this.removeGraphQLAndNestedProperties(record), + after: null, + }, + }; + }), + authContext.workspace.id, + ); + } + + private removeGraphQLAndNestedProperties( + record: ObjectRecord, + ) { + if (!record) { + return {}; + } + + const sanitizedRecord = {}; + + for (const [key, value] of Object.entries(record)) { + if (value && typeof value === 'object' && value['edges']) { + continue; + } + + if (key === '__typename') { + continue; + } + + sanitizedRecord[key] = value; + } + + return sanitizedRecord; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts index bf8eb52d0a57..bd27522ce1b2 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/cursors.util.ts @@ -2,6 +2,7 @@ import { Record as IRecord, RecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { GraphqlQueryRunnerException, @@ -44,3 +45,25 @@ export const encodeCursor = ( return Buffer.from(JSON.stringify(cursorData)).toString('base64'); }; + +export const getCursor = ( + args: FindManyResolverArgs, +): Record | undefined => { + if (args.after) return decodeCursor(args.after); + if (args.before) return decodeCursor(args.before); + + return undefined; +}; + +export const getPaginationInfo = ( + objectRecords: any[], + limit: number, + isForwardPagination: boolean, +) => { + const hasMoreRecords = objectRecords.length > limit; + + return { + hasNextPage: isForwardPagination && hasMoreRecords, + hasPreviousPage: !isForwardPagination && hasMoreRecords, + }; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts index 45883f99ddf3..d960c3d45a7f 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface.ts @@ -4,6 +4,10 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { + ObjectMetadataMap, + ObjectMetadataMapItem, +} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; export interface WorkspaceQueryRunnerOptions { authContext: AuthContext; @@ -11,4 +15,6 @@ export interface WorkspaceQueryRunnerOptions { objectMetadataItem: ObjectMetadataInterface; fieldMetadataCollection: FieldMetadataInterface[]; objectMetadataCollection: ObjectMetadataInterface[]; + objectMetadataMap: ObjectMetadataMap; + objectMetadataMapItem: ObjectMetadataMapItem; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type.ts index b75c939d1ac7..034c73bda552 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type.ts @@ -9,6 +9,7 @@ import { FindManyResolverArgs, FindOneResolverArgs, RestoreManyResolverArgs, + SearchResolverArgs, UpdateManyResolverArgs, UpdateOneResolverArgs, } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -42,4 +43,6 @@ export type WorkspacePreQueryHookPayload = T extends 'createMany' ? DestroyManyResolverArgs : T extends 'destroyOne' ? DestroyOneResolverArgs - : never; + : T extends 'search' + ? SearchResolverArgs + : never; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts index 872ab7906fca..06a8d5507b2d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { CreateManyResolverArgs, @@ -32,12 +33,14 @@ export class CreateManyResolverFactory return async (_source, args, _context, info) => { try { - const options = { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, }; const isQueryRunnerTwentyORMEnabled = diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts index edf9206a1cde..5922d05550d6 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/create-one-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { CreateOneResolverArgs, @@ -32,12 +33,14 @@ export class CreateOneResolverFactory return async (_source, args, _context, info) => { try { - const options = { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, }; const isQueryRunnerTwentyORMEnabled = @@ -50,13 +53,7 @@ export class CreateOneResolverFactory return await this.graphqlQueryRunnerService.createOne(args, options); } - return await this.workspaceQueryRunnerService.createOne(args, { - authContext: internalContext.authContext, - objectMetadataItem: internalContext.objectMetadataItem, - info, - fieldMetadataCollection: internalContext.fieldMetadataCollection, - objectMetadataCollection: internalContext.objectMetadataCollection, - }); + return await this.workspaceQueryRunnerService.createOne(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts index a8d36f3e4900..4a32ad5ea1c5 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { DeleteManyResolverArgs, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class DeleteManyResolverFactory @@ -18,6 +22,8 @@ export class DeleteManyResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,27 @@ export class DeleteManyResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.deleteMany(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.deleteMany(args, options); + } + + return await this.workspaceQueryRunnerService.deleteMany(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts index 93f249cdd6ac..d58ebe02fd56 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/delete-one-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { DeleteOneResolverArgs, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class DeleteOneResolverFactory @@ -18,6 +22,8 @@ export class DeleteOneResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,27 @@ export class DeleteOneResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.deleteOne(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.deleteOne(args, options); + } + + return await this.workspaceQueryRunnerService.deleteOne(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts index 4a064a406b9b..2e6cf835effa 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { DestroyManyResolverArgs, @@ -27,13 +28,20 @@ export class DestroyManyResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.destroyMany(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + return await this.workspaceQueryRunnerService.destroyMany( + args, + options, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts index 4c204d6e8c0c..bb1e2aaaa9ba 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { DestroyOneResolverArgs, @@ -27,13 +28,17 @@ export class DestroyOneResolverFactory return async (_source, args, context, info) => { try { - return await this.graphQLQueryRunnerService.destroyOne(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + return await this.graphQLQueryRunnerService.destroyOne(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts index 0a1494efb666..f8b57ad22cca 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { FindDuplicatesResolverArgs, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class FindDuplicatesResolverFactory @@ -18,6 +22,8 @@ export class FindDuplicatesResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,33 @@ export class FindDuplicatesResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.findDuplicates(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.findDuplicates( + args, + options, + ); + } + + return await this.workspaceQueryRunnerService.findDuplicates( + args, + options, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts index 2dd452a2976c..c695079e2f62 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { FindManyResolverArgs, @@ -27,12 +28,14 @@ export class FindManyResolverFactory return async (_source, args, _context, info) => { try { - const options = { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, }; return await this.graphqlQueryRunnerService.findMany(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts index 3dbbc2330d06..00845e841710 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/find-one-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { FindOneResolverArgs, @@ -27,12 +28,14 @@ export class FindOneResolverFactory return async (_source, args, _context, info) => { try { - const options = { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, }; return await this.graphqlQueryRunnerService.findOne(args, options); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts index ceba95306aed..d92210040535 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { Resolver, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class RestoreManyResolverFactory @@ -18,6 +22,8 @@ export class RestoreManyResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,33 @@ export class RestoreManyResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.restoreMany(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.restoreMany( + args, + options, + ); + } + + return await this.workspaceQueryRunnerService.restoreMany( + args, + options, + ); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts index 5b32d527960f..9d559b656194 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { Resolver, @@ -25,13 +26,17 @@ export class SearchResolverFactory return async (_source, args, _context, info) => { try { - return await this.graphqlQueryRunnerService.search(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + return await this.graphqlQueryRunnerService.search(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts index c2328d12ba05..11027e4cc4fd 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { Resolver, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class UpdateManyResolverFactory @@ -18,6 +22,8 @@ export class UpdateManyResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,27 @@ export class UpdateManyResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.updateMany(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.updateMany(args, options); + } + + return await this.workspaceQueryRunnerService.updateMany(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts index c7a7dc6bacd6..13a2e4f714d1 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/factories/update-one-resolver.factory.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface'; import { Resolver, @@ -7,8 +8,11 @@ import { } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface'; +import { GraphqlQueryRunnerService } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-runner.service'; import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util'; import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; @Injectable() export class UpdateOneResolverFactory @@ -18,6 +22,8 @@ export class UpdateOneResolverFactory constructor( private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly featureFlagService: FeatureFlagService, + private readonly graphqlQueryRunnerService: GraphqlQueryRunnerService, ) {} create( @@ -27,13 +33,27 @@ export class UpdateOneResolverFactory return async (_source, args, context, info) => { try { - return await this.workspaceQueryRunnerService.updateOne(args, { + const options: WorkspaceQueryRunnerOptions = { authContext: internalContext.authContext, objectMetadataItem: internalContext.objectMetadataItem, info, fieldMetadataCollection: internalContext.fieldMetadataCollection, objectMetadataCollection: internalContext.objectMetadataCollection, - }); + objectMetadataMap: internalContext.objectMetadataMap, + objectMetadataMapItem: internalContext.objectMetadataMapItem, + }; + + const isQueryRunnerTwentyORMEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsQueryRunnerTwentyORMEnabled, + internalContext.authContext.workspace.id, + ); + + if (isQueryRunnerTwentyORMEnabled) { + return await this.graphqlQueryRunnerService.updateOne(args, options); + } + + return await this.workspaceQueryRunnerService.updateOne(args, options); } catch (error) { workspaceQueryRunnerGraphqlApiExceptionHandler(error); } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts index 616c734581df..a652e3065c81 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory.ts @@ -11,6 +11,7 @@ import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-res import { SearchResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory'; import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { ObjectMetadataMap } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; import { getResolverName } from 'src/engine/utils/get-resolver-name.util'; import { CreateManyResolverFactory } from './factories/create-many-resolver.factory'; @@ -49,6 +50,7 @@ export class WorkspaceResolverFactory { async create( authContext: AuthContext, objectMetadataCollection: ObjectMetadataInterface[], + objectMetadataMap: ObjectMetadataMap, workspaceResolverBuilderMethods: WorkspaceResolverBuilderMethods, ): Promise { const factories = new Map< @@ -94,7 +96,9 @@ export class WorkspaceResolverFactory { authContext, objectMetadataItem: objectMetadata, fieldMetadataCollection: objectMetadata.fields, - objectMetadataCollection: objectMetadataCollection, + objectMetadataCollection, + objectMetadataMap, + objectMetadataMapItem: objectMetadataMap[objectMetadata.nameSingular], }); } @@ -117,7 +121,9 @@ export class WorkspaceResolverFactory { authContext, objectMetadataItem: objectMetadata, fieldMetadataCollection: objectMetadata.fields, - objectMetadataCollection: objectMetadataCollection, + objectMetadataCollection, + objectMetadataMap, + objectMetadataMapItem: objectMetadataMap[objectMetadata.nameSingular], }); } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts index f5a6aec8b1a2..d0ab66983309 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface.ts @@ -2,10 +2,16 @@ import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metada import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { + ObjectMetadataMap, + ObjectMetadataMapItem, +} from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; export interface WorkspaceSchemaBuilderContext { authContext: AuthContext; - objectMetadataItem: ObjectMetadataInterface; fieldMetadataCollection: FieldMetadataInterface[]; objectMetadataCollection: ObjectMetadataInterface[]; + objectMetadataItem: ObjectMetadataInterface; + objectMetadataMap: ObjectMetadataMap; + objectMetadataMapItem: ObjectMetadataMapItem; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts index 336fa825f81c..32a44ad4d26e 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts @@ -117,6 +117,7 @@ export class WorkspaceSchemaFactory { const autoGeneratedResolvers = await this.workspaceResolverFactory.create( authContext, objectMetadataCollection, + objectMetadataMap, workspaceResolverBuilderMethodNames, ); const scalarsResolvers = diff --git a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts index 4fec6c7ab443..625da7f23b07 100644 --- a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts +++ b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/__tests__/map-field-metadata-to-graphql-query.utils.spec.ts @@ -31,6 +31,9 @@ describe('mapFieldMetadataToGraphqlQuery', () => { }); describe('should handle all field metadata types', () => { Object.values(FieldMetadataType).forEach((fieldMetadataType) => { + if (fieldMetadataType === FieldMetadataType.TS_VECTOR) { + return; + } it(`with field type ${fieldMetadataType}`, () => { const field = { type: fieldMetadataType, diff --git a/packages/twenty-server/src/engine/core-modules/duplicate/duplicate.service.ts b/packages/twenty-server/src/engine/core-modules/duplicate/duplicate.service.ts index d7ed6f87bd85..48d021b5e689 100644 --- a/packages/twenty-server/src/engine/core-modules/duplicate/duplicate.service.ts +++ b/packages/twenty-server/src/engine/core-modules/duplicate/duplicate.service.ts @@ -1,15 +1,15 @@ import { Injectable } from '@nestjs/common'; -import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { Record as IRecord, Record, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface'; +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { settings } from 'src/engine/constants/settings'; +import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; -import { DUPLICATE_CRITERIA_COLLECTION } from 'src/engine/core-modules/duplicate/constants/duplicate-criteria.constants'; @Injectable() export class DuplicateService { @@ -94,80 +94,4 @@ export class DuplicateService { duplicateCriteria.objectName === objectMetadataItem.nameSingular, ); } - - /** - * TODO: Remove this code by September 1st, 2024 if it isn't used - * It was build to be used by the upsertMany function, but it was not used. - * It's a re-implementation of the methods to findDuplicates, but done - * at the SQL layer instead of doing it at the GraphQL layer - * - async findDuplicate( - data: Partial, - objectMetadata: ObjectMetadataInterface, - workspaceId: string, - ) { - const dataSourceSchema = - this.workspaceDataSourceService.getSchemaName(workspaceId); - - const { duplicateWhereClause, duplicateWhereParameters } = - this.buildDuplicateConditionForUpsert(objectMetadata, data); - - const results = await this.workspaceDataSourceService.executeRawQuery( - ` - SELECT - * - FROM - ${dataSourceSchema}."${computeObjectTargetTable( - objectMetadata, - )}" p - WHERE - ${duplicateWhereClause} - `, - duplicateWhereParameters, - workspaceId, - ); - - return results.length > 0 ? results[0] : null; - } - - private buildDuplicateConditionForUpsert( - objectMetadata: ObjectMetadataInterface, - data: Partial, - ) { - const criteriaCollection = this.getApplicableDuplicateCriteriaCollection( - objectMetadata, - ).filter( - (duplicateCriteria) => duplicateCriteria.useAsUniqueKeyForUpsert === true, - ); - - const whereClauses: string[] = []; - const whereParameters: any[] = []; - let parameterIndex = 1; - - criteriaCollection.forEach((c) => { - const clauseParts: string[] = []; - - c.columnNames.forEach((column) => { - const dataKey = Object.keys(data).find( - (key) => key.toLowerCase() === column.toLowerCase(), - ); - - if (dataKey) { - clauseParts.push(`p."${column}" = $${parameterIndex}`); - whereParameters.push(data[dataKey]); - parameterIndex++; - } - }); - if (clauseParts.length > 0) { - whereClauses.push(`(${clauseParts.join(' AND ')})`); - } - }); - - const duplicateWhereClause = whereClauses.join(' OR '); - const duplicateWhereParameters = whereParameters; - - return { duplicateWhereClause, duplicateWhereParameters }; - } - * - */ } diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts index 5c885c312dfc..5f10e3be0d75 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/__tests__/components.utils.spec.ts @@ -10,7 +10,8 @@ describe('computeSchemaComponents', () => { it('should test all non-deprecated field types', () => { expect(fields.map((field) => field.type)).toEqual( Object.keys(FieldMetadataType).filter( - (key) => key !== FieldMetadataType.LINK, + (key) => + key !== FieldMetadataType.LINK && key !== FieldMetadataType.TS_VECTOR, ), ); }); @@ -21,6 +22,7 @@ describe('computeSchemaComponents', () => { ] as ObjectMetadataEntity[]), ).toEqual({ ObjectName: { + description: undefined, type: 'object', properties: { fieldUuid: { @@ -195,6 +197,7 @@ describe('computeSchemaComponents', () => { 'API', 'IMPORT', 'MANUAL', + 'SYSTEM', ], }, }, @@ -203,6 +206,7 @@ describe('computeSchemaComponents', () => { required: ['fieldNumber'], }, 'ObjectName for Update': { + description: undefined, type: 'object', properties: { fieldUuid: { @@ -377,6 +381,7 @@ describe('computeSchemaComponents', () => { 'API', 'IMPORT', 'MANUAL', + 'SYSTEM', ], }, }, @@ -384,6 +389,7 @@ describe('computeSchemaComponents', () => { }, }, 'ObjectName for Response': { + description: undefined, type: 'object', properties: { fieldUuid: { @@ -558,6 +564,7 @@ describe('computeSchemaComponents', () => { 'API', 'IMPORT', 'MANUAL', + 'SYSTEM', ], }, workspaceMemberId: { diff --git a/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files.ts b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files.ts index 8fed171beec6..3d3a8c2a8952 100644 --- a/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files.ts +++ b/packages/twenty-server/src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files.ts @@ -1,5 +1,5 @@ -import path, { join } from 'path'; import fs from 'fs/promises'; +import path, { join } from 'path'; import { ASSET_PATH } from 'src/constants/assets-path'; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type.ts index 1efa0eeffff5..80f4689aef74 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type.ts @@ -12,6 +12,7 @@ export enum FieldActorSource { API = 'API', IMPORT = 'IMPORT', MANUAL = 'MANUAL', + SYSTEM = 'SYSTEM', } export const actorCompositeType: CompositeType = { diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts new file mode 100644 index 000000000000..81ede4ec4faa --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata-validation.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@nestjs/common'; + +import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; + +@Injectable() +export class FieldMetadataValidationService< + T extends FieldMetadataType | 'default' = 'default', +> { + constructor() {} + + validateSettingsOrThrow({ + fieldType, + settings, + }: { + fieldType: FieldMetadataType; + settings: FieldMetadataSettings; + }) { + switch (fieldType) { + case FieldMetadataType.NUMBER: + this.validateNumberSettings(settings); + break; + default: + break; + } + } + + private validateNumberSettings(settings: FieldMetadataSettings) { + if ('decimals' in settings) { + const { decimals } = settings; + + if ( + decimals !== undefined && + (decimals < 0 || !Number.isInteger(decimals)) + ) { + throw new FieldMetadataException( + `Decimals value "${decimals}" must be a positive integer`, + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); + } + } + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts index 377575ba9bfd..b6cb8e8ed1d7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts @@ -12,6 +12,7 @@ import { ActorModule } from 'src/engine/core-modules/actor/actor.module'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; +import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service'; import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver'; import { FieldMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor'; import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-default-value.validator'; @@ -44,7 +45,11 @@ import { UpdateFieldInput } from './dtos/update-field.input'; TypeORMModule, ActorModule, ], - services: [IsFieldMetadataDefaultValue, FieldMetadataService], + services: [ + IsFieldMetadataDefaultValue, + FieldMetadataService, + FieldMetadataValidationService, + ], resolvers: [ { EntityClass: FieldMetadataEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index ac2c5b1d438d..019cfcea7bdf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -56,6 +56,7 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity'; +import { FieldMetadataValidationService } from './field-metadata-validation.service'; import { FieldMetadataEntity, FieldMetadataType, @@ -82,6 +83,7 @@ export class FieldMetadataService extends TypeOrmQueryService( + fieldMetadataInput.type, fieldMetadataInput, objectMetadata, ); @@ -391,6 +394,7 @@ export class FieldMetadataService extends TypeOrmQueryService( + existingFieldMetadata.type, fieldMetadataInput, objectMetadata, ); @@ -707,7 +711,11 @@ export class FieldMetadataService extends TypeOrmQueryService(fieldMetadataInput: T, objectMetadata: ObjectMetadataEntity): T { + >( + fieldMetadataType: FieldMetadataType, + fieldMetadataInput: T, + objectMetadata: ObjectMetadataEntity, + ): T { if (fieldMetadataInput.name) { try { validateFieldNameValidityOrThrow(fieldMetadataInput.name); @@ -748,6 +756,13 @@ export class FieldMetadataService extends TypeOrmQueryService [ - { - name: computeObjectTargetTable(createdObjectMetadata), - action: WorkspaceMigrationTableActionType.CREATE, - } satisfies WorkspaceMigrationTableAction, // Add activity target relation { name: computeObjectTargetTable(activityTargetObjectMetadata), diff --git a/packages/twenty-server/src/engine/twenty-orm/base.workspace-entity.ts b/packages/twenty-server/src/engine/twenty-orm/base.workspace-entity.ts index d9a19a689301..53355304c346 100644 --- a/packages/twenty-server/src/engine/twenty-orm/base.workspace-entity.ts +++ b/packages/twenty-server/src/engine/twenty-orm/base.workspace-entity.ts @@ -55,5 +55,5 @@ export abstract class BaseWorkspaceEntity { }, }) @WorkspaceIsNullable() - deletedAt?: string | null; + deletedAt: string | null; } diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts index f6f31f32c9db..cd3851393678 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/format-data.util.ts @@ -1,9 +1,11 @@ -import { isPlainObject } from '@nestjs/common/utils/shared.utils'; +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; -import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { ObjectMetadataMapItem } from 'src/engine/metadata-modules/utils/generate-object-metadata-map.util'; -import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection'; +import { CompositeFieldMetadataType } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; +import { capitalize } from 'src/utils/capitalize'; export function formatData( data: T, @@ -17,49 +19,70 @@ export function formatData( return data.map((item) => formatData(item, objectMetadata)) as T; } - const compositeFieldMetadataCollection = - getCompositeFieldMetadataCollection(objectMetadata); - - const compositeFieldMetadataMap = new Map( - compositeFieldMetadataCollection.map((fieldMetadata) => [ - fieldMetadata.name, - fieldMetadata, - ]), - ); - const newData: object = {}; + const newData: Record = {}; for (const [key, value] of Object.entries(data)) { - const fieldMetadata = compositeFieldMetadataMap.get(key); + const fieldMetadata = objectMetadata.fields[key]; if (!fieldMetadata) { - if (isPlainObject(value)) { - newData[key] = formatData(value, objectMetadata); - } else { - newData[key] = value; - } - continue; + throw new Error( + `Field metadata for field "${key}" is missing in object metadata`, + ); } - const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); + if (isCompositeFieldMetadataType(fieldMetadata.type)) { + const formattedCompositeField = formatCompositeField( + value, + fieldMetadata, + ); - if (!compositeType) { - continue; + Object.assign(newData, formattedCompositeField); + } else { + newData[key] = formatFieldMetadataValue(value, fieldMetadata); } + } - for (const compositeProperty of compositeType.properties) { - const compositeKey = computeCompositeColumnName( - fieldMetadata.name, - compositeProperty, - ); - const value = data?.[key]?.[compositeProperty.name]; + return newData as T; +} - if (value === undefined || value === null) { - continue; - } +function formatCompositeField( + value: any, + fieldMetadata: FieldMetadataInterface, +): Record { + const compositeType = compositeTypeDefinitions.get( + fieldMetadata.type as CompositeFieldMetadataType, + ); - newData[compositeKey] = data[key][compositeProperty.name]; + if (!compositeType) { + throw new Error( + `Composite type definition not found for type: ${fieldMetadata.type}`, + ); + } + + const formattedCompositeField: Record = {}; + + for (const property of compositeType.properties) { + const subFieldKey = property.name; + const fullFieldName = `${fieldMetadata.name}${capitalize(subFieldKey)}`; + + if (value && value[subFieldKey] !== undefined) { + formattedCompositeField[fullFieldName] = formatFieldMetadataValue( + value[subFieldKey], + property as unknown as FieldMetadataInterface, + ); } } - return newData as T; + return formattedCompositeField; +} + +function formatFieldMetadataValue( + value: any, + fieldMetadata: FieldMetadataInterface, +) { + if (fieldMetadata.type === FieldMetadataType.RAW_JSON) { + return JSON.parse(value as string); + } + + return value; } diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts index 81949e0743a3..2b29ea908cd8 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts @@ -1,6 +1,9 @@ import { isPlainObject } from '@nestjs/common/utils/shared.utils'; +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; + import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { @@ -81,9 +84,15 @@ export function formatResult( if (!compositePropertyArgs && !relationMetadata) { if (isPlainObject(value)) { newData[key] = formatResult(value, objectMetadata, objectMetadataMap); + } else if (objectMetadata.fields[key]) { + newData[key] = formatFieldMetadataValue( + value, + objectMetadata.fields[key], + ); } else { newData[key] = value; } + continue; } @@ -129,3 +138,18 @@ export function formatResult( return newData as T; } + +function formatFieldMetadataValue( + value: any, + fieldMetadata: FieldMetadataInterface, +) { + if ( + typeof value === 'string' && + (fieldMetadata.type === FieldMetadataType.MULTI_SELECT || + fieldMetadata.type === FieldMetadataType.ARRAY) + ) { + return value.replace(/{|}/g, '').split(','); + } + + return value; +} diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/company.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/company.ts index 2f3966e1043e..9c5591ada4bf 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/company.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/company.ts @@ -1,5 +1,7 @@ import { EntityManager } from 'typeorm'; +import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; + export const AIRBNB_ID = 'c776ee49-f608-4a77-8cc8-6fe96ae1e43f'; export const QONTO_ID = 'f45ee421-8a3e-4aa5-a1cf-7207cc6754e1'; export const STRIPE_ID = '1f70157c-4ea5-4d81-bc49-e1401abfbb94'; @@ -43,7 +45,7 @@ export const companyPrefillData = async ( addressAddressCountry: 'United States', employees: 5000, position: 1, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', }, @@ -59,7 +61,7 @@ export const companyPrefillData = async ( addressAddressCountry: 'France', employees: 800, position: 2, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', }, @@ -75,7 +77,7 @@ export const companyPrefillData = async ( addressAddressCountry: 'Ireland', employees: 8000, position: 3, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', }, @@ -91,7 +93,7 @@ export const companyPrefillData = async ( addressAddressCountry: 'United States', employees: 800, position: 4, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', }, @@ -107,7 +109,7 @@ export const companyPrefillData = async ( addressAddressCountry: 'United States', employees: 400, position: 5, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', }, diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/person.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/person.ts index ec07c61f15b2..8b2972c6dbff 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/person.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/person.ts @@ -1,5 +1,6 @@ import { EntityManager } from 'typeorm'; +import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { AIRBNB_ID, FIGMA_ID, @@ -40,7 +41,7 @@ export const personPrefillData = async ( avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-3.png', position: 1, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', phonesPrimaryPhoneNumber: '1234567890', @@ -55,7 +56,7 @@ export const personPrefillData = async ( avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-89.png', position: 2, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', phonesPrimaryPhoneNumber: '677118822', @@ -70,7 +71,7 @@ export const personPrefillData = async ( avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-47.png', position: 3, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', phonesPrimaryPhoneNumber: '987625341', @@ -85,7 +86,7 @@ export const personPrefillData = async ( avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-40.png', position: 4, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', phonesPrimaryPhoneNumber: '09882261', @@ -100,7 +101,7 @@ export const personPrefillData = async ( avatarUrl: 'https://twentyhq.github.io/placeholder-images/people/image-68.png', position: 5, - createdBySource: 'MANUAL', + createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', phonesPrimaryPhoneNumber: '88226173', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/custom-table-default-column.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/custom-table-default-column.util.ts deleted file mode 100644 index 11a831f809d4..000000000000 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/custom-table-default-column.util.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { TableColumnOptions } from 'typeorm'; - -import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; - -export const customTableDefaultColumns = ( - tableName: string, -): TableColumnOptions[] => [ - { - name: 'id', - type: 'uuid', - isPrimary: true, - default: 'public.uuid_generate_v4()', - }, - { - name: 'createdAt', - type: 'timestamptz', - default: 'now()', - }, - { - name: 'updatedAt', - type: 'timestamptz', - default: 'now()', - }, - { - name: 'deletedAt', - type: 'timestamptz', - isNullable: true, - }, - { - name: 'position', - type: 'float', - isNullable: true, - }, - { - name: 'name', - type: 'text', - isNullable: false, - default: "'Untitled'", - }, - { - name: 'createdBySource', - type: 'enum', - enumName: `${tableName}_createdBySource_enum`, - enum: Object.values(FieldActorSource), - isNullable: false, - default: `'${FieldActorSource.MANUAL}'`, - }, - { name: 'createdByWorkspaceMemberId', type: 'uuid', isNullable: true }, - { - name: 'createdByName', - type: 'text', - isNullable: false, - default: "''", - }, -]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/table-default-column.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/table-default-column.util.ts new file mode 100644 index 000000000000..8967eb83f0d9 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/utils/table-default-column.util.ts @@ -0,0 +1,10 @@ +import { TableColumnOptions } from 'typeorm'; + +export const tableDefaultColumns = (): TableColumnOptions[] => [ + { + name: 'id', + type: 'uuid', + isPrimary: true, + default: 'public.uuid_generate_v4()', + }, +]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts index 2d6a20e147e1..db2072c8447b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service.ts @@ -26,10 +26,10 @@ import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { WorkspaceMigrationEnumService } from 'src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service'; import { convertOnDeleteActionToOnDelete } from 'src/engine/workspace-manager/workspace-migration-runner/utils/convert-on-delete-action-to-on-delete.util'; +import { tableDefaultColumns } from 'src/engine/workspace-manager/workspace-migration-runner/utils/table-default-column.util'; import { isDefined } from 'src/utils/is-defined'; import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service'; -import { customTableDefaultColumns } from './utils/custom-table-default-column.util'; @Injectable() export class WorkspaceMigrationRunnerService { @@ -121,7 +121,12 @@ export class WorkspaceMigrationRunnerService { ) { switch (tableMigration.action) { case WorkspaceMigrationTableActionType.CREATE: - await this.createTable(queryRunner, schemaName, tableMigration.name); + await this.createTable( + queryRunner, + schemaName, + tableMigration.name, + tableMigration.columns, + ); break; case WorkspaceMigrationTableActionType.ALTER: { if (tableMigration.newName) { @@ -244,16 +249,26 @@ export class WorkspaceMigrationRunnerService { queryRunner: QueryRunner, schemaName: string, tableName: string, + columns?: WorkspaceMigrationColumnAction[], ) { await queryRunner.createTable( new Table({ name: tableName, schema: schemaName, - columns: customTableDefaultColumns(tableName), + columns: tableDefaultColumns(), }), true, ); + if (columns && columns.length > 0) { + await this.handleColumnChanges( + queryRunner, + schemaName, + tableName, + columns, + ); + } + // Enable totalCount for the table await queryRunner.query(` COMMENT ON TABLE "${schemaName}"."${tableName}" IS '@graphql({"totalCount": {"enabled": true}})'; diff --git a/packages/twenty-server/src/modules/calendar/common/types/calendar-event.ts b/packages/twenty-server/src/modules/calendar/common/types/calendar-event.ts index 00f4c82ac5c5..ae817cc3fae7 100644 --- a/packages/twenty-server/src/modules/calendar/common/types/calendar-event.ts +++ b/packages/twenty-server/src/modules/calendar/common/types/calendar-event.ts @@ -5,6 +5,7 @@ export type CalendarEvent = Omit< CalendarEventWorkspaceEntity, | 'createdAt' | 'updatedAt' + | 'deletedAt' | 'calendarChannelEventAssociations' | 'calendarEventParticipants' | 'conferenceLink' @@ -19,6 +20,7 @@ export type CalendarEventParticipant = Omit< | 'id' | 'createdAt' | 'updatedAt' + | 'deletedAt' | 'personId' | 'workspaceMemberId' | 'person' diff --git a/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts b/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts index 19e6746ce1a3..35affc12d9a7 100644 --- a/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts +++ b/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts @@ -161,6 +161,7 @@ export class MessageChannelSyncStatusService { await messageChannelRepository.update(messageChannelIds, { syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING, + syncStageStartedAt: new Date().toISOString(), }); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-partial-message-list-fetch.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-partial-message-list-fetch.service.ts index bdf13895045a..b80ebc9c86ad 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-partial-message-list-fetch.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-partial-message-list-fetch.service.ts @@ -54,7 +54,6 @@ export class MessagingPartialMessageListFetchService { }, { throttleFailureCount: 0, - syncStageStartedAt: null, }, ); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/types/message.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/types/message.ts index 4ec483869a8d..b665e54898a8 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/types/message.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/types/message.ts @@ -6,6 +6,7 @@ export type Message = Omit< MessageWorkspaceEntity, | 'createdAt' | 'updatedAt' + | 'deletedAt' | 'messageChannelMessageAssociations' | 'messageParticipants' | 'messageThread' @@ -25,6 +26,7 @@ export type MessageParticipant = Omit< | 'id' | 'createdAt' | 'updatedAt' + | 'deletedAt' | 'personId' | 'workspaceMemberId' | 'person' diff --git a/packages/twenty-server/test/people.integration-spec.ts b/packages/twenty-server/test/people.integration-spec.ts index fd568bd5ba40..28b981e22dbe 100644 --- a/packages/twenty-server/test/people.integration-spec.ts +++ b/packages/twenty-server/test/people.integration-spec.ts @@ -27,7 +27,7 @@ describe('peopleResolver (integration)', () => { whatsapp { primaryPhoneNumber } - workPrefereance + workPreference performanceRating } } @@ -69,7 +69,7 @@ describe('peopleResolver (integration)', () => { expect(people).toHaveProperty('companyId'); expect(people).toHaveProperty('intro'); expect(people).toHaveProperty('whatsapp'); - expect(people).toHaveProperty('workPrefereance'); + expect(people).toHaveProperty('workPreference'); expect(people).toHaveProperty('performanceRating'); } }); diff --git a/packages/twenty-server/test/serverless-functions.integration-spec.ts b/packages/twenty-server/test/serverless-functions.integration-spec.ts deleted file mode 100644 index b4b87ff7caed..000000000000 --- a/packages/twenty-server/test/serverless-functions.integration-spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import request from 'supertest'; - -const client = request(`http://localhost:${APP_PORT}`); - -describe('serverlessFunctionsResolver (integration)', () => { - it('should find many serverlessFunctions', () => { - const queryData = { - query: ` - query serverlessFunctions { - serverlessFunctions { - edges { - node { - id - name - description - sourceCodeHash - runtime - latestVersion - syncStatus - createdAt - updatedAt - } - } - } - } - `, - }; - - return client - .post('/graphql') - .set('Authorization', `Bearer ${ACCESS_TOKEN}`) - .send(queryData) - .expect(200) - .expect((res) => { - expect(res.body.data).toBeDefined(); - expect(res.body.errors).toBeUndefined(); - }) - .expect((res) => { - const data = res.body.data.serverlessFunctions; - - expect(data).toBeDefined(); - expect(Array.isArray(data.edges)).toBe(true); - - const edges = data.edges; - - if (edges.length > 0) { - const serverlessFunctions = edges[0].node; - - expect(serverlessFunctions).toHaveProperty('id'); - expect(serverlessFunctions).toHaveProperty('name'); - expect(serverlessFunctions).toHaveProperty('description'); - expect(serverlessFunctions).toHaveProperty('sourceCodeHash'); - expect(serverlessFunctions).toHaveProperty('runtime'); - expect(serverlessFunctions).toHaveProperty('latestVersion'); - expect(serverlessFunctions).toHaveProperty('syncStatus'); - expect(serverlessFunctions).toHaveProperty('createdAt'); - expect(serverlessFunctions).toHaveProperty('updatedAt'); - } - }); - }); -}); diff --git a/packages/twenty-server/test/utils/setup-test.ts b/packages/twenty-server/test/utils/setup-test.ts index aac860fc79ee..bc206be689a6 100644 --- a/packages/twenty-server/test/utils/setup-test.ts +++ b/packages/twenty-server/test/utils/setup-test.ts @@ -1,5 +1,5 @@ -import 'tsconfig-paths/register'; import { JestConfigWithTsJest } from 'ts-jest'; +import 'tsconfig-paths/register'; import { createApp } from './create-app'; diff --git a/packages/twenty-ui/package.json b/packages/twenty-ui/package.json index 64e99d25f0a9..3e9aadc6ce64 100644 --- a/packages/twenty-ui/package.json +++ b/packages/twenty-ui/package.json @@ -1,6 +1,6 @@ { "name": "twenty-ui", - "version": "0.31.canary", + "version": "0.31.0-canary", "type": "module", "main": "./src/index.ts", "exports": { diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 97a8013b6b0d..a7518f4e1510 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -139,10 +139,11 @@ export { IconPilcrow, IconPlayerPlay, IconPlayerStop, - IconPower, + IconPlaylistAdd, IconPlaystationSquare, IconPlug, IconPlus, + IconPower, IconPresentation, IconProgressCheck, IconPuzzle, @@ -154,6 +155,7 @@ export { IconReload, IconRepeat, IconRestore, + IconRobot, IconRocket, IconRotate, IconRotate2, @@ -184,7 +186,6 @@ export { IconWand, IconWorld, IconX, - IconPlaylistAdd, } from '@tabler/icons-react'; export type { TablerIconsProps } from '@tabler/icons-react'; diff --git a/packages/twenty-website/package.json b/packages/twenty-website/package.json index e60004faf0f8..f3ca9e2137db 100644 --- a/packages/twenty-website/package.json +++ b/packages/twenty-website/package.json @@ -1,6 +1,6 @@ { "name": "twenty-website", - "version": "0.31.canary", + "version": "0.31.0-canary", "private": true, "scripts": { "nx": "NX_DEFAULT_PROJECT=twenty-website node ../../node_modules/nx/bin/nx.js", diff --git a/packages/twenty-website/src/content/developers/local-setup.mdx b/packages/twenty-website/src/content/developers/local-setup.mdx index 7e436c1aa304..69cf9cb630e7 100644 --- a/packages/twenty-website/src/content/developers/local-setup.mdx +++ b/packages/twenty-website/src/content/developers/local-setup.mdx @@ -185,6 +185,24 @@ yarn ## Step 7: Running the project +Start your redis server: + + + Depending on your Linux distribution, Redis server might be started automatically. + If not, check the [Redis installation guide](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) for your distro. + + + ```bash + brew services start redis + ``` + + + Depending on your Linux distribution, Redis server might be started automatically. + If not, check the [Redis installation guide](https://redis.io/docs/latest/operate/oss_and_stack/install/install-redis/) for your distro. + + + + Setup your database with the following command: ```bash npx nx database:reset twenty-server